Skip to main content

bacnet_ops/engine/
bbmd.rs

1//! BBMD table reads through the engine, under the rate cap: the read side
2//! of BVLL only (Annex J.2.3 and J.2.7), never a table write or a
3//! registration.
4
5use std::any::Any;
6use std::net::SocketAddrV4;
7
8use bacnet_transport::bip::{BipTransport, BvlcExchangeError};
9use bacnet_transport::port::TransportPort;
10
11use super::Engine;
12use crate::bbmd::{BdtRow, FdtRow, Outcome, Probe, mac};
13
14impl<T: TransportPort + 'static> Engine<T> {
15    /// The client's B/IP transport, when it has one: the BBMD reads need it.
16    pub fn bip(&self) -> Option<&BipTransport> {
17        (self.client.transport() as &dyn Any).downcast_ref()
18    }
19
20    /// The BBMD the client last tried to register with as a foreign device,
21    /// if any. Reads the transport's registration state and sends nothing.
22    pub fn registration_bbmd(&self) -> Option<SocketAddrV4> {
23        self.bip()?
24            .bvlc_client_snapshot()
25            .foreign_registration
26            .last_bbmd
27    }
28
29    /// Read `bbmd`'s BDT, after a slot under the local network's rate and
30    /// the global one. On a client without B/IP it sends nothing.
31    pub async fn read_bdt(&self, bbmd: SocketAddrV4) -> Probe {
32        let Some(bip) = self.bip() else {
33            return Outcome::Unreachable(NOT_BIP.into());
34        };
35        self.limits.slot(Some(0)).await;
36        settle(bip.read_bdt(&mac(bbmd)).await, |e| BdtRow::from(e))
37    }
38
39    /// Read `bbmd`'s FDT, as [`read_bdt`](Self::read_bdt).
40    pub async fn read_fdt(&self, bbmd: SocketAddrV4) -> Outcome<Vec<FdtRow>> {
41        let Some(bip) = self.bip() else {
42            return Outcome::Unreachable(NOT_BIP.into());
43        };
44        self.limits.slot(Some(0)).await;
45        settle(bip.read_fdt(&mac(bbmd)).await, |e| FdtRow::from(e))
46    }
47}
48
49/// Why a client without B/IP reads no BBMD.
50const NOT_BIP: &str = "BBMD tables need BACnet/IP";
51
52/// A table read's outcome. A request the node refused before sending
53/// never left, so the call isn't charged for it.
54fn settle<E, R>(read: Result<Vec<E>, BvlcExchangeError>, row: fn(&E) -> R) -> Outcome<Vec<R>> {
55    match read {
56        Ok(entries) => Outcome::Table(entries.iter().map(row).collect()),
57        Err(error) => {
58            if matches!(error, BvlcExchangeError::Refused(_)) {
59                crate::limits::refund();
60            }
61            Outcome::from_error(&error)
62        }
63    }
64}