use std::sync::Arc;
use bacnet_encoding::constructed::encode_authentication_factor;
use bacnet_types::constructed::{BACnetAuthenticationFactor, BACnetAuthenticationPolicy};
use bacnet_types::enums::AuthorizationMode;
use super::credential_data_input_formats::{is_factor_type, undefined_factor};
use super::point_authorization::Authorization;
use super::point_event_time::next_event_time;
use super::*;
use crate::clock::ClockReader;
pub const MAX_AUTHENTICATION_POLICIES: u32 = 256;
pub struct AccessPointObject {
oid: ObjectIdentifier,
name: String,
description: String,
access_event: AccessEvent,
access_event_tag: u64,
access_event_time: BACnetTimeStamp,
access_event_credential: BACnetDeviceObjectReference,
access_event_authentication_factor: BACnetAuthenticationFactor,
authentication_status: AuthenticationStatus,
access_doors: Vec<BACnetDeviceObjectReference>,
authorization: Authorization,
event_state: EventState,
status_flags: StatusFlags,
out_of_service: bool,
reliability: Reliability,
clock: Option<Arc<dyn ClockReader>>,
}
impl AccessPointObject {
pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
let oid = ObjectIdentifier::new(ObjectType::ACCESS_POINT, instance)?;
Ok(Self {
oid,
name: name.into(),
description: String::new(),
access_event: AccessEvent::NONE,
access_event_tag: 0,
access_event_time: never_updated(),
access_event_credential: no_credential(),
access_event_authentication_factor: undefined_factor(),
authentication_status: AuthenticationStatus::READY,
access_doors: Vec::new(),
authorization: Authorization::new(),
event_state: EventState::NORMAL,
status_flags: StatusFlags::empty(),
out_of_service: false,
reliability: Reliability::NO_FAULT_DETECTED,
clock: None,
})
}
pub fn set_access_event(&mut self, report: AccessEventReport) -> Result<(), Error> {
let credential = report.credential.unwrap_or_else(no_credential);
crate::device_reference::check_device_member(credential.device_identifier)?;
if credential.object_identifier.object_type() != ObjectType::ACCESS_CREDENTIAL {
return Err(common::value_out_of_range_error());
}
let empty = |instance: u32| instance == ObjectIdentifier::MAX_INSTANCE;
if credential.device_identifier.is_some_and(|device| {
empty(device.instance_number()) != empty(credential.object_identifier.instance_number())
}) {
return Err(common::value_out_of_range_error());
}
let factor = report
.authentication_factor
.unwrap_or_else(undefined_factor);
if !is_factor_type(factor.format_type) || !access_event_in_range(report.event) {
return Err(common::value_out_of_range_error());
}
self.access_event_time = report
.time
.unwrap_or_else(|| next_event_time(self.clock.as_deref(), &self.access_event_time));
self.access_event = report.event;
self.access_event_tag = report.tag;
self.access_event_credential = credential;
self.access_event_authentication_factor = factor;
Ok(())
}
pub fn set_authentication_status(&mut self, status: AuthenticationStatus) -> Result<(), Error> {
if status.to_raw() > AuthenticationStatus::IN_PROGRESS.to_raw() {
return Err(common::value_out_of_range_error());
}
self.authentication_status = status;
Ok(())
}
pub fn authentication_status(&self) -> AuthenticationStatus {
if self.out_of_service {
AuthenticationStatus::DISABLED
} else {
self.authentication_status
}
}
pub fn set_access_doors(
&mut self,
doors: impl IntoIterator<Item = impl Into<BACnetDeviceObjectReference>>,
) -> Result<(), Error> {
self.access_doors = references_to(ObjectType::ACCESS_DOOR, doors)?;
Ok(())
}
pub fn set_number_of_authentication_policies(&mut self, count: u32) -> Result<(), Error> {
self.authorization.set_policies(count)?;
self.refresh_reliability();
Ok(())
}
pub fn set_authentication_policies(
&mut self,
policies: impl IntoIterator<Item = (impl Into<String>, BACnetAuthenticationPolicy)>,
) -> Result<(), Error> {
self.authorization.set_policy_list(
policies
.into_iter()
.map(|(name, policy)| (name.into(), policy))
.collect(),
)?;
self.refresh_reliability();
Ok(())
}
fn refresh_reliability(&mut self) {
if !self.out_of_service {
self.reliability = if self.authorization.active_policy() == 0
|| self.authorization.holds_invalid_policy()
{
Reliability::CONFIGURATION_ERROR
} else {
Reliability::NO_FAULT_DETECTED
};
}
}
pub(super) fn serves_policy_list(&self) -> bool {
self.authorization.serves_policy_list()
}
pub fn set_supported_authorization_modes(
&mut self,
modes: impl IntoIterator<Item = AuthorizationMode>,
) -> Result<(), Error> {
self.authorization.set_supported_modes(modes)
}
pub fn set_priority_for_writing(&mut self, priority: u8) -> Result<(), Error> {
self.authorization.set_priority_for_writing(priority)
}
fn record_out_of_service_event(&mut self, event: AccessEvent) {
self.access_event = event;
self.access_event_tag = self.access_event_tag.wrapping_add(1);
self.access_event_time = next_event_time(self.clock.as_deref(), &self.access_event_time);
self.access_event_credential = no_credential();
self.access_event_authentication_factor = undefined_factor();
}
}
fn no_credential() -> BACnetDeviceObjectReference {
ObjectIdentifier::new(
ObjectType::ACCESS_CREDENTIAL,
ObjectIdentifier::MAX_INSTANCE,
)
.expect("4194303 is within the instance range")
.into()
}
fn access_event_in_range(event: AccessEvent) -> bool {
AccessEvent::ALL_NAMED
.iter()
.any(|&(_, named)| named == event)
|| (512..=65_535).contains(&event.to_raw())
}
impl BACnetObject for AccessPointObject {
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) {
return result;
}
if let Some(result) = self.authorization.read(property, array_index) {
return result;
}
match property {
p if p == PropertyIdentifier::OBJECT_TYPE => {
Ok(PropertyValue::Enumerated(ObjectType::ACCESS_POINT.to_raw()))
}
p if p == PropertyIdentifier::ACCESS_EVENT => {
Ok(PropertyValue::Enumerated(self.access_event.to_raw()))
}
p if p == PropertyIdentifier::ACCESS_EVENT_TAG => {
Ok(PropertyValue::Unsigned(self.access_event_tag))
}
p if p == PropertyIdentifier::ACCESS_EVENT_TIME => {
timestamp_value(&self.access_event_time)
}
p if p == PropertyIdentifier::ACCESS_EVENT_CREDENTIAL => Ok(
crate::device_reference::reference_value(&self.access_event_credential),
),
p if p == PropertyIdentifier::ACCESS_EVENT_AUTHENTICATION_FACTOR => {
let mut buf = BytesMut::new();
encode_authentication_factor(&mut buf, &self.access_event_authentication_factor);
Ok(PropertyValue::ApplicationData(buf.to_vec()))
}
p if p == PropertyIdentifier::AUTHENTICATION_STATUS => Ok(PropertyValue::Enumerated(
self.authentication_status().to_raw(),
)),
p if p == PropertyIdentifier::ACCESS_DOORS => common::read_array(
crate::device_reference::reference_elements(&self.access_doors),
array_index,
),
p if p == PropertyIdentifier::EVENT_STATE => {
Ok(PropertyValue::Enumerated(self.event_state.to_raw()))
}
_ => Err(common::unknown_property_error()),
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
let was_out_of_service = self.out_of_service;
if let Some(result) =
common::write_out_of_service(&mut self.out_of_service, property, &value)
{
match (was_out_of_service, self.out_of_service) {
(false, true) => self.record_out_of_service_event(AccessEvent::OUT_OF_SERVICE),
(true, false) => {
self.record_out_of_service_event(AccessEvent::OUT_OF_SERVICE_RELINQUISHED);
self.refresh_reliability();
}
_ => {}
}
return result;
}
if let Some(result) = common::write_description(&mut self.description, property, &value) {
return result;
}
if property == PropertyIdentifier::RELIABILITY {
if !self.out_of_service {
return Err(common::write_access_denied_error());
}
self.reliability = simulated_reliability(&value)?;
return Ok(());
}
if let Some(result) = self.authorization.write(property, array_index, &value) {
self.refresh_reliability();
return result;
}
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_topology::for_access_point_object(self)
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
}
fn supports_cov(&self) -> bool {
true
}
fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
self.clock = clock;
}
fn report_access_input_internal(&mut self, input: AccessControlInput) -> Result<(), Error> {
let AccessControlInput::AccessEvent(report) = input else {
return Err(common::optional_functionality_not_supported_error());
};
if self.out_of_service || self.reliability != Reliability::NO_FAULT_DETECTED {
return Err(common::write_access_denied_error());
}
self.set_access_event(report)
}
}