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::Arc;
7use std::sync::atomic::{AtomicU16, Ordering};
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 Self::parse_npdu(&control.npdu, control.link_layer_group)
26 }
27
28 /// Parse a decoded NPDU with its actual logical link delivery kind.
29 pub fn parse_npdu(npdu: &bacnet_encoding::npdu::Npdu, link_layer_group: bool) -> Option<Self> {
30 if !npdu.is_network_message() || npdu.source.is_some() || npdu.destination.is_some() {
31 return None;
32 }
33 match npdu.message_type {
34 Some(t)
35 if t == NetworkMessageType::WHAT_IS_NETWORK_NUMBER && npdu.payload.is_empty() =>
36 {
37 Some(Self::WhatIs)
38 }
39 Some(t) if t == NetworkMessageType::NETWORK_NUMBER_IS && link_layer_group => {
40 // The router core's view, the one reader of this payload
41 // (#1818): it refuses any length but three octets.
42 let NetworkNumberIs { network, flag } =
43 NetworkNumberIs::parse(&npdu.payload).ok()?;
44 (network != 0 && network != u16::MAX && flag <= NetworkNumberIs::CONFIGURED)
45 .then_some(Self::NumberIs {
46 number: network,
47 flag,
48 })
49 }
50 _ => None,
51 }
52 }
53}
54
55/// Encode a known number as a complete local-broadcast control NPDU.
56/// Learned-configured state still transmits flag zero; unknown has no reply.
57pub fn number_is_reply(state: NetworkNumber) -> Option<[u8; 6]> {
58 let (number, quality) = state.snapshot();
59 if number == 0 {
60 return None;
61 }
62 let [high, low, flag] = NetworkNumberIs {
63 network: number,
64 flag: u8::from(quality == 3),
65 }
66 .to_bytes();
67 Some([
68 1,
69 0x80,
70 NetworkMessageType::NETWORK_NUMBER_IS.to_raw(),
71 high,
72 low,
73 flag,
74 ])
75}
76
77/// The number of the network a single-port node is attached to, shared
78/// between the task that owns its Number controls and the senders that read
79/// it. Each [`NetworkLayer`](crate::layer::NetworkLayer) holds one
80/// ([`NetworkLayer::local_network_number`](crate::layer::NetworkLayer::local_network_number)),
81/// and clones share one value. Reading or publishing takes no lock.
82///
83/// It starts unknown. The layer never learns a number by itself: the owner of
84/// its Number controls publishes the state it holds whenever an announcement
85/// may have changed it, and a registered Network Port's owner also publishes
86/// the port's number at startup. The value is a copy of that state, never a
87/// second authority.
88#[derive(Clone, Debug, Default)]
89pub struct LocalNetworkNumber(Arc<AtomicU16>);
90
91impl LocalNetworkNumber {
92 /// Record the number `state` holds. An unknown state records unknown;
93 /// a known number is always in 1..=65534.
94 pub fn publish(&self, state: NetworkNumber) {
95 self.0.store(state.snapshot().0, Ordering::Release);
96 }
97
98 /// The last number published, or `None` while it is unknown.
99 pub fn get(&self) -> Option<u16> {
100 match self.0.load(Ordering::Acquire) {
101 0 => None,
102 number => Some(number),
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests;