Skip to main content

bacnet_network/
network_number.rs

1//! Shared packet contract for the two local nonrouter Network Number controls.
2use crate::layer::ReceivedNetworkControl;
3use bacnet_npdu::NetworkMessageType;
4use bacnet_router_core::message::NetworkNumberIs;
5use bacnet_types::network_number::NetworkNumber;
6use std::sync::atomic::{AtomicU16, Ordering};
7use std::sync::Arc;
8
9/// A validated local control, independent of transport and configured authority.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum NumberControl {
12    /// A local query received by unicast or logical broadcast.
13    WhatIs,
14    /// A valid logical-broadcast announcement.
15    NumberIs {
16        /// Peer-reported number, in 1..=65534.
17        number: u16,
18        /// Peer-reported configured flag, zero or one.
19        flag: u8,
20    },
21}
22impl NumberControl {
23    /// Refuse routed, malformed or ineligible controls without changing state.
24    pub fn parse(control: &ReceivedNetworkControl) -> Option<Self> {
25        let npdu = &control.npdu;
26        if !npdu.is_network_message() || npdu.source.is_some() || npdu.destination.is_some() {
27            return None;
28        }
29        match npdu.message_type {
30            Some(t)
31                if t == NetworkMessageType::WHAT_IS_NETWORK_NUMBER && npdu.payload.is_empty() =>
32            {
33                Some(Self::WhatIs)
34            }
35            Some(t) if t == NetworkMessageType::NETWORK_NUMBER_IS && control.link_layer_group => {
36                // The router core's view, the one reader of this payload
37                // (#1818): it refuses any length but three octets.
38                let NetworkNumberIs { network, flag } =
39                    NetworkNumberIs::parse(&npdu.payload).ok()?;
40                (network != 0 && network != u16::MAX && flag <= NetworkNumberIs::CONFIGURED)
41                    .then_some(Self::NumberIs {
42                        number: network,
43                        flag,
44                    })
45            }
46            _ => None,
47        }
48    }
49}
50
51/// Encode a known number as a complete local-broadcast control NPDU.
52/// Learned-configured state still transmits flag zero; unknown has no reply.
53pub fn number_is_reply(state: NetworkNumber) -> Option<[u8; 6]> {
54    let (number, quality) = state.snapshot();
55    if number == 0 {
56        return None;
57    }
58    let [high, low, flag] = NetworkNumberIs {
59        network: number,
60        flag: u8::from(quality == 3),
61    }
62    .to_bytes();
63    Some([
64        1,
65        0x80,
66        NetworkMessageType::NETWORK_NUMBER_IS.to_raw(),
67        high,
68        low,
69        flag,
70    ])
71}
72
73/// The number of the network a single-port node is attached to, shared
74/// between the task that owns its Number controls and the senders that read
75/// it. Each [`NetworkLayer`](crate::layer::NetworkLayer) holds one
76/// ([`NetworkLayer::local_network_number`](crate::layer::NetworkLayer::local_network_number)),
77/// and clones share one value. Reading or publishing takes no lock.
78///
79/// It starts unknown. The layer never learns a number by itself: the owner of
80/// its Number controls publishes the state it holds whenever an announcement
81/// may have changed it, and a registered Network Port's owner also publishes
82/// the port's number at startup. The value is a copy of that state, never a
83/// second authority.
84#[derive(Clone, Debug, Default)]
85pub struct LocalNetworkNumber(Arc<AtomicU16>);
86
87impl LocalNetworkNumber {
88    /// Record the number `state` holds. An unknown state records unknown;
89    /// a known number is always in 1..=65534.
90    pub fn publish(&self, state: NetworkNumber) {
91        self.0.store(state.snapshot().0, Ordering::Release);
92    }
93
94    /// The last number published, or `None` while it is unknown.
95    pub fn get(&self) -> Option<u16> {
96        match self.0.load(Ordering::Acquire) {
97            0 => None,
98            number => Some(number),
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests;