Skip to main content

bacnet_network/
layer.rs

1//! NetworkLayer for local BACnet packet assembly and dispatch.
2//!
3//! The network layer wraps a transport and provides APDU-level send/receive
4//! by handling NPDU encoding/decoding. This is a non-router implementation:
5//! it does not forward messages between networks, but it can address remote
6//! devices through local routers via NPDU destination fields (DNET/DADR).
7//!
8//! # Receive-queue admission
9//!
10//! [`NetworkLayer`] and [`BACnetRouter`](crate::router::BACnetRouter) use the
11//! same drop-arriving policy for application delivery. Each receive queue holds
12//! **256 items**. Admission never waits for a consumer and never evicts an older
13//! queued item. NetworkLayer APDU and opted-in control queues have independent
14//! capacities, so saturation of either does not stall admission to the other.
15//! All router ports share one local APDU queue; its saturation does not stop
16//! forwarding or inline network-message handling.
17//!
18//! ## Raw and tracked receivers
19//!
20//! | Receiver kind | Entry points | Capacity | Per-source quota | Accounting |
21//! | --- | --- | --- | --- | --- |
22//! | Raw APDU | [`NetworkLayer::start`], [`BACnetRouter::start`](crate::router::BACnetRouter::start) with [`RouterOptions::new`](crate::router::RouterOptions::new) | 256 | None | Full/Closed counted internally; no admission snapshot or depth/high-water tracking |
23//! | Tracked APDU | [`NetworkLayer::start_with_admission`], [`RouterOptions::track_admission`](crate::router::RouterOptions::track_admission) | 256 | 16 queued APDUs per key, defined below | Exact depth/high-water and Full/fairness/Closed totals via [`AdmissionReceiver::counters`] |
24//! | Raw control | [`NetworkLayer::enable_network_control_receiver`], [`RouterOptions::network_control_receiver`](crate::router::RouterOptions::network_control_receiver) | 256, separate from APDUs | None | Full/Closed counted internally; no admission snapshot or depth/high-water tracking |
25//! | Tracked control | [`NetworkLayer::enable_network_control_receiver_with_admission`], [`RouterOptions::network_control_receiver_with_admission`](crate::router::RouterOptions::network_control_receiver_with_admission) | 256, separate from APDUs | None | Exact depth/high-water and Full/Closed totals; fairness always zero |
26//!
27//! Raw receivers remain `tokio::sync::mpsc::Receiver`; tracked receivers are the
28//! additive [`AdmissionReceiver`] alternatives. APDU and control opt-ins are
29//! independent. Routers process network messages inline; their control receiver
30//! gets only the Reject-Message-To-Network messages addressed to the router
31//! itself, and every closed admission attempt counts as Closed without
32//! disabling the stream. [`priority_channel`](crate::priority_channel) is not
33//! used by these ingress paths.
34//!
35//! ## Source keys and drop precedence
36//!
37//! Tracked APDU queues cap each key at **16 queued, unconsumed APDUs**:
38//! - NetworkLayer: the complete transport-native [`ReceivedApdu::source_mac`]
39//!   byte value. Its single transport needs no separate port key.
40//! - Router local delivery: ([ingress port network number](ReceivedApdu::ingress_network),
41//!   [`source_mac`](ReceivedApdu::source_mac)). Identical MAC bytes on different
42//!   ports have separate quotas. Neither key uses routed NPDU SNET/SADR
43//!   ([`source_network`](ReceivedApdu::source_network)) or authenticates a peer.
44//!
45//! Each successful [`recv`](AdmissionReceiver::recv) or
46//! [`try_recv`](AdmissionReceiver::try_recv) releases a queue slot and, for
47//! tracked APDUs, that key's quota slot before returning the item. Retaining or
48//! processing the returned item does not occupy either slot. Sixteen keys at
49//! their quota fill the 256-item queue; a key below quota can still encounter
50//! global Full. This is a queued-item cap, not a byte/rate limit or a delivery
51//! guarantee. Raw receivers and control queues have no per-source quota.
52//!
53//! Drop attribution is **Closed > fairness > Full**: closed admission wins;
54//! otherwise a tracked APDU whose key already holds 16 items is a fairness drop,
55//! even if the queue also holds 256 items; otherwise global saturation is Full.
56//! Only the winning reason is counted. Every admission drop releases the
57//! arriving item's owned payload, metadata and any reply sender without sending
58//! reply bytes or generating a wire rejection. This does not suppress ordinary
59//! router forwarding/reject behavior or change transport no-reply handling.
60//!
61//! ## Close, drop, stop, and observation
62//!
63//! Closing either receiver kind prevents further admission but retains queued
64//! items for draining. Dropping a receiver discards them and releases their
65//! payloads, metadata and reply senders without sending reply bytes. For tracked
66//! receivers, close preserves depth until dequeued; drop sets depth to zero.
67//! Neither operation resets high-water/drop totals, and discarding queued items
68//! is not an admission drop.
69//!
70//! NetworkLayer ends its dispatcher on the first APDU admission attempt after
71//! that receiver closes/drops, also ending control delivery. A closed/dropped
72//! control receiver instead disables only that stream on the first subsequent
73//! control admission attempt. Later discarded controls (like controls received
74//! without either opt-in) are not admission attempts. Router forwarding and
75//! inline control handling continue after local delivery closes/drops; each
76//! subsequent local admission attempt is counted as Closed.
77//!
78//! [`NetworkLayer::stop`] after start and [`BACnetRouter::stop`](crate::router::BACnetRouter::stop)
79//! end dispatch without waiting for application queues to drain; queued items
80//! remain drainable. Stopping is not an admission drop. An independently owned
81//! [`QueueAdmissionCounters`] handle remains readable after the receiver, layer,
82//! or router is dropped; it keeps accounting alive, not the channel or tasks.
83//! [`snapshot`](QueueAdmissionCounters::snapshot) is consistent for one queue,
84//! not atomic across queues, and exposes counts only, not payloads, per-source
85//! identities or per-source totals. Its three drop totals saturate at `u64::MAX`.
86
87use crate::network_number::LocalNetworkNumber;
88use bacnet_encoding::npdu::{encode_npdu, Npdu, NpduAddress};
89use bacnet_transport::port::{
90    DataAttribute, GroupDestinations, TransportPort, TransportProvenance,
91};
92use bacnet_types::enums::{NetworkPriority, PduType};
93use bacnet_types::error::Error;
94use bacnet_types::MacAddr;
95use bytes::{Bytes, BytesMut};
96use std::sync::atomic::{AtomicU64, Ordering};
97use std::sync::Arc;
98use tokio::sync::oneshot;
99use tokio::task::JoinHandle;
100
101#[path = "layer_issuance.rs"]
102mod issuance;
103pub use issuance::IssuedApdu;
104
105#[path = "layer_admission.rs"]
106mod admission;
107pub(crate) use admission::AdmissionSender;
108pub use admission::{AdmissionReceiver, QueueAdmissionCounters, QueueAdmissionSnapshot};
109
110/// A received APDU with source addressing information.
111pub struct ReceivedApdu {
112    /// Raw APDU bytes.
113    pub apdu: Bytes,
114    /// Source MAC address in transport-native format (claimed identity; see
115    /// [`TransportProvenance`]. On SC this is the source VMAC, not a
116    /// certificate principal).
117    pub source_mac: MacAddr,
118    /// BACnet network number of the router ingress port at admission time.
119    ///
120    /// Router deliveries set this to `Some`, independently of the routed source
121    /// address. Non-router [`NetworkLayer`] deliveries use `None` because the
122    /// layer owns one transport without an assigned ingress network number.
123    /// Both raw and tracked deliveries carry this metadata. For
124    /// [`RouterOptions::track_admission`](crate::router::RouterOptions::track_admission),
125    /// it scopes [`source_mac`](Self::source_mac) to the ingress port for the
126    /// 16-queued-APDU quota; [`source_network`](Self::source_network) is not part
127    /// of that key. See the [receive-queue contract](self#receive-queue-admission).
128    pub ingress_network: Option<u16>,
129    /// Source network address if the APDU was routed (NPDU had source field).
130    /// Claimed SNET/SADR, never a credential (RB-07 compat mode: decisions
131    /// unchanged; RB-09 consumes provenance later).
132    pub source_network: Option<NpduAddress>,
133    /// Whether the NPDU arrived through a data-link multicast or broadcast.
134    ///
135    /// This preserves raw data-link provenance independently of [`Self::is_group`].
136    pub link_layer_group: bool,
137    /// Whether the APDU's effective BACnet destination was multicast or broadcast.
138    ///
139    /// A specific DNET/DADR remains a unicast even when a router used a
140    /// group data-link destination to reach that remote device.
141    pub is_group: bool,
142    /// Whether the NPDU named the global broadcast network (DNET 65535).
143    ///
144    /// A global broadcast is also a group delivery, but [`Self::is_group`]
145    /// alone cannot tell it from a local or one-network broadcast once a
146    /// router has passed it on. A Notification Forwarder ignores event
147    /// notifications that arrive this way (Clause 12.51).
148    pub global_broadcast: bool,
149    /// Data-link attributes associated with the NPDU, if the transport supplied any.
150    pub data_attributes: Vec<DataAttribute>,
151    /// Honest transport + origin provenance, immutable by value (RB-07).
152    /// Threaded from [`bacnet_transport::port::ReceivedNpdu`]; cloning
153    /// preserves the meaning and never duplicates reply authority. Compat
154    /// mode: forwarding/learning/admission decisions ignore it.
155    pub provenance: TransportProvenance,
156    /// Sealed route back to this envelope's original verified direct socket.
157    pub direct_response: Option<bacnet_transport::port::DirectResponse>,
158    /// Optional reply channel for MS/TP DataExpectingReply flows.
159    /// The application layer can send NPDU-wrapped reply bytes through this channel.
160    /// An APDU dropped at queue admission releases this sender without sending
161    /// bytes, just as when the application receiver is closed. The transport
162    /// observes channel closure and retains its existing no-reply handling.
163    pub reply_tx: Option<oneshot::Sender<Bytes>>,
164}
165
166/// A decoded network-layer control message received from a transport peer.
167///
168/// Non-router users opt in to this stream before [`NetworkLayer::start`].
169/// Without that opt-in, network messages retain their historical discard/log
170/// behavior. A router opts in with
171/// [`RouterOptions::network_control_receiver`](crate::router::RouterOptions::network_control_receiver),
172/// and its stream carries only the rejects addressed to the router itself.
173#[derive(Clone)]
174pub struct ReceivedNetworkControl {
175    /// Decoded NPDU, including network-message type and typed-address fields.
176    pub npdu: Npdu,
177    /// Immediate transport peer that sent the message (claimed identity; see
178    /// [`TransportProvenance`]).
179    pub source_mac: MacAddr,
180    /// Whether the data-link delivery was multicast or broadcast.
181    pub link_layer_group: bool,
182    /// Data-link attributes supplied by the transport.
183    pub data_attributes: Vec<DataAttribute>,
184    /// Honest transport + origin provenance, immutable by value (RB-07).
185    /// Compat mode: decisions unchanged.
186    pub provenance: TransportProvenance,
187    /// Monotonic decoded-ingress sequence assigned before channel delivery.
188    ///
189    /// A consumer can compare this with [`NetworkLayer::network_control_ingress_sequence`]
190    /// when activating state so controls already queued at that point cannot
191    /// be mistaken for feedback about the new state.
192    pub ingress_sequence: u64,
193}
194
195impl std::fmt::Debug for ReceivedNetworkControl {
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        // Redacted like ReceivedApdu: lengths only, no key material.
198        f.debug_struct("ReceivedNetworkControl")
199            .field("npdu", &self.npdu)
200            .field("source_mac_len", &self.source_mac.len())
201            .field("link_layer_group", &self.link_layer_group)
202            .field("data_attributes", &self.data_attributes)
203            .field("provenance", &self.provenance)
204            .field("ingress_sequence", &self.ingress_sequence)
205            .finish_non_exhaustive()
206    }
207}
208
209impl Clone for ReceivedApdu {
210    fn clone(&self) -> Self {
211        Self {
212            apdu: self.apdu.clone(),
213            source_mac: self.source_mac.clone(),
214            ingress_network: self.ingress_network,
215            source_network: self.source_network.clone(),
216            link_layer_group: self.link_layer_group,
217            is_group: self.is_group,
218            global_broadcast: self.global_broadcast,
219            data_attributes: self.data_attributes.clone(),
220            provenance: self.provenance,
221            direct_response: self.direct_response.clone(),
222            reply_tx: None,
223        }
224    }
225}
226
227impl std::fmt::Debug for ReceivedApdu {
228    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229        // Redacted: MAC bytes, routed source bytes, and payload lengths only;
230        // no key material. Follows the TimeSyncSourceRestriction
231        // finish_non_exhaustive pattern.
232        let source_network_len = self
233            .source_network
234            .as_ref()
235            .map(|source| (source.network, source.mac_address.len()));
236        f.debug_struct("ReceivedApdu")
237            .field("apdu_len", &self.apdu.len())
238            .field("source_mac_len", &self.source_mac.len())
239            .field("ingress_network", &self.ingress_network)
240            .field("source_network", &source_network_len)
241            .field("link_layer_group", &self.link_layer_group)
242            .field("is_group", &self.is_group)
243            .field("global_broadcast", &self.global_broadcast)
244            .field("data_attributes", &self.data_attributes)
245            .field("provenance", &self.provenance)
246            .field("reply_tx", &self.reply_tx.as_ref().map(|_| "Some(...)"))
247            .finish_non_exhaustive()
248    }
249}
250
251/// Whether an NPDU destination names the global broadcast network. Ingress
252/// has already dropped one that also carries a DADR (#1379), so on a
253/// delivered NPDU this is a plain global broadcast.
254pub(crate) fn is_global_broadcast(destination: Option<&NpduAddress>) -> bool {
255    destination.is_some_and(|destination| destination.network == 0xFFFF)
256}
257
258/// Refuse a caller-supplied DNET that a send naming one network cannot take.
259/// Network numbers start at 1 (Clause 6.2.2.1); the local network is reached
260/// with no DNET at all, and `local_form` tells the caller how to do that.
261/// DNET 0xFFFF selects every device on every network (Clauses 6.1 and
262/// 6.3.2), and its sender puts it on its own network with the broadcast MAC
263/// so that each router there can pass it on. Only
264/// [`NetworkLayer::broadcast_global_apdu`] sends it that way, so every send
265/// that checks here refuses DNET 0xFFFF, with or without a DADR (#1340,
266/// #1380), and points to that method.
267pub(crate) fn check_destination(network: u16, local_form: &str) -> Result<(), Error> {
268    match network {
269        0 => Err(Error::Encoding(format!(
270            "dest_network 0 is not a network number; {local_form}"
271        ))),
272        0xFFFF => Err(Error::Encoding(
273            "dest_network 0xFFFF is the global broadcast; \
274             use broadcast_global_apdu for a global broadcast"
275                .into(),
276        )),
277        _ => Ok(()),
278    }
279}
280
281/// Refuse an APDU that a broadcast may not carry, before anything is encoded
282/// or sent. Clause 6.3 lets only an Unconfirmed-Request PDU go to a broadcast
283/// network address, whether local, remote or global: any other PDU names one
284/// peer's transaction, and every device reached would have to answer it or
285/// act on it (#1479). `what` names the broadcast for the error, which also
286/// names the PDU type the APDU's first octet gives (Clause 20.1).
287pub(crate) fn check_broadcast_apdu(apdu: &[u8], what: &str) -> Result<(), Error> {
288    let pdu_type = match apdu.first() {
289        Some(&first) if PduType::from_raw(first >> 4) == PduType::UNCONFIRMED_REQUEST => {
290            return Ok(())
291        }
292        Some(&first) => format!("PDU type {}", PduType::from_raw(first >> 4)),
293        None => "an empty APDU".to_string(),
294    };
295    Err(Error::Encoding(format!(
296        "{what} carries only an UNCONFIRMED_REQUEST APDU (Clause 6.3), not {pdu_type}; \
297         send anything else to one device"
298    )))
299}
300
301pub(crate) fn is_group_delivery(link_layer_group: bool, destination: Option<&NpduAddress>) -> bool {
302    match destination {
303        None => link_layer_group,
304        Some(destination) => destination.network == 0xFFFF || destination.mac_address.is_empty(),
305    }
306}
307
308/// A remote device reached through a local router.
309#[derive(Debug, Clone, Copy)]
310pub struct RoutedTarget<'a> {
311    /// Destination network number written to the NPDU DNET.
312    pub network: u16,
313    /// Destination MAC on that network (NPDU DADR).
314    pub mac: &'a [u8],
315    /// Next-hop router MAC on the local network (the link-layer destination).
316    pub router_mac: &'a [u8],
317}
318
319/// Non-router BACnet network layer.
320///
321/// Wraps a [`TransportPort`] and provides APDU-level send/receive by handling
322/// NPDU framing. This layer does not act as a router (it does not forward
323/// messages between networks), but it can send to remote devices through
324/// local routers using NPDU destination addressing.
325/// See the [receive-queue contract](self#receive-queue-admission) for raw/tracked
326/// receiver choices, admission limits, drop attribution and lifecycle.
327/// An inbound frame from a link-layer source MAC longer than
328/// [`NpduAddress::MAX_MAC_LEN`], or whose DLEN or SLEN is past it, is dropped
329/// before admission and counted by [`Self::address_length_drops`]; one whose
330/// DNET 0xFFFF carries a DADR is dropped and counted by
331/// [`Self::global_broadcast_dadr_drops`], and a broadcast whose APDU isn't an
332/// Unconfirmed-Request by [`Self::broadcast_pdu_type_drops`].
333pub struct NetworkLayer<T: TransportPort> {
334    transport: T,
335    response_scope: bacnet_transport::port::DirectResponseScope,
336    dispatch_task: Option<JoinHandle<()>>,
337    network_control_tx: Option<AdmissionSender<ReceivedNetworkControl>>,
338    network_control_ingress_sequence: Arc<AtomicU64>,
339    address_length_drops: Arc<AtomicU64>,
340    global_broadcast_dadr_drops: Arc<AtomicU64>,
341    broadcast_pdu_type_drops: Arc<AtomicU64>,
342    local_network_number: LocalNetworkNumber,
343    /// The group destinations the transport's local broadcast stands for,
344    /// taken when the layer is made and again when the transport starts and
345    /// knows its own addresses, so a send never calls back into the
346    /// transport to judge its destination.
347    local_broadcasts: GroupDestinations,
348}
349
350impl<T: TransportPort + 'static> NetworkLayer<T> {
351    /// Create a new network layer wrapping the given transport.
352    pub fn new(transport: T) -> Self {
353        Self {
354            local_broadcasts: transport.local_broadcast_destinations(),
355            transport,
356            response_scope: Default::default(),
357            dispatch_task: None,
358            network_control_tx: None,
359            network_control_ingress_sequence: Arc::new(AtomicU64::new(0)),
360            address_length_drops: Arc::new(AtomicU64::new(0)),
361            global_broadcast_dadr_drops: Arc::new(AtomicU64::new(0)),
362            broadcast_pdu_type_drops: Arc::new(AtomicU64::new(0)),
363            local_network_number: LocalNetworkNumber::default(),
364        }
365    }
366
367    /// Send an APDU to a specific local destination by MAC address.
368    ///
369    /// With no DNET, the link's broadcast MAC or any other group address
370    /// ([`TransportPort::is_group_destination`]) makes this a local
371    /// broadcast, which may carry only an Unconfirmed-Request APDU (Clause
372    /// 6.3).
373    ///
374    /// An Unconfirmed-Request to one of
375    /// [`TransportPort::local_broadcast_destinations`] goes out as the
376    /// link's local broadcast, through `send_broadcast`, since B/IP refuses a
377    /// unicast to a group address (#1677). On B/IP those are the groups at
378    /// the link's own UDP port. The layer takes that rule when it is made
379    /// and when it starts, so no send calls back into the transport; a
380    /// transport that gives no rule has every send go as addressed.
381    ///
382    /// Anything else goes to the transport as given, and B/IP refuses a
383    /// group address there. This layer doesn't refuse it itself, so that is
384    /// the caller's to keep: `BACnetClient`, the endpoint's egress and its
385    /// requester, which take caller-chosen MACs, refuse anything but an
386    /// Unconfirmed-Request to one (#1479). The server refuses one for the
387    /// confirmed requests it starts itself, to event recipients, Channel
388    /// and Command targets, audit recipients and bound devices (#1493). Its
389    /// replies and COV notifications go to the source a request came from,
390    /// and the server, the client and the endpoint ignore a confirmed
391    /// request from a group source MAC (#1504), so none of those goes to a
392    /// group on this link. A routed request's SADR is another network's to
393    /// judge: a reply to a group SADR is dropped only where the final router
394    /// is a [`BACnetRouter`](crate::router::BACnetRouter).
395    pub async fn send_apdu(
396        &self,
397        apdu: &[u8],
398        destination_mac: &[u8],
399        expecting_reply: bool,
400        priority: NetworkPriority,
401    ) -> Result<(), Error> {
402        self.send_apdu_with_data_attributes(apdu, destination_mac, expecting_reply, priority, &[])
403            .await
404    }
405
406    /// Send an APDU with data attributes to a specific local destination.
407    pub async fn send_apdu_with_data_attributes(
408        &self,
409        apdu: &[u8],
410        destination_mac: &[u8],
411        expecting_reply: bool,
412        priority: NetworkPriority,
413        data_attributes: &[DataAttribute],
414    ) -> Result<(), Error> {
415        let npdu = Npdu {
416            is_network_message: false,
417            expecting_reply,
418            priority,
419            destination: None,
420            source: None,
421            payload: Bytes::copy_from_slice(apdu),
422            ..Npdu::default()
423        };
424
425        let mut buf = BytesMut::with_capacity(2 + apdu.len());
426        encode_npdu(&mut buf, &npdu)?;
427
428        // An Unconfirmed-Request for a group address of this link is this
429        // link's local broadcast, so it goes out as one: B/IP refuses a
430        // unicast to a group address (#1677). The rule is the one the
431        // transport gave at start, so no send calls back into it.
432        let unconfirmed = apdu
433            .first()
434            .is_some_and(|&first| PduType::from_raw(first >> 4) == PduType::UNCONFIRMED_REQUEST);
435        if unconfirmed && self.local_broadcasts.contains(destination_mac) {
436            return self
437                .transport
438                .send_broadcast_with_data_attributes(&buf, data_attributes)
439                .await;
440        }
441        self.transport
442            .send_unicast_with_data_attributes(&buf, destination_mac, data_attributes)
443            .await
444    }
445
446    /// Broadcast an APDU on the local network. Anything but an
447    /// Unconfirmed-Request APDU is refused before it is sent (Clause 6.3,
448    /// #1479).
449    pub async fn broadcast_apdu(
450        &self,
451        apdu: &[u8],
452        expecting_reply: bool,
453        priority: NetworkPriority,
454    ) -> Result<(), Error> {
455        self.broadcast_apdu_with_data_attributes(apdu, expecting_reply, priority, &[])
456            .await
457    }
458
459    /// Broadcast an APDU with data attributes on the local network.
460    pub async fn broadcast_apdu_with_data_attributes(
461        &self,
462        apdu: &[u8],
463        expecting_reply: bool,
464        priority: NetworkPriority,
465        data_attributes: &[DataAttribute],
466    ) -> Result<(), Error> {
467        check_broadcast_apdu(apdu, "a local broadcast")?;
468        let npdu = Npdu {
469            is_network_message: false,
470            expecting_reply,
471            priority,
472            destination: None,
473            source: None,
474            payload: Bytes::copy_from_slice(apdu),
475            ..Npdu::default()
476        };
477
478        let mut buf = BytesMut::with_capacity(2 + apdu.len());
479        encode_npdu(&mut buf, &npdu)?;
480
481        self.transport
482            .send_broadcast_with_data_attributes(&buf, data_attributes)
483            .await
484    }
485
486    /// Broadcast an APDU globally (DNET=0xFFFF, hop_count=255).
487    ///
488    /// Unlike `broadcast_apdu()` which only reaches the local subnet, this
489    /// sets DNET=0xFFFF so routers will forward to all reachable networks.
490    /// Anything but an Unconfirmed-Request APDU is refused before it is sent
491    /// (Clause 6.3, #1479).
492    pub async fn broadcast_global_apdu(
493        &self,
494        apdu: &[u8],
495        expecting_reply: bool,
496        priority: NetworkPriority,
497    ) -> Result<(), Error> {
498        self.broadcast_global_apdu_with_data_attributes(apdu, expecting_reply, priority, &[])
499            .await
500    }
501
502    /// Broadcast an APDU globally with data attributes (DNET=0xFFFF, hop_count=255).
503    pub async fn broadcast_global_apdu_with_data_attributes(
504        &self,
505        apdu: &[u8],
506        expecting_reply: bool,
507        priority: NetworkPriority,
508        data_attributes: &[DataAttribute],
509    ) -> Result<(), Error> {
510        check_broadcast_apdu(apdu, "a global broadcast")?;
511        let npdu = Npdu {
512            is_network_message: false,
513            expecting_reply,
514            priority,
515            destination: Some(NpduAddress {
516                network: 0xFFFF,
517                mac_address: MacAddr::new(),
518            }),
519            source: None,
520            hop_count: 255,
521            payload: Bytes::copy_from_slice(apdu),
522            ..Npdu::default()
523        };
524
525        let mut buf = BytesMut::with_capacity(8 + apdu.len());
526        encode_npdu(&mut buf, &npdu)?;
527        self.transport
528            .send_broadcast_with_data_attributes(&buf, data_attributes)
529            .await
530    }
531
532    /// Broadcast an APDU to a specific remote network via routers.
533    ///
534    /// Like `broadcast_global_apdu()` but targets a single network number
535    /// instead of all networks (DNET=0xFFFF). Refuses `dest_network` 0 and
536    /// 0xFFFF, and anything but an Unconfirmed-Request APDU (Clause 6.3,
537    /// #1479), without sending anything. This is the one send that puts a
538    /// remote network's broadcast on the link's broadcast MAC;
539    /// [`Self::send_apdu_routed`] with an empty `dest_mac` sends it to a
540    /// known router instead.
541    pub async fn broadcast_to_network(
542        &self,
543        apdu: &[u8],
544        dest_network: u16,
545        expecting_reply: bool,
546        priority: NetworkPriority,
547    ) -> Result<(), Error> {
548        self.broadcast_to_network_with_data_attributes(
549            apdu,
550            dest_network,
551            expecting_reply,
552            priority,
553            &[],
554        )
555        .await
556    }
557
558    /// Broadcast an APDU with data attributes to a specific remote network via routers.
559    pub async fn broadcast_to_network_with_data_attributes(
560        &self,
561        apdu: &[u8],
562        dest_network: u16,
563        expecting_reply: bool,
564        priority: NetworkPriority,
565        data_attributes: &[DataAttribute],
566    ) -> Result<(), Error> {
567        check_destination(dest_network, "use broadcast_apdu for the local network")?;
568        check_broadcast_apdu(apdu, "a broadcast to one network")?;
569        let npdu = Npdu {
570            is_network_message: false,
571            expecting_reply,
572            priority,
573            destination: Some(NpduAddress {
574                network: dest_network,
575                mac_address: MacAddr::new(),
576            }),
577            source: None,
578            hop_count: 255,
579            payload: Bytes::copy_from_slice(apdu),
580            ..Npdu::default()
581        };
582
583        let mut buf = BytesMut::with_capacity(8 + apdu.len());
584        encode_npdu(&mut buf, &npdu)?;
585        self.transport
586            .send_broadcast_with_data_attributes(&buf, data_attributes)
587            .await
588    }
589
590    /// Send an APDU to a remote device through a local router.
591    ///
592    /// The NPDU is sent via unicast to `router_mac` (the next-hop router on
593    /// the local network), but the NPDU header addresses the final destination
594    /// with `dest_network` / `dest_mac`. Refuses `dest_network` 0 and 0xFFFF
595    /// without sending anything; this form and
596    /// [`Self::send_apdu_routed_via_local_broadcast`] share that check. A
597    /// global broadcast unicast to one router would reach only that router's
598    /// networks, so it goes through [`Self::broadcast_global_apdu`] instead.
599    ///
600    /// An empty `dest_mac` (DLEN 0) asks the router to broadcast on
601    /// `dest_network`, a remote broadcast, so it carries only an
602    /// Unconfirmed-Request APDU (Clause 6.3); anything else is refused
603    /// without sending anything (#1479).
604    pub async fn send_apdu_routed(
605        &self,
606        apdu: &[u8],
607        dest_network: u16,
608        dest_mac: &[u8],
609        router_mac: &[u8],
610        expecting_reply: bool,
611        priority: NetworkPriority,
612    ) -> Result<(), Error> {
613        self.send_apdu_routed_with_data_attributes(
614            apdu,
615            RoutedTarget {
616                network: dest_network,
617                mac: dest_mac,
618                router_mac,
619            },
620            expecting_reply,
621            priority,
622            &[],
623        )
624        .await
625    }
626
627    /// Send an APDU with data attributes to a remote device through a local router.
628    pub async fn send_apdu_routed_with_data_attributes(
629        &self,
630        apdu: &[u8],
631        target: RoutedTarget<'_>,
632        expecting_reply: bool,
633        priority: NetworkPriority,
634        data_attributes: &[DataAttribute],
635    ) -> Result<(), Error> {
636        check_destination(target.network, "use send_apdu for a local device")?;
637        if target.mac.is_empty() {
638            check_broadcast_apdu(
639                apdu,
640                "a routed send with an empty dest_mac (a remote broadcast)",
641            )?;
642        }
643        let buf = Self::encode_routed_npdu_buf(
644            apdu,
645            target.network,
646            target.mac,
647            expecting_reply,
648            priority,
649        )?;
650        self.transport
651            .send_unicast_with_data_attributes(&buf, target.router_mac, data_attributes)
652            .await
653    }
654
655    /// Send a routed APDU with a broadcast link DA, for when the next-hop
656    /// router's MAC is unknown.
657    ///
658    /// Clause 6.5.3 normally addresses the frame to the MAC of the router
659    /// that serves DNET, and falls back to a link broadcast while that
660    /// router has not been located yet. DNET/DADR still select a single
661    /// device in the NPDU. Clause 6.3 reserves broadcast network addresses
662    /// for Unconfirmed-Request PDUs, but that limits the network-layer
663    /// address, not the link DA: a link broadcast carrying a one-device
664    /// DNET/DADR is fine for any PDU type, so this send form stays
665    /// conformant. DNET 0xFFFF would make it a global broadcast, which this
666    /// form doesn't mean, so it refuses that DNET as [`Self::send_apdu_routed`]
667    /// does. An empty `dest_mac` would make it a broadcast on `dest_network`,
668    /// which [`Self::broadcast_to_network`] already sends, so this form
669    /// refuses it too and points there, leaving one way to send a remote
670    /// broadcast with the link's broadcast MAC (#1479).
671    pub async fn send_apdu_routed_via_local_broadcast(
672        &self,
673        apdu: &[u8],
674        dest_network: u16,
675        dest_mac: &[u8],
676        expecting_reply: bool,
677        priority: NetworkPriority,
678    ) -> Result<(), Error> {
679        self.send_apdu_routed_via_local_broadcast_with_data_attributes(
680            apdu,
681            dest_network,
682            dest_mac,
683            expecting_reply,
684            priority,
685            &[],
686        )
687        .await
688    }
689
690    /// Send a routed APDU with data attributes and a broadcast link DA.
691    pub async fn send_apdu_routed_via_local_broadcast_with_data_attributes(
692        &self,
693        apdu: &[u8],
694        dest_network: u16,
695        dest_mac: &[u8],
696        expecting_reply: bool,
697        priority: NetworkPriority,
698        data_attributes: &[DataAttribute],
699    ) -> Result<(), Error> {
700        check_destination(dest_network, "use send_apdu for a local device")?;
701        if dest_mac.is_empty() {
702            return Err(Error::Encoding(format!(
703                "an empty dest_mac addresses the broadcast on network {dest_network}, \
704                 not one device; use broadcast_to_network for a remote broadcast"
705            )));
706        }
707        let buf =
708            Self::encode_routed_npdu_buf(apdu, dest_network, dest_mac, expecting_reply, priority)?;
709        self.transport
710            .send_broadcast_with_data_attributes(&buf, data_attributes)
711            .await
712    }
713
714    /// Attach selected-object protection before starting the owned transport.
715    #[doc(hidden)]
716    pub fn retain_network_port_lease_internal(&mut self, lease: Arc<()>) -> Result<(), Error> {
717        self.transport.retain_network_port_lease_internal(lease)
718    }
719
720    /// Access the underlying transport.
721    ///
722    /// Useful for transport-specific operations like BBMD registration
723    /// after the network layer has been started.
724    pub fn transport(&self) -> &T {
725        &self.transport
726    }
727
728    /// Get the transport's local MAC address.
729    pub fn local_mac(&self) -> &[u8] {
730        self.transport.local_mac()
731    }
732
733    /// The number of the network this layer's one port is attached to, as
734    /// the owner of its Number controls last published it, and unknown until
735    /// then. The full server and the standalone client publish into their own
736    /// layers internally; a stack built directly on this layer publishes its
737    /// own, and can clone the handle to publish from another task.
738    pub fn local_network_number(&self) -> &LocalNetworkNumber {
739        &self.local_network_number
740    }
741
742    /// Sequence assigned to the most recent opted-in control admission attempt.
743    ///
744    /// The value advances before admission, including Full and Closed drops,
745    /// but not for controls discarded without an enabled stream or after its
746    /// first Closed drop. See the [receive-queue contract](self#receive-queue-admission).
747    /// At counter exhaustion it remains at `u64::MAX`, which makes sequence-based
748    /// consumers fail closed rather than accepting an alias.
749    pub fn network_control_ingress_sequence(&self) -> u64 {
750        self.network_control_ingress_sequence.load(Ordering::SeqCst)
751    }
752
753    /// Inbound NPDUs dropped since this layer was created because an address
754    /// in them was past [`NpduAddress::MAX_MAC_LEN`]: their DLEN or SLEN
755    /// (#1141), or the link-layer source MAC the transport reported (#1198).
756    /// Saturates at `u64::MAX`.
757    ///
758    /// Only routers answer an NPDU with Reject-Message-To-Network (Clause
759    /// 6.6.3.5), so this non-router layer discards the NPDU and counts it.
760    /// The NPDU reaches neither the APDU nor the control receiver, so it
761    /// never shows up in their admission counters. No built-in transport
762    /// reports a source MAC that long, so a nonzero count with well-formed
763    /// traffic points at a custom transport.
764    pub fn address_length_drops(&self) -> u64 {
765        self.address_length_drops.load(Ordering::Relaxed)
766    }
767
768    /// Inbound NPDUs dropped since this layer was created because their DNET
769    /// was 0xFFFF and they also carried a DADR (#1379). Saturates at
770    /// `u64::MAX`.
771    ///
772    /// DNET 0xFFFF already names every device on every network (Clauses
773    /// 6.2.2 and 6.3.2), so a DADR beside it contradicts it. Such an NPDU
774    /// reaches neither the APDU nor the control receiver, so it never shows
775    /// up in their admission counters. It is counted here rather than in
776    /// [`Self::address_length_drops`], whose addresses are too long to hold:
777    /// this one is well formed, only contradictory, and points at the peer
778    /// that sent it.
779    pub fn global_broadcast_dadr_drops(&self) -> u64 {
780        self.global_broadcast_dadr_drops.load(Ordering::Relaxed)
781    }
782
783    /// Inbound NPDUs dropped since this layer was created because they were
784    /// addressed to a broadcast, global (DNET 0xFFFF) or remote (a DNET with
785    /// DLEN 0), and carried an APDU other than an Unconfirmed-Request
786    /// (#1491). Saturates at `u64::MAX`.
787    ///
788    /// Only an Unconfirmed-Request may go to a broadcast network address
789    /// (Clause 6.3), so this layer hands no other APDU sent that way to the
790    /// application, and the NPDU never shows up in the receivers' admission
791    /// counters. A remote broadcast, which a non-router discards anyway, is
792    /// counted here too when its APDU is of another type. The PDU type comes
793    /// from the first APDU octet, with no decode; network messages, and an
794    /// APDU sent with no DNET, are not affected.
795    pub fn broadcast_pdu_type_drops(&self) -> u64 {
796        self.broadcast_pdu_type_drops.load(Ordering::Relaxed)
797    }
798
799    /// Encode an APDU into an NPDU whose destination is `dest_network` /
800    /// `dest_mac`, ready for whichever link send the caller chooses. Each
801    /// caller checks the DNET, and what an empty `dest_mac` may carry, first.
802    fn encode_routed_npdu_buf(
803        apdu: &[u8],
804        dest_network: u16,
805        dest_mac: &[u8],
806        expecting_reply: bool,
807        priority: NetworkPriority,
808    ) -> Result<BytesMut, Error> {
809        let npdu = Npdu {
810            is_network_message: false,
811            expecting_reply,
812            priority,
813            destination: Some(NpduAddress {
814                network: dest_network,
815                mac_address: MacAddr::from_slice(dest_mac),
816            }),
817            source: None,
818            hop_count: 255,
819            payload: Bytes::copy_from_slice(apdu),
820            ..Npdu::default()
821        };
822        let mut buf = BytesMut::with_capacity(8 + dest_mac.len() + apdu.len());
823        encode_npdu(&mut buf, &npdu)?;
824        Ok(buf)
825    }
826
827    /// Stop the network layer and underlying transport.
828    ///
829    /// After start, aborts and awaits dispatch before stopping the transport;
830    /// it does not wait for consumers, even with full APDU/control queues.
831    /// Queued items remain drainable, retaining their reply senders until
832    /// consumed or the receiver is dropped. Stop does not count as an admission
833    /// drop or clear depth/high-water/drop totals. See the
834    /// [receive-queue contract](self#receive-queue-admission).
835    pub async fn stop(&mut self) -> Result<(), Error> {
836        self.seal_responses();
837        if let Some(task) = self.abort_dispatch_task() {
838            let _ = task.await;
839        }
840        self.transport.stop().await
841    }
842}
843
844/// Advance a control ingress sequence, saturating at `u64::MAX`. Shared by
845/// [`NetworkLayer`] and the router's control receiver.
846pub(crate) fn next_ingress_sequence(sequence: &AtomicU64) -> u64 {
847    #[allow(deprecated, reason = "try_update needs Rust 1.95; the MSRV is 1.93")]
848    let previous = sequence
849        .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |value| {
850            value.checked_add(1)
851        })
852        .unwrap_or(u64::MAX);
853    previous.saturating_add(1)
854}
855
856/// Count one dropped NPDU in `counter`, saturating at `u64::MAX`. Shared by
857/// [`NetworkLayer`] and the router for each of their drop counters.
858pub(crate) fn count_drop(counter: &AtomicU64) {
859    #[allow(deprecated, reason = "try_update needs Rust 1.95; the MSRV is 1.93")]
860    let _ = counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_add(1));
861}
862
863impl<T: TransportPort> NetworkLayer<T> {
864    fn abort_dispatch_task(&mut self) -> Option<JoinHandle<()>> {
865        let task = self.dispatch_task.take()?;
866        task.abort();
867        Some(task)
868    }
869}
870
871impl<T: TransportPort> Drop for NetworkLayer<T> {
872    fn drop(&mut self) {
873        self.response_scope.seal();
874        let _ = self.abort_dispatch_task();
875        self.transport.abort();
876    }
877}
878
879#[cfg(test)]
880#[path = "issuance_tests.rs"]
881mod issuance_tests;
882
883#[cfg(test)]
884mod tests {
885    use super::*;
886    use bacnet_encoding::npdu::decode_npdu;
887    use bacnet_transport::bip::BipTransport;
888    use bacnet_transport::sc::{LoopbackWebSocket, ScTransport, WebSocketPort};
889    use bacnet_transport::sc_frame::{
890        decode_sc_message, encode_sc_message, ScFunction, ScMessage, Vmac,
891    };
892    use std::net::Ipv4Addr;
893    use tokio::time::{timeout, Duration};
894
895    async fn sc_hub_accept(ws_hub: &LoopbackWebSocket, hub_vmac: Vmac) {
896        let data = ws_hub.recv().await.unwrap();
897        let req = decode_sc_message(&data).unwrap();
898        assert_eq!(req.function, ScFunction::ConnectRequest);
899
900        let mut accept_payload = Vec::with_capacity(26);
901        accept_payload.extend_from_slice(&hub_vmac);
902        accept_payload.extend_from_slice(&[0x33; 16]);
903        accept_payload.extend_from_slice(&1476u16.to_be_bytes());
904        accept_payload.extend_from_slice(&1476u16.to_be_bytes());
905
906        let accept = ScMessage {
907            function: ScFunction::ConnectAccept,
908            message_id: req.message_id,
909            originating_vmac: None,
910            destination_vmac: None,
911            dest_options: Vec::new(),
912            data_options: Vec::new(),
913            payload: Bytes::from(accept_payload),
914        };
915        let mut buf = BytesMut::new();
916        encode_sc_message(&mut buf, &accept);
917        ws_hub.send(&buf).await.unwrap();
918    }
919
920    async fn assert_sc_socket_closed_after_drop(ws_hub: &LoopbackWebSocket, context: &str) {
921        timeout(Duration::from_secs(1), async {
922            while let Ok(data) = ws_hub.recv().await {
923                let msg = decode_sc_message(&data).unwrap();
924                assert_ne!(
925                    msg.function,
926                    ScFunction::HeartbeatAck,
927                    "{context} must not leave SC answering heartbeats"
928                );
929            }
930        })
931        .await
932        .unwrap_or_else(|_| panic!("{context} did not close the SC WebSocket"));
933
934        let heartbeat = ScMessage {
935            function: ScFunction::HeartbeatRequest,
936            message_id: 0x66,
937            originating_vmac: None,
938            destination_vmac: None,
939            dest_options: Vec::new(),
940            data_options: Vec::new(),
941            payload: Bytes::new(),
942        };
943        let mut buf = BytesMut::new();
944        encode_sc_message(&mut buf, &heartbeat);
945        assert!(
946            ws_hub.send(&buf).await.is_err(),
947            "{context} must reject post-drop Heartbeat-Request on the closed socket"
948        );
949    }
950
951    #[tokio::test]
952    async fn network_layer_drop_releases_sc_transport_socket() {
953        let (ws_client, ws_hub) = LoopbackWebSocket::pair();
954        let hub_vmac = [0x10; 6];
955        let mut net =
956            NetworkLayer::new(ScTransport::new(ws_client, [0x01; 6]).with_device_uuid([1; 16]));
957
958        let hub_accept_task = tokio::spawn(async move {
959            sc_hub_accept(&ws_hub, hub_vmac).await;
960            ws_hub
961        });
962
963        let _rx = net.start().await.unwrap();
964        let ws_hub = hub_accept_task.await.unwrap();
965
966        drop(net);
967
968        assert_sc_socket_closed_after_drop(&ws_hub, "dropped NetworkLayer").await;
969    }
970
971    #[tokio::test]
972    async fn send_apdu_data_attributes_reach_sc_data_options() {
973        let (ws_client, ws_hub) = LoopbackWebSocket::pair();
974        let hub_vmac = [0x10; 6];
975        let dest_vmac: Vmac = [0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
976        let mut net =
977            NetworkLayer::new(ScTransport::new(ws_client, [0x01; 6]).with_device_uuid([1; 16]));
978        let data_attributes = vec![
979            DataAttribute {
980                option_type: 1,
981                must_understand: true,
982                data: Vec::new(),
983            },
984            DataAttribute {
985                option_type: 31,
986                must_understand: false,
987                data: vec![0x12, 0x34, 0x56],
988            },
989        ];
990
991        let hub_accept_task = tokio::spawn(async move {
992            sc_hub_accept(&ws_hub, hub_vmac).await;
993            ws_hub
994        });
995
996        let _rx = net.start().await.unwrap();
997        let ws_hub = hub_accept_task.await.unwrap();
998
999        let apdu = Bytes::from_static(&[0x10, 0x08]);
1000        net.send_apdu_with_data_attributes(
1001            &apdu,
1002            &dest_vmac,
1003            false,
1004            NetworkPriority::NORMAL,
1005            &data_attributes,
1006        )
1007        .await
1008        .unwrap();
1009
1010        let data = ws_hub.recv().await.unwrap();
1011        let msg = decode_sc_message(&data).unwrap();
1012        assert_eq!(msg.function, ScFunction::EncapsulatedNpdu);
1013        assert_eq!(msg.destination_vmac, Some(dest_vmac));
1014        assert_eq!(msg.data_options.len(), 2);
1015        assert_eq!(msg.data_options[0].option_type, 1);
1016        assert!(msg.data_options[0].must_understand);
1017        assert_eq!(msg.data_options[1].option_type, 31);
1018        assert!(!msg.data_options[1].must_understand);
1019        assert_eq!(msg.data_options[1].data, vec![0x12, 0x34, 0x56]);
1020
1021        let npdu = decode_npdu(msg.payload).unwrap();
1022        assert_eq!(npdu.payload, apdu);
1023        assert!(!npdu.expecting_reply);
1024        assert_eq!(npdu.priority, NetworkPriority::NORMAL);
1025
1026        net.stop().await.unwrap();
1027    }
1028
1029    #[tokio::test]
1030    async fn end_to_end_who_is() {
1031        use bacnet_encoding::apdu::{decode_apdu, encode_apdu, Apdu, UnconfirmedRequest};
1032        use bacnet_types::enums::UnconfirmedServiceChoice;
1033
1034        let transport_a = BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::BROADCAST);
1035        let transport_b = BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::BROADCAST);
1036
1037        let mut net_a = NetworkLayer::new(transport_a);
1038        let mut net_b = NetworkLayer::new(transport_b);
1039
1040        let _rx_a = net_a.start().await.unwrap();
1041        let mut rx_b = net_b.start().await.unwrap();
1042
1043        let who_is_apdu = Apdu::UnconfirmedRequest(UnconfirmedRequest {
1044            service_choice: UnconfirmedServiceChoice::WHO_IS,
1045            service_request: Bytes::new(),
1046        });
1047        let mut apdu_buf = BytesMut::new();
1048        encode_apdu(&mut apdu_buf, &who_is_apdu).expect("valid APDU encoding");
1049
1050        net_a
1051            .send_apdu(&apdu_buf, net_b.local_mac(), false, NetworkPriority::NORMAL)
1052            .await
1053            .unwrap();
1054
1055        let received = timeout(Duration::from_secs(2), rx_b.recv())
1056            .await
1057            .expect("Timed out waiting for APDU")
1058            .expect("Channel closed");
1059
1060        let decoded_apdu = decode_apdu(received.apdu.clone()).unwrap();
1061        match decoded_apdu {
1062            Apdu::UnconfirmedRequest(req) => {
1063                assert_eq!(req.service_choice, UnconfirmedServiceChoice::WHO_IS);
1064                assert!(req.service_request.is_empty());
1065            }
1066            other => panic!("Expected UnconfirmedRequest, got {:?}", other),
1067        }
1068
1069        net_a.stop().await.unwrap();
1070        net_b.stop().await.unwrap();
1071    }
1072
1073    #[test]
1074    fn transport_accessor() {
1075        let transport = BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::BROADCAST);
1076        let net = NetworkLayer::new(transport);
1077        let mac = net.transport().local_mac();
1078        assert_eq!(mac.len(), 6);
1079    }
1080}
1081
1082#[cfg(test)]
1083#[path = "layer_delivery_tests.rs"]
1084mod delivery_tests;
1085
1086#[cfg(test)]
1087#[path = "layer_admission_tests.rs"]
1088mod admission_tests;
1089
1090#[cfg(test)]
1091#[path = "layer_sc_data_options_tests.rs"]
1092mod sc_data_options_tests;
1093
1094#[cfg(test)]
1095#[path = "broadcast_pdu_tests.rs"]
1096mod broadcast_pdu_tests;
1097
1098#[cfg(test)]
1099#[path = "layer_group_send_tests.rs"]
1100mod group_send_tests;