use super::*;
use crate::durable::SaveWait;
use bacnet_types::enums::PropertyIdentifier as P;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration;
#[derive(Default)]
pub(super) struct MemoryPersistence {
saved: Mutex<Option<(ObjectIdentifier, AccessRightsSnapshot)>>,
pub(super) fail: AtomicBool,
saves: AtomicU64,
hold: Mutex<Option<Hold>>,
}
struct Hold {
started: mpsc::Sender<AccessRightsSnapshot>,
go: mpsc::Receiver<()>,
}
pub(super) struct Held {
pub(super) started: mpsc::Receiver<AccessRightsSnapshot>,
pub(super) go: mpsc::Sender<()>,
}
pub(super) const WAIT: Duration = Duration::from_secs(10);
impl MemoryPersistence {
pub(super) fn hold(&self) -> Held {
let (started, started_rx) = mpsc::channel();
let (go_tx, go) = mpsc::channel();
*self.hold.lock().unwrap() = Some(Hold { started, go });
Held {
started: started_rx,
go: go_tx,
}
}
pub(super) fn snapshot(&self) -> Option<AccessRightsSnapshot> {
self.saved
.lock()
.unwrap()
.clone()
.map(|(_, snapshot)| snapshot)
}
pub(super) fn saves(&self) -> u64 {
self.saves.load(Ordering::SeqCst)
}
pub(super) fn preload(&self, rights: ObjectIdentifier, snapshot: AccessRightsSnapshot) {
*self.saved.lock().unwrap() = Some((rights, snapshot));
}
}
impl AccessRightsPersistence for MemoryPersistence {
fn load(&self, rights: ObjectIdentifier) -> Result<Option<AccessRightsSnapshot>, Error> {
Ok(self
.saved
.lock()
.unwrap()
.clone()
.filter(|(oid, _)| *oid == rights)
.map(|(_, snapshot)| snapshot))
}
fn save(&self, rights: ObjectIdentifier, snapshot: &AccessRightsSnapshot) -> Result<(), Error> {
if let Some(hold) = &*self.hold.lock().unwrap() {
let _ = hold.started.send(snapshot.clone());
let _ = hold.go.recv();
}
if self.fail.load(Ordering::SeqCst) {
return Err(Error::Encoding("storage unavailable".into()));
}
*self.saved.lock().unwrap() = Some((rights, snapshot.clone()));
self.saves.fetch_add(1, Ordering::SeqCst);
Ok(())
}
}
pub(super) fn persistent(storage: &Arc<MemoryPersistence>) -> AccessRightsObject {
AccessRightsObject::with_persistence(
1,
"AR",
Arc::clone(storage) as Arc<dyn AccessRightsPersistence>,
)
.unwrap()
}
pub(super) fn block_on(wait: &SaveWait) {
let (done, finished) = mpsc::channel();
let blocking = wait.clone();
std::thread::spawn(move || {
blocking.block();
let _ = done.send(());
});
finished
.recv_timeout(WAIT)
.expect("the wait did not end in time");
}
pub(super) fn zone_rule(instance: u32) -> BACnetAccessRule {
let zone = ObjectIdentifier::new(ObjectType::ACCESS_ZONE, instance).unwrap();
BACnetAccessRule::new(None, Some(zone.into()), true)
}
pub(super) fn door_rule() -> BACnetAccessRule {
let door = ObjectIdentifier::new(ObjectType::ACCESS_DOOR, 1).unwrap();
BACnetAccessRule::new(None, Some(door.into()), true)
}
pub(super) fn octets(rules: &[BACnetAccessRule]) -> PropertyValue {
let mut buf = BytesMut::new();
for rule in rules {
encode_access_rule(&mut buf, rule);
}
PropertyValue::ApplicationData(buf.to_vec())
}
pub(super) fn write(
rights: &mut AccessRightsObject,
property: P,
index: Option<u32>,
value: PropertyValue,
) -> Result<(), Error> {
rights.write_property(property, index, value, None)
}
pub(super) fn write_positive(
rights: &mut AccessRightsObject,
rules: &[BACnetAccessRule],
) -> Result<(), Error> {
write(rights, P::POSITIVE_ACCESS_RULES, None, octets(rules))
}
pub(super) fn positive_only(rules: &[BACnetAccessRule]) -> AccessRightsSnapshot {
AccessRightsSnapshot {
positive_access_rules: Some(rules.to_vec()),
..AccessRightsSnapshot::default()
}
}
pub(super) fn assert_refused(result: Result<(), Error>, class: ErrorClass, code: ErrorCode) {
match result {
Err(Error::Protocol {
class: got_class,
code: got_code,
}) => assert_eq!(
(got_class, got_code),
(class.to_raw() as u32, code.to_raw() as u32),
"expected {class:?} / {code:?}"
),
other => panic!("expected {class:?} / {code:?}, got {other:?}"),
}
}