bacnet_objects/access_control/
rights.rs1use bacnet_encoding::constructed::encode_access_rule;
2use bacnet_types::constructed::BACnetAccessRule;
3use bacnet_types::enums::{
4 AccessRuleLocationSpecifier, AccessRuleTimeRangeSpecifier, ErrorClass, ErrorCode,
5};
6
7use super::*;
8use crate::durable::DurableWrites;
9
10#[path = "rights_persistence.rs"]
11mod persistence;
12#[path = "rights_saving.rs"]
13mod saving;
14pub use persistence::{AccessRightsPersistence, AccessRightsSnapshot, FileAccessRightsPersistence};
15
16pub const MAX_ACCESS_RULES: usize = 1024;
23
24pub struct AccessRightsObject {
50 oid: ObjectIdentifier,
51 name: String,
52 description: String,
53 global_identifier: u64,
54 enable: bool,
55 positive_access_rules: Vec<BACnetAccessRule>,
56 negative_access_rules: Vec<BACnetAccessRule>,
57 accompaniment: Option<BACnetDeviceObjectReference>,
59 status_flags: StatusFlags,
60 reliability: Reliability,
61 storage: Option<saving::Storage>,
64 writes: u64,
67 written: saving::Written,
69}
70
71impl AccessRightsObject {
72 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
76 let oid = ObjectIdentifier::new(ObjectType::ACCESS_RIGHTS, instance)?;
77 Ok(Self {
78 oid,
79 name: name.into(),
80 description: String::new(),
81 global_identifier: 0,
82 enable: true,
83 positive_access_rules: Vec::new(),
84 negative_access_rules: Vec::new(),
85 accompaniment: None,
86 status_flags: StatusFlags::empty(),
87 reliability: Reliability::NO_FAULT_DETECTED,
88 storage: None,
89 writes: 0,
90 written: saving::Written::default(),
91 })
92 }
93
94 pub fn set_enable(&mut self, enable: bool) {
103 if !self.keeps_saved(PropertyIdentifier::LOG_ENABLE) {
104 self.enable = enable;
105 }
106 }
107
108 pub fn enable(&self) -> bool {
110 self.enable
111 }
112
113 pub fn set_positive_access_rules(
136 &mut self,
137 rules: impl IntoIterator<Item = BACnetAccessRule>,
138 ) -> Result<(), Error> {
139 let rules = checked_rules(rules)?;
140 if !self.keeps_saved(PropertyIdentifier::POSITIVE_ACCESS_RULES) {
141 self.positive_access_rules = rules;
142 }
143 Ok(())
144 }
145
146 pub fn set_negative_access_rules(
150 &mut self,
151 rules: impl IntoIterator<Item = BACnetAccessRule>,
152 ) -> Result<(), Error> {
153 let rules = checked_rules(rules)?;
154 if !self.keeps_saved(PropertyIdentifier::NEGATIVE_ACCESS_RULES) {
155 self.negative_access_rules = rules;
156 }
157 Ok(())
158 }
159
160 pub fn positive_access_rules(&self) -> &[BACnetAccessRule] {
162 &self.positive_access_rules
163 }
164
165 pub fn negative_access_rules(&self) -> &[BACnetAccessRule] {
167 &self.negative_access_rules
168 }
169
170 pub fn set_accompaniment(
191 &mut self,
192 accompaniment: Option<BACnetDeviceObjectReference>,
193 ) -> Result<(), Error> {
194 if let Some(reference) = &accompaniment {
195 check_accompaniment(reference)?;
196 }
197 if !self.keeps_saved(PropertyIdentifier::ACCOMPANIMENT) {
198 self.accompaniment = accompaniment;
199 }
200 Ok(())
201 }
202
203 pub fn accompaniment(&self) -> Option<&BACnetDeviceObjectReference> {
205 self.accompaniment.as_ref()
206 }
207}
208
209pub(super) fn check_accompaniment(reference: &BACnetDeviceObjectReference) -> Result<(), Error> {
215 crate::device_reference::check_device_member(reference.device_identifier)?;
216 let named = matches!(
217 reference.object_identifier.object_type(),
218 ObjectType::ACCESS_RIGHTS | ObjectType::ACCESS_CREDENTIAL | ObjectType::ACCESS_USER
219 );
220 if named || unspecified(reference.object_identifier, reference.device_identifier) {
221 Ok(())
222 } else {
223 Err(common::value_out_of_range_error())
224 }
225}
226
227pub(super) fn checked_rules(
230 rules: impl IntoIterator<Item = BACnetAccessRule>,
231) -> Result<Vec<BACnetAccessRule>, Error> {
232 let rules: Vec<BACnetAccessRule> = rules.into_iter().collect();
233 check_rule_count(rules.len())?;
234 rules.iter().try_for_each(check_access_rule)?;
235 Ok(rules)
236}
237
238pub(super) fn check_rule_count(count: usize) -> Result<(), Error> {
241 if count > MAX_ACCESS_RULES {
242 Err(common::protocol_error(
243 ErrorClass::RESOURCES,
244 ErrorCode::NO_SPACE_TO_WRITE_PROPERTY,
245 ))
246 } else {
247 Ok(())
248 }
249}
250
251pub(super) fn check_access_rule(rule: &BACnetAccessRule) -> Result<(), Error> {
255 let time_range_device = rule.time_range.as_ref().and_then(|r| r.device_identifier);
256 crate::device_reference::check_device_member(time_range_device)?;
257 let location_device = rule.location.as_ref().and_then(|r| r.device_identifier);
258 crate::device_reference::check_device_member(location_device)?;
259
260 let time_range_ok = match rule.time_range_specifier {
261 AccessRuleTimeRangeSpecifier::SPECIFIED => rule.time_range.is_some(),
262 AccessRuleTimeRangeSpecifier::ALWAYS => rule
263 .time_range
264 .as_ref()
265 .is_none_or(|r| unspecified(r.object_identifier, r.device_identifier)),
266 _ => false,
267 };
268 let location_ok = match rule.location_specifier {
269 AccessRuleLocationSpecifier::SPECIFIED => rule.location.as_ref().is_some_and(|r| {
270 matches!(
271 r.object_identifier.object_type(),
272 ObjectType::ACCESS_POINT | ObjectType::ACCESS_ZONE
273 ) || unspecified(r.object_identifier, r.device_identifier)
274 }),
275 AccessRuleLocationSpecifier::ALL => rule
276 .location
277 .as_ref()
278 .is_none_or(|r| unspecified(r.object_identifier, r.device_identifier)),
279 _ => false,
280 };
281 if time_range_ok && location_ok {
282 Ok(())
283 } else {
284 Err(common::value_out_of_range_error())
285 }
286}
287
288fn unspecified(object: ObjectIdentifier, device: Option<ObjectIdentifier>) -> bool {
292 let unused = |oid: ObjectIdentifier| oid.instance_number() == ObjectIdentifier::MAX_INSTANCE;
293 unused(object) && device.is_none_or(unused)
294}
295
296fn rule_values(rules: &[BACnetAccessRule]) -> Vec<PropertyValue> {
299 rules
300 .iter()
301 .map(|rule| {
302 let mut buf = BytesMut::new();
303 encode_access_rule(&mut buf, rule);
304 PropertyValue::ApplicationData(buf.to_vec())
305 })
306 .collect()
307}
308
309impl BACnetObject for AccessRightsObject {
310 fn object_identifier(&self) -> ObjectIdentifier {
311 self.oid
312 }
313
314 fn object_name(&self) -> &str {
315 &self.name
316 }
317
318 fn read_property(
319 &self,
320 property: PropertyIdentifier,
321 array_index: Option<u32>,
322 ) -> Result<PropertyValue, Error> {
323 if let Some(result) =
326 read_common_properties!(self, property, array_index, no_out_of_service)
327 {
328 return result;
329 }
330 match property {
331 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
332 ObjectType::ACCESS_RIGHTS.to_raw(),
333 )),
334 p if p == PropertyIdentifier::GLOBAL_IDENTIFIER => {
335 Ok(PropertyValue::Unsigned(self.global_identifier))
336 }
337 p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.enable)),
340 p if p == PropertyIdentifier::POSITIVE_ACCESS_RULES => {
341 common::read_array(rule_values(&self.positive_access_rules), array_index)
342 }
343 p if p == PropertyIdentifier::NEGATIVE_ACCESS_RULES => {
344 common::read_array(rule_values(&self.negative_access_rules), array_index)
345 }
346 p if p == PropertyIdentifier::ACCOMPANIMENT => match &self.accompaniment {
347 Some(reference) => Ok(crate::device_reference::reference_value(reference)),
348 None => Err(common::unknown_property_error()),
349 },
350 _ => Err(common::unknown_property_error()),
351 }
352 }
353
354 fn write_property(
355 &mut self,
356 property: PropertyIdentifier,
357 array_index: Option<u32>,
358 value: PropertyValue,
359 _priority: Option<u8>,
360 ) -> Result<(), Error> {
361 if let Some(result) = common::write_description(&mut self.description, property, &value) {
362 return result;
363 }
364 match property {
365 p if p == PropertyIdentifier::GLOBAL_IDENTIFIER => {
366 if let PropertyValue::Unsigned(v) = value {
367 self.global_identifier = v;
368 Ok(())
369 } else {
370 Err(common::invalid_data_type_error())
371 }
372 }
373 p if p == PropertyIdentifier::LOG_ENABLE
379 || p == PropertyIdentifier::POSITIVE_ACCESS_RULES
380 || p == PropertyIdentifier::NEGATIVE_ACCESS_RULES =>
381 {
382 self.write_saved(property, array_index, value)
383 }
384 p if p == PropertyIdentifier::ACCOMPANIMENT => {
386 if self.accompaniment.is_none() {
387 return Err(common::unknown_property_error());
388 }
389 self.write_saved(property, array_index, value)
390 }
391 _ => Err(crate::common::unhandled_write_error(
392 self.property_metadata().as_ref(),
393 property,
394 array_index,
395 )),
396 }
397 }
398
399 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
400 super::metadata_identity::for_access_rights_object(self)
401 }
402
403 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
404 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
405 }
406
407 fn advance_monotonic_time_internal(&mut self, now: std::time::Duration) -> bool {
408 self.expire_staged_write(now);
409 false
410 }
411
412 fn durable_writes_internal(&mut self) -> Option<&mut dyn DurableWrites> {
413 Some(self)
414 }
415}
416
417#[cfg(test)]
418#[path = "rights_tests.rs"]
419mod tests;
420
421#[cfg(test)]
422#[path = "rights_test_storage.rs"]
423mod test_storage;
424
425#[cfg(test)]
426#[path = "rights_persistence_tests.rs"]
427mod persistence_tests;
428
429#[cfg(test)]
430#[path = "rights_staging_tests.rs"]
431mod staging_tests;
432
433#[cfg(test)]
434#[path = "rights_fold_tests.rs"]
435mod fold_tests;
436
437#[cfg(test)]
438#[path = "rights_accompaniment_tests.rs"]
439mod accompaniment_tests;
440
441