bacnet_objects/access_control/
rights_persistence.rs1use std::path::Path;
17
18use bacnet_encoding::constructed::tagged::{
19 decode_ctx_boolean, expect_closing, expect_opening, next_is_context, next_is_opening,
20};
21use bacnet_encoding::constructed::{
22 decode_access_rule, decode_device_object_reference, encode_access_rule,
23 encode_device_object_reference,
24};
25use bacnet_encoding::{primitives, tags};
26use bacnet_types::constructed::{BACnetAccessRule, BACnetDeviceObjectReference};
27use bacnet_types::error::Error;
28use bacnet_types::primitives::ObjectIdentifier;
29use bytes::BytesMut;
30
31use super::MAX_ACCESS_RULES;
32use crate::durable::file::ObjectFile;
33
34#[derive(Clone, Debug, Default, PartialEq)]
38pub struct AccessRightsSnapshot {
39 pub positive_access_rules: Option<Vec<BACnetAccessRule>>,
41 pub negative_access_rules: Option<Vec<BACnetAccessRule>>,
43 pub enable: Option<bool>,
45 pub accompaniment: Option<BACnetDeviceObjectReference>,
47}
48
49pub trait AccessRightsPersistence: Send + Sync {
56 fn load(&self, rights: ObjectIdentifier) -> Result<Option<AccessRightsSnapshot>, Error>;
58
59 fn save(&self, rights: ObjectIdentifier, snapshot: &AccessRightsSnapshot) -> Result<(), Error>;
61}
62
63const MAGIC: &[u8; 8] = b"RBNACR01";
68pub(super) const MAX_FILE_BYTES: u64 = 128 * 1024;
73const POSITIVE_TAG: u8 = 0;
75const NEGATIVE_TAG: u8 = 1;
76const ENABLE_TAG: u8 = 2;
77const ACCOMPANIMENT_TAG: u8 = 3;
78const WHAT: &str = "Access Rights file";
80
81#[derive(Clone, Debug)]
105pub struct FileAccessRightsPersistence {
106 file: ObjectFile,
107}
108
109impl FileAccessRightsPersistence {
110 pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
112 Ok(Self {
113 file: ObjectFile::new(path.as_ref(), MAGIC, MAX_FILE_BYTES, "Access Rights")?,
114 })
115 }
116
117 pub fn path(&self) -> &Path {
119 self.file.path()
120 }
121
122 fn undecodable(&self) -> Error {
123 self.file.corrupt("holds an entry that does not decode")
124 }
125
126 fn decode_array(
129 &self,
130 body: &[u8],
131 pos: &mut usize,
132 tag: u8,
133 ) -> Result<Option<Vec<BACnetAccessRule>>, Error> {
134 if !next_is_opening(body, *pos, tag).map_err(|_| self.undecodable())? {
135 return Ok(None);
136 }
137 let mut at = expect_opening(body, *pos, tag, WHAT).map_err(|_| self.undecodable())?;
138 let mut rules = Vec::new();
139 loop {
140 let (next, after) = tags::decode_tag(body, at).map_err(|_| self.undecodable())?;
142 if next.is_closing_tag(tag) {
143 *pos = after;
144 return Ok(Some(rules));
145 }
146 if rules.len() == MAX_ACCESS_RULES {
147 return Err(self.file.corrupt("holds more entries than the cap"));
148 }
149 let (rule, end) = decode_access_rule(body, at).map_err(|_| self.undecodable())?;
150 rules.push(rule);
151 at = end;
152 }
153 }
154
155 fn decode_accompaniment(
158 &self,
159 body: &[u8],
160 pos: &mut usize,
161 ) -> Result<Option<BACnetDeviceObjectReference>, Error> {
162 if !next_is_opening(body, *pos, ACCOMPANIMENT_TAG).map_err(|_| self.undecodable())? {
163 return Ok(None);
164 }
165 let decoded = expect_opening(body, *pos, ACCOMPANIMENT_TAG, WHAT)
166 .and_then(|at| decode_device_object_reference(body, at))
167 .and_then(|(reference, at)| {
168 expect_closing(body, at, ACCOMPANIMENT_TAG, WHAT).map(|end| (reference, end))
169 });
170 let (reference, end) = decoded.map_err(|_| self.undecodable())?;
171 *pos = end;
172 Ok(Some(reference))
173 }
174}
175
176impl AccessRightsPersistence for FileAccessRightsPersistence {
177 fn load(&self, rights: ObjectIdentifier) -> Result<Option<AccessRightsSnapshot>, Error> {
178 let Some(body) = self.file.load(rights)? else {
179 return Ok(None);
180 };
181 let mut pos = 0;
182 let positive_access_rules = self.decode_array(&body, &mut pos, POSITIVE_TAG)?;
183 let negative_access_rules = self.decode_array(&body, &mut pos, NEGATIVE_TAG)?;
184 let enable = if next_is_context(&body, pos, ENABLE_TAG).map_err(|_| self.undecodable())? {
185 let (enable, end) =
186 decode_ctx_boolean(&body, pos, ENABLE_TAG, WHAT).map_err(|_| self.undecodable())?;
187 pos = end;
188 Some(enable)
189 } else {
190 None
191 };
192 let accompaniment = self.decode_accompaniment(&body, &mut pos)?;
193 if pos != body.len() {
194 return Err(self.undecodable());
195 }
196 Ok(Some(AccessRightsSnapshot {
197 positive_access_rules,
198 negative_access_rules,
199 enable,
200 accompaniment,
201 }))
202 }
203
204 fn save(&self, rights: ObjectIdentifier, snapshot: &AccessRightsSnapshot) -> Result<(), Error> {
205 let mut body = BytesMut::new();
206 for (tag, rules) in [
207 (POSITIVE_TAG, &snapshot.positive_access_rules),
208 (NEGATIVE_TAG, &snapshot.negative_access_rules),
209 ] {
210 if let Some(rules) = rules {
211 tags::encode_opening_tag(&mut body, tag);
212 for rule in rules {
213 encode_access_rule(&mut body, rule);
214 }
215 tags::encode_closing_tag(&mut body, tag);
216 }
217 }
218 if let Some(enable) = snapshot.enable {
219 primitives::encode_ctx_boolean(&mut body, ENABLE_TAG, enable);
220 }
221 if let Some(reference) = &snapshot.accompaniment {
222 tags::encode_opening_tag(&mut body, ACCOMPANIMENT_TAG);
223 encode_device_object_reference(&mut body, reference);
224 tags::encode_closing_tag(&mut body, ACCOMPANIMENT_TAG);
225 }
226 self.file.save(rights, &body)
227 }
228}