use bacnet_encoding::constructed::encode_authentication_policy;
use bacnet_types::constructed::{device_identifier_is_device, BACnetAuthenticationPolicy};
use bacnet_types::enums::{AuthorizationMode, ObjectType, PropertyIdentifier as P};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use bytes::BytesMut;
use super::MAX_AUTHENTICATION_POLICIES;
use crate::common;
const FIRST_PROPRIETARY_MODE: u32 = 64;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct PolicyDefinition {
name: String,
policy: BACnetAuthenticationPolicy,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct Authorization {
policies: u32,
definitions: Option<Vec<PolicyDefinition>>,
active_policy: u32,
mode: AuthorizationMode,
supported_modes: Vec<AuthorizationMode>,
priority_for_writing: u8,
}
impl Authorization {
pub(super) fn new() -> Self {
Self {
policies: 1,
definitions: None,
active_policy: 1,
mode: AuthorizationMode::AUTHORIZE,
supported_modes: vec![AuthorizationMode::AUTHORIZE],
priority_for_writing: 16,
}
}
pub(super) fn active_policy(&self) -> u32 {
self.active_policy
}
pub(super) fn serves_policy_list(&self) -> bool {
self.definitions.is_some()
}
pub(super) fn holds_invalid_policy(&self) -> bool {
self.definitions.as_ref().is_some_and(|definitions| {
definitions
.iter()
.any(|definition| !is_well_formed(&definition.policy))
})
}
pub(super) fn read(
&self,
property: P,
array_index: Option<u32>,
) -> Option<Result<PropertyValue, Error>> {
let value = match property {
P::ACTIVE_AUTHENTICATION_POLICY => PropertyValue::Unsigned(self.active_policy.into()),
P::NUMBER_OF_AUTHENTICATION_POLICIES => PropertyValue::Unsigned(self.policies.into()),
P::AUTHORIZATION_MODE => PropertyValue::Enumerated(self.mode.to_raw()),
P::PRIORITY_FOR_WRITING => PropertyValue::Unsigned(self.priority_for_writing.into()),
P::AUTHENTICATION_POLICY_LIST => {
let elements = self
.definitions
.as_ref()?
.iter()
.map(|definition| {
let mut buf = BytesMut::new();
encode_authentication_policy(&mut buf, &definition.policy);
PropertyValue::ApplicationData(buf.to_vec())
})
.collect();
return Some(common::read_array(elements, array_index));
}
P::AUTHENTICATION_POLICY_NAMES => {
let elements = self
.definitions
.as_ref()?
.iter()
.map(|definition| PropertyValue::CharacterString(definition.name.clone()))
.collect();
return Some(common::read_array(elements, array_index));
}
_ => return None,
};
Some(Ok(value))
}
pub(super) fn write(
&mut self,
property: P,
array_index: Option<u32>,
value: &PropertyValue,
) -> Option<Result<(), Error>> {
if property != P::ACTIVE_AUTHENTICATION_POLICY && property != P::AUTHORIZATION_MODE {
return None;
}
if array_index.is_some() {
return Some(Err(common::property_is_not_an_array_error()));
}
Some(if property == P::ACTIVE_AUTHENTICATION_POLICY {
self.write_active_policy(value)
} else {
self.write_mode(value)
})
}
fn write_active_policy(&mut self, value: &PropertyValue) -> Result<(), Error> {
let PropertyValue::Unsigned(policy) = *value else {
return Err(common::invalid_data_type_error());
};
self.active_policy = u32::try_from(policy)
.ok()
.filter(|&policy| self.is_usable(policy))
.ok_or_else(common::value_out_of_range_error)?;
Ok(())
}
fn is_usable(&self, policy: u32) -> bool {
if !(1..=self.policies).contains(&policy) {
return false;
}
self.definitions.as_ref().is_none_or(|definitions| {
definitions
.get(policy as usize - 1)
.is_some_and(|definition| is_well_formed(&definition.policy))
})
}
fn settle_active_policy(&mut self) {
if self.active_policy != 0 && !self.is_usable(self.active_policy) {
self.active_policy = 0;
}
}
fn write_mode(&mut self, value: &PropertyValue) -> Result<(), Error> {
let PropertyValue::Enumerated(raw) = *value else {
return Err(common::invalid_data_type_error());
};
let mode = AuthorizationMode::from_raw(raw);
if !self.supported_modes.contains(&mode) {
return Err(common::value_out_of_range_error());
}
self.mode = mode;
Ok(())
}
pub(super) fn set_policies(&mut self, count: u32) -> Result<(), Error> {
if count == 0 || (self.definitions.is_some() && count > MAX_AUTHENTICATION_POLICIES) {
return Err(common::value_out_of_range_error());
}
self.policies = count;
if let Some(definitions) = &mut self.definitions {
definitions.resize_with(count as usize, PolicyDefinition::default);
}
self.settle_active_policy();
Ok(())
}
pub(super) fn set_policy_list(
&mut self,
policies: Vec<(String, BACnetAuthenticationPolicy)>,
) -> Result<(), Error> {
let count = u32::try_from(policies.len())
.ok()
.filter(|count| (1..=MAX_AUTHENTICATION_POLICIES).contains(count))
.ok_or_else(common::value_out_of_range_error)?;
self.policies = count;
self.definitions = Some(
policies
.into_iter()
.map(|(name, policy)| PolicyDefinition { name, policy })
.collect(),
);
self.settle_active_policy();
Ok(())
}
pub(super) fn set_supported_modes(
&mut self,
modes: impl IntoIterator<Item = AuthorizationMode>,
) -> Result<(), Error> {
let mut modes: Vec<_> = modes.into_iter().collect();
if !modes.iter().all(|&mode| in_production(mode))
|| !modes.contains(&AuthorizationMode::AUTHORIZE)
|| !modes.contains(&self.mode)
{
return Err(common::value_out_of_range_error());
}
modes.sort_unstable_by_key(|mode| mode.to_raw());
modes.dedup();
self.supported_modes = modes;
Ok(())
}
pub(super) fn set_priority_for_writing(&mut self, priority: u8) -> Result<(), Error> {
if !(1..=16).contains(&priority) {
return Err(common::value_out_of_range_error());
}
self.priority_for_writing = priority;
Ok(())
}
}
fn is_well_formed(policy: &BACnetAuthenticationPolicy) -> bool {
let mut previous = 0;
!policy.policy.is_empty()
&& policy.policy.iter().all(|entry| {
let reference = &entry.credential_data_input;
let in_sequence =
entry.index == previous.max(1) || previous.checked_add(1) == Some(entry.index);
previous = entry.index;
in_sequence
&& reference.object_identifier.object_type() == ObjectType::CREDENTIAL_DATA_INPUT
&& device_identifier_is_device(reference.device_identifier)
})
}
fn in_production(mode: AuthorizationMode) -> bool {
let raw = mode.to_raw();
raw <= AuthorizationMode::NONE.to_raw() || (FIRST_PROPRIETARY_MODE..=65_535).contains(&raw)
}