Skip to main content

bacnet_server/server/
event_information_budget.rs

1//! GetEventInformation admission and response-page limits.
2use super::*;
3
4/// Positive local limits; callback allocations, CPU and RSS are not bounded.
5/// Every remaining object's strict projection is validated on admitted requests.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct GetEventInformationBudget {
8    /// Total database objects, including classes and objects before the cursor.
9    pub max_objects: usize,
10    /// Maximum summaries retained in a response page.
11    pub max_returned_summaries: usize,
12    /// Complete encoded service ACK, excluding APDU/NPDU envelopes.
13    pub max_service_ack_bytes: usize,
14}
15
16impl Default for GetEventInformationBudget {
17    fn default() -> Self {
18        Self {
19            max_objects: 4096,
20            max_returned_summaries: 256,
21            max_service_ack_bytes: 16384,
22        }
23    }
24}
25
26impl GetEventInformationBudget {
27    /// Reject zero configuration before startup or SC dialing.
28    pub fn validate(&self) -> Result<(), Error> {
29        for (name, value) in [
30            ("event_information_max_objects", self.max_objects),
31            (
32                "event_information_max_returned_summaries",
33                self.max_returned_summaries,
34            ),
35            (
36                "event_information_max_service_ack_bytes",
37                self.max_service_ack_bytes,
38            ),
39        ] {
40            if value == 0 {
41                return Err(Error::Encoding(format!("{name} must be positive")));
42            }
43        }
44        Ok(())
45    }
46}
47
48impl<T: TransportPort + 'static> ServerBuilder<T> {
49    /// Set GetEventInformation limits, validated before startup.
50    pub fn get_event_information_budget(mut self, budget: GetEventInformationBudget) -> Self {
51        self.config.get_event_information_budget = budget;
52        self
53    }
54}
55impl BipServerBuilder {
56    /// Set GetEventInformation limits, validated before startup.
57    pub fn get_event_information_budget(mut self, budget: GetEventInformationBudget) -> Self {
58        self.config.get_event_information_budget = budget;
59        self
60    }
61}
62#[cfg(feature = "sc-tls")]
63impl ScServerBuilder {
64    /// Set GetEventInformation limits, validated before SC dialing.
65    pub fn get_event_information_budget(mut self, budget: GetEventInformationBudget) -> Self {
66        self.config.get_event_information_budget = budget;
67        self
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::server::test_transport::TestTransport;
75
76    #[tokio::test]
77    async fn get_event_information_all_builders_validate_before_start_or_dial() {
78        let default = GetEventInformationBudget::default();
79        assert_eq!(
80            (
81                default.max_objects,
82                default.max_returned_summaries,
83                default.max_service_ack_bytes
84            ),
85            (4096, 256, 16384)
86        );
87        assert_eq!(
88            ServerConfig::default().get_event_information_budget,
89            default
90        );
91        assert!(format!("{:?}", ServerConfig::default()).contains("get_event_information_budget"));
92        GetEventInformationBudget {
93            max_objects: usize::MAX,
94            max_returned_summaries: usize::MAX,
95            max_service_ack_bytes: usize::MAX,
96        }
97        .validate()
98        .unwrap();
99        for budget in [
100            GetEventInformationBudget {
101                max_objects: 0,
102                ..default
103            },
104            GetEventInformationBudget {
105                max_returned_summaries: 0,
106                ..default
107            },
108            GetEventInformationBudget {
109                max_service_ack_bytes: 0,
110                ..default
111            },
112        ] {
113            let direct = BACnetServer::start(
114                ServerConfig {
115                    get_event_information_budget: budget,
116                    ..Default::default()
117                },
118                ObjectDatabase::new(),
119                TestTransport::never_start(),
120            )
121            .await
122            .err();
123            let generic = BACnetServer::generic_builder()
124                .transport(TestTransport::never_start())
125                .get_event_information_budget(budget)
126                .build()
127                .await
128                .err();
129            let bip = BACnetServer::bip_builder()
130                .port(0)
131                .get_event_information_budget(budget)
132                .build()
133                .await
134                .err();
135            for error in [direct, generic, bip] {
136                assert!(
137                    matches!(error, Some(Error::Encoding(m)) if m.contains("event_information_max_"))
138                );
139            }
140            #[cfg(feature = "sc-tls")]
141            {
142                let error = BACnetServer::sc_builder()
143                    .hub_url("not-a-websocket-url")
144                    .tls_config(crate::server::sc_builder::test_tls_config())
145                    .device_uuid(crate::server::sc_builder::TEST_DEVICE_UUID)
146                    .get_event_information_budget(budget)
147                    .build()
148                    .await
149                    .err();
150                assert!(
151                    matches!(error, Some(Error::Encoding(m)) if m.contains("event_information_max_"))
152                );
153            }
154        }
155    }
156}