Skip to main content

bacnet_client/client/
network_reports.rs

1//! Router discovery and network reports (Clause 6.4, #960): the client
2//! sends Who-Is-Router-To-Network and What-Is-Network-Number, and hands the
3//! I-Am-Router-To-Network, Network-Number-Is and Reject-Message-To-Network
4//! messages it receives to the subscribers of
5//! [`BACnetClient::network_reports`]. The client keeps the network-control
6//! channel: the dispatch task reads each control once, publishes its report,
7//! and then passes it on to the local Number controls or the routed-path
8//! limits as before.
9
10use super::i_have::collect_until;
11use super::*;
12use bacnet_network::layer::{NetworkReport, ReceivedNetworkControl, ReceivedNetworkReport};
13use bacnet_network::network_number::NumberControl;
14use bacnet_npdu::NetworkMessageType;
15use bacnet_router_core::message::Networks;
16
17/// Network-report broadcast channel capacity. A subscriber this far behind
18/// loses the oldest and is told how many with `RecvError::Lagged`; the
19/// dispatch task never waits for a subscriber.
20pub const NETWORK_REPORT_CHANNEL_CAPACITY: usize = 64;
21
22/// A router heard from during [`BACnetClient::discover_routers`].
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct DiscoveredRouter {
25    /// The router's MAC on the local network: the next hop to the networks
26    /// below.
27    pub mac: MacAddr,
28    /// Every network its I-Am-Router-To-Network messages listed, ascending,
29    /// each once.
30    pub networks: Vec<u16>,
31}
32
33/// Read `control` as a report and hand it to the subscribers; `number` is
34/// what [`NumberControl::parse`] made of it. With no subscriber nothing is
35/// parsed. Never waits.
36pub(super) fn publish(
37    tx: &broadcast::Sender<ReceivedNetworkReport>,
38    control: &ReceivedNetworkControl,
39    number: Option<NumberControl>,
40) {
41    if tx.receiver_count() == 0 {
42        return;
43    }
44    if let Some(report) = ReceivedNetworkReport::parse_with(control, number) {
45        let _ = tx.send(report);
46    }
47}
48
49/// Add one I-Am-Router-To-Network's `listed` networks to the entry for
50/// `mac`, made at the end when it is new.
51fn record(routers: &mut Vec<DiscoveredRouter>, mac: MacAddr, listed: Vec<u16>) {
52    let index = match routers.iter().position(|router| router.mac == mac) {
53        Some(index) => index,
54        None => {
55            routers.push(DiscoveredRouter {
56                mac,
57                networks: Vec::new(),
58            });
59            routers.len() - 1
60        }
61    };
62    let networks = &mut routers[index].networks;
63    networks.extend(listed);
64    networks.sort_unstable();
65    networks.dedup();
66}
67
68/// Refuse a network number no router can be asked about: 0, or 0xFFFF,
69/// the global broadcast.
70fn check_network(network: u16) -> Result<(), Error> {
71    if network == 0 || network == u16::MAX {
72        return Err(Error::Encoding(format!(
73            "network {network} is not a network number a router can reach; use 1..=65534"
74        )));
75    }
76    Ok(())
77}
78
79impl<T: TransportPort + 'static> BACnetClient<T> {
80    /// Get a receiver for the network reports this client receives: the
81    /// routers that announce themselves, Network-Number-Is broadcasts and
82    /// the rejects of messages it sent. Each call returns a new independent
83    /// receiver. A receiver that falls [`NETWORK_REPORT_CHANNEL_CAPACITY`]
84    /// behind loses the oldest.
85    pub fn network_reports(&self) -> broadcast::Receiver<ReceivedNetworkReport> {
86        self.network_report_tx.subscribe()
87    }
88
89    /// Send a Who-Is-Router-To-Network (Clause 6.4.1), asking which router
90    /// reaches `network`, or, for `None`, every router for all the networks
91    /// it reaches. It is broadcast on the local network when
92    /// `destination_mac` is `None`, else sent to that router alone. Answers
93    /// come as [`NetworkReport::IAmRouterToNetwork`] through
94    /// [`Self::network_reports`]. `network` 0 and 0xFFFF are refused.
95    pub async fn who_is_router_to_network(
96        &self,
97        network: Option<u16>,
98        destination_mac: Option<&[u8]>,
99    ) -> Result<(), Error> {
100        let mut payload = [0u8; 2];
101        let len = match network {
102            Some(network) => {
103                check_network(network)?;
104                Networks::write([network], &mut payload)
105                    .map_err(|e| Error::Encoding(e.to_string()))?
106            }
107            None => 0,
108        };
109        self.network
110            .send_network_message(
111                NetworkMessageType::WHO_IS_ROUTER_TO_NETWORK,
112                &payload[..len],
113                destination_mac,
114            )
115            .await
116    }
117
118    /// Send a What-Is-Network-Number (Clause 6.4.19), broadcast when
119    /// `destination_mac` is `None`. Any node holding this network's number,
120    /// a router in particular, broadcasts a Network-Number-Is in reply,
121    /// which comes as [`NetworkReport::NetworkNumberIs`] through
122    /// [`Self::network_reports`]. Ask once and keep the answer: the
123    /// standard doesn't want the question repeated.
124    pub async fn what_is_network_number(
125        &self,
126        destination_mac: Option<&[u8]>,
127    ) -> Result<(), Error> {
128        self.network
129            .send_network_message(
130                NetworkMessageType::WHAT_IS_NETWORK_NUMBER,
131                &[],
132                destination_mac,
133            )
134            .await
135    }
136
137    /// Broadcast a Who-Is-Router-To-Network for `network` (or every
138    /// network), then collect the I-Am-Router-To-Network messages heard for
139    /// `wait`: one entry per router MAC, in the order first heard. For a
140    /// `network`, only the routers that list it are kept. No answer is an
141    /// empty list, not an error.
142    pub async fn discover_routers(
143        &self,
144        network: Option<u16>,
145        wait: Duration,
146    ) -> Result<Vec<DiscoveredRouter>, Error> {
147        // Subscribe first, so no answer can arrive before the receiver.
148        let mut rx = self.network_reports();
149        self.who_is_router_to_network(network, None).await?;
150        let deadline = tokio::time::Instant::now() + wait;
151        let mut routers: Vec<DiscoveredRouter> = Vec::new();
152        collect_until(&mut rx, deadline, "I-Am-Router-To-Network", |received| {
153            let NetworkReport::IAmRouterToNetwork(listed) = received.report else {
154                return;
155            };
156            if network.is_some_and(|wanted| !listed.contains(&wanted)) {
157                return;
158            }
159            record(&mut routers, received.source_mac, listed);
160        })
161        .await;
162        Ok(routers)
163    }
164}
165
166#[cfg(test)]
167#[path = "network_reports_tests.rs"]
168mod tests;