Skip to main content

bacnet_objects/access_control/
rights_saving.rs

1//! When an Access Rights object saves Positive_Access_Rules,
2//! Negative_Access_Rules, Enable (#1392) and Accompaniment (#1393).
3//!
4//! An object built with [`AccessRightsObject::with_persistence`] saves each
5//! write of those four properties before serving it: whole arrays, single
6//! elements and index-0 resizes alike, since storage holds whole arrays. A
7//! write it cannot save is refused with DEVICE / OPERATIONAL_PROBLEM, and the
8//! old value stays. This is the Notification Class's scheme
9//! ([`crate::durable`]), shared with it through `StagedSaves`: the bundled
10//! server stages the write, the save runs on the object's writer thread while
11//! the database guard is dropped, and the write then takes the saved state
12//! without saving again. A WritePropertyMultiple's several writes to the
13//! object stage one save of the state they leave together, and each takes
14//! its own step of it in turn (#1423). A write that was not staged queues its
15//! save and waits for it where it is.
16//!
17//! A staged write its request releases without making is dropped, and the
18//! object at once queues a save of what it serves, so storage goes back to
19//! that. A staged write whose request vanished is dropped the same way once
20//! [`STAGED_WRITE_LIFETIME`](crate::durable::STAGED_WRITE_LIFETIME) has
21//! passed since its save finished, by the next write that stages or by the
22//! server's once-a-second operation task. The server's `stop()` drops one
23//! still held and waits for the correcting save, and an object dropped with
24//! one still held saves its served state as it goes, unless the staged save
25//! failed (#1363). Either way a restart serves what the object served.
26//!
27//! A written value wins over what the application configures: once a write
28//! has set a property and it was saved, a rebuilt object serves the saved
29//! value, and that property's setter checks what it is given without
30//! storing it. Configuration alone is never saved, but a write saves the
31//! whole array it leaves, so an element or index-0 write to an array no
32//! write has set yet also saves the configured rules it didn't touch.
33
34use 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
49/// The state a saved write leaves: the property it sets, as the object
50/// holds it.
51pub(super) enum NextState {
52    PositiveAccessRules(Vec<BACnetAccessRule>),
53    NegativeAccessRules(Vec<BACnetAccessRule>),
54    Enable(bool),
55    Accompaniment(BACnetDeviceObjectReference),
56}
57
58/// An object's writer and the write staged on it.
59pub(super) type Storage = StagedSaves<AccessRightsSnapshot, NextState>;
60
61/// Which saved properties a write has set, now or before a restart.
62#[derive(Clone, Copy, Debug, Default)]
63pub(super) struct Written {
64    positive: bool,
65    negative: bool,
66    enable: bool,
67    accompaniment: bool,
68}
69
70/// Whether `property` is one an Access Rights object saves.
71fn 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    /// An Access Rights object that keeps written rule arrays, Enable and
83    /// Accompaniment in `persistence`, starting from what is saved there for
84    /// this object, if anything. A saved Accompaniment serves the optional
85    /// row whether or not the application sets one.
86    ///
87    /// Each saved array and Accompaniment goes through the setters' checks,
88    /// so this fails when a saved array holds more than
89    /// [`MAX_ACCESS_RULES`](super::MAX_ACCESS_RULES) rules or a rule the
90    /// setters refuse, or the saved Accompaniment is one
91    /// [`set_accompaniment`](AccessRightsObject::set_accompaniment) refuses,
92    /// as well as when storage cannot be read.
93    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    /// Whether storage holds a value a write set for `property`
133    /// (Positive_Access_Rules, Negative_Access_Rules, Enable, property 133,
134    /// or Accompaniment), now or before a restart. That property's setter
135    /// then leaves it alone. Always false without persistence, and for any
136    /// other property.
137    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    /// Block until the saves queued so far have run. Dropping the object
149    /// waits for them too.
150    pub fn wait_for_saves(&self) {
151        if let Some(storage) = &self.storage {
152            storage.wait_idle();
153        }
154    }
155
156    /// Whether a setter call for `property` leaves it alone, because a
157    /// saved write set it.
158    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    /// What storage holds for the state the object serves.
171    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    /// The state a write of `value` to `property` (at `array_index`) would
186    /// leave on top of the `earlier` steps of its request, or the write's
187    /// refusal. The checks are the ones every write makes, with or without
188    /// persistence.
189    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        // An element or index-0 write edits the array an earlier step left.
197        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            // Only the application adds the optional row.
212            PropertyIdentifier::ACCOMPANIMENT if self.accompaniment.is_none() => {
213                return Err(common::unknown_property_error());
214            }
215            // Enable and Accompaniment are no arrays: the handlers refuse an
216            // index before the object sees it, and so does a direct call.
217            _ 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        // An indexed write edits the array the object serves; a whole write
232        // replaces it.
233        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    /// Write Positive_Access_Rules, Negative_Access_Rules, Enable or
268    /// Accompaniment, saving the result before the object serves it. A
269    /// staged write takes the state already saved; any other saves now. A
270    /// state that cannot be saved is refused with DEVICE /
271    /// OPERATIONAL_PROBLEM and the old value stays.
272    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        // A write the object refuses, such as one past the end of an array,
284        // leaves a write staged for another request alone (#1424).
285        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            // This write will be made, so it supersedes a staged one: storage
290            // goes back to the served state ahead of its save.
291            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    /// Take the staged step for a write of `value` to `property` (at
300    /// `array_index`), if it is the next one: the object then serves its
301    /// state, or the save's error comes back. `None` when nothing staged is
302    /// this write.
303    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        // Steps the request has still to take keep the state served now.
314        storage.correct(|| self.snapshot());
315        self.storage = Some(storage);
316        taken
317    }
318
319    /// From the server's operation task at monotonic `now`: drop a staged
320    /// write whose request is gone and queue the save of the served state
321    /// (see the module docs). The save runs on the writer thread.
322    pub(super) fn expire_staged_write(&mut self, now: Duration) {
323        self.with_storage(|storage| storage.expire(now));
324    }
325
326    /// Run `f` on storage; then, if `f` dropped a staged write, queue the
327    /// save that puts storage back to the served state. `None` without
328    /// persistence.
329    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
338/// Put the state `next` leaves into `snapshot`.
339fn 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    /// Fold the request's writes of the saved properties into one staged
367    /// save (#1423), so a head end that provisions both rule arrays and
368    /// Enable in one WritePropertyMultiple saves once, off the guard.
369    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}