Skip to main content

bacnet_server/cov/
sample.rs

1//! Bounded immutable values retained by COV subscription snapshots.
2use bacnet_objects::cov_change::{reports_change, same_value};
3use bacnet_types::{
4    enums::{ErrorClass, ErrorCode},
5    error::Error,
6    primitives::PropertyValue,
7};
8use std::sync::Arc;
9
10/// A validated typed COV baseline. Clones share immutable, normalized storage.
11/// Local retention limits: 32 nested Lists, 1,024 nodes and 65,536 payload bytes.
12#[derive(Debug, Clone)]
13pub struct CovSample(Arc<PropertyValue>);
14impl CovSample {
15    /// Validate before copying or encoding. Caller-owned spare capacity is not retained.
16    pub fn new(value: &PropertyValue) -> Result<Self, Error> {
17        let (mut nodes, mut bytes) = (0usize, 0usize);
18        validate(value, 0, &mut nodes, &mut bytes)?;
19        Ok(Self(Arc::new(normalize(value))))
20    }
21    /// Immutable typed content; mutation and unchecked baseline construction are unavailable.
22    pub fn value(&self) -> &PropertyValue {
23        &self.0
24    }
25
26    /// Whether this sample is a change from `previous` worth reporting,
27    /// by the shared Clause 13.1 test ([`reports_change`]); with no
28    /// previous sample it always is.
29    pub(crate) fn reports(
30        &self,
31        previous: Option<&Self>,
32        increment: Option<f64>,
33        numeric: bool,
34    ) -> bool {
35        previous.is_none_or(|previous| {
36            reports_change(self.value(), previous.value(), increment, numeric)
37        })
38    }
39}
40impl PartialEq for CovSample {
41    fn eq(&self, other: &Self) -> bool {
42        same_value(self.value(), other.value())
43    }
44}
45impl Eq for CovSample {}
46fn capacity_error() -> Error {
47    Error::protocol(
48        ErrorClass::RESOURCES,
49        ErrorCode::NO_SPACE_TO_ADD_LIST_ELEMENT,
50    )
51}
52fn validate(
53    v: &PropertyValue,
54    depth: usize,
55    nodes: &mut usize,
56    bytes: &mut usize,
57) -> Result<(), Error> {
58    *nodes = nodes.checked_add(1).ok_or_else(capacity_error)?;
59    if *nodes > 1024 {
60        return Err(capacity_error());
61    }
62    use PropertyValue::*;
63    let size = match v {
64        List(values) => {
65            if depth == 32 {
66                return Err(capacity_error());
67            }
68            for value in values {
69                validate(value, depth + 1, nodes, bytes)?;
70            }
71            0
72        }
73        OctetString(v) | ApplicationData(v) => v.len(),
74        CharacterString(v) => v.len(),
75        BitString { data, .. } => data.len(),
76        Null => 0,
77        Boolean(_) => 1,
78        Real(_) | Signed(_) | Enumerated(_) | Date(_) | Time(_) | ObjectIdentifier(_) => 4,
79        Unsigned(_) | Double(_) => 8,
80    };
81    *bytes = bytes.checked_add(size).ok_or_else(capacity_error)?;
82    if *bytes > 65536 {
83        return Err(capacity_error());
84    }
85    Ok(())
86}
87fn normalize(v: &PropertyValue) -> PropertyValue {
88    use PropertyValue::*;
89    fn bytes(v: &[u8]) -> Vec<u8> {
90        v.to_vec().into_boxed_slice().into_vec()
91    }
92    match v {
93        List(v) => List(
94            v.iter()
95                .map(normalize)
96                .collect::<Vec<_>>()
97                .into_boxed_slice()
98                .into_vec(),
99        ),
100        OctetString(v) => OctetString(bytes(v)),
101        ApplicationData(v) => ApplicationData(bytes(v)),
102        CharacterString(v) => CharacterString(v.as_str().into()),
103        BitString { unused_bits, data } => BitString {
104            unused_bits: *unused_bits,
105            data: bytes(data),
106        },
107        _ => v.clone(),
108    }
109}
110
111#[cfg(test)]
112mod tests;