Skip to main content

rustbac_datalink/bip/
transport.rs

1use crate::bip::bvlc::{BvlcFunction, BvlcHeader};
2use crate::{DataLink, DataLinkAddress, DataLinkError};
3use rustbac_core::encoding::{reader::Reader, writer::Writer};
4use std::io;
5use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
6use std::sync::Arc;
7use tokio::net::UdpSocket;
8use tokio::sync::Mutex;
9use tokio::time::{timeout, Duration, Instant};
10
11const MAX_BIP_FRAME_LEN: usize = 1600;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct BroadcastDistributionEntry {
15    pub address: SocketAddrV4,
16    pub mask: Ipv4Addr,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct ForeignDeviceTableEntry {
21    pub address: SocketAddrV4,
22    pub ttl_seconds: u16,
23    pub remaining_seconds: u16,
24}
25
26#[derive(Debug, Clone)]
27pub struct BacnetIpTransport {
28    socket: Arc<UdpSocket>,
29    bbmd: Option<SocketAddr>,
30    bbmd_command_lock: Arc<Mutex<()>>,
31}
32
33impl BacnetIpTransport {
34    pub async fn bind(bind_addr: SocketAddr) -> Result<Self, DataLinkError> {
35        let socket = UdpSocket::bind(bind_addr).await?;
36        socket.set_broadcast(true)?;
37        Ok(Self {
38            socket: Arc::new(socket),
39            bbmd: None,
40            bbmd_command_lock: Arc::new(Mutex::new(())),
41        })
42    }
43
44    pub async fn bind_foreign(
45        bind_addr: SocketAddr,
46        bbmd_addr: SocketAddr,
47    ) -> Result<Self, DataLinkError> {
48        let socket = UdpSocket::bind(bind_addr).await?;
49        socket.set_broadcast(true)?;
50        Ok(Self {
51            socket: Arc::new(socket),
52            bbmd: Some(bbmd_addr),
53            bbmd_command_lock: Arc::new(Mutex::new(())),
54        })
55    }
56
57    pub fn local_addr(&self) -> Result<SocketAddr, DataLinkError> {
58        self.socket.local_addr().map_err(DataLinkError::Io)
59    }
60
61    pub fn bbmd_addr(&self) -> Option<SocketAddr> {
62        self.bbmd
63    }
64
65    fn require_bbmd(&self) -> Result<SocketAddr, DataLinkError> {
66        self.bbmd.ok_or(DataLinkError::BbmdNotConfigured)
67    }
68
69    fn parse_bvlc_result(payload: &[u8]) -> Result<(), DataLinkError> {
70        if payload.len() < 2 {
71            return Err(DataLinkError::InvalidFrame);
72        }
73        let code = u16::from_be_bytes([payload[0], payload[1]]);
74        if code == 0 {
75            Ok(())
76        } else {
77            Err(DataLinkError::BvlcResult(code))
78        }
79    }
80
81    async fn send_bvlc_to_bbmd(
82        &self,
83        function: BvlcFunction,
84        payload: &[u8],
85    ) -> Result<(), DataLinkError> {
86        let bbmd = self.bbmd.ok_or(DataLinkError::BbmdNotConfigured)?;
87        let total_len = 4usize
88            .checked_add(payload.len())
89            .ok_or(DataLinkError::FrameTooLarge)?;
90        if total_len > usize::from(u16::MAX) {
91            return Err(DataLinkError::FrameTooLarge);
92        }
93
94        let mut frame = vec![0u8; total_len];
95        let mut w = Writer::new(&mut frame);
96        BvlcHeader {
97            function,
98            length: total_len as u16,
99        }
100        .encode(&mut w)
101        .map_err(|_| DataLinkError::InvalidFrame)?;
102        w.write_all(payload)
103            .map_err(|_| DataLinkError::InvalidFrame)?;
104
105        self.socket.send_to(w.as_written(), bbmd).await?;
106        Ok(())
107    }
108
109    async fn recv_bvlc_reply(
110        &self,
111        expected: BvlcFunction,
112        timeout_duration: Duration,
113    ) -> Result<Vec<u8>, DataLinkError> {
114        let bbmd = self.require_bbmd()?;
115        let deadline = Instant::now() + timeout_duration;
116        let mut rx = [0u8; 1600];
117        loop {
118            let remaining = deadline.saturating_duration_since(Instant::now());
119            if remaining.is_zero() {
120                return Err(DataLinkError::Io(io::Error::new(
121                    io::ErrorKind::TimedOut,
122                    "bbmd response timeout",
123                )));
124            }
125
126            let (n, src) = timeout(remaining, self.socket.recv_from(&mut rx))
127                .await
128                .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "bbmd response timeout"))?
129                .map_err(DataLinkError::Io)?;
130            if src != bbmd {
131                continue;
132            }
133
134            let mut r = Reader::new(&rx[..n]);
135            let hdr = BvlcHeader::decode(&mut r).map_err(|_| DataLinkError::InvalidFrame)?;
136            let payload = r
137                .read_exact(hdr.length as usize - 4)
138                .map_err(|_| DataLinkError::InvalidFrame)?;
139
140            if hdr.function == expected {
141                return Ok(payload.to_vec());
142            }
143
144            if hdr.function == BvlcFunction::Result {
145                Self::parse_bvlc_result(payload)?;
146                if expected == BvlcFunction::Result {
147                    return Ok(payload.to_vec());
148                }
149                return Err(DataLinkError::InvalidFrame);
150            }
151        }
152    }
153
154    pub async fn register_foreign_device_no_wait(
155        &self,
156        ttl_seconds: u16,
157    ) -> Result<(), DataLinkError> {
158        let _guard = self.bbmd_command_lock.lock().await;
159        let payload = ttl_seconds.to_be_bytes();
160        self.send_bvlc_to_bbmd(BvlcFunction::RegisterForeignDevice, &payload)
161            .await
162    }
163
164    pub async fn register_foreign_device(&self, ttl_seconds: u16) -> Result<(), DataLinkError> {
165        let _guard = self.bbmd_command_lock.lock().await;
166        let payload = ttl_seconds.to_be_bytes();
167        self.send_bvlc_to_bbmd(BvlcFunction::RegisterForeignDevice, &payload)
168            .await?;
169        let payload = self
170            .recv_bvlc_reply(BvlcFunction::Result, Duration::from_secs(2))
171            .await?;
172        Self::parse_bvlc_result(&payload)
173    }
174
175    pub async fn read_broadcast_distribution_table(
176        &self,
177    ) -> Result<Vec<BroadcastDistributionEntry>, DataLinkError> {
178        let _guard = self.bbmd_command_lock.lock().await;
179        self.send_bvlc_to_bbmd(BvlcFunction::ReadBroadcastDistributionTable, &[])
180            .await?;
181        let payload = self
182            .recv_bvlc_reply(
183                BvlcFunction::ReadBroadcastDistributionTableAck,
184                Duration::from_secs(2),
185            )
186            .await?;
187        if payload.len() % 10 != 0 {
188            return Err(DataLinkError::InvalidFrame);
189        }
190
191        let mut out = Vec::with_capacity(payload.len() / 10);
192        for chunk in payload.chunks_exact(10) {
193            out.push(BroadcastDistributionEntry {
194                address: SocketAddrV4::new(
195                    Ipv4Addr::new(chunk[0], chunk[1], chunk[2], chunk[3]),
196                    u16::from_be_bytes([chunk[4], chunk[5]]),
197                ),
198                mask: Ipv4Addr::new(chunk[6], chunk[7], chunk[8], chunk[9]),
199            });
200        }
201        Ok(out)
202    }
203
204    pub async fn write_broadcast_distribution_table(
205        &self,
206        entries: &[BroadcastDistributionEntry],
207    ) -> Result<(), DataLinkError> {
208        let _guard = self.bbmd_command_lock.lock().await;
209        let mut payload = Vec::with_capacity(entries.len() * 10);
210        for entry in entries {
211            payload.extend_from_slice(&entry.address.ip().octets());
212            payload.extend_from_slice(&entry.address.port().to_be_bytes());
213            payload.extend_from_slice(&entry.mask.octets());
214        }
215
216        self.send_bvlc_to_bbmd(BvlcFunction::WriteBroadcastDistributionTable, &payload)
217            .await?;
218        let payload = self
219            .recv_bvlc_reply(BvlcFunction::Result, Duration::from_secs(2))
220            .await?;
221        Self::parse_bvlc_result(&payload)
222    }
223
224    pub async fn read_foreign_device_table(
225        &self,
226    ) -> Result<Vec<ForeignDeviceTableEntry>, DataLinkError> {
227        let _guard = self.bbmd_command_lock.lock().await;
228        self.send_bvlc_to_bbmd(BvlcFunction::ReadForeignDeviceTable, &[])
229            .await?;
230        let payload = self
231            .recv_bvlc_reply(
232                BvlcFunction::ReadForeignDeviceTableAck,
233                Duration::from_secs(2),
234            )
235            .await?;
236        if payload.len() % 10 != 0 {
237            return Err(DataLinkError::InvalidFrame);
238        }
239
240        let mut out = Vec::with_capacity(payload.len() / 10);
241        for chunk in payload.chunks_exact(10) {
242            out.push(ForeignDeviceTableEntry {
243                address: SocketAddrV4::new(
244                    Ipv4Addr::new(chunk[0], chunk[1], chunk[2], chunk[3]),
245                    u16::from_be_bytes([chunk[4], chunk[5]]),
246                ),
247                ttl_seconds: u16::from_be_bytes([chunk[6], chunk[7]]),
248                remaining_seconds: u16::from_be_bytes([chunk[8], chunk[9]]),
249            });
250        }
251        Ok(out)
252    }
253
254    pub async fn delete_foreign_device_table_entry(
255        &self,
256        address: SocketAddrV4,
257    ) -> Result<(), DataLinkError> {
258        let _guard = self.bbmd_command_lock.lock().await;
259        let mut payload = [0u8; 6];
260        payload[..4].copy_from_slice(&address.ip().octets());
261        payload[4..].copy_from_slice(&address.port().to_be_bytes());
262        self.send_bvlc_to_bbmd(BvlcFunction::DeleteForeignDeviceTableEntry, &payload)
263            .await?;
264        let payload = self
265            .recv_bvlc_reply(BvlcFunction::Result, Duration::from_secs(2))
266            .await?;
267        Self::parse_bvlc_result(&payload)
268    }
269}
270
271impl DataLink for BacnetIpTransport {
272    async fn send(&self, address: DataLinkAddress, payload: &[u8]) -> Result<(), DataLinkError> {
273        let addr = address.as_socket_addr();
274        let is_broadcast = matches!(addr.ip(), IpAddr::V4(v4) if v4.is_broadcast());
275
276        let (function, target_addr) = if is_broadcast {
277            if let Some(bbmd) = self.bbmd {
278                (BvlcFunction::DistributeBroadcastToNetwork, bbmd)
279            } else {
280                (BvlcFunction::OriginalBroadcastNpdu, addr)
281            }
282        } else {
283            (BvlcFunction::OriginalUnicastNpdu, addr)
284        };
285
286        let mut frame = [0u8; MAX_BIP_FRAME_LEN];
287        let total_len = 4usize
288            .checked_add(payload.len())
289            .ok_or(DataLinkError::FrameTooLarge)?;
290        if total_len > frame.len() {
291            return Err(DataLinkError::FrameTooLarge);
292        }
293
294        let mut w = Writer::new(&mut frame);
295        BvlcHeader {
296            function,
297            length: total_len as u16,
298        }
299        .encode(&mut w)
300        .map_err(|_| DataLinkError::InvalidFrame)?;
301        w.write_all(payload)
302            .map_err(|_| DataLinkError::FrameTooLarge)?;
303
304        self.socket.send_to(w.as_written(), target_addr).await?;
305        Ok(())
306    }
307
308    async fn recv(&self, buf: &mut [u8]) -> Result<(usize, DataLinkAddress), DataLinkError> {
309        let mut frame = [0u8; MAX_BIP_FRAME_LEN];
310        let (n, src) = self.socket.recv_from(&mut frame).await?;
311        let mut r = Reader::new(&frame[..n]);
312        let hdr = BvlcHeader::decode(&mut r).map_err(|_| DataLinkError::InvalidFrame)?;
313
314        match hdr.function {
315            BvlcFunction::OriginalUnicastNpdu
316            | BvlcFunction::OriginalBroadcastNpdu
317            | BvlcFunction::DistributeBroadcastToNetwork => {
318                let payload_len = hdr.length as usize - 4;
319                let payload = r
320                    .read_exact(payload_len)
321                    .map_err(|_| DataLinkError::InvalidFrame)?;
322                if payload.len() > buf.len() {
323                    return Err(DataLinkError::FrameTooLarge);
324                }
325                buf[..payload.len()].copy_from_slice(payload);
326                Ok((payload.len(), DataLinkAddress::Ip(src)))
327            }
328            BvlcFunction::ForwardedNpdu => {
329                let forwarded = r
330                    .read_exact(hdr.length as usize - 4)
331                    .map_err(|_| DataLinkError::InvalidFrame)?;
332                if forwarded.len() < 6 {
333                    return Err(DataLinkError::InvalidFrame);
334                }
335                let origin_ip =
336                    Ipv4Addr::new(forwarded[0], forwarded[1], forwarded[2], forwarded[3]);
337                let origin_port = u16::from_be_bytes([forwarded[4], forwarded[5]]);
338                let payload = &forwarded[6..];
339                if payload.len() > buf.len() {
340                    return Err(DataLinkError::FrameTooLarge);
341                }
342                buf[..payload.len()].copy_from_slice(payload);
343                Ok((
344                    payload.len(),
345                    DataLinkAddress::Ip(SocketAddr::new(IpAddr::V4(origin_ip), origin_port)),
346                ))
347            }
348            BvlcFunction::Unknown(v) => Err(DataLinkError::UnsupportedBvlcFunction(v)),
349            _ => Err(DataLinkError::InvalidFrame),
350        }
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::{BacnetIpTransport, BroadcastDistributionEntry, ForeignDeviceTableEntry};
357    use crate::bip::bvlc::{BvlcFunction, BvlcHeader, BVLC_TYPE_BIP};
358    use crate::{DataLink, DataLinkAddress, DataLinkError};
359    use rustbac_core::encoding::{reader::Reader, writer::Writer};
360    use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
361    use tokio::net::UdpSocket;
362    use tokio::time::{timeout, Duration};
363
364    #[tokio::test]
365    async fn recv_forwarded_npdu_returns_forwarded_origin() {
366        let transport =
367            BacnetIpTransport::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
368                .await
369                .unwrap();
370        let target = transport.local_addr().unwrap();
371        let sender = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
372            .await
373            .unwrap();
374
375        let mut frame = [0u8; 64];
376        let mut w = Writer::new(&mut frame);
377        BvlcHeader {
378            function: BvlcFunction::ForwardedNpdu,
379            length: 4 + 6 + 3,
380        }
381        .encode(&mut w)
382        .unwrap();
383        w.write_all(&[10, 1, 2, 3]).unwrap();
384        w.write_be_u16(47808).unwrap();
385        w.write_all(&[1, 2, 3]).unwrap();
386
387        sender.send_to(w.as_written(), target).await.unwrap();
388
389        let mut out = [0u8; 16];
390        let (n, src) = transport.recv(&mut out).await.unwrap();
391        assert_eq!(n, 3);
392        assert_eq!(&out[..3], &[1, 2, 3]);
393        assert_eq!(
394            src,
395            DataLinkAddress::Ip(SocketAddr::new(
396                IpAddr::V4(Ipv4Addr::new(10, 1, 2, 3)),
397                47808
398            ))
399        );
400    }
401
402    #[tokio::test]
403    async fn register_foreign_device_success() {
404        let bbmd = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
405            .await
406            .unwrap();
407        let bbmd_addr = bbmd.local_addr().unwrap();
408
409        let transport = BacnetIpTransport::bind_foreign(
410            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
411            bbmd_addr,
412        )
413        .await
414        .unwrap();
415
416        let responder = tokio::spawn(async move {
417            let mut recv = [0u8; 64];
418            let (n, src) = bbmd.recv_from(&mut recv).await.unwrap();
419            let mut r = Reader::new(&recv[..n]);
420            let hdr = BvlcHeader::decode(&mut r).unwrap();
421            assert_eq!(hdr.function, BvlcFunction::RegisterForeignDevice);
422
423            let reply = [BVLC_TYPE_BIP, 0x00, 0x00, 0x06, 0x00, 0x00];
424            bbmd.send_to(&reply, src).await.unwrap();
425        });
426
427        transport.register_foreign_device(60).await.unwrap();
428        responder.await.unwrap();
429    }
430
431    #[tokio::test]
432    async fn register_foreign_device_no_wait_sends_ttl() {
433        let bbmd = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
434            .await
435            .unwrap();
436        let bbmd_addr = bbmd.local_addr().unwrap();
437        let transport = BacnetIpTransport::bind_foreign(
438            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
439            bbmd_addr,
440        )
441        .await
442        .unwrap();
443
444        transport.register_foreign_device_no_wait(90).await.unwrap();
445
446        let mut recv = [0u8; 64];
447        let (n, _) = bbmd.recv_from(&mut recv).await.unwrap();
448        let mut r = Reader::new(&recv[..n]);
449        let hdr = BvlcHeader::decode(&mut r).unwrap();
450        assert_eq!(hdr.function, BvlcFunction::RegisterForeignDevice);
451        assert_eq!(r.read_be_u16().unwrap(), 90);
452    }
453
454    #[tokio::test]
455    async fn read_broadcast_distribution_table_parses_entries() {
456        let bbmd = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
457            .await
458            .unwrap();
459        let bbmd_addr = bbmd.local_addr().unwrap();
460        let transport = BacnetIpTransport::bind_foreign(
461            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
462            bbmd_addr,
463        )
464        .await
465        .unwrap();
466
467        let responder = tokio::spawn(async move {
468            let mut recv = [0u8; 128];
469            let (n, src) = bbmd.recv_from(&mut recv).await.unwrap();
470            let mut r = Reader::new(&recv[..n]);
471            let hdr = BvlcHeader::decode(&mut r).unwrap();
472            assert_eq!(hdr.function, BvlcFunction::ReadBroadcastDistributionTable);
473
474            let mut reply = [0u8; 32];
475            let mut w = Writer::new(&mut reply);
476            BvlcHeader {
477                function: BvlcFunction::ReadBroadcastDistributionTableAck,
478                length: 14,
479            }
480            .encode(&mut w)
481            .unwrap();
482            w.write_all(&[192, 168, 10, 20]).unwrap();
483            w.write_be_u16(47808).unwrap();
484            w.write_all(&[255, 255, 255, 0]).unwrap();
485            bbmd.send_to(w.as_written(), src).await.unwrap();
486        });
487
488        let entries = transport.read_broadcast_distribution_table().await.unwrap();
489        assert_eq!(
490            entries,
491            vec![BroadcastDistributionEntry {
492                address: SocketAddrV4::new(Ipv4Addr::new(192, 168, 10, 20), 47808),
493                mask: Ipv4Addr::new(255, 255, 255, 0),
494            }]
495        );
496        responder.await.unwrap();
497    }
498
499    #[tokio::test]
500    async fn write_broadcast_distribution_table_sends_entries() {
501        let bbmd = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
502            .await
503            .unwrap();
504        let bbmd_addr = bbmd.local_addr().unwrap();
505        let transport = BacnetIpTransport::bind_foreign(
506            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
507            bbmd_addr,
508        )
509        .await
510        .unwrap();
511
512        let responder = tokio::spawn(async move {
513            let mut recv = [0u8; 128];
514            let (n, src) = bbmd.recv_from(&mut recv).await.unwrap();
515            let mut r = Reader::new(&recv[..n]);
516            let hdr = BvlcHeader::decode(&mut r).unwrap();
517            assert_eq!(hdr.function, BvlcFunction::WriteBroadcastDistributionTable);
518            let payload = r.read_exact(hdr.length as usize - 4).unwrap();
519            assert_eq!(payload, &[10, 1, 2, 3, 0xBA, 0xC0, 255, 255, 255, 0][..]);
520
521            let reply = [BVLC_TYPE_BIP, 0x00, 0x00, 0x06, 0x00, 0x00];
522            bbmd.send_to(&reply, src).await.unwrap();
523        });
524
525        let entries = [BroadcastDistributionEntry {
526            address: SocketAddrV4::new(Ipv4Addr::new(10, 1, 2, 3), 47808),
527            mask: Ipv4Addr::new(255, 255, 255, 0),
528        }];
529        transport
530            .write_broadcast_distribution_table(&entries)
531            .await
532            .unwrap();
533        responder.await.unwrap();
534    }
535
536    #[tokio::test]
537    async fn read_foreign_device_table_parses_entries() {
538        let bbmd = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
539            .await
540            .unwrap();
541        let bbmd_addr = bbmd.local_addr().unwrap();
542        let transport = BacnetIpTransport::bind_foreign(
543            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
544            bbmd_addr,
545        )
546        .await
547        .unwrap();
548
549        let responder = tokio::spawn(async move {
550            let mut recv = [0u8; 128];
551            let (n, src) = bbmd.recv_from(&mut recv).await.unwrap();
552            let mut r = Reader::new(&recv[..n]);
553            let hdr = BvlcHeader::decode(&mut r).unwrap();
554            assert_eq!(hdr.function, BvlcFunction::ReadForeignDeviceTable);
555
556            let mut reply = [0u8; 32];
557            let mut w = Writer::new(&mut reply);
558            BvlcHeader {
559                function: BvlcFunction::ReadForeignDeviceTableAck,
560                length: 14,
561            }
562            .encode(&mut w)
563            .unwrap();
564            w.write_all(&[172, 16, 0, 42]).unwrap();
565            w.write_be_u16(47808).unwrap();
566            w.write_be_u16(120).unwrap();
567            w.write_be_u16(90).unwrap();
568            bbmd.send_to(w.as_written(), src).await.unwrap();
569        });
570
571        let entries = transport.read_foreign_device_table().await.unwrap();
572        assert_eq!(
573            entries,
574            vec![ForeignDeviceTableEntry {
575                address: SocketAddrV4::new(Ipv4Addr::new(172, 16, 0, 42), 47808),
576                ttl_seconds: 120,
577                remaining_seconds: 90,
578            }]
579        );
580        responder.await.unwrap();
581    }
582
583    #[tokio::test]
584    async fn delete_foreign_device_table_entry_sends_target() {
585        let bbmd = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
586            .await
587            .unwrap();
588        let bbmd_addr = bbmd.local_addr().unwrap();
589        let transport = BacnetIpTransport::bind_foreign(
590            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
591            bbmd_addr,
592        )
593        .await
594        .unwrap();
595
596        let responder = tokio::spawn(async move {
597            let mut recv = [0u8; 128];
598            let (n, src) = bbmd.recv_from(&mut recv).await.unwrap();
599            let mut r = Reader::new(&recv[..n]);
600            let hdr = BvlcHeader::decode(&mut r).unwrap();
601            assert_eq!(hdr.function, BvlcFunction::DeleteForeignDeviceTableEntry);
602            let payload = r.read_exact(hdr.length as usize - 4).unwrap();
603            assert_eq!(payload, &[10, 20, 30, 40, 0xBA, 0xC0][..]);
604
605            let reply = [BVLC_TYPE_BIP, 0x00, 0x00, 0x06, 0x00, 0x00];
606            bbmd.send_to(&reply, src).await.unwrap();
607        });
608
609        transport
610            .delete_foreign_device_table_entry(SocketAddrV4::new(
611                Ipv4Addr::new(10, 20, 30, 40),
612                47808,
613            ))
614            .await
615            .unwrap();
616        responder.await.unwrap();
617    }
618
619    #[tokio::test]
620    async fn broadcast_uses_distribute_to_network_when_bbmd_configured() {
621        let bbmd = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
622            .await
623            .unwrap();
624        let bbmd_addr = bbmd.local_addr().unwrap();
625
626        let transport = BacnetIpTransport::bind_foreign(
627            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
628            bbmd_addr,
629        )
630        .await
631        .unwrap();
632
633        transport
634            .send(DataLinkAddress::local_broadcast(47808), &[1, 2, 3])
635            .await
636            .unwrap();
637
638        let mut recv = [0u8; 64];
639        let (n, _) = bbmd.recv_from(&mut recv).await.unwrap();
640        let mut r = Reader::new(&recv[..n]);
641        let hdr = BvlcHeader::decode(&mut r).unwrap();
642        assert_eq!(hdr.function, BvlcFunction::DistributeBroadcastToNetwork);
643    }
644
645    #[tokio::test]
646    async fn bbmd_admin_commands_are_serialized() {
647        let bbmd = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
648            .await
649            .unwrap();
650        let bbmd_addr = bbmd.local_addr().unwrap();
651        let transport = BacnetIpTransport::bind_foreign(
652            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
653            bbmd_addr,
654        )
655        .await
656        .unwrap();
657
658        let t1 = transport.clone();
659        let t2 = transport.clone();
660
661        let first = tokio::spawn(async move { t1.read_broadcast_distribution_table().await });
662        let second = tokio::spawn(async move { t2.read_foreign_device_table().await });
663
664        let mut recv = [0u8; 128];
665        let (n1, src1) = bbmd.recv_from(&mut recv).await.unwrap();
666        let mut r1 = Reader::new(&recv[..n1]);
667        let hdr1 = BvlcHeader::decode(&mut r1).unwrap();
668        assert_eq!(hdr1.function, BvlcFunction::ReadBroadcastDistributionTable);
669
670        // Second command should not send until first receives a response.
671        let no_second_yet = timeout(Duration::from_millis(100), bbmd.recv_from(&mut recv)).await;
672        assert!(no_second_yet.is_err());
673
674        let mut reply1 = [0u8; 14];
675        let mut w1 = Writer::new(&mut reply1);
676        BvlcHeader {
677            function: BvlcFunction::ReadBroadcastDistributionTableAck,
678            length: 14,
679        }
680        .encode(&mut w1)
681        .unwrap();
682        w1.write_all(&[192, 168, 1, 1]).unwrap();
683        w1.write_be_u16(47808).unwrap();
684        w1.write_all(&[255, 255, 255, 0]).unwrap();
685        bbmd.send_to(w1.as_written(), src1).await.unwrap();
686
687        let (n2, src2) = bbmd.recv_from(&mut recv).await.unwrap();
688        let mut r2 = Reader::new(&recv[..n2]);
689        let hdr2 = BvlcHeader::decode(&mut r2).unwrap();
690        assert_eq!(hdr2.function, BvlcFunction::ReadForeignDeviceTable);
691
692        let mut reply2 = [0u8; 14];
693        let mut w2 = Writer::new(&mut reply2);
694        BvlcHeader {
695            function: BvlcFunction::ReadForeignDeviceTableAck,
696            length: 14,
697        }
698        .encode(&mut w2)
699        .unwrap();
700        w2.write_all(&[10, 0, 0, 2]).unwrap();
701        w2.write_be_u16(47808).unwrap();
702        w2.write_be_u16(60).unwrap();
703        w2.write_be_u16(30).unwrap();
704        bbmd.send_to(w2.as_written(), src2).await.unwrap();
705
706        let first_entries = first.await.unwrap().unwrap();
707        let second_entries = second.await.unwrap().unwrap();
708        assert_eq!(first_entries.len(), 1);
709        assert_eq!(second_entries.len(), 1);
710    }
711
712    #[tokio::test]
713    async fn unknown_bvlc_function_errors() {
714        let transport =
715            BacnetIpTransport::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
716                .await
717                .unwrap();
718        let target = transport.local_addr().unwrap();
719        let sender = UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
720            .await
721            .unwrap();
722
723        let frame = [BVLC_TYPE_BIP, 0x99, 0x00, 0x04];
724        sender.send_to(&frame, target).await.unwrap();
725
726        let mut out = [0u8; 16];
727        let err = transport.recv(&mut out).await.unwrap_err();
728        assert!(matches!(err, DataLinkError::UnsupportedBvlcFunction(0x99)));
729    }
730}