Skip to main content

bacnet_server/server/
alarm_summary_budget.rs

1//! Local GetAlarmSummary scan and response limits.
2use super::*;
3
4/// Positive local limits for a complete GetAlarmSummary response.
5///
6/// Bounds total database objects before callbacks, and logical encoded service
7/// ACK bytes (not APDU/NPDU or peer APDU size). Does not bound one callback,
8/// property read, metadata operation, allocation capacity, CPU time or RSS.
9/// Byte refusal does not roll back reads already performed.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct GetAlarmSummaryBudget {
12    /// Maximum total database objects, including non-alarming objects (4096).
13    pub max_objects: usize,
14    /// Maximum encoded service ACK logical bytes (16384).
15    pub max_service_ack_bytes: usize,
16}
17
18impl Default for GetAlarmSummaryBudget {
19    fn default() -> Self {
20        Self {
21            max_objects: 4096,
22            max_service_ack_bytes: 16384,
23        }
24    }
25}
26
27impl GetAlarmSummaryBudget {
28    /// Reject zero; larger positive values are an operator policy choice.
29    pub fn validate(&self) -> Result<(), Error> {
30        for (name, value) in [
31            ("alarm_summary_max_objects", self.max_objects),
32            (
33                "alarm_summary_max_service_ack_bytes",
34                self.max_service_ack_bytes,
35            ),
36        ] {
37            if value == 0 {
38                return Err(Error::Encoding(format!("{name} must be positive")));
39            }
40        }
41        Ok(())
42    }
43}
44
45impl<T: TransportPort + 'static> ServerBuilder<T> {
46    /// Set limits validated before transport startup.
47    pub fn get_alarm_summary_budget(mut self, budget: GetAlarmSummaryBudget) -> Self {
48        self.config.get_alarm_summary_budget = budget;
49        self
50    }
51}
52
53impl BipServerBuilder {
54    /// Set limits validated before transport startup.
55    pub fn get_alarm_summary_budget(mut self, budget: GetAlarmSummaryBudget) -> Self {
56        self.config.get_alarm_summary_budget = budget;
57        self
58    }
59}
60
61#[cfg(feature = "sc-tls")]
62impl ScServerBuilder {
63    /// Set limits validated before SC dialing.
64    pub fn get_alarm_summary_budget(mut self, budget: GetAlarmSummaryBudget) -> Self {
65        self.config.get_alarm_summary_budget = budget;
66        self
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use crate::server::test_transport::TestTransport;
74
75    #[tokio::test]
76    async fn alarm_summary_defaults_builders_and_pre_start_validation() {
77        let defaults = GetAlarmSummaryBudget::default();
78        assert_eq!(
79            (defaults.max_objects, defaults.max_service_ack_bytes),
80            (4096, 16384)
81        );
82        assert_eq!(ServerConfig::default().get_alarm_summary_budget, defaults);
83        assert!(format!("{:?}", ServerConfig::default()).contains("get_alarm_summary_budget"));
84        GetAlarmSummaryBudget {
85            max_objects: usize::MAX,
86            max_service_ack_bytes: usize::MAX,
87        }
88        .validate()
89        .unwrap();
90        for budget in [
91            GetAlarmSummaryBudget {
92                max_objects: 0,
93                ..defaults
94            },
95            GetAlarmSummaryBudget {
96                max_service_ack_bytes: 0,
97                ..defaults
98            },
99        ] {
100            let direct = BACnetServer::start(
101                ServerConfig {
102                    get_alarm_summary_budget: budget,
103                    ..Default::default()
104                },
105                ObjectDatabase::new(),
106                TestTransport::never_start(),
107            )
108            .await
109            .err();
110            let generic = BACnetServer::generic_builder()
111                .transport(TestTransport::never_start())
112                .get_alarm_summary_budget(budget)
113                .build()
114                .await
115                .err();
116            let bip = BACnetServer::bip_builder()
117                .port(0)
118                .get_alarm_summary_budget(budget)
119                .build()
120                .await
121                .err();
122            for error in [direct, generic, bip] {
123                assert!(
124                    matches!(error, Some(Error::Encoding(m)) if m.contains("alarm_summary_max_"))
125                );
126            }
127        }
128    }
129}