Skip to main content

bacnet_types/
network_number.rs

1//! Pure single-link Network Number state and Clause 6.4.20 precedence.
2/// One live number/quality pair. The default is UNKNOWN/zero.
3/// Known numbers are always in 1..=65534; peer observations cannot create local configuration.
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5pub struct NetworkNumber {
6    number: u16,
7    quality: u8,
8}
9impl NetworkNumber {
10    /// Initialize from explicit local configuration: zero is UNKNOWN.
11    /// Returns `None` for reserved number 65535.
12    pub const fn configured(number: u16) -> Option<Self> {
13        if number == u16::MAX {
14            return None;
15        }
16        Some(Self {
17            number,
18            quality: if number == 0 { 0 } else { 3 },
19        })
20    }
21    /// Number and Clause 12.56 quality from the same state version.
22    pub fn snapshot(self) -> (u16, u8) {
23        (self.number, self.quality)
24    }
25    /// Apply Clause 6.4.20 precedence. Unusable values are local refusal policy.
26    pub fn observe(&mut self, number: u16, flag: u8) -> Observation {
27        if number == 0 || number == u16::MAX || flag > 1 {
28            return Observation::Ignored;
29        }
30        if self.quality == 3 {
31            return if flag == 1 && number != self.number {
32                Observation::ConfiguredConflict
33            } else {
34                Observation::Ignored
35            };
36        }
37        if flag == 1 || self.quality < 2 {
38            self.number = number;
39            self.quality = if flag == 1 { 2 } else { 1 };
40            Observation::Applied
41        } else {
42            Observation::Ignored
43        }
44    }
45}
46/// Result of observing a peer announcement; logging belongs to the caller.
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum Observation {
49    /// The announcement applied to the live pair.
50    Applied,
51    /// Invalid input or a lower-priority announcement left the pair unchanged.
52    Ignored,
53    /// A configured peer announced a different number from local configuration.
54    ConfiguredConflict,
55}
56#[cfg(test)]
57mod tests {
58    use super::*;
59    #[test]
60    fn network_number_quality_precedence_and_numeric_policy() {
61        assert_eq!(NetworkNumber::configured(u16::MAX), None);
62        let mut state = NetworkNumber::default();
63        assert_eq!(state.snapshot(), (0, 0));
64        for (number, flag, expected) in [
65            (10, 0, (10, 1)),
66            (11, 0, (11, 1)),
67            (11, 1, (11, 2)),
68            (11, 0, (11, 2)),
69            (12, 0, (11, 2)),
70            (12, 1, (12, 2)),
71            (0, 1, (12, 2)),
72            (65535, 1, (12, 2)),
73            (15, 2, (12, 2)),
74            (65534, 1, (65534, 2)),
75            (1, 1, (1, 2)),
76        ] {
77            state.observe(number, flag);
78            assert_eq!(state.snapshot(), expected);
79        }
80        let mut configured = NetworkNumber::configured(17).unwrap();
81        assert_eq!(configured.observe(18, 1), Observation::ConfiguredConflict);
82        configured.observe(19, 0);
83        assert_eq!(configured.snapshot(), (17, 3));
84        assert_eq!(NetworkNumber::configured(0).unwrap().snapshot(), (0, 0));
85    }
86}