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