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