use bacnet_types::calendar::SpecificDate;
use bacnet_types::constructed::{BACnetAssignedAccessRights, BACnetCredentialAuthenticationFactor};
use bacnet_types::enums::{
AccessAuthenticationFactorDisable, AccessCredentialDisable, AccessCredentialDisableReason,
AuthenticationFactorType, AuthorizationExemption, ObjectType,
};
use bacnet_types::error::Error;
use bacnet_types::primitives::{Date, ObjectIdentifier, PropertyValue, Time};
use crate::clock::ClockFrame;
use crate::common;
const VENDOR: std::ops::RangeInclusive<u32> = 64..=65535;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct Instant {
day: SpecificDate,
hour: u8,
minute: u8,
second: u8,
hundredths: u8,
}
impl Instant {
fn new(date: &Date, time: &Time) -> Option<Self> {
let day = SpecificDate::from_date(date)?;
time.is_specific().then_some(Self {
day,
hour: time.hour,
minute: time.minute,
second: time.second,
hundredths: time.hundredths,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct WindowLimit {
date: Date,
time: Time,
instant: Option<Instant>,
}
impl WindowLimit {
pub(super) const OPEN: Self = Self {
date: Date {
year: Date::UNSPECIFIED,
month: Date::UNSPECIFIED,
day: Date::UNSPECIFIED,
day_of_week: Date::UNSPECIFIED,
},
time: Time {
hour: Time::UNSPECIFIED,
minute: Time::UNSPECIFIED,
second: Time::UNSPECIFIED,
hundredths: Time::UNSPECIFIED,
},
instant: None,
};
pub(super) fn new(date: Date, time: Time) -> Result<Self, Error> {
if date.encode() == Self::OPEN.date.encode() && time.encode() == Self::OPEN.time.encode() {
return Ok(Self::OPEN);
}
let instant = Instant::new(&date, &time).ok_or_else(common::value_out_of_range_error)?;
Ok(Self {
date,
time,
instant: Some(instant),
})
}
pub(super) fn from_property(value: PropertyValue) -> Result<Self, Error> {
match value {
PropertyValue::List(items) => match items.as_slice() {
[PropertyValue::Date(date), PropertyValue::Time(time)] => Self::new(*date, *time),
_ => Err(common::invalid_data_type_error()),
},
_ => Err(common::invalid_data_type_error()),
}
}
pub(super) fn to_property(self) -> PropertyValue {
PropertyValue::List(vec![
PropertyValue::Date(self.date),
PropertyValue::Time(self.time),
])
}
pub(super) fn parts(self) -> (Date, Time) {
(self.date, self.time)
}
}
pub(super) fn window_reasons(
frame: Option<ClockFrame>,
activation: WindowLimit,
expiration: WindowLimit,
) -> impl Iterator<Item = AccessCredentialDisableReason> {
let now = frame.and_then(|frame| Instant::new(&frame.local_date, &frame.local_time));
let not_yet_active =
matches!((now, activation.instant), (Some(now), Some(start)) if now < start);
let expired = matches!((now, expiration.instant), (Some(now), Some(end)) if now > end);
[
not_yet_active.then_some(AccessCredentialDisableReason::DISABLED_NOT_YET_ACTIVE),
expired.then_some(AccessCredentialDisableReason::DISABLED_EXPIRED),
]
.into_iter()
.flatten()
}
pub(super) fn credential_disable_reason(
value: AccessCredentialDisable,
) -> Option<AccessCredentialDisableReason> {
match value {
AccessCredentialDisable::NONE => None,
AccessCredentialDisable::DISABLE_MANUAL => {
Some(AccessCredentialDisableReason::DISABLED_MANUAL)
}
AccessCredentialDisable::DISABLE_LOCKOUT => {
Some(AccessCredentialDisableReason::DISABLED_LOCKOUT)
}
_ => Some(AccessCredentialDisableReason::DISABLED),
}
}
pub(super) fn check_credential_disable(value: AccessCredentialDisable) -> Result<(), Error> {
let raw = value.to_raw();
if raw <= AccessCredentialDisable::DISABLE_LOCKOUT.to_raw() || VENDOR.contains(&raw) {
Ok(())
} else {
Err(common::value_out_of_range_error())
}
}
pub(super) fn check_local_reason(reason: AccessCredentialDisableReason) -> Result<(), Error> {
let raw = reason.to_raw();
let named = raw <= AccessCredentialDisableReason::DISABLED_MANUAL.to_raw();
let window_owned = matches!(
reason,
AccessCredentialDisableReason::DISABLED_NOT_YET_ACTIVE
| AccessCredentialDisableReason::DISABLED_EXPIRED
);
if (named && !window_owned) || VENDOR.contains(&raw) {
Ok(())
} else {
Err(common::value_out_of_range_error())
}
}
pub(super) fn check_authentication_factor(
factor: &BACnetCredentialAuthenticationFactor,
) -> Result<(), Error> {
let disable = factor.disable.to_raw();
let disable_ok = disable <= AccessAuthenticationFactorDisable::DISABLED_DESTROYED.to_raw()
|| VENDOR.contains(&disable);
let format = factor.authentication_factor.format_type.to_raw();
if disable_ok && format <= AuthenticationFactorType::USER_PASSWORD.to_raw() {
Ok(())
} else {
Err(common::value_out_of_range_error())
}
}
const EXEMPTION_VENDOR: std::ops::RangeInclusive<u32> = 64..=255;
pub(super) fn check_authorization_exemption(
exemption: AuthorizationExemption,
) -> Result<(), Error> {
let raw = exemption.to_raw();
if raw <= AuthorizationExemption::AUTHORIZATION_DELAY.to_raw()
|| EXEMPTION_VENDOR.contains(&raw)
{
Ok(())
} else {
Err(common::value_out_of_range_error())
}
}
pub(super) fn check_assigned_access_rights(
element: &BACnetAssignedAccessRights,
) -> Result<(), Error> {
let reference = &element.assigned_access_rights;
crate::device_reference::check_device_member(reference.device_identifier)?;
let object = reference.object_identifier;
if object.object_type() == ObjectType::ACCESS_RIGHTS
|| object.instance_number() == ObjectIdentifier::MAX_INSTANCE
{
Ok(())
} else {
Err(common::value_out_of_range_error())
}
}