bacnet_objects/access_control/
credential.rs1use std::sync::Arc;
2
3use bacnet_encoding::constructed::{
4 encode_assigned_access_rights, encode_credential_authentication_factor,
5};
6use bacnet_types::constructed::{BACnetAssignedAccessRights, BACnetCredentialAuthenticationFactor};
7use bacnet_types::enums::{
8 AccessCredentialDisable, AccessCredentialDisableReason, AuthorizationExemption,
9};
10use bytes::BytesMut;
11
12use super::credential_rules::{self as rules, WindowLimit};
13use super::*;
14use crate::clock::ClockReader;
15
16pub struct AccessCredentialObject {
49 oid: ObjectIdentifier,
50 name: String,
51 description: String,
52 global_identifier: u32,
53 local_reasons: Vec<AccessCredentialDisableReason>,
55 credential_disable: AccessCredentialDisable,
56 activation_time: WindowLimit,
57 expiration_time: WindowLimit,
58 authentication_factors: Vec<BACnetCredentialAuthenticationFactor>,
59 assigned_access_rights: Vec<BACnetAssignedAccessRights>,
60 authorization_exemptions: Option<Vec<AuthorizationExemption>>,
62 status_flags: StatusFlags,
63 reliability: Reliability,
64 clock: Option<Arc<dyn ClockReader>>,
66}
67
68impl AccessCredentialObject {
69 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
73 let oid = ObjectIdentifier::new(ObjectType::ACCESS_CREDENTIAL, instance)?;
74 Ok(Self {
75 oid,
76 name: name.into(),
77 description: String::new(),
78 global_identifier: 0,
79 local_reasons: Vec::new(),
80 credential_disable: AccessCredentialDisable::NONE,
81 activation_time: WindowLimit::OPEN,
82 expiration_time: WindowLimit::OPEN,
83 authentication_factors: Vec::new(),
84 assigned_access_rights: Vec::new(),
85 authorization_exemptions: None,
86 status_flags: StatusFlags::empty(),
87 reliability: Reliability::NO_FAULT_DETECTED,
88 clock: None,
89 })
90 }
91
92 pub fn set_global_identifier(&mut self, value: u32) {
94 self.global_identifier = value;
95 }
96
97 pub fn set_credential_disable(&mut self, value: AccessCredentialDisable) -> Result<(), Error> {
100 rules::check_credential_disable(value)?;
101 self.credential_disable = value;
102 Ok(())
103 }
104
105 pub fn set_activation_time(&mut self, date: Date, time: Time) -> Result<(), Error> {
108 self.activation_time = WindowLimit::new(date, time)?;
109 Ok(())
110 }
111
112 pub fn set_expiration_time(&mut self, date: Date, time: Time) -> Result<(), Error> {
115 self.expiration_time = WindowLimit::new(date, time)?;
116 Ok(())
117 }
118
119 pub fn activation_time(&self) -> (Date, Time) {
121 self.activation_time.parts()
122 }
123
124 pub fn expiration_time(&self) -> (Date, Time) {
126 self.expiration_time.parts()
127 }
128
129 pub fn add_disable_reason(
137 &mut self,
138 reason: AccessCredentialDisableReason,
139 ) -> Result<(), Error> {
140 rules::check_local_reason(reason)?;
141 if let Err(index) = self
142 .local_reasons
143 .binary_search_by_key(&reason.to_raw(), |r| r.to_raw())
144 {
145 self.local_reasons.insert(index, reason);
146 }
147 Ok(())
148 }
149
150 pub fn remove_disable_reason(&mut self, reason: AccessCredentialDisableReason) -> bool {
153 let before = self.local_reasons.len();
154 self.local_reasons.retain(|r| *r != reason);
155 self.local_reasons.len() != before
156 }
157
158 pub fn reason_for_disable(&self) -> Vec<AccessCredentialDisableReason> {
161 let frame = self.clock.as_ref().and_then(|clock| clock.read_clock());
162 let mut reasons = self.local_reasons.clone();
163 reasons.extend(rules::credential_disable_reason(self.credential_disable));
164 reasons.extend(rules::window_reasons(
165 frame,
166 self.activation_time,
167 self.expiration_time,
168 ));
169 reasons.sort_by_key(|r| r.to_raw());
170 reasons.dedup();
171 reasons
172 }
173
174 pub fn credential_status(&self) -> BinaryPV {
176 if self.reason_for_disable().is_empty() {
177 BinaryPV::ACTIVE
178 } else {
179 BinaryPV::INACTIVE
180 }
181 }
182
183 pub fn set_authentication_factors(
187 &mut self,
188 factors: Vec<BACnetCredentialAuthenticationFactor>,
189 ) -> Result<(), Error> {
190 factors
191 .iter()
192 .try_for_each(rules::check_authentication_factor)?;
193 self.authentication_factors = factors;
194 Ok(())
195 }
196
197 pub fn authentication_factors(&self) -> &[BACnetCredentialAuthenticationFactor] {
199 &self.authentication_factors
200 }
201
202 pub fn set_assigned_access_rights(
207 &mut self,
208 rights: Vec<BACnetAssignedAccessRights>,
209 ) -> Result<(), Error> {
210 rights
211 .iter()
212 .try_for_each(rules::check_assigned_access_rights)?;
213 self.assigned_access_rights = rights;
214 Ok(())
215 }
216
217 pub fn assigned_access_rights(&self) -> &[BACnetAssignedAccessRights] {
219 &self.assigned_access_rights
220 }
221
222 pub fn set_authorization_exemptions(
230 &mut self,
231 exemptions: Option<Vec<AuthorizationExemption>>,
232 ) -> Result<(), Error> {
233 if let Some(list) = &exemptions {
234 list.iter()
235 .copied()
236 .try_for_each(rules::check_authorization_exemption)?;
237 }
238 self.authorization_exemptions = exemptions;
239 Ok(())
240 }
241
242 pub fn authorization_exemptions(&self) -> Option<&[AuthorizationExemption]> {
244 self.authorization_exemptions.as_deref()
245 }
246}
247
248fn framed<T>(items: &[T], encode: fn(&mut BytesMut, &T)) -> Vec<PropertyValue> {
250 items
251 .iter()
252 .map(|item| {
253 let mut buf = BytesMut::new();
254 encode(&mut buf, item);
255 PropertyValue::ApplicationData(buf.to_vec())
256 })
257 .collect()
258}
259
260impl BACnetObject for AccessCredentialObject {
261 fn object_identifier(&self) -> ObjectIdentifier {
262 self.oid
263 }
264
265 fn object_name(&self) -> &str {
266 &self.name
267 }
268
269 fn read_property(
270 &self,
271 property: PropertyIdentifier,
272 array_index: Option<u32>,
273 ) -> Result<PropertyValue, Error> {
274 if let Some(result) =
277 read_common_properties!(self, property, array_index, no_out_of_service)
278 {
279 return result;
280 }
281 match property {
282 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
283 ObjectType::ACCESS_CREDENTIAL.to_raw(),
284 )),
285 p if p == PropertyIdentifier::GLOBAL_IDENTIFIER => {
286 Ok(PropertyValue::Unsigned(self.global_identifier.into()))
287 }
288 p if p == PropertyIdentifier::CREDENTIAL_STATUS => {
289 Ok(PropertyValue::Enumerated(self.credential_status().to_raw()))
290 }
291 p if p == PropertyIdentifier::REASON_FOR_DISABLE => Ok(PropertyValue::List(
292 self.reason_for_disable()
293 .into_iter()
294 .map(|reason| PropertyValue::Enumerated(reason.to_raw()))
295 .collect(),
296 )),
297 p if p == PropertyIdentifier::AUTHENTICATION_FACTORS => common::read_array(
298 framed(
299 &self.authentication_factors,
300 encode_credential_authentication_factor,
301 ),
302 array_index,
303 ),
304 p if p == PropertyIdentifier::ACTIVATION_TIME => Ok(self.activation_time.to_property()),
305 p if p == PropertyIdentifier::EXPIRATION_TIME => Ok(self.expiration_time.to_property()),
306 p if p == PropertyIdentifier::CREDENTIAL_DISABLE => {
307 Ok(PropertyValue::Enumerated(self.credential_disable.to_raw()))
308 }
309 p if p == PropertyIdentifier::ASSIGNED_ACCESS_RIGHTS => common::read_array(
310 framed(&self.assigned_access_rights, encode_assigned_access_rights),
311 array_index,
312 ),
313 p if p == PropertyIdentifier::AUTHORIZATION_EXEMPTIONS => {
314 match &self.authorization_exemptions {
315 Some(list) => Ok(PropertyValue::List(
316 list.iter()
317 .map(|exemption| PropertyValue::Enumerated(exemption.to_raw()))
318 .collect(),
319 )),
320 None => Err(common::unknown_property_error()),
321 }
322 }
323 _ => Err(common::unknown_property_error()),
324 }
325 }
326
327 fn write_property(
328 &mut self,
329 property: PropertyIdentifier,
330 array_index: Option<u32>,
331 value: PropertyValue,
332 _priority: Option<u8>,
333 ) -> Result<(), Error> {
334 if let Some(result) = common::write_description(&mut self.description, property, &value) {
335 return result;
336 }
337 match property {
338 p if p == PropertyIdentifier::GLOBAL_IDENTIFIER => {
339 let PropertyValue::Unsigned(raw) = value else {
340 return Err(common::invalid_data_type_error());
341 };
342 self.global_identifier = common::u64_to_u32(raw)?;
343 Ok(())
344 }
345 p if p == PropertyIdentifier::CREDENTIAL_DISABLE => {
346 let PropertyValue::Enumerated(raw) = value else {
347 return Err(common::invalid_data_type_error());
348 };
349 self.set_credential_disable(AccessCredentialDisable::from_raw(raw))
350 }
351 p if p == PropertyIdentifier::ACTIVATION_TIME => {
352 self.activation_time = WindowLimit::from_property(value)?;
353 Ok(())
354 }
355 p if p == PropertyIdentifier::EXPIRATION_TIME => {
356 self.expiration_time = WindowLimit::from_property(value)?;
357 Ok(())
358 }
359 _ => Err(crate::common::unhandled_write_error(
362 self.property_metadata().as_ref(),
363 property,
364 array_index,
365 )),
366 }
367 }
368
369 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
370 super::metadata_identity::for_access_credential_object(self)
371 }
372
373 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
374 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
375 }
376
377 fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
378 self.clock = clock;
379 }
380}
381
382