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
8use bacnet_encoding::npdu::{decode_npdu, encode_npdu, Npdu, NpduAddress};
9use bacnet_transport::port::{DataAttribute, TransportPort};
10use bacnet_types::enums::NetworkPriority;
11use bacnet_types::error::Error;
12use bacnet_types::MacAddr;
13use bytes::{Bytes, BytesMut};
14use std::sync::atomic::{AtomicU64, Ordering};
15use std::sync::Arc;
16use tokio::sync::{mpsc, oneshot};
17use tokio::task::JoinHandle;
18use tracing::{debug, warn};
19
20/// A received APDU with source addressing information.
21pub struct ReceivedApdu {
22    /// Raw APDU bytes.
23    pub apdu: Bytes,
24    /// Source MAC address in transport-native format.
25    pub source_mac: MacAddr,
26    /// Source network address if the APDU was routed (NPDU had source field).
27    pub source_network: Option<NpduAddress>,
28    /// Whether the NPDU arrived through a data-link multicast or broadcast.
29    ///
30    /// This preserves raw data-link provenance independently of [`Self::is_group`].
31    pub link_layer_group: bool,
32    /// Whether the APDU's effective BACnet destination was multicast or broadcast.
33    ///
34    /// A specific DNET/DADR remains a unicast even when a router used a
35    /// group data-link destination to reach that remote device.
36    pub is_group: bool,
37    /// Data-link attributes associated with the NPDU, if the transport supplied any.
38    pub data_attributes: Vec<DataAttribute>,
39    /// Optional reply channel for MS/TP DataExpectingReply flows.
40    /// The application layer can send NPDU-wrapped reply bytes through this channel.
41    pub reply_tx: Option<oneshot::Sender<Bytes>>,
42}
43
44/// A decoded network-layer control message received from a transport peer.
45///
46/// Non-router users opt in to this stream before [`NetworkLayer::start`].
47/// Without that opt-in, network messages retain their historical discard/log
48/// behavior.
49#[derive(Debug, Clone)]
50pub struct ReceivedNetworkControl {
51    /// Decoded NPDU, including network-message type and typed-address fields.
52    pub npdu: Npdu,
53    /// Immediate transport peer that sent the message.
54    pub source_mac: MacAddr,
55    /// Whether the data-link delivery was multicast or broadcast.
56    pub link_layer_group: bool,
57    /// Data-link attributes supplied by the transport.
58    pub data_attributes: Vec<DataAttribute>,
59    /// Monotonic decoded-ingress sequence assigned before channel delivery.
60    ///
61    /// A consumer can compare this with [`NetworkLayer::network_control_ingress_sequence`]
62    /// when activating state so controls already queued at that point cannot
63    /// be mistaken for feedback about the new state.
64    pub ingress_sequence: u64,
65}
66
67impl Clone for ReceivedApdu {
68    fn clone(&self) -> Self {
69        Self {
70            apdu: self.apdu.clone(),
71            source_mac: self.source_mac.clone(),
72            source_network: self.source_network.clone(),
73            link_layer_group: self.link_layer_group,
74            is_group: self.is_group,
75            data_attributes: self.data_attributes.clone(),
76            reply_tx: None,
77        }
78    }
79}
80
81impl std::fmt::Debug for ReceivedApdu {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        f.debug_struct("ReceivedApdu")
84            .field("apdu", &self.apdu)
85            .field("source_mac", &self.source_mac)
86            .field("source_network", &self.source_network)
87            .field("link_layer_group", &self.link_layer_group)
88            .field("is_group", &self.is_group)
89            .field("data_attributes", &self.data_attributes)
90            .field("reply_tx", &self.reply_tx.as_ref().map(|_| "Some(...)"))
91            .finish()
92    }
93}
94
95pub(crate) fn is_group_delivery(link_layer_group: bool, destination: Option<&NpduAddress>) -> bool {
96    match destination {
97        None => link_layer_group,
98        Some(destination) => destination.network == 0xFFFF || destination.mac_address.is_empty(),
99    }
100}
101
102/// Non-router BACnet network layer.
103///
104/// Wraps a [`TransportPort`] and provides APDU-level send/receive by handling
105/// NPDU framing. This layer does not act as a router (it does not forward
106/// messages between networks), but it can send to remote devices through
107/// local routers using NPDU destination addressing.
108pub struct NetworkLayer<T: TransportPort> {
109    transport: T,
110    dispatch_task: Option<JoinHandle<()>>,
111    network_control_tx: Option<mpsc::Sender<ReceivedNetworkControl>>,
112    network_control_ingress_sequence: Arc<AtomicU64>,
113}
114
115impl<T: TransportPort + 'static> NetworkLayer<T> {
116    /// Create a new network layer wrapping the given transport.
117    pub fn new(transport: T) -> Self {
118        Self {
119            transport,
120            dispatch_task: None,
121            network_control_tx: None,
122            network_control_ingress_sequence: Arc::new(AtomicU64::new(0)),
123        }
124    }
125
126    /// Enable the one-consumer decoded network-control stream.
127    ///
128    /// This must be called before [`Self::start`] and at most once. Dropping
129    /// the returned receiver disables control delivery without affecting APDU
130    /// ingress.
131    pub fn enable_network_control_receiver(
132        &mut self,
133    ) -> Result<mpsc::Receiver<ReceivedNetworkControl>, Error> {
134        if self.dispatch_task.is_some() {
135            return Err(Error::Encoding(
136                "network-control receiver must be enabled before NetworkLayer::start".into(),
137            ));
138        }
139        if self.network_control_tx.is_some() {
140            return Err(Error::Encoding(
141                "network-control receiver is already enabled".into(),
142            ));
143        }
144        let (tx, rx) = mpsc::channel(256);
145        self.network_control_tx = Some(tx);
146        Ok(rx)
147    }
148
149    /// Start the network layer. Returns a receiver for incoming APDUs.
150    ///
151    /// This starts the underlying transport and spawns a dispatch task that
152    /// decodes incoming NPDUs and extracts APDUs.
153    pub async fn start(&mut self) -> Result<mpsc::Receiver<ReceivedApdu>, Error> {
154        let mut npdu_rx = self.transport.start().await?;
155        let mut network_control_tx = self.network_control_tx.take();
156        let network_control_ingress_sequence = Arc::clone(&self.network_control_ingress_sequence);
157
158        let (apdu_tx, apdu_rx) = mpsc::channel(256);
159
160        let dispatch_task = tokio::spawn(async move {
161            while let Some(received) = npdu_rx.recv().await {
162                match decode_npdu(received.npdu.clone()) {
163                    Ok(npdu) => {
164                        if npdu.is_network_message {
165                            if let Some(tx) = network_control_tx.as_ref() {
166                                let ingress_sequence = next_ingress_sequence(
167                                    network_control_ingress_sequence.as_ref(),
168                                );
169                                let control = ReceivedNetworkControl {
170                                    npdu,
171                                    source_mac: received.source_mac,
172                                    link_layer_group: received.link_layer_group,
173                                    data_attributes: received.data_attributes,
174                                    ingress_sequence,
175                                };
176                                if tx.send(control).await.is_err() {
177                                    network_control_tx = None;
178                                    debug!("Network-control receiver closed; resuming discard behavior");
179                                }
180                            } else {
181                                debug!(
182                                    message_type = npdu.message_type,
183                                    "Ignoring network layer message (non-router mode)"
184                                );
185                            }
186                            continue;
187                        }
188
189                        // Non-routing node: discard messages with a specific DNET.
190                        if let Some(ref dest) = npdu.destination {
191                            if dest.network != 0xFFFF {
192                                debug!(
193                                    dnet = dest.network,
194                                    "Discarding routed message (non-router)"
195                                );
196                                continue;
197                            }
198                        }
199
200                        let source_network = npdu.source.clone();
201                        let is_group =
202                            is_group_delivery(received.link_layer_group, npdu.destination.as_ref());
203
204                        let apdu = ReceivedApdu {
205                            apdu: npdu.payload,
206                            source_mac: received.source_mac,
207                            source_network,
208                            link_layer_group: received.link_layer_group,
209                            is_group,
210                            data_attributes: received.data_attributes,
211                            reply_tx: received.reply_tx,
212                        };
213
214                        if apdu_tx.send(apdu).await.is_err() {
215                            break;
216                        }
217                    }
218                    Err(e) => {
219                        warn!(error = %e, "Failed to decode NPDU");
220                    }
221                }
222            }
223        });
224
225        self.dispatch_task = Some(dispatch_task);
226
227        Ok(apdu_rx)
228    }
229
230    /// Send an APDU to a specific local destination by MAC address.
231    pub async fn send_apdu(
232        &self,
233        apdu: &[u8],
234        destination_mac: &[u8],
235        expecting_reply: bool,
236        priority: NetworkPriority,
237    ) -> Result<(), Error> {
238        self.send_apdu_with_data_attributes(apdu, destination_mac, expecting_reply, priority, &[])
239            .await
240    }
241
242    /// Send an APDU with data attributes to a specific local destination.
243    pub async fn send_apdu_with_data_attributes(
244        &self,
245        apdu: &[u8],
246        destination_mac: &[u8],
247        expecting_reply: bool,
248        priority: NetworkPriority,
249        data_attributes: &[DataAttribute],
250    ) -> Result<(), Error> {
251        let npdu = Npdu {
252            is_network_message: false,
253            expecting_reply,
254            priority,
255            destination: None,
256            source: None,
257            payload: Bytes::copy_from_slice(apdu),
258            ..Npdu::default()
259        };
260
261        let mut buf = BytesMut::with_capacity(2 + apdu.len());
262        encode_npdu(&mut buf, &npdu)?;
263
264        self.transport
265            .send_unicast_with_data_attributes(&buf, destination_mac, data_attributes)
266            .await
267    }
268
269    /// Broadcast an APDU on the local network.
270    pub async fn broadcast_apdu(
271        &self,
272        apdu: &[u8],
273        expecting_reply: bool,
274        priority: NetworkPriority,
275    ) -> Result<(), Error> {
276        self.broadcast_apdu_with_data_attributes(apdu, expecting_reply, priority, &[])
277            .await
278    }
279
280    /// Broadcast an APDU with data attributes on the local network.
281    pub async fn broadcast_apdu_with_data_attributes(
282        &self,
283        apdu: &[u8],
284        expecting_reply: bool,
285        priority: NetworkPriority,
286        data_attributes: &[DataAttribute],
287    ) -> Result<(), Error> {
288        let npdu = Npdu {
289            is_network_message: false,
290            expecting_reply,
291            priority,
292            destination: None,
293            source: None,
294            payload: Bytes::copy_from_slice(apdu),
295            ..Npdu::default()
296        };
297
298        let mut buf = BytesMut::with_capacity(2 + apdu.len());
299        encode_npdu(&mut buf, &npdu)?;
300
301        self.transport
302            .send_broadcast_with_data_attributes(&buf, data_attributes)
303            .await
304    }
305
306    /// Broadcast an APDU globally (DNET=0xFFFF, hop_count=255).
307    ///
308    /// Unlike `broadcast_apdu()` which only reaches the local subnet, this
309    /// sets DNET=0xFFFF so routers will forward to all reachable networks.
310    pub async fn broadcast_global_apdu(
311        &self,
312        apdu: &[u8],
313        expecting_reply: bool,
314        priority: NetworkPriority,
315    ) -> Result<(), Error> {
316        self.broadcast_global_apdu_with_data_attributes(apdu, expecting_reply, priority, &[])
317            .await
318    }
319
320    /// Broadcast an APDU globally with data attributes (DNET=0xFFFF, hop_count=255).
321    pub async fn broadcast_global_apdu_with_data_attributes(
322        &self,
323        apdu: &[u8],
324        expecting_reply: bool,
325        priority: NetworkPriority,
326        data_attributes: &[DataAttribute],
327    ) -> Result<(), Error> {
328        let npdu = Npdu {
329            is_network_message: false,
330            expecting_reply,
331            priority,
332            destination: Some(NpduAddress {
333                network: 0xFFFF,
334                mac_address: MacAddr::new(),
335            }),
336            source: None,
337            hop_count: 255,
338            payload: Bytes::copy_from_slice(apdu),
339            ..Npdu::default()
340        };
341
342        let mut buf = BytesMut::with_capacity(8 + apdu.len());
343        encode_npdu(&mut buf, &npdu)?;
344        self.transport
345            .send_broadcast_with_data_attributes(&buf, data_attributes)
346            .await
347    }
348
349    /// Broadcast an APDU to a specific remote network via routers.
350    ///
351    /// Like `broadcast_global_apdu()` but targets a single network number
352    /// instead of all networks (DNET=0xFFFF).
353    pub async fn broadcast_to_network(
354        &self,
355        apdu: &[u8],
356        dest_network: u16,
357        expecting_reply: bool,
358        priority: NetworkPriority,
359    ) -> Result<(), Error> {
360        self.broadcast_to_network_with_data_attributes(
361            apdu,
362            dest_network,
363            expecting_reply,
364            priority,
365            &[],
366        )
367        .await
368    }
369
370    /// Broadcast an APDU with data attributes to a specific remote network via routers.
371    pub async fn broadcast_to_network_with_data_attributes(
372        &self,
373        apdu: &[u8],
374        dest_network: u16,
375        expecting_reply: bool,
376        priority: NetworkPriority,
377        data_attributes: &[DataAttribute],
378    ) -> Result<(), Error> {
379        if dest_network == 0xFFFF {
380            return Err(Error::Encoding(
381                "dest_network 0xFFFF is reserved for global broadcasts; use broadcast_global_apdu instead".into(),
382            ));
383        }
384        let npdu = Npdu {
385            is_network_message: false,
386            expecting_reply,
387            priority,
388            destination: Some(NpduAddress {
389                network: dest_network,
390                mac_address: MacAddr::new(),
391            }),
392            source: None,
393            hop_count: 255,
394            payload: Bytes::copy_from_slice(apdu),
395            ..Npdu::default()
396        };
397
398        let mut buf = BytesMut::with_capacity(8 + apdu.len());
399        encode_npdu(&mut buf, &npdu)?;
400        self.transport
401            .send_broadcast_with_data_attributes(&buf, data_attributes)
402            .await
403    }
404
405    /// Send an APDU to a remote device through a local router.
406    ///
407    /// The NPDU is sent via unicast to `router_mac` (the next-hop router on
408    /// the local network), but the NPDU header addresses the final destination
409    /// with `dest_network` / `dest_mac`.
410    pub async fn send_apdu_routed(
411        &self,
412        apdu: &[u8],
413        dest_network: u16,
414        dest_mac: &[u8],
415        router_mac: &[u8],
416        expecting_reply: bool,
417        priority: NetworkPriority,
418    ) -> Result<(), Error> {
419        self.send_apdu_routed_with_data_attributes(
420            apdu,
421            dest_network,
422            dest_mac,
423            router_mac,
424            expecting_reply,
425            priority,
426            &[],
427        )
428        .await
429    }
430
431    /// Send an APDU with data attributes to a remote device through a local router.
432    pub async fn send_apdu_routed_with_data_attributes(
433        &self,
434        apdu: &[u8],
435        dest_network: u16,
436        dest_mac: &[u8],
437        router_mac: &[u8],
438        expecting_reply: bool,
439        priority: NetworkPriority,
440        data_attributes: &[DataAttribute],
441    ) -> Result<(), Error> {
442        let buf =
443            Self::encode_routed_npdu_buf(apdu, dest_network, dest_mac, expecting_reply, priority)?;
444        self.transport
445            .send_unicast_with_data_attributes(&buf, router_mac, data_attributes)
446            .await
447    }
448
449    /// Send a routed APDU with a broadcast link DA, for when the next-hop
450    /// router's MAC is unknown.
451    ///
452    /// Clause 6.5.3: the data link DA "shall be the MAC address of the BACnet
453    /// router corresponding to the DNET parameter or the appropriate
454    /// broadcast DA if the address of the router is initially unknown". The
455    /// NPDU still addresses one device via DNET/DADR, which is why Clause
456    /// 6.3's broadcast restriction does not bite: "a MAC layer multicast or
457    /// broadcast address may be used for other PDU types when the network
458    /// layer address restricts the destination to a single device".
459    pub async fn send_apdu_routed_via_local_broadcast(
460        &self,
461        apdu: &[u8],
462        dest_network: u16,
463        dest_mac: &[u8],
464        expecting_reply: bool,
465        priority: NetworkPriority,
466    ) -> Result<(), Error> {
467        self.send_apdu_routed_via_local_broadcast_with_data_attributes(
468            apdu,
469            dest_network,
470            dest_mac,
471            expecting_reply,
472            priority,
473            &[],
474        )
475        .await
476    }
477
478    /// Send a routed APDU with data attributes and a broadcast link DA.
479    pub async fn send_apdu_routed_via_local_broadcast_with_data_attributes(
480        &self,
481        apdu: &[u8],
482        dest_network: u16,
483        dest_mac: &[u8],
484        expecting_reply: bool,
485        priority: NetworkPriority,
486        data_attributes: &[DataAttribute],
487    ) -> Result<(), Error> {
488        let buf =
489            Self::encode_routed_npdu_buf(apdu, dest_network, dest_mac, expecting_reply, priority)?;
490        self.transport
491            .send_broadcast_with_data_attributes(&buf, data_attributes)
492            .await
493    }
494
495    /// Access the underlying transport.
496    ///
497    /// Useful for transport-specific operations like BBMD registration
498    /// after the network layer has been started.
499    pub fn transport(&self) -> &T {
500        &self.transport
501    }
502
503    /// Get the transport's local MAC address.
504    pub fn local_mac(&self) -> &[u8] {
505        self.transport.local_mac()
506    }
507
508    /// Sequence assigned to the most recently decoded network-control ingress.
509    ///
510    /// The value is updated before the control is queued for its opt-in
511    /// consumer. At counter exhaustion it remains at `u64::MAX`, which makes
512    /// sequence-based consumers fail closed rather than accepting an alias.
513    pub fn network_control_ingress_sequence(&self) -> u64 {
514        self.network_control_ingress_sequence.load(Ordering::SeqCst)
515    }
516
517    /// Encode an APDU into an NPDU whose destination is `dest_network` /
518    /// `dest_mac`, ready for whichever link send the caller chooses.
519    fn encode_routed_npdu_buf(
520        apdu: &[u8],
521        dest_network: u16,
522        dest_mac: &[u8],
523        expecting_reply: bool,
524        priority: NetworkPriority,
525    ) -> Result<BytesMut, Error> {
526        let npdu = Npdu {
527            is_network_message: false,
528            expecting_reply,
529            priority,
530            destination: Some(NpduAddress {
531                network: dest_network,
532                mac_address: MacAddr::from_slice(dest_mac),
533            }),
534            source: None,
535            hop_count: 255,
536            payload: Bytes::copy_from_slice(apdu),
537            ..Npdu::default()
538        };
539        let mut buf = BytesMut::with_capacity(8 + dest_mac.len() + apdu.len());
540        encode_npdu(&mut buf, &npdu)?;
541        Ok(buf)
542    }
543
544    /// Stop the network layer and underlying transport.
545    pub async fn stop(&mut self) -> Result<(), Error> {
546        if let Some(task) = self.abort_dispatch_task() {
547            let _ = task.await;
548        }
549        self.transport.stop().await
550    }
551}
552
553fn next_ingress_sequence(sequence: &AtomicU64) -> u64 {
554    let previous = sequence
555        .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |value| {
556            value.checked_add(1)
557        })
558        .unwrap_or(u64::MAX);
559    previous.saturating_add(1)
560}
561
562impl<T: TransportPort> NetworkLayer<T> {
563    fn abort_dispatch_task(&mut self) -> Option<JoinHandle<()>> {
564        let task = self.dispatch_task.take()?;
565        task.abort();
566        Some(task)
567    }
568}
569
570impl<T: TransportPort> Drop for NetworkLayer<T> {
571    fn drop(&mut self) {
572        let _ = self.abort_dispatch_task();
573        self.transport.abort();
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580    use bacnet_transport::bip::BipTransport;
581    use bacnet_transport::sc::{LoopbackWebSocket, ScTransport, WebSocketPort};
582    use bacnet_transport::sc_frame::{
583        decode_sc_message, encode_sc_message, ScFunction, ScMessage, Vmac,
584    };
585    use std::net::Ipv4Addr;
586    use tokio::time::{timeout, Duration};
587
588    async fn sc_hub_accept(ws_hub: &LoopbackWebSocket, hub_vmac: Vmac) {
589        let data = ws_hub.recv().await.unwrap();
590        let req = decode_sc_message(&data).unwrap();
591        assert_eq!(req.function, ScFunction::ConnectRequest);
592
593        let mut accept_payload = Vec::with_capacity(26);
594        accept_payload.extend_from_slice(&hub_vmac);
595        accept_payload.extend_from_slice(&[0u8; 16]);
596        accept_payload.extend_from_slice(&1476u16.to_be_bytes());
597        accept_payload.extend_from_slice(&1476u16.to_be_bytes());
598
599        let accept = ScMessage {
600            function: ScFunction::ConnectAccept,
601            message_id: req.message_id,
602            originating_vmac: None,
603            destination_vmac: None,
604            dest_options: Vec::new(),
605            data_options: Vec::new(),
606            payload: Bytes::from(accept_payload),
607        };
608        let mut buf = BytesMut::new();
609        encode_sc_message(&mut buf, &accept);
610        ws_hub.send(&buf).await.unwrap();
611    }
612
613    async fn assert_sc_socket_closed_after_drop(ws_hub: &LoopbackWebSocket, context: &str) {
614        timeout(Duration::from_secs(1), async {
615            loop {
616                match ws_hub.recv().await {
617                    Ok(data) => {
618                        let msg = decode_sc_message(&data).unwrap();
619                        assert_ne!(
620                            msg.function,
621                            ScFunction::HeartbeatAck,
622                            "{context} must not leave SC answering heartbeats"
623                        );
624                    }
625                    Err(_) => break,
626                }
627            }
628        })
629        .await
630        .unwrap_or_else(|_| panic!("{context} did not close the SC WebSocket"));
631
632        let heartbeat = ScMessage {
633            function: ScFunction::HeartbeatRequest,
634            message_id: 0x66,
635            originating_vmac: None,
636            destination_vmac: None,
637            dest_options: Vec::new(),
638            data_options: Vec::new(),
639            payload: Bytes::new(),
640        };
641        let mut buf = BytesMut::new();
642        encode_sc_message(&mut buf, &heartbeat);
643        assert!(
644            ws_hub.send(&buf).await.is_err(),
645            "{context} must reject post-drop Heartbeat-Request on the closed socket"
646        );
647    }
648
649    #[tokio::test]
650    async fn network_layer_drop_releases_sc_transport_socket() {
651        let (ws_client, ws_hub) = LoopbackWebSocket::pair();
652        let hub_vmac = [0x10; 6];
653        let mut net = NetworkLayer::new(ScTransport::new(ws_client, [0x01; 6]));
654
655        let hub_accept_task = tokio::spawn(async move {
656            sc_hub_accept(&ws_hub, hub_vmac).await;
657            ws_hub
658        });
659
660        let _rx = net.start().await.unwrap();
661        let ws_hub = hub_accept_task.await.unwrap();
662
663        drop(net);
664
665        assert_sc_socket_closed_after_drop(&ws_hub, "dropped NetworkLayer").await;
666    }
667
668    #[tokio::test]
669    async fn send_apdu_data_attributes_reach_sc_data_options() {
670        let (ws_client, ws_hub) = LoopbackWebSocket::pair();
671        let hub_vmac = [0x10; 6];
672        let dest_vmac: Vmac = [0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
673        let mut net = NetworkLayer::new(ScTransport::new(ws_client, [0x01; 6]));
674        let data_attributes = vec![
675            DataAttribute {
676                option_type: 1,
677                must_understand: true,
678                data: Vec::new(),
679            },
680            DataAttribute {
681                option_type: 31,
682                must_understand: false,
683                data: vec![0x12, 0x34, 0x56],
684            },
685        ];
686
687        let hub_accept_task = tokio::spawn(async move {
688            sc_hub_accept(&ws_hub, hub_vmac).await;
689            ws_hub
690        });
691
692        let _rx = net.start().await.unwrap();
693        let ws_hub = hub_accept_task.await.unwrap();
694
695        let apdu = Bytes::from_static(&[0x10, 0x08]);
696        net.send_apdu_with_data_attributes(
697            &apdu,
698            &dest_vmac,
699            false,
700            NetworkPriority::NORMAL,
701            &data_attributes,
702        )
703        .await
704        .unwrap();
705
706        let data = ws_hub.recv().await.unwrap();
707        let msg = decode_sc_message(&data).unwrap();
708        assert_eq!(msg.function, ScFunction::EncapsulatedNpdu);
709        assert_eq!(msg.destination_vmac, Some(dest_vmac));
710        assert_eq!(msg.data_options.len(), 2);
711        assert_eq!(msg.data_options[0].option_type, 1);
712        assert!(msg.data_options[0].must_understand);
713        assert_eq!(msg.data_options[1].option_type, 31);
714        assert!(!msg.data_options[1].must_understand);
715        assert_eq!(msg.data_options[1].data, vec![0x12, 0x34, 0x56]);
716
717        let npdu = decode_npdu(msg.payload).unwrap();
718        assert_eq!(npdu.payload, apdu);
719        assert!(!npdu.expecting_reply);
720        assert_eq!(npdu.priority, NetworkPriority::NORMAL);
721
722        net.stop().await.unwrap();
723    }
724
725    #[tokio::test]
726    async fn end_to_end_who_is() {
727        use bacnet_encoding::apdu::{decode_apdu, encode_apdu, Apdu, UnconfirmedRequest};
728        use bacnet_types::enums::UnconfirmedServiceChoice;
729
730        let transport_a = BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::BROADCAST);
731        let transport_b = BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::BROADCAST);
732
733        let mut net_a = NetworkLayer::new(transport_a);
734        let mut net_b = NetworkLayer::new(transport_b);
735
736        let _rx_a = net_a.start().await.unwrap();
737        let mut rx_b = net_b.start().await.unwrap();
738
739        let who_is_apdu = Apdu::UnconfirmedRequest(UnconfirmedRequest {
740            service_choice: UnconfirmedServiceChoice::WHO_IS,
741            service_request: Bytes::new(),
742        });
743        let mut apdu_buf = BytesMut::new();
744        encode_apdu(&mut apdu_buf, &who_is_apdu).expect("valid APDU encoding");
745
746        net_a
747            .send_apdu(&apdu_buf, net_b.local_mac(), false, NetworkPriority::NORMAL)
748            .await
749            .unwrap();
750
751        let received = timeout(Duration::from_secs(2), rx_b.recv())
752            .await
753            .expect("Timed out waiting for APDU")
754            .expect("Channel closed");
755
756        let decoded_apdu = decode_apdu(received.apdu.clone()).unwrap();
757        match decoded_apdu {
758            Apdu::UnconfirmedRequest(req) => {
759                assert_eq!(req.service_choice, UnconfirmedServiceChoice::WHO_IS);
760                assert!(req.service_request.is_empty());
761            }
762            other => panic!("Expected UnconfirmedRequest, got {:?}", other),
763        }
764
765        net_a.stop().await.unwrap();
766        net_b.stop().await.unwrap();
767    }
768
769    #[test]
770    fn global_broadcast_npdu_has_dnet_ffff() {
771        use bacnet_encoding::npdu::{decode_npdu, encode_npdu, Npdu, NpduAddress};
772        use bacnet_types::enums::NetworkPriority;
773
774        let npdu = Npdu {
775            is_network_message: false,
776            expecting_reply: false,
777            priority: NetworkPriority::NORMAL,
778            destination: Some(NpduAddress {
779                network: 0xFFFF,
780                mac_address: MacAddr::new(),
781            }),
782            source: None,
783            hop_count: 255,
784            payload: Bytes::from_static(&[0xAA]),
785            ..Npdu::default()
786        };
787
788        let mut buf = bytes::BytesMut::new();
789        encode_npdu(&mut buf, &npdu).unwrap();
790        let decoded = decode_npdu(Bytes::from(buf)).unwrap();
791        let dest = decoded.destination.unwrap();
792        assert_eq!(dest.network, 0xFFFF);
793        assert!(dest.mac_address.is_empty());
794        assert_eq!(decoded.hop_count, 255);
795    }
796
797    #[test]
798    fn transport_accessor() {
799        let transport = BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::BROADCAST);
800        let net = NetworkLayer::new(transport);
801        let mac = net.transport().local_mac();
802        assert_eq!(mac.len(), 6);
803    }
804
805    #[test]
806    fn routed_send_encodes_dnet_dadr() {
807        use bacnet_encoding::npdu::{decode_npdu, encode_npdu, Npdu, NpduAddress};
808        use bacnet_types::enums::NetworkPriority;
809
810        let npdu = Npdu {
811            is_network_message: false,
812            expecting_reply: true,
813            priority: NetworkPriority::NORMAL,
814            destination: Some(NpduAddress {
815                network: 100,
816                mac_address: MacAddr::from_slice(&[1, 2, 3, 4, 5, 6]),
817            }),
818            source: None,
819            hop_count: 255,
820            payload: Bytes::from_static(&[0xAA, 0xBB]),
821            ..Npdu::default()
822        };
823
824        let mut buf = bytes::BytesMut::new();
825        encode_npdu(&mut buf, &npdu).unwrap();
826        let decoded = decode_npdu(Bytes::from(buf)).unwrap();
827        let dest = decoded.destination.unwrap();
828        assert_eq!(dest.network, 100);
829        assert_eq!(dest.mac_address.as_slice(), &[1, 2, 3, 4, 5, 6]);
830        assert_eq!(decoded.hop_count, 255);
831        assert!(decoded.expecting_reply);
832    }
833
834    #[test]
835    fn broadcast_to_network_encodes_specific_dnet() {
836        use bacnet_encoding::npdu::{decode_npdu, encode_npdu, Npdu, NpduAddress};
837        use bacnet_types::enums::NetworkPriority;
838
839        let npdu = Npdu {
840            is_network_message: false,
841            expecting_reply: false,
842            priority: NetworkPriority::NORMAL,
843            destination: Some(NpduAddress {
844                network: 42,
845                mac_address: MacAddr::new(),
846            }),
847            source: None,
848            hop_count: 255,
849            payload: Bytes::from_static(&[0xCC]),
850            ..Npdu::default()
851        };
852
853        let mut buf = bytes::BytesMut::new();
854        encode_npdu(&mut buf, &npdu).unwrap();
855        let decoded = decode_npdu(Bytes::from(buf)).unwrap();
856        let dest = decoded.destination.unwrap();
857        assert_eq!(dest.network, 42);
858        assert!(dest.mac_address.is_empty());
859        assert_eq!(decoded.hop_count, 255);
860        assert!(!decoded.expecting_reply);
861    }
862}
863
864#[cfg(test)]
865#[path = "layer_delivery_tests.rs"]
866mod delivery_tests;
867
868#[cfg(test)]
869#[path = "layer_sc_data_options_tests.rs"]
870mod sc_data_options_tests;