bacnet_objects/access_control/
rights_saving.rs1use crate::prelude::*;
35use alloc::sync::Arc;
36use core::time::Duration;
37
38use bacnet_types::constructed::{BACnetAccessRule, BACnetDeviceObjectReference};
39use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier};
40use bacnet_types::error::Error;
41use bacnet_types::primitives::PropertyValue;
42
43use super::persistence::{AccessRightsPersistence, AccessRightsSnapshot};
44use super::{AccessRightsObject, check_accompaniment, checked_rules};
45use crate::common;
46use crate::durable::staged::{self, StagedSaves, Step};
47use crate::durable::{DurableWrites, PendingWrite, SaveWait, SaveWriter, StageStep};
48
49pub(super) enum NextState {
52 PositiveAccessRules(Vec<BACnetAccessRule>),
53 NegativeAccessRules(Vec<BACnetAccessRule>),
54 Enable(bool),
55 Accompaniment(BACnetDeviceObjectReference),
56}
57
58pub(super) type Storage = StagedSaves<AccessRightsSnapshot, NextState>;
60
61#[derive(Clone, Copy, Debug, Default)]
63pub(super) struct Written {
64 positive: bool,
65 negative: bool,
66 enable: bool,
67 accompaniment: bool,
68}
69
70fn saved_property(property: PropertyIdentifier) -> bool {
72 matches!(
73 property,
74 PropertyIdentifier::POSITIVE_ACCESS_RULES
75 | PropertyIdentifier::NEGATIVE_ACCESS_RULES
76 | PropertyIdentifier::LOG_ENABLE
77 | PropertyIdentifier::ACCOMPANIMENT
78 )
79}
80
81impl AccessRightsObject {
82 pub fn with_persistence(
94 instance: u32,
95 name: impl Into<String>,
96 persistence: Arc<dyn AccessRightsPersistence>,
97 ) -> Result<Self, Error> {
98 let mut rights = Self::new(instance, name)?;
99 let oid = rights.oid;
100 if let Some(saved) = persistence.load(oid)? {
101 if let Some(rules) = saved.positive_access_rules {
102 rights.positive_access_rules = checked_rules(rules)?;
103 rights.written.positive = true;
104 }
105 if let Some(rules) = saved.negative_access_rules {
106 rights.negative_access_rules = checked_rules(rules)?;
107 rights.written.negative = true;
108 }
109 if let Some(enable) = saved.enable {
110 rights.enable = enable;
111 rights.written.enable = true;
112 }
113 if let Some(reference) = saved.accompaniment {
114 check_accompaniment(&reference)?;
115 rights.accompaniment = Some(reference);
116 rights.written.accompaniment = true;
117 }
118 }
119 let writer = SaveWriter::new(
120 format!("bacnet-ar-{instance}-save"),
121 move |snapshot: &AccessRightsSnapshot| persistence.save(oid, snapshot),
122 move |_, result| {
123 if let Err(error) = result {
124 log_warn!(rights = %oid, %error, "Failed to save Access Rights state");
125 }
126 },
127 );
128 rights.storage = Some(StagedSaves::new(writer));
129 Ok(rights)
130 }
131
132 pub fn property_saved(&self, property: PropertyIdentifier) -> bool {
138 self.storage.is_some()
139 && match property {
140 PropertyIdentifier::POSITIVE_ACCESS_RULES => self.written.positive,
141 PropertyIdentifier::NEGATIVE_ACCESS_RULES => self.written.negative,
142 PropertyIdentifier::LOG_ENABLE => self.written.enable,
143 PropertyIdentifier::ACCOMPANIMENT => self.written.accompaniment,
144 _ => false,
145 }
146 }
147
148 pub fn wait_for_saves(&self) {
151 if let Some(storage) = &self.storage {
152 storage.wait_idle();
153 }
154 }
155
156 pub(super) fn keeps_saved(&self, property: PropertyIdentifier) -> bool {
159 let kept = self.property_saved(property);
160 if kept {
161 log_debug!(
162 rights = %self.oid,
163 ?property,
164 "Saved Access Rights value kept over a configured one"
165 );
166 }
167 kept
168 }
169
170 fn snapshot(&self) -> AccessRightsSnapshot {
172 let served = |written: bool, rules: &Vec<BACnetAccessRule>| written.then(|| rules.clone());
173 AccessRightsSnapshot {
174 positive_access_rules: served(self.written.positive, &self.positive_access_rules),
175 negative_access_rules: served(self.written.negative, &self.negative_access_rules),
176 enable: self.written.enable.then_some(self.enable),
177 accompaniment: self
178 .written
179 .accompaniment
180 .then(|| self.accompaniment.clone())
181 .flatten(),
182 }
183 }
184
185 fn next_state(
190 &self,
191 earlier: &[Step<NextState>],
192 property: PropertyIdentifier,
193 array_index: Option<u32>,
194 value: PropertyValue,
195 ) -> Result<NextState, Error> {
196 let latest = |positive: bool| {
198 earlier.iter().rev().find_map(|step| match &step.next {
199 NextState::PositiveAccessRules(rules) if positive => Some(rules),
200 NextState::NegativeAccessRules(rules) if !positive => Some(rules),
201 _ => None,
202 })
203 };
204 let current = match property {
205 PropertyIdentifier::POSITIVE_ACCESS_RULES => {
206 latest(true).unwrap_or(&self.positive_access_rules)
207 }
208 PropertyIdentifier::NEGATIVE_ACCESS_RULES => {
209 latest(false).unwrap_or(&self.negative_access_rules)
210 }
211 PropertyIdentifier::ACCOMPANIMENT if self.accompaniment.is_none() => {
213 return Err(common::unknown_property_error());
214 }
215 _ if array_index.is_some() => return Err(common::property_is_not_an_array_error()),
218 PropertyIdentifier::ACCOMPANIMENT => {
219 let reference: BACnetDeviceObjectReference =
220 crate::device_reference::decode_reference(&value)?;
221 check_accompaniment(&reference)?;
222 return Ok(NextState::Accompaniment(reference));
223 }
224 _ => {
225 return match value {
226 PropertyValue::Boolean(enable) => Ok(NextState::Enable(enable)),
227 _ => Err(common::invalid_data_type_error()),
228 };
229 }
230 };
231 let mut next = match array_index {
234 Some(_) => current.clone(),
235 None => Vec::new(),
236 };
237 super::super::rights_writes::write_rules(&mut next, array_index, value)?;
238 Ok(if property == PropertyIdentifier::POSITIVE_ACCESS_RULES {
239 NextState::PositiveAccessRules(next)
240 } else {
241 NextState::NegativeAccessRules(next)
242 })
243 }
244
245 fn install(&mut self, next: NextState) {
246 match next {
247 NextState::PositiveAccessRules(rules) => {
248 self.positive_access_rules = rules;
249 self.written.positive = true;
250 }
251 NextState::NegativeAccessRules(rules) => {
252 self.negative_access_rules = rules;
253 self.written.negative = true;
254 }
255 NextState::Enable(enable) => {
256 self.enable = enable;
257 self.written.enable = true;
258 }
259 NextState::Accompaniment(reference) => {
260 self.accompaniment = Some(reference);
261 self.written.accompaniment = true;
262 }
263 }
264 self.writes = self.writes.wrapping_add(1);
265 }
266
267 pub(super) fn write_saved(
273 &mut self,
274 property: PropertyIdentifier,
275 array_index: Option<u32>,
276 value: PropertyValue,
277 ) -> Result<(), Error> {
278 let refused =
279 |_| common::protocol_error(ErrorClass::DEVICE, ErrorCode::OPERATIONAL_PROBLEM);
280 if let Some(taken) = self.take_staged(property, array_index, &value) {
281 return taken.map_err(refused);
282 }
283 let next = self.next_state(&[], property, array_index, value)?;
286 if self.storage.is_some() {
287 let mut snapshot = self.snapshot();
288 apply(&mut snapshot, &next);
289 self.with_storage(Storage::drop_staged);
292 let storage = self.storage.as_mut().expect("checked above");
293 storage.save_now(snapshot).map_err(refused)?;
294 }
295 self.install(next);
296 Ok(())
297 }
298
299 fn take_staged(
304 &mut self,
305 property: PropertyIdentifier,
306 array_index: Option<u32>,
307 value: &PropertyValue,
308 ) -> Option<Result<(), Error>> {
309 let mut storage = self.storage.take()?;
310 let taken = storage
311 .claim(property, array_index, value, self.writes)
312 .map(|claimed| claimed.map(|next| self.install(next)));
313 storage.correct(|| self.snapshot());
315 self.storage = Some(storage);
316 taken
317 }
318
319 pub(super) fn expire_staged_write(&mut self, now: Duration) {
323 self.with_storage(|storage| storage.expire(now));
324 }
325
326 fn with_storage<R>(&mut self, f: impl FnOnce(&mut Storage) -> R) -> Option<R> {
330 let mut storage = self.storage.take()?;
331 let result = f(&mut storage);
332 storage.correct(|| self.snapshot());
333 self.storage = Some(storage);
334 Some(result)
335 }
336}
337
338fn apply(snapshot: &mut AccessRightsSnapshot, next: &NextState) {
340 match next {
341 NextState::PositiveAccessRules(rules) => {
342 snapshot.positive_access_rules = Some(rules.clone());
343 }
344 NextState::NegativeAccessRules(rules) => {
345 snapshot.negative_access_rules = Some(rules.clone());
346 }
347 NextState::Enable(enable) => snapshot.enable = Some(*enable),
348 NextState::Accompaniment(reference) => snapshot.accompaniment = Some(reference.clone()),
349 }
350}
351
352impl DurableWrites for AccessRightsObject {
353 fn stage_write(
354 &mut self,
355 property: PropertyIdentifier,
356 array_index: Option<u32>,
357 value: &PropertyValue,
358 ) -> StageStep {
359 self.stage_writes(&[PendingWrite {
360 property,
361 array_index,
362 value: value.clone(),
363 }])
364 }
365
366 fn stage_writes(&mut self, writes: &[PendingWrite]) -> StageStep {
370 if self.storage.is_none() || !writes.iter().any(|write| saved_property(write.property)) {
371 return StageStep::Skip;
372 }
373 if let Some(wait) = self.with_storage(Storage::busy).flatten() {
374 return StageStep::Busy(wait);
375 }
376 let steps = staged::steps(writes, |earlier, write| {
377 if !saved_property(write.property) {
378 return Ok(None);
379 }
380 let PendingWrite {
381 property,
382 array_index,
383 value,
384 } = write;
385 self.next_state(earlier, *property, *array_index, value.clone())
386 .map(Some)
387 });
388 let served = self.snapshot();
389 let base = self.writes;
390 let storage = self.storage.as_mut().expect("checked above");
391 storage.stage(steps, base, served, apply)
392 }
393
394 fn release_staged_write(&mut self, staged: &SaveWait) {
395 self.with_storage(|storage| storage.release(staged));
396 }
397
398 fn has_staged_write(&self) -> bool {
399 self.storage.as_ref().is_some_and(Storage::is_staged)
400 }
401
402 fn settle_forgotten_writes(&mut self) -> Option<SaveWait> {
403 let mut storage = self.storage.take()?;
404 let wait = storage.drop_forgotten(|| self.snapshot());
405 self.storage = Some(storage);
406 Some(wait)
407 }
408}