1use 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
20pub struct ReceivedApdu {
22 pub apdu: Bytes,
24 pub source_mac: MacAddr,
26 pub source_network: Option<NpduAddress>,
28 pub link_layer_group: bool,
32 pub is_group: bool,
37 pub data_attributes: Vec<DataAttribute>,
39 pub reply_tx: Option<oneshot::Sender<Bytes>>,
42}
43
44#[derive(Debug, Clone)]
50pub struct ReceivedNetworkControl {
51 pub npdu: Npdu,
53 pub source_mac: MacAddr,
55 pub link_layer_group: bool,
57 pub data_attributes: Vec<DataAttribute>,
59 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
102pub 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn transport(&self) -> &T {
500 &self.transport
501 }
502
503 pub fn local_mac(&self) -> &[u8] {
505 self.transport.local_mac()
506 }
507
508 pub fn network_control_ingress_sequence(&self) -> u64 {
514 self.network_control_ingress_sequence.load(Ordering::SeqCst)
515 }
516
517 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 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;