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 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
48/// The state a saved write leaves: the property it sets, as the object
49/// holds it.
50pub(super) enum NextState {
51    PositiveAccessRules(Vec<BACnetAccessRule>),
52    NegativeAccessRules(Vec<BACnetAccessRule>),
53    Enable(bool),
54    Accompaniment(BACnetDeviceObjectReference),
55}
56
57/// An object's writer and the write staged on it.
58pub(super) type Storage = StagedSaves<AccessRightsSnapshot, NextState>;
59
60/// Which saved properties a write has set, now or before a restart.
61#[derive(Clone, Copy, Debug, Default)]
62pub(super) struct Written {
63    positive: bool,
64    negative: bool,
65    enable: bool,
66    accompaniment: bool,
67}
68
69/// Whether `property` is one an Access Rights object saves.
70fn 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    /// An Access Rights object that keeps written rule arrays, Enable and
82    /// Accompaniment in `persistence`, starting from what is saved there for
83    /// this object, if anything. A saved Accompaniment serves the optional
84    /// row whether or not the application sets one.
85    ///
86    /// Each saved array and Accompaniment goes through the setters' checks,
87    /// so this fails when a saved array holds more than
88    /// [`MAX_ACCESS_RULES`](super::MAX_ACCESS_RULES) rules or a rule the
89    /// setters refuse, or the saved Accompaniment is one
90    /// [`set_accompaniment`](AccessRightsObject::set_accompaniment) refuses,
91    /// as well as when storage cannot be read.
92    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    /// Whether storage holds a value a write set for `property`
132    /// (Positive_Access_Rules, Negative_Access_Rules, Enable, property 133,
133    /// or Accompaniment), now or before a restart. That property's setter
134    /// then leaves it alone. Always false without persistence, and for any
135    /// other property.
136    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    /// Block until the saves queued so far have run. Dropping the object
148    /// waits for them too.
149    pub fn wait_for_saves(&self) {
150        if let Some(storage) = &self.storage {
151            storage.wait_idle();
152        }
153    }
154
155    /// Whether a setter call for `property` leaves it alone, because a
156    /// saved write set it.
157    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    /// What storage holds for the state the object serves.
170    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    /// The state a write of `value` to `property` (at `array_index`) would
185    /// leave on top of the `earlier` steps of its request, or the write's
186    /// refusal. The checks are the ones every write makes, with or without
187    /// persistence.
188    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        // An element or index-0 write edits the array an earlier step left.
196        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            // Only the application adds the optional row.
211            PropertyIdentifier::ACCOMPANIMENT if self.accompaniment.is_none() => {
212                return Err(common::unknown_property_error())
213            }
214            // Enable and Accompaniment are no arrays: the handlers refuse an
215            // index before the object sees it, and so does a direct call.
216            _ 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        // An indexed write edits the array the object serves; a whole write
231        // replaces it.
232        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    /// Write Positive_Access_Rules, Negative_Access_Rules, Enable or
267    /// Accompaniment, saving the result before the object serves it. A
268    /// staged write takes the state already saved; any other saves now. A
269    /// state that cannot be saved is refused with DEVICE /
270    /// OPERATIONAL_PROBLEM and the old value stays.
271    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        // A write the object refuses, such as one past the end of an array,
283        // leaves a write staged for another request alone (#1424).
284        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            // This write will be made, so it supersedes a staged one: storage
289            // goes back to the served state ahead of its save.
290            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    /// Take the staged step for a write of `value` to `property` (at
299    /// `array_index`), if it is the next one: the object then serves its
300    /// state, or the save's error comes back. `None` when nothing staged is
301    /// this write.
302    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        // Steps the request has still to take keep the state served now.
313        storage.correct(|| self.snapshot());
314        self.storage = Some(storage);
315        taken
316    }
317
318    /// From the server's operation task at monotonic `now`: drop a staged
319    /// write whose request is gone and queue the save of the served state
320    /// (see the module docs). The save runs on the writer thread.
321    pub(super) fn expire_staged_write(&mut self, now: Duration) {
322        self.with_storage(|storage| storage.expire(now));
323    }
324
325    /// Run `f` on storage; then, if `f` dropped a staged write, queue the
326    /// save that puts storage back to the served state. `None` without
327    /// persistence.
328    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
337/// Put the state `next` leaves into `snapshot`.
338fn 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    /// Fold the request's writes of the saved properties into one staged
366    /// save (#1423), so a head end that provisions both rule arrays and
367    /// Enable in one WritePropertyMultiple saves once, off the guard.
368    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}