use std::sync::Arc;
use std::time::Duration;
use bacnet_types::constructed::{BACnetAccessRule, BACnetDeviceObjectReference};
use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use super::persistence::{AccessRightsPersistence, AccessRightsSnapshot};
use super::{check_accompaniment, checked_rules, AccessRightsObject};
use crate::common;
use crate::durable::staged::{self, StagedSaves, Step};
use crate::durable::{DurableWrites, PendingWrite, SaveWait, SaveWriter, StageStep};
pub(super) enum NextState {
PositiveAccessRules(Vec<BACnetAccessRule>),
NegativeAccessRules(Vec<BACnetAccessRule>),
Enable(bool),
Accompaniment(BACnetDeviceObjectReference),
}
pub(super) type Storage = StagedSaves<AccessRightsSnapshot, NextState>;
#[derive(Clone, Copy, Debug, Default)]
pub(super) struct Written {
positive: bool,
negative: bool,
enable: bool,
accompaniment: bool,
}
fn saved_property(property: PropertyIdentifier) -> bool {
matches!(
property,
PropertyIdentifier::POSITIVE_ACCESS_RULES
| PropertyIdentifier::NEGATIVE_ACCESS_RULES
| PropertyIdentifier::LOG_ENABLE
| PropertyIdentifier::ACCOMPANIMENT
)
}
impl AccessRightsObject {
pub fn with_persistence(
instance: u32,
name: impl Into<String>,
persistence: Arc<dyn AccessRightsPersistence>,
) -> Result<Self, Error> {
let mut rights = Self::new(instance, name)?;
let oid = rights.oid;
if let Some(saved) = persistence.load(oid)? {
if let Some(rules) = saved.positive_access_rules {
rights.positive_access_rules = checked_rules(rules)?;
rights.written.positive = true;
}
if let Some(rules) = saved.negative_access_rules {
rights.negative_access_rules = checked_rules(rules)?;
rights.written.negative = true;
}
if let Some(enable) = saved.enable {
rights.enable = enable;
rights.written.enable = true;
}
if let Some(reference) = saved.accompaniment {
check_accompaniment(&reference)?;
rights.accompaniment = Some(reference);
rights.written.accompaniment = true;
}
}
let writer = SaveWriter::new(
format!("bacnet-ar-{instance}-save"),
move |snapshot: &AccessRightsSnapshot| persistence.save(oid, snapshot),
move |_, result| {
if let Err(error) = result {
tracing::warn!(rights = %oid, %error, "Failed to save Access Rights state");
}
},
);
rights.storage = Some(StagedSaves::new(writer));
Ok(rights)
}
pub fn property_saved(&self, property: PropertyIdentifier) -> bool {
self.storage.is_some()
&& match property {
PropertyIdentifier::POSITIVE_ACCESS_RULES => self.written.positive,
PropertyIdentifier::NEGATIVE_ACCESS_RULES => self.written.negative,
PropertyIdentifier::LOG_ENABLE => self.written.enable,
PropertyIdentifier::ACCOMPANIMENT => self.written.accompaniment,
_ => false,
}
}
pub fn wait_for_saves(&self) {
if let Some(storage) = &self.storage {
storage.wait_idle();
}
}
pub(super) fn keeps_saved(&self, property: PropertyIdentifier) -> bool {
let kept = self.property_saved(property);
if kept {
tracing::debug!(
rights = %self.oid,
?property,
"Saved Access Rights value kept over a configured one"
);
}
kept
}
fn snapshot(&self) -> AccessRightsSnapshot {
let served = |written: bool, rules: &Vec<BACnetAccessRule>| written.then(|| rules.clone());
AccessRightsSnapshot {
positive_access_rules: served(self.written.positive, &self.positive_access_rules),
negative_access_rules: served(self.written.negative, &self.negative_access_rules),
enable: self.written.enable.then_some(self.enable),
accompaniment: self
.written
.accompaniment
.then(|| self.accompaniment.clone())
.flatten(),
}
}
fn next_state(
&self,
earlier: &[Step<NextState>],
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
) -> Result<NextState, Error> {
let latest = |positive: bool| {
earlier.iter().rev().find_map(|step| match &step.next {
NextState::PositiveAccessRules(rules) if positive => Some(rules),
NextState::NegativeAccessRules(rules) if !positive => Some(rules),
_ => None,
})
};
let current = match property {
PropertyIdentifier::POSITIVE_ACCESS_RULES => {
latest(true).unwrap_or(&self.positive_access_rules)
}
PropertyIdentifier::NEGATIVE_ACCESS_RULES => {
latest(false).unwrap_or(&self.negative_access_rules)
}
PropertyIdentifier::ACCOMPANIMENT if self.accompaniment.is_none() => {
return Err(common::unknown_property_error())
}
_ if array_index.is_some() => return Err(common::property_is_not_an_array_error()),
PropertyIdentifier::ACCOMPANIMENT => {
let reference: BACnetDeviceObjectReference =
crate::device_reference::decode_reference(&value)?;
check_accompaniment(&reference)?;
return Ok(NextState::Accompaniment(reference));
}
_ => {
return match value {
PropertyValue::Boolean(enable) => Ok(NextState::Enable(enable)),
_ => Err(common::invalid_data_type_error()),
}
}
};
let mut next = match array_index {
Some(_) => current.clone(),
None => Vec::new(),
};
super::super::rights_writes::write_rules(&mut next, array_index, value)?;
Ok(if property == PropertyIdentifier::POSITIVE_ACCESS_RULES {
NextState::PositiveAccessRules(next)
} else {
NextState::NegativeAccessRules(next)
})
}
fn install(&mut self, next: NextState) {
match next {
NextState::PositiveAccessRules(rules) => {
self.positive_access_rules = rules;
self.written.positive = true;
}
NextState::NegativeAccessRules(rules) => {
self.negative_access_rules = rules;
self.written.negative = true;
}
NextState::Enable(enable) => {
self.enable = enable;
self.written.enable = true;
}
NextState::Accompaniment(reference) => {
self.accompaniment = Some(reference);
self.written.accompaniment = true;
}
}
self.writes = self.writes.wrapping_add(1);
}
pub(super) fn write_saved(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
) -> Result<(), Error> {
let refused =
|_| common::protocol_error(ErrorClass::DEVICE, ErrorCode::OPERATIONAL_PROBLEM);
if let Some(taken) = self.take_staged(property, array_index, &value) {
return taken.map_err(refused);
}
let next = self.next_state(&[], property, array_index, value)?;
if self.storage.is_some() {
let mut snapshot = self.snapshot();
apply(&mut snapshot, &next);
self.with_storage(Storage::drop_staged);
let storage = self.storage.as_mut().expect("checked above");
storage.save_now(snapshot).map_err(refused)?;
}
self.install(next);
Ok(())
}
fn take_staged(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: &PropertyValue,
) -> Option<Result<(), Error>> {
let mut storage = self.storage.take()?;
let taken = storage
.claim(property, array_index, value, self.writes)
.map(|claimed| claimed.map(|next| self.install(next)));
storage.correct(|| self.snapshot());
self.storage = Some(storage);
taken
}
pub(super) fn expire_staged_write(&mut self, now: Duration) {
self.with_storage(|storage| storage.expire(now));
}
fn with_storage<R>(&mut self, f: impl FnOnce(&mut Storage) -> R) -> Option<R> {
let mut storage = self.storage.take()?;
let result = f(&mut storage);
storage.correct(|| self.snapshot());
self.storage = Some(storage);
Some(result)
}
}
fn apply(snapshot: &mut AccessRightsSnapshot, next: &NextState) {
match next {
NextState::PositiveAccessRules(rules) => {
snapshot.positive_access_rules = Some(rules.clone());
}
NextState::NegativeAccessRules(rules) => {
snapshot.negative_access_rules = Some(rules.clone());
}
NextState::Enable(enable) => snapshot.enable = Some(*enable),
NextState::Accompaniment(reference) => snapshot.accompaniment = Some(reference.clone()),
}
}
impl DurableWrites for AccessRightsObject {
fn stage_write(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: &PropertyValue,
) -> StageStep {
self.stage_writes(&[PendingWrite {
property,
array_index,
value: value.clone(),
}])
}
fn stage_writes(&mut self, writes: &[PendingWrite]) -> StageStep {
if self.storage.is_none() || !writes.iter().any(|write| saved_property(write.property)) {
return StageStep::Skip;
}
if let Some(wait) = self.with_storage(Storage::busy).flatten() {
return StageStep::Busy(wait);
}
let steps = staged::steps(writes, |earlier, write| {
if !saved_property(write.property) {
return Ok(None);
}
let PendingWrite {
property,
array_index,
value,
} = write;
self.next_state(earlier, *property, *array_index, value.clone())
.map(Some)
});
let served = self.snapshot();
let base = self.writes;
let storage = self.storage.as_mut().expect("checked above");
storage.stage(steps, base, served, apply)
}
fn release_staged_write(&mut self, staged: &SaveWait) {
self.with_storage(|storage| storage.release(staged));
}
fn has_staged_write(&self) -> bool {
self.storage.as_ref().is_some_and(Storage::is_staged)
}
fn settle_forgotten_writes(&mut self) -> Option<SaveWait> {
let mut storage = self.storage.take()?;
let wait = storage.drop_forgotten(|| self.snapshot());
self.storage = Some(storage);
Some(wait)
}
}