bacnet_objects/access_control/
rights_persistence.rs1use crate::prelude::*;
17#[cfg(feature = "std")]
18use std::path::Path;
19
20#[cfg(feature = "std")]
21use bacnet_encoding::constructed::tagged::{
22 decode_ctx_boolean, expect_closing, expect_opening, next_is_context, next_is_opening,
23};
24#[cfg(feature = "std")]
25use bacnet_encoding::constructed::{
26 decode_access_rule, decode_device_object_reference, encode_access_rule,
27 encode_device_object_reference,
28};
29#[cfg(feature = "std")]
30use bacnet_encoding::{primitives, tags};
31use bacnet_types::constructed::{BACnetAccessRule, BACnetDeviceObjectReference};
32use bacnet_types::error::Error;
33use bacnet_types::primitives::ObjectIdentifier;
34#[cfg(feature = "std")]
35use bytes::BytesMut;
36
37#[cfg(feature = "std")]
38use super::MAX_ACCESS_RULES;
39#[cfg(feature = "std")]
40use crate::durable::file::ObjectFile;
41
42#[derive(Clone, Debug, Default, PartialEq)]
46pub struct AccessRightsSnapshot {
47 pub positive_access_rules: Option<Vec<BACnetAccessRule>>,
49 pub negative_access_rules: Option<Vec<BACnetAccessRule>>,
51 pub enable: Option<bool>,
53 pub accompaniment: Option<BACnetDeviceObjectReference>,
55}
56
57pub trait AccessRightsPersistence: Send + Sync {
64 fn load(&self, rights: ObjectIdentifier) -> Result<Option<AccessRightsSnapshot>, Error>;
66
67 fn save(&self, rights: ObjectIdentifier, snapshot: &AccessRightsSnapshot) -> Result<(), Error>;
69}
70
71#[cfg(feature = "std")]
76const MAGIC: &[u8; 8] = b"RBNACR01";
77#[cfg(feature = "std")]
82pub(super) const MAX_FILE_BYTES: u64 = 128 * 1024;
83#[cfg(feature = "std")]
85const POSITIVE_TAG: u8 = 0;
86#[cfg(feature = "std")]
87const NEGATIVE_TAG: u8 = 1;
88#[cfg(feature = "std")]
89const ENABLE_TAG: u8 = 2;
90#[cfg(feature = "std")]
91const ACCOMPANIMENT_TAG: u8 = 3;
92#[cfg(feature = "std")]
94const WHAT: &str = "Access Rights file";
95
96#[derive(Clone, Debug)]
120#[cfg(feature = "std")]
121pub struct FileAccessRightsPersistence {
122 file: ObjectFile,
123}
124
125#[cfg(feature = "std")]
126impl FileAccessRightsPersistence {
127 pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
129 Ok(Self {
130 file: ObjectFile::new(path.as_ref(), MAGIC, MAX_FILE_BYTES, "Access Rights")?,
131 })
132 }
133
134 pub fn path(&self) -> &Path {
136 self.file.path()
137 }
138
139 fn undecodable(&self) -> Error {
140 self.file.corrupt("holds an entry that does not decode")
141 }
142
143 fn decode_array(
146 &self,
147 body: &[u8],
148 pos: &mut usize,
149 tag: u8,
150 ) -> Result<Option<Vec<BACnetAccessRule>>, Error> {
151 if !next_is_opening(body, *pos, tag).map_err(|_| self.undecodable())? {
152 return Ok(None);
153 }
154 let mut at = expect_opening(body, *pos, tag, WHAT).map_err(|_| self.undecodable())?;
155 let mut rules = Vec::new();
156 loop {
157 let (next, after) = tags::decode_tag(body, at).map_err(|_| self.undecodable())?;
159 if next.is_closing_tag(tag) {
160 *pos = after;
161 return Ok(Some(rules));
162 }
163 if rules.len() == MAX_ACCESS_RULES {
164 return Err(self.file.corrupt("holds more entries than the cap"));
165 }
166 let (rule, end) = decode_access_rule(body, at).map_err(|_| self.undecodable())?;
167 rules.push(rule);
168 at = end;
169 }
170 }
171
172 fn decode_accompaniment(
175 &self,
176 body: &[u8],
177 pos: &mut usize,
178 ) -> Result<Option<BACnetDeviceObjectReference>, Error> {
179 if !next_is_opening(body, *pos, ACCOMPANIMENT_TAG).map_err(|_| self.undecodable())? {
180 return Ok(None);
181 }
182 let decoded = expect_opening(body, *pos, ACCOMPANIMENT_TAG, WHAT)
183 .and_then(|at| decode_device_object_reference(body, at))
184 .and_then(|(reference, at)| {
185 expect_closing(body, at, ACCOMPANIMENT_TAG, WHAT).map(|end| (reference, end))
186 });
187 let (reference, end) = decoded.map_err(|_| self.undecodable())?;
188 *pos = end;
189 Ok(Some(reference))
190 }
191}
192
193#[cfg(feature = "std")]
194impl AccessRightsPersistence for FileAccessRightsPersistence {
195 fn load(&self, rights: ObjectIdentifier) -> Result<Option<AccessRightsSnapshot>, Error> {
196 let Some(body) = self.file.load(rights)? else {
197 return Ok(None);
198 };
199 let mut pos = 0;
200 let positive_access_rules = self.decode_array(&body, &mut pos, POSITIVE_TAG)?;
201 let negative_access_rules = self.decode_array(&body, &mut pos, NEGATIVE_TAG)?;
202 let enable = if next_is_context(&body, pos, ENABLE_TAG).map_err(|_| self.undecodable())? {
203 let (enable, end) =
204 decode_ctx_boolean(&body, pos, ENABLE_TAG, WHAT).map_err(|_| self.undecodable())?;
205 pos = end;
206 Some(enable)
207 } else {
208 None
209 };
210 let accompaniment = self.decode_accompaniment(&body, &mut pos)?;
211 if pos != body.len() {
212 return Err(self.undecodable());
213 }
214 Ok(Some(AccessRightsSnapshot {
215 positive_access_rules,
216 negative_access_rules,
217 enable,
218 accompaniment,
219 }))
220 }
221
222 fn save(&self, rights: ObjectIdentifier, snapshot: &AccessRightsSnapshot) -> Result<(), Error> {
223 let mut body = BytesMut::new();
224 for (tag, rules) in [
225 (POSITIVE_TAG, &snapshot.positive_access_rules),
226 (NEGATIVE_TAG, &snapshot.negative_access_rules),
227 ] {
228 if let Some(rules) = rules {
229 tags::encode_opening_tag(&mut body, tag);
230 for rule in rules {
231 encode_access_rule(&mut body, rule);
232 }
233 tags::encode_closing_tag(&mut body, tag);
234 }
235 }
236 if let Some(enable) = snapshot.enable {
237 primitives::encode_ctx_boolean(&mut body, ENABLE_TAG, enable);
238 }
239 if let Some(reference) = &snapshot.accompaniment {
240 tags::encode_opening_tag(&mut body, ACCOMPANIMENT_TAG);
241 encode_device_object_reference(&mut body, reference);
242 tags::encode_closing_tag(&mut body, ACCOMPANIMENT_TAG);
243 }
244 self.file.save(rights, &body)
245 }
246}