use std::path::Path;
use bacnet_encoding::constructed::tagged::{
decode_ctx_boolean, expect_closing, expect_opening, next_is_context, next_is_opening,
};
use bacnet_encoding::constructed::{
decode_access_rule, decode_device_object_reference, encode_access_rule,
encode_device_object_reference,
};
use bacnet_encoding::{primitives, tags};
use bacnet_types::constructed::{BACnetAccessRule, BACnetDeviceObjectReference};
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use bytes::BytesMut;
use super::MAX_ACCESS_RULES;
use crate::durable::file::ObjectFile;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct AccessRightsSnapshot {
pub positive_access_rules: Option<Vec<BACnetAccessRule>>,
pub negative_access_rules: Option<Vec<BACnetAccessRule>>,
pub enable: Option<bool>,
pub accompaniment: Option<BACnetDeviceObjectReference>,
}
pub trait AccessRightsPersistence: Send + Sync {
fn load(&self, rights: ObjectIdentifier) -> Result<Option<AccessRightsSnapshot>, Error>;
fn save(&self, rights: ObjectIdentifier, snapshot: &AccessRightsSnapshot) -> Result<(), Error>;
}
const MAGIC: &[u8; 8] = b"RBNACR01";
pub(super) const MAX_FILE_BYTES: u64 = 128 * 1024;
const POSITIVE_TAG: u8 = 0;
const NEGATIVE_TAG: u8 = 1;
const ENABLE_TAG: u8 = 2;
const ACCOMPANIMENT_TAG: u8 = 3;
const WHAT: &str = "Access Rights file";
#[derive(Clone, Debug)]
pub struct FileAccessRightsPersistence {
file: ObjectFile,
}
impl FileAccessRightsPersistence {
pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
Ok(Self {
file: ObjectFile::new(path.as_ref(), MAGIC, MAX_FILE_BYTES, "Access Rights")?,
})
}
pub fn path(&self) -> &Path {
self.file.path()
}
fn undecodable(&self) -> Error {
self.file.corrupt("holds an entry that does not decode")
}
fn decode_array(
&self,
body: &[u8],
pos: &mut usize,
tag: u8,
) -> Result<Option<Vec<BACnetAccessRule>>, Error> {
if !next_is_opening(body, *pos, tag).map_err(|_| self.undecodable())? {
return Ok(None);
}
let mut at = expect_opening(body, *pos, tag, WHAT).map_err(|_| self.undecodable())?;
let mut rules = Vec::new();
loop {
let (next, after) = tags::decode_tag(body, at).map_err(|_| self.undecodable())?;
if next.is_closing_tag(tag) {
*pos = after;
return Ok(Some(rules));
}
if rules.len() == MAX_ACCESS_RULES {
return Err(self.file.corrupt("holds more entries than the cap"));
}
let (rule, end) = decode_access_rule(body, at).map_err(|_| self.undecodable())?;
rules.push(rule);
at = end;
}
}
fn decode_accompaniment(
&self,
body: &[u8],
pos: &mut usize,
) -> Result<Option<BACnetDeviceObjectReference>, Error> {
if !next_is_opening(body, *pos, ACCOMPANIMENT_TAG).map_err(|_| self.undecodable())? {
return Ok(None);
}
let decoded = expect_opening(body, *pos, ACCOMPANIMENT_TAG, WHAT)
.and_then(|at| decode_device_object_reference(body, at))
.and_then(|(reference, at)| {
expect_closing(body, at, ACCOMPANIMENT_TAG, WHAT).map(|end| (reference, end))
});
let (reference, end) = decoded.map_err(|_| self.undecodable())?;
*pos = end;
Ok(Some(reference))
}
}
impl AccessRightsPersistence for FileAccessRightsPersistence {
fn load(&self, rights: ObjectIdentifier) -> Result<Option<AccessRightsSnapshot>, Error> {
let Some(body) = self.file.load(rights)? else {
return Ok(None);
};
let mut pos = 0;
let positive_access_rules = self.decode_array(&body, &mut pos, POSITIVE_TAG)?;
let negative_access_rules = self.decode_array(&body, &mut pos, NEGATIVE_TAG)?;
let enable = if next_is_context(&body, pos, ENABLE_TAG).map_err(|_| self.undecodable())? {
let (enable, end) =
decode_ctx_boolean(&body, pos, ENABLE_TAG, WHAT).map_err(|_| self.undecodable())?;
pos = end;
Some(enable)
} else {
None
};
let accompaniment = self.decode_accompaniment(&body, &mut pos)?;
if pos != body.len() {
return Err(self.undecodable());
}
Ok(Some(AccessRightsSnapshot {
positive_access_rules,
negative_access_rules,
enable,
accompaniment,
}))
}
fn save(&self, rights: ObjectIdentifier, snapshot: &AccessRightsSnapshot) -> Result<(), Error> {
let mut body = BytesMut::new();
for (tag, rules) in [
(POSITIVE_TAG, &snapshot.positive_access_rules),
(NEGATIVE_TAG, &snapshot.negative_access_rules),
] {
if let Some(rules) = rules {
tags::encode_opening_tag(&mut body, tag);
for rule in rules {
encode_access_rule(&mut body, rule);
}
tags::encode_closing_tag(&mut body, tag);
}
}
if let Some(enable) = snapshot.enable {
primitives::encode_ctx_boolean(&mut body, ENABLE_TAG, enable);
}
if let Some(reference) = &snapshot.accompaniment {
tags::encode_opening_tag(&mut body, ACCOMPANIMENT_TAG);
encode_device_object_reference(&mut body, reference);
tags::encode_closing_tag(&mut body, ACCOMPANIMENT_TAG);
}
self.file.save(rights, &body)
}
}