use std::sync::Arc;
use bacnet_encoding::constructed::{
encode_assigned_access_rights, encode_credential_authentication_factor,
};
use bacnet_types::constructed::{BACnetAssignedAccessRights, BACnetCredentialAuthenticationFactor};
use bacnet_types::enums::{AccessCredentialDisable, AccessCredentialDisableReason};
use bytes::BytesMut;
use super::credential_rules::{self as rules, WindowLimit};
use super::*;
use crate::clock::ClockReader;
pub struct AccessCredentialObject {
oid: ObjectIdentifier,
name: String,
description: String,
global_identifier: u32,
local_reasons: Vec<AccessCredentialDisableReason>,
credential_disable: AccessCredentialDisable,
activation_time: WindowLimit,
expiration_time: WindowLimit,
authentication_factors: Vec<BACnetCredentialAuthenticationFactor>,
assigned_access_rights: Vec<BACnetAssignedAccessRights>,
status_flags: StatusFlags,
reliability: Reliability,
clock: Option<Arc<dyn ClockReader>>,
}
impl AccessCredentialObject {
pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
let oid = ObjectIdentifier::new(ObjectType::ACCESS_CREDENTIAL, instance)?;
Ok(Self {
oid,
name: name.into(),
description: String::new(),
global_identifier: 0,
local_reasons: Vec::new(),
credential_disable: AccessCredentialDisable::NONE,
activation_time: WindowLimit::OPEN,
expiration_time: WindowLimit::OPEN,
authentication_factors: Vec::new(),
assigned_access_rights: Vec::new(),
status_flags: StatusFlags::empty(),
reliability: Reliability::NO_FAULT_DETECTED,
clock: None,
})
}
pub fn set_global_identifier(&mut self, value: u32) {
self.global_identifier = value;
}
pub fn set_credential_disable(&mut self, value: AccessCredentialDisable) -> Result<(), Error> {
rules::check_credential_disable(value)?;
self.credential_disable = value;
Ok(())
}
pub fn set_activation_time(&mut self, date: Date, time: Time) -> Result<(), Error> {
self.activation_time = WindowLimit::new(date, time)?;
Ok(())
}
pub fn set_expiration_time(&mut self, date: Date, time: Time) -> Result<(), Error> {
self.expiration_time = WindowLimit::new(date, time)?;
Ok(())
}
pub fn activation_time(&self) -> (Date, Time) {
self.activation_time.parts()
}
pub fn expiration_time(&self) -> (Date, Time) {
self.expiration_time.parts()
}
pub fn add_disable_reason(
&mut self,
reason: AccessCredentialDisableReason,
) -> Result<(), Error> {
rules::check_local_reason(reason)?;
if let Err(index) = self
.local_reasons
.binary_search_by_key(&reason.to_raw(), |r| r.to_raw())
{
self.local_reasons.insert(index, reason);
}
Ok(())
}
pub fn remove_disable_reason(&mut self, reason: AccessCredentialDisableReason) -> bool {
let before = self.local_reasons.len();
self.local_reasons.retain(|r| *r != reason);
self.local_reasons.len() != before
}
pub fn reason_for_disable(&self) -> Vec<AccessCredentialDisableReason> {
let frame = self.clock.as_ref().and_then(|clock| clock.read_clock());
let mut reasons = self.local_reasons.clone();
reasons.extend(rules::credential_disable_reason(self.credential_disable));
reasons.extend(rules::window_reasons(
frame,
self.activation_time,
self.expiration_time,
));
reasons.sort_by_key(|r| r.to_raw());
reasons.dedup();
reasons
}
pub fn credential_status(&self) -> BinaryPV {
if self.reason_for_disable().is_empty() {
BinaryPV::ACTIVE
} else {
BinaryPV::INACTIVE
}
}
pub fn set_authentication_factors(
&mut self,
factors: Vec<BACnetCredentialAuthenticationFactor>,
) -> Result<(), Error> {
factors
.iter()
.try_for_each(rules::check_authentication_factor)?;
self.authentication_factors = factors;
Ok(())
}
pub fn authentication_factors(&self) -> &[BACnetCredentialAuthenticationFactor] {
&self.authentication_factors
}
pub fn set_assigned_access_rights(
&mut self,
rights: Vec<BACnetAssignedAccessRights>,
) -> Result<(), Error> {
rights
.iter()
.try_for_each(rules::check_assigned_access_rights)?;
self.assigned_access_rights = rights;
Ok(())
}
pub fn assigned_access_rights(&self) -> &[BACnetAssignedAccessRights] {
&self.assigned_access_rights
}
}
fn framed<T>(items: &[T], encode: fn(&mut BytesMut, &T)) -> Vec<PropertyValue> {
items
.iter()
.map(|item| {
let mut buf = BytesMut::new();
encode(&mut buf, item);
PropertyValue::ApplicationData(buf.to_vec())
})
.collect()
}
impl BACnetObject for AccessCredentialObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
&self.name
}
fn read_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
if let Some(result) =
read_common_properties!(self, property, array_index, no_out_of_service)
{
return result;
}
match property {
p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
ObjectType::ACCESS_CREDENTIAL.to_raw(),
)),
p if p == PropertyIdentifier::GLOBAL_IDENTIFIER => {
Ok(PropertyValue::Unsigned(self.global_identifier.into()))
}
p if p == PropertyIdentifier::CREDENTIAL_STATUS => {
Ok(PropertyValue::Enumerated(self.credential_status().to_raw()))
}
p if p == PropertyIdentifier::REASON_FOR_DISABLE => Ok(PropertyValue::List(
self.reason_for_disable()
.into_iter()
.map(|reason| PropertyValue::Enumerated(reason.to_raw()))
.collect(),
)),
p if p == PropertyIdentifier::AUTHENTICATION_FACTORS => common::read_array(
framed(
&self.authentication_factors,
encode_credential_authentication_factor,
),
array_index,
),
p if p == PropertyIdentifier::ACTIVATION_TIME => Ok(self.activation_time.to_property()),
p if p == PropertyIdentifier::EXPIRATION_TIME => Ok(self.expiration_time.to_property()),
p if p == PropertyIdentifier::CREDENTIAL_DISABLE => {
Ok(PropertyValue::Enumerated(self.credential_disable.to_raw()))
}
p if p == PropertyIdentifier::ASSIGNED_ACCESS_RIGHTS => common::read_array(
framed(&self.assigned_access_rights, encode_assigned_access_rights),
array_index,
),
_ => Err(common::unknown_property_error()),
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
if let Some(result) = common::write_description(&mut self.description, property, &value) {
return result;
}
match property {
p if p == PropertyIdentifier::GLOBAL_IDENTIFIER => {
let PropertyValue::Unsigned(raw) = value else {
return Err(common::invalid_data_type_error());
};
self.global_identifier = common::u64_to_u32(raw)?;
Ok(())
}
p if p == PropertyIdentifier::CREDENTIAL_DISABLE => {
let PropertyValue::Enumerated(raw) = value else {
return Err(common::invalid_data_type_error());
};
self.set_credential_disable(AccessCredentialDisable::from_raw(raw))
}
p if p == PropertyIdentifier::ACTIVATION_TIME => {
self.activation_time = WindowLimit::from_property(value)?;
Ok(())
}
p if p == PropertyIdentifier::EXPIRATION_TIME => {
self.expiration_time = WindowLimit::from_property(value)?;
Ok(())
}
_ => Err(crate::common::unhandled_write_error(
self.property_metadata().as_ref(),
property,
array_index,
)),
}
}
fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
super::metadata_identity::for_access_credential_object(self)
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
}
fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
self.clock = clock;
}
}