1use super::*;
2use bacnet_objects::staging::StagingWritePlan;
3
4#[cfg(test)]
5#[path = "input_present_value_tests.rs"]
6mod input_present_value_tests;
7
8#[cfg(test)]
9#[path = "staging_local_writes_tests.rs"]
10mod staging_local_writes_tests;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17enum LocalWrite {
18 Network {
20 property: PropertyIdentifier,
21 array_index: Option<u32>,
22 priority: Option<u8>,
23 },
24 ApplicationInputPresentValue,
26}
27
28impl<T: TransportPort + 'static> BACnetServer<T> {
29 pub async fn set_life_safety_operation_expected_local(
37 &self,
38 oid: &ObjectIdentifier,
39 operation: LifeSafetyOperation,
40 ) -> Result<(), Error> {
41 let changes = {
42 let mut db = self.db.write().await;
43 let snapshots = crate::life_safety_cov::LifeSafetyCovSnapshots::capture_oid(&db, *oid);
44 let object = db.get_mut(oid).ok_or_else(|| Error::Protocol {
45 class: ErrorClass::OBJECT.to_raw() as u32,
46 code: ErrorCode::UNKNOWN_OBJECT.to_raw() as u32,
47 })?;
48 object.set_life_safety_operation_expected_internal(operation)?;
49 snapshots.changes(&db, std::slice::from_ref(oid))
50 };
51 for change in changes {
52 Self::fire_life_safety_cov_notifications(
53 &self.db,
54 &self.network,
55 &self.cov_table,
56 &self.cov_in_flight,
57 &self.notification_transactions,
58 &self.comm_state,
59 &self.config,
60 &change.object_identifier,
61 &change.changed_properties,
62 )
63 .await;
64 }
65 Ok(())
66 }
67
68 pub async fn write_local(
80 &self,
81 oid: &ObjectIdentifier,
82 property: PropertyIdentifier,
83 array_index: Option<u32>,
84 value: PropertyValue,
85 priority: Option<u8>,
86 ) -> Result<(), Error> {
87 self.write_local_as(
88 oid,
89 LocalWrite::Network {
90 property,
91 array_index,
92 priority,
93 },
94 value,
95 )
96 .await
97 }
98
99 pub async fn set_present_value_local(
115 &self,
116 oid: &ObjectIdentifier,
117 value: PropertyValue,
118 ) -> Result<(), Error> {
119 self.write_local_as(oid, LocalWrite::ApplicationInputPresentValue, value)
120 .await
121 }
122
123 async fn write_local_as(
124 &self,
125 oid: &ObjectIdentifier,
126 write: LocalWrite,
127 value: PropertyValue,
128 ) -> Result<(), Error> {
129 let renaming = matches!(
132 write,
133 LocalWrite::Network { property, .. } if property == PropertyIdentifier::OBJECT_NAME
134 );
135 let life_safety = crate::life_safety_cov::is_life_safety_object(*oid);
136 let (exact_changes, staging_plans) = {
137 let mut db = self.db.write().await;
138 let snapshots = crate::life_safety_cov::LifeSafetyCovSnapshots::capture_oid(&db, *oid);
139 if db.get(oid).is_none() {
140 return Err(Error::Protocol {
141 class: ErrorClass::OBJECT.to_raw() as u32,
142 code: ErrorCode::UNKNOWN_OBJECT.to_raw() as u32,
143 });
144 }
145 if renaming {
146 if let PropertyValue::CharacterString(ref new_name) = value {
147 db.check_name_available(oid, new_name)?;
148 }
149 }
150 let object = db.get_mut(oid).expect("existence checked above");
151 match write {
152 LocalWrite::Network {
153 property,
154 array_index,
155 priority,
156 } => object.write_property(property, array_index, value, priority)?,
157 LocalWrite::ApplicationInputPresentValue => {
158 object.set_present_value_internal(value)?
159 }
160 }
161 if renaming {
162 db.update_name_index(oid);
163 }
164 let staging_plans = Self::take_staging_plans(&mut db, std::slice::from_ref(oid));
165 (
166 snapshots.changes(&db, std::slice::from_ref(oid)),
167 staging_plans,
168 )
169 };
170
171 Self::fire_event_notifications_with_bindings(
172 &self.db,
173 &self.network,
174 &self.comm_state,
175 &self.server_tsm,
176 &self.notification_transactions,
177 &self.device_bindings,
178 oid,
179 self.config.cov_retry_timeout_ms,
180 )
181 .await;
182 if life_safety {
183 for change in exact_changes {
184 Self::fire_life_safety_cov_notifications(
185 &self.db,
186 &self.network,
187 &self.cov_table,
188 &self.cov_in_flight,
189 &self.notification_transactions,
190 &self.comm_state,
191 &self.config,
192 &change.object_identifier,
193 &change.changed_properties,
194 )
195 .await;
196 }
197 } else {
198 Self::fire_cov_notifications(
199 &self.db,
200 &self.network,
201 &self.cov_table,
202 &self.cov_in_flight,
203 &self.notification_transactions,
204 &self.comm_state,
205 &self.config,
206 oid,
207 )
208 .await;
209 }
210 Self::execute_staging_plans(
211 &self.db,
212 &self.network,
213 &self.cov_table,
214 &self.cov_in_flight,
215 &self.server_tsm,
216 &self.notification_transactions,
217 &self.device_bindings,
218 &self.comm_state,
219 &self.config,
220 staging_plans,
221 )
222 .await;
223 Ok(())
224 }
225
226 pub(super) fn take_staging_plans(
227 db: &mut ObjectDatabase,
228 oids: &[ObjectIdentifier],
229 ) -> Vec<StagingWritePlan> {
230 oids.iter()
231 .filter_map(|oid| {
232 db.get_mut(oid)
233 .and_then(|object| object.take_staging_write_plan_internal())
234 })
235 .collect()
236 }
237
238 #[allow(clippy::too_many_arguments)]
243 pub(super) async fn execute_staging_plans(
244 db: &Arc<RwLock<ObjectDatabase>>,
245 network: &Arc<NetworkLayer<T>>,
246 cov_table: &Arc<RwLock<CovSubscriptionTable>>,
247 cov_in_flight: &Arc<Semaphore>,
248 server_tsm: &Arc<Mutex<ServerTsm>>,
249 notification_transactions: &Arc<NotificationTransactions>,
250 device_bindings: &Arc<RwLock<DeviceBindingTable>>,
251 comm_state: &Arc<AtomicU8>,
252 config: &ServerConfig,
253 plans: Vec<StagingWritePlan>,
254 ) {
255 for plan in plans {
256 let mut all_succeeded = true;
257 for target in &plan.writes {
258 enum TargetResult {
259 Applied,
260 Failed,
261 Stale,
262 }
263
264 let result = {
265 let mut database = db.write().await;
266 let current = database
267 .get(&plan.source)
268 .and_then(|source| source.staging_generation_internal())
269 == Some(plan.generation);
270 if !current {
271 TargetResult::Stale
272 } else {
273 match database.get_mut(&target.object_identifier) {
274 Some(object) => match object.write_property(
275 PropertyIdentifier::PRESENT_VALUE,
276 None,
277 PropertyValue::Enumerated(u32::from(target.active)),
278 Some(plan.priority),
279 ) {
280 Ok(()) => TargetResult::Applied,
281 Err(_) => TargetResult::Failed,
282 },
283 None => TargetResult::Failed,
284 }
285 }
286 };
287
288 match result {
289 TargetResult::Stale => break,
290 TargetResult::Failed => all_succeeded = false,
291 TargetResult::Applied => {
292 Self::fire_event_notifications_with_bindings(
293 db,
294 network,
295 comm_state,
296 server_tsm,
297 notification_transactions,
298 device_bindings,
299 &target.object_identifier,
300 config.cov_retry_timeout_ms,
301 )
302 .await;
303 Self::fire_cov_notifications(
304 db,
305 network,
306 cov_table,
307 cov_in_flight,
308 notification_transactions,
309 comm_state,
310 config,
311 &target.object_identifier,
312 )
313 .await;
314 }
315 }
316 }
317
318 let reliability_changed = {
319 let mut database = db.write().await;
320 database.get_mut(&plan.source).is_some_and(|source| {
321 source.complete_staging_write_plan_internal(plan.generation, all_succeeded)
322 })
323 };
324 if reliability_changed {
325 Self::fire_event_notifications_with_bindings(
326 db,
327 network,
328 comm_state,
329 server_tsm,
330 notification_transactions,
331 device_bindings,
332 &plan.source,
333 config.cov_retry_timeout_ms,
334 )
335 .await;
336 Self::fire_cov_notifications(
337 db,
338 network,
339 cov_table,
340 cov_in_flight,
341 notification_transactions,
342 comm_state,
343 config,
344 &plan.source,
345 )
346 .await;
347 }
348 }
349 }
350}