Skip to main content

bacnet_server/cov/
sample.rs

1//! Bounded immutable values retained by COV subscription snapshots.
2use bacnet_types::{
3    enums::{ErrorClass, ErrorCode},
4    error::Error,
5    primitives::PropertyValue,
6};
7use std::sync::Arc;
8
9/// A validated typed COV baseline. Clones share immutable, normalized storage.
10/// Local retention limits: 32 nested Lists, 1,024 nodes and 65,536 payload bytes.
11#[derive(Debug, Clone)]
12pub struct CovSample(Arc<PropertyValue>);
13impl CovSample {
14    /// Validate before copying or encoding. Caller-owned spare capacity is not retained.
15    pub fn new(value: &PropertyValue) -> Result<Self, Error> {
16        let (mut nodes, mut bytes) = (0usize, 0usize);
17        validate(value, 0, &mut nodes, &mut bytes)?;
18        Ok(Self(Arc::new(normalize(value))))
19    }
20    /// Immutable typed content; mutation and unchecked baseline construction are unavailable.
21    pub fn value(&self) -> &PropertyValue {
22        &self.0
23    }
24
25    pub(crate) fn reports(
26        &self,
27        previous: Option<&Self>,
28        increment: Option<f64>,
29        numeric: bool,
30    ) -> bool {
31        let Some(previous) = previous else {
32            return true;
33        };
34        if !numeric {
35            return self != previous;
36        }
37        use PropertyValue::*;
38        match (self.value(), previous.value()) {
39            (Unsigned(a), Unsigned(b)) => integer_delta(a.abs_diff(*b), increment),
40            (Signed(a), Signed(b)) => integer_delta(u64::from(a.abs_diff(*b)), increment),
41            (Real(a), Real(b)) => {
42                if !a.is_finite() || !b.is_finite() {
43                    a.to_bits() != b.to_bits()
44                } else {
45                    float_delta((f64::from(*a) - f64::from(*b)).abs(), increment)
46                }
47            }
48            (Double(a), Double(b)) => {
49                if !a.is_finite() || !b.is_finite() {
50                    a.to_bits() != b.to_bits()
51                } else {
52                    float_delta((a - b).abs(), increment)
53                }
54            }
55            _ => self != previous,
56        }
57    }
58}
59impl PartialEq for CovSample {
60    fn eq(&self, other: &Self) -> bool {
61        equal(self.value(), other.value())
62    }
63}
64impl Eq for CovSample {}
65fn integer_delta(delta: u64, increment: Option<f64>) -> bool {
66    match increment {
67        None => delta != 0,
68        Some(i) if i <= 0.0 => delta != 0,
69        Some(i) if i.is_nan() || i >= 18_446_744_073_709_551_616.0 => false,
70        Some(i) => delta >= i.ceil() as u64,
71    }
72}
73fn float_delta(delta: f64, increment: Option<f64>) -> bool {
74    match increment {
75        None => delta != 0.0,
76        Some(i) if i <= 0.0 => delta != 0.0,
77        Some(i) if !i.is_finite() => false,
78        Some(i) => delta >= i,
79    }
80}
81fn capacity_error() -> Error {
82    Error::Protocol {
83        class: ErrorClass::RESOURCES.to_raw() as u32,
84        code: ErrorCode::NO_SPACE_TO_ADD_LIST_ELEMENT.to_raw() as u32,
85    }
86}
87fn validate(
88    v: &PropertyValue,
89    depth: usize,
90    nodes: &mut usize,
91    bytes: &mut usize,
92) -> Result<(), Error> {
93    *nodes = nodes.checked_add(1).ok_or_else(capacity_error)?;
94    if *nodes > 1024 {
95        return Err(capacity_error());
96    }
97    use PropertyValue::*;
98    let size = match v {
99        List(values) => {
100            if depth == 32 {
101                return Err(capacity_error());
102            }
103            for value in values {
104                validate(value, depth + 1, nodes, bytes)?;
105            }
106            0
107        }
108        OctetString(v) | ApplicationData(v) => v.len(),
109        CharacterString(v) => v.len(),
110        BitString { data, .. } => data.len(),
111        Null => 0,
112        Boolean(_) => 1,
113        Real(_) | Signed(_) | Enumerated(_) | Date(_) | Time(_) | ObjectIdentifier(_) => 4,
114        Unsigned(_) | Double(_) => 8,
115    };
116    *bytes = bytes.checked_add(size).ok_or_else(capacity_error)?;
117    if *bytes > 65536 {
118        return Err(capacity_error());
119    }
120    Ok(())
121}
122fn normalize(v: &PropertyValue) -> PropertyValue {
123    use PropertyValue::*;
124    fn bytes(v: &[u8]) -> Vec<u8> {
125        v.to_vec().into_boxed_slice().into_vec()
126    }
127    match v {
128        List(v) => List(
129            v.iter()
130                .map(normalize)
131                .collect::<Vec<_>>()
132                .into_boxed_slice()
133                .into_vec(),
134        ),
135        OctetString(v) => OctetString(bytes(v)),
136        ApplicationData(v) => ApplicationData(bytes(v)),
137        CharacterString(v) => CharacterString(v.as_str().into()),
138        BitString { unused_bits, data } => BitString {
139            unused_bits: *unused_bits,
140            data: bytes(data),
141        },
142        _ => v.clone(),
143    }
144}
145fn equal(a: &PropertyValue, b: &PropertyValue) -> bool {
146    use PropertyValue::*;
147    match (a, b) {
148        (Real(a), Real(b)) => a.to_bits() == b.to_bits(),
149        (Double(a), Double(b)) => a.to_bits() == b.to_bits(),
150        (List(a), List(b)) => a.len() == b.len() && a.iter().zip(b).all(|(a, b)| equal(a, b)),
151        _ => a == b,
152    }
153}
154
155#[cfg(test)]
156mod tests;