use bacnet_types::enums::{
ErrorClass, ErrorCode, EventState, ObjectType, PropertyIdentifier, Reliability,
};
use bacnet_types::error::Error;
use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier, PropertyValue, StatusFlags};
use std::borrow::Cow;
use crate::common::{
self, read_common_properties, read_generic_event_properties, write_generic_event_properties,
};
use crate::event::{history::EventHistory, ChangeOfStateDetector};
use crate::traits::{BACnetObject, ReliabilityEvaluation};
pub(crate) const MAX_ALARM_VALUES: usize = 1024;
fn decode_alarm_values_write(
array_index: Option<u32>,
value: PropertyValue,
number_of_states: u32,
) -> Result<Vec<u32>, Error> {
if array_index.is_some() {
return Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::PROPERTY_IS_NOT_AN_ARRAY.to_raw() as u32,
});
}
let PropertyValue::List(values) = value else {
return Err(common::invalid_data_type_error());
};
if values.len() > MAX_ALARM_VALUES {
return Err(common::at_list_element(
Error::Protocol {
class: ErrorClass::RESOURCES.to_raw() as u32,
code: ErrorCode::NO_SPACE_TO_WRITE_PROPERTY.to_raw() as u32,
},
MAX_ALARM_VALUES,
));
}
values
.into_iter()
.enumerate()
.map(|(index, value)| {
match value {
PropertyValue::Unsigned(state)
if (1..=u64::from(number_of_states)).contains(&state) =>
{
Ok(state as u32)
}
PropertyValue::Unsigned(_) => Err(common::value_out_of_range_error()),
_ => Err(common::invalid_data_type_error()),
}
.map_err(|error| common::at_list_element(error, index))
})
.collect()
}
fn require_nonzero_states(number_of_states: u32) -> Result<(), Error> {
if number_of_states == 0 {
return Err(Error::OutOfRange(
"number_of_states must be at least 1".into(),
));
}
Ok(())
}
fn resize_state_text(
current_number_of_states: &mut u32,
state_text: &mut Vec<String>,
number_of_states: u32,
) -> Result<(), Error> {
require_nonzero_states(number_of_states)?;
let new_len = number_of_states as usize;
if new_len < state_text.len() {
state_text.truncate(new_len);
} else {
state_text.extend((state_text.len() + 1..=new_len).map(|state| format!("State {state}")));
}
*current_number_of_states = number_of_states;
Ok(())
}
const CREATION_ONLY: &[PropertyIdentifier] = &[PropertyIdentifier::NUMBER_OF_STATES];
pub const MAX_NUMBER_OF_STATES: u32 = 1024;
fn check_state_count(
current: u32,
count: u64,
held: impl IntoIterator<Item = u32>,
) -> Result<u32, Error> {
let count = u32::try_from(count)
.ok()
.filter(|count| (1..=MAX_NUMBER_OF_STATES).contains(count))
.ok_or_else(common::value_out_of_range_error)?;
let stranded = |state: u32| (1..=current).contains(&state) && state > count;
if held.into_iter().any(stranded) {
return Err(common::value_out_of_range_error());
}
Ok(count)
}
fn initialize_states(
number_of_states: &mut u32,
state_text: &mut Vec<String>,
held: impl IntoIterator<Item = u32>,
property: PropertyIdentifier,
value: PropertyValue,
) -> Result<(), Error> {
if property != PropertyIdentifier::NUMBER_OF_STATES {
return Err(common::write_access_denied_error());
}
let PropertyValue::Unsigned(count) = value else {
return Err(common::invalid_data_type_error());
};
let count = check_state_count(*number_of_states, count, held)?;
resize_state_text(number_of_states, state_text, count)
}
fn write_whole_state_text(
number_of_states: &mut u32,
state_text: &mut Vec<String>,
held: impl IntoIterator<Item = u32>,
value: PropertyValue,
) -> Result<(), Error> {
let labels = match value {
PropertyValue::CharacterString(label) => vec![label],
PropertyValue::List(values) => values
.into_iter()
.map(|value| match value {
PropertyValue::CharacterString(label) => Ok(label),
_ => Err(common::invalid_data_type_error()),
})
.collect::<Result<Vec<_>, _>>()?,
_ => return Err(common::invalid_data_type_error()),
};
let count = if labels.len() == *number_of_states as usize {
*number_of_states
} else {
check_state_count(*number_of_states, labels.len() as u64, held)?
};
*state_text = labels;
*number_of_states = count;
Ok(())
}
fn write_state_text(
number_of_states: &mut u32,
state_text: &mut Vec<String>,
held: impl IntoIterator<Item = u32>,
array_index: Option<u32>,
value: PropertyValue,
) -> Result<(), Error> {
match array_index {
None => write_whole_state_text(number_of_states, state_text, held, value),
Some(0) => {
let PropertyValue::Unsigned(count) = value else {
return Err(common::invalid_data_type_error());
};
let count = check_state_count(*number_of_states, count, held)?;
resize_state_text(number_of_states, state_text, count)
}
Some(index) if (index as usize) <= state_text.len() => {
let PropertyValue::CharacterString(label) = value else {
return Err(common::invalid_data_type_error());
};
state_text[(index - 1) as usize] = label;
Ok(())
}
Some(_) => Err(common::invalid_array_index_error()),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OwnedMultiStateFault {
ConfigurationError,
MultiStateOutOfRange,
}
impl OwnedMultiStateFault {
fn reliability(self) -> Reliability {
match self {
Self::ConfigurationError => Reliability::CONFIGURATION_ERROR,
Self::MultiStateOutOfRange => Reliability::MULTI_STATE_OUT_OF_RANGE,
}
}
}
#[derive(Debug, Default)]
struct MultiStateReliabilityState {
pub(crate) owned_fault: Option<OwnedMultiStateFault>,
}
impl MultiStateReliabilityState {
fn clear_ownership(&mut self) {
self.owned_fault = None;
}
fn evaluate(
&mut self,
configuration_invalid: bool,
present_value: u32,
number_of_states: u32,
reliability: &mut Reliability,
) -> ReliabilityEvaluation {
let observed_fault = if configuration_invalid {
Some(OwnedMultiStateFault::ConfigurationError)
} else if !(1..=number_of_states).contains(&present_value) {
Some(OwnedMultiStateFault::MultiStateOutOfRange)
} else {
None
};
let (new_reliability, new_owner) = if self.owned_fault.is_some() {
(
observed_fault
.map(OwnedMultiStateFault::reliability)
.unwrap_or(Reliability::NO_FAULT_DETECTED),
observed_fault,
)
} else if *reliability == Reliability::NO_FAULT_DETECTED {
let Some(fault) = observed_fault else {
return ReliabilityEvaluation::Unchanged;
};
(fault.reliability(), Some(fault))
} else {
return ReliabilityEvaluation::Unchanged;
};
self.owned_fault = new_owner;
if new_reliability == *reliability {
return ReliabilityEvaluation::Unchanged;
}
let old_reliability = *reliability;
*reliability = new_reliability;
ReliabilityEvaluation::Changed {
old_reliability,
new_reliability,
}
}
}
mod input;
mod output;
mod value;
pub use input::*;
pub use output::*;
pub use value::*;
#[cfg(test)]
mod tests;