pub fn compute_status_flags(
base_flags: bacnet_types::primitives::StatusFlags,
reliability: bacnet_types::enums::Reliability,
out_of_service: bool,
event_state: bacnet_types::enums::EventState,
) -> bacnet_types::primitives::PropertyValue {
let mut flags = base_flags;
if event_state != bacnet_types::enums::EventState::NORMAL {
flags |= bacnet_types::primitives::StatusFlags::IN_ALARM;
} else {
flags -= bacnet_types::primitives::StatusFlags::IN_ALARM;
}
if reliability != bacnet_types::enums::Reliability::NO_FAULT_DETECTED {
flags |= bacnet_types::primitives::StatusFlags::FAULT;
} else {
flags -= bacnet_types::primitives::StatusFlags::FAULT;
}
if out_of_service {
flags |= bacnet_types::primitives::StatusFlags::OUT_OF_SERVICE;
} else {
flags -= bacnet_types::primitives::StatusFlags::OUT_OF_SERVICE;
}
bacnet_types::primitives::PropertyValue::BitString {
unused_bits: 4,
data: vec![flags.bits() << 4],
}
}
#[inline]
pub(crate) fn protocol_error(
class: bacnet_types::enums::ErrorClass,
code: bacnet_types::enums::ErrorCode,
) -> bacnet_types::error::Error {
bacnet_types::error::Error::Protocol {
class: class.to_raw() as u32,
code: code.to_raw() as u32,
}
}
pub(crate) fn unhandled_write_error(
metadata: &[crate::property_metadata::PropertyMetadata],
property: bacnet_types::enums::PropertyIdentifier,
array_index: Option<u32>,
) -> bacnet_types::error::Error {
if array_index.is_some()
|| metadata
.iter()
.any(|row| row.property_identifier == property)
{
write_access_denied_error()
} else {
unknown_property_error()
}
}
pub fn read_property_list_property(
props: &[bacnet_types::enums::PropertyIdentifier],
array_index: Option<u32>,
) -> Result<bacnet_types::primitives::PropertyValue, bacnet_types::error::Error> {
use bacnet_types::enums::PropertyIdentifier;
let filtered: Vec<_> = props
.iter()
.copied()
.filter(|p| {
*p != PropertyIdentifier::OBJECT_IDENTIFIER
&& *p != PropertyIdentifier::OBJECT_NAME
&& *p != PropertyIdentifier::OBJECT_TYPE
&& *p != PropertyIdentifier::PROPERTY_LIST
})
.collect();
match array_index {
None => {
let elements = filtered
.iter()
.map(|p| bacnet_types::primitives::PropertyValue::Enumerated(p.to_raw()))
.collect();
Ok(bacnet_types::primitives::PropertyValue::List(elements))
}
Some(0) => Ok(bacnet_types::primitives::PropertyValue::Unsigned(
filtered.len() as u64,
)),
Some(idx) => {
let i = (idx - 1) as usize;
if i < filtered.len() {
Ok(bacnet_types::primitives::PropertyValue::Enumerated(
filtered[i].to_raw(),
))
} else {
Err(invalid_array_index_error())
}
}
}
}
macro_rules! read_identity_properties {
($self:expr, $property:expr, $array_index:expr) => {
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::OBJECT_IDENTIFIER => Some(Ok(
bacnet_types::primitives::PropertyValue::ObjectIdentifier($self.oid),
)),
p if p == bacnet_types::enums::PropertyIdentifier::OBJECT_NAME => Some(Ok(
bacnet_types::primitives::PropertyValue::CharacterString($self.name.clone()),
)),
p if p == bacnet_types::enums::PropertyIdentifier::DESCRIPTION => Some(Ok(
bacnet_types::primitives::PropertyValue::CharacterString($self.description.clone()),
)),
p if p == bacnet_types::enums::PropertyIdentifier::PROPERTY_LIST => {
let props = $self.property_list();
Some($crate::common::read_property_list_property(
&props,
$array_index,
))
}
_ => None,
}
};
}
pub(crate) use read_identity_properties;
macro_rules! read_common_properties {
(@status $self:expr, $property:expr, $array_index:expr, $out_of_service:expr) => {{
let out_of_service: Option<bool> = $out_of_service;
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::STATUS_FLAGS => {
Some(Ok($crate::common::compute_status_flags(
$self.status_flags,
$self.reliability,
out_of_service.unwrap_or(false),
bacnet_types::enums::EventState::NORMAL,
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::OUT_OF_SERVICE => out_of_service
.map(|value| Ok(bacnet_types::primitives::PropertyValue::Boolean(value))),
p if p == bacnet_types::enums::PropertyIdentifier::RELIABILITY => Some(Ok(
bacnet_types::primitives::PropertyValue::Enumerated($self.reliability.to_raw()),
)),
_ => $crate::common::read_identity_properties!($self, $property, $array_index),
}
}};
($self:expr, $property:expr, $array_index:expr, no_out_of_service) => {
$crate::common::read_common_properties!(@status $self, $property, $array_index, None)
};
($self:expr, $property:expr, $array_index:expr) => {
$crate::common::read_common_properties!(
@status $self,
$property,
$array_index,
Some($self.out_of_service)
)
};
}
pub(crate) use read_common_properties;
#[inline]
pub(crate) fn unknown_property_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::UNKNOWN_PROPERTY,
)
}
#[inline]
pub(crate) fn write_out_of_service(
out_of_service: &mut bool,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
if property == bacnet_types::enums::PropertyIdentifier::OUT_OF_SERVICE {
if let bacnet_types::primitives::PropertyValue::Boolean(v) = value {
*out_of_service = *v;
Some(Ok(()))
} else if matches!(value, bacnet_types::primitives::PropertyValue::Null) {
Some(Ok(()))
} else {
Some(Err(invalid_data_type_error()))
}
} else {
None
}
}
#[inline]
pub(crate) fn write_out_of_service_with_reliability_restore(
out_of_service: &mut bool,
reliability: &mut bacnet_types::enums::Reliability,
saved_reliability: &mut Option<bacnet_types::enums::Reliability>,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
write_out_of_service_with_restore(
out_of_service,
reliability,
saved_reliability,
Some(bacnet_types::enums::Reliability::NO_FAULT_DETECTED),
property,
value,
)
}
#[inline]
pub(crate) fn write_out_of_service_with_restore<T: Clone>(
out_of_service: &mut bool,
served: &mut T,
set_aside: &mut Option<T>,
fallback: Option<T>,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
if property != bacnet_types::enums::PropertyIdentifier::OUT_OF_SERVICE {
return None;
}
Some(match value {
bacnet_types::primitives::PropertyValue::Boolean(v) => {
match (*out_of_service, *v) {
(false, true) => *set_aside = Some(served.clone()),
(true, false) => {
if let Some(own) = set_aside.take().or(fallback) {
*served = own;
}
}
_ => {}
}
*out_of_service = *v;
Ok(())
}
bacnet_types::primitives::PropertyValue::Null => Ok(()),
_ => Err(invalid_data_type_error()),
})
}
pub(crate) use crate::reliability_inhibit::ReliabilityInhibitState;
#[inline]
pub(crate) fn write_description(
description: &mut String,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
if property == bacnet_types::enums::PropertyIdentifier::DESCRIPTION {
Some(match value {
bacnet_types::primitives::PropertyValue::CharacterString(s) => {
*description = s.clone();
Ok(())
}
bacnet_types::primitives::PropertyValue::Null => Ok(()),
_ => Err(invalid_data_type_error()),
})
} else {
None
}
}
pub(crate) fn write_object_name(
name: &mut String,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
if property == bacnet_types::enums::PropertyIdentifier::OBJECT_NAME {
if let bacnet_types::primitives::PropertyValue::CharacterString(s) = value {
if s.is_empty() {
Some(Err(value_out_of_range_error()))
} else {
*name = s.clone();
Some(Ok(()))
}
} else {
Some(Err(invalid_data_type_error()))
}
} else {
None
}
}
#[inline]
pub(crate) fn write_access_denied_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::WRITE_ACCESS_DENIED,
)
}
#[inline]
pub(crate) fn optional_functionality_not_supported_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::OBJECT,
bacnet_types::enums::ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
)
}
#[inline]
pub(crate) fn invalid_data_type_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::INVALID_DATA_TYPE,
)
}
#[inline]
pub(crate) fn value_out_of_range_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::VALUE_OUT_OF_RANGE,
)
}
mod command_write;
mod encoded_elements;
mod list_element;
pub(crate) use command_write::decode_command_write;
pub(crate) use encoded_elements::{chunks, decode_element, decode_elements, decode_single_element};
#[cfg(test)]
pub(crate) use list_element::assert_list_element_refused;
pub(crate) use list_element::at_list_element;
#[inline]
pub(crate) fn invalid_data_encoding_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::INVALID_DATA_ENCODING,
)
}
#[inline]
pub(crate) fn check_fixed_width_bit_string(
unused_bits: u8,
data: &[u8],
named_bits: u8,
) -> Result<u8, bacnet_types::error::Error> {
if data.len() == 1 && unused_bits == 8 - named_bits {
Ok(data[0])
} else {
Err(invalid_data_encoding_error())
}
}
#[inline]
pub(crate) fn is_reliability_value_valid(value: bacnet_types::enums::Reliability) -> bool {
bacnet_types::enums::Reliability::ALL_NAMED
.iter()
.any(|&(_, named)| named == value)
|| (64..=65_535).contains(&value.to_raw())
}
#[inline]
pub(crate) fn invalid_array_index_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::INVALID_ARRAY_INDEX,
)
}
pub(crate) fn read_array(
values: Vec<bacnet_types::primitives::PropertyValue>,
array_index: Option<u32>,
) -> Result<bacnet_types::primitives::PropertyValue, bacnet_types::error::Error> {
use bacnet_types::primitives::PropertyValue;
match array_index {
None => Ok(PropertyValue::List(values)),
Some(0) => Ok(PropertyValue::Unsigned(values.len() as u64)),
Some(index) => values
.into_iter()
.nth((index - 1) as usize)
.ok_or_else(invalid_array_index_error),
}
}
#[inline]
pub(crate) fn property_is_not_an_array_error() -> bacnet_types::error::Error {
protocol_error(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::PROPERTY_IS_NOT_AN_ARRAY,
)
}
#[inline]
pub(crate) fn reject_non_finite(v: f32) -> Result<(), bacnet_types::error::Error> {
if v.is_finite() {
Ok(())
} else {
Err(value_out_of_range_error())
}
}
#[inline]
pub(crate) fn u64_to_u32(v: u64) -> Result<u32, bacnet_types::error::Error> {
u32::try_from(v).map_err(|_| value_out_of_range_error())
}
#[inline]
pub(crate) fn recalculate_from_priority_array<T: Copy>(
priority_array: &[Option<T>; 16],
relinquish_default: T,
) -> T {
priority_array
.iter()
.flatten()
.next()
.copied()
.unwrap_or(relinquish_default)
}
pub(crate) fn current_command_priority<T>(
priority_array: &[Option<T>; 16],
) -> bacnet_types::primitives::PropertyValue {
for (i, slot) in priority_array.iter().enumerate() {
if slot.is_some() {
return bacnet_types::primitives::PropertyValue::Unsigned((i + 1) as u64);
}
}
bacnet_types::primitives::PropertyValue::Null
}
macro_rules! read_generic_event_properties {
($self:expr, $property:expr) => {
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::EVENT_STATE => {
Some(Ok(bacnet_types::primitives::PropertyValue::Enumerated(
$self.event_detector.event_state.to_raw(),
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::EVENT_ENABLE => {
Some(Ok(bacnet_types::primitives::PropertyValue::BitString {
unused_bits: 5,
data: vec![$self.event_detector.event_enable.to_bacnet()],
}))
}
p if p == bacnet_types::enums::PropertyIdentifier::NOTIFY_TYPE => {
Some(Ok(bacnet_types::primitives::PropertyValue::Enumerated(
$self.event_detector.notify_type.to_raw(),
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::NOTIFICATION_CLASS => {
Some(Ok(bacnet_types::primitives::PropertyValue::Unsigned(
$self.event_detector.notification_class as u64,
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::TIME_DELAY => {
Some(Ok(bacnet_types::primitives::PropertyValue::Unsigned(
$self.event_detector.time_delay as u64,
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::TIME_DELAY_NORMAL => {
Some(Ok(bacnet_types::primitives::PropertyValue::Unsigned(
$self
.event_detector
.time_delay_normal
.unwrap_or($self.event_detector.time_delay) as u64,
)))
}
p if p == bacnet_types::enums::PropertyIdentifier::ACKED_TRANSITIONS => {
Some(Ok(bacnet_types::primitives::PropertyValue::BitString {
unused_bits: 5,
data: vec![$self.event_detector.acked_transitions.to_bacnet()],
}))
}
_ => None,
}
};
}
pub(crate) use read_generic_event_properties;
macro_rules! read_analog_event_properties {
($self:expr, $property:expr) => {
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::HIGH_LIMIT => Some(Ok(
bacnet_types::primitives::PropertyValue::Real($self.event_detector.high_limit),
)),
p if p == bacnet_types::enums::PropertyIdentifier::LOW_LIMIT => Some(Ok(
bacnet_types::primitives::PropertyValue::Real($self.event_detector.low_limit),
)),
p if p == bacnet_types::enums::PropertyIdentifier::DEADBAND => Some(Ok(
bacnet_types::primitives::PropertyValue::Real($self.event_detector.deadband),
)),
p if p == bacnet_types::enums::PropertyIdentifier::LIMIT_ENABLE => {
Some(Ok(bacnet_types::primitives::PropertyValue::BitString {
unused_bits: 6,
data: vec![$self.event_detector.limit_enable.to_bacnet()],
}))
}
_ => None,
}
};
}
pub(crate) use read_analog_event_properties;
macro_rules! write_analog_event_properties {
($self:expr, $property:expr, $value:expr) => {
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::HIGH_LIMIT => {
if let bacnet_types::primitives::PropertyValue::Real(v) = $value {
if let Err(e) = $crate::common::reject_non_finite(v) {
Some(Err(e))
} else {
$self.event_detector.high_limit = v;
Some(Ok(()))
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::LOW_LIMIT => {
if let bacnet_types::primitives::PropertyValue::Real(v) = $value {
if let Err(e) = $crate::common::reject_non_finite(v) {
Some(Err(e))
} else {
$self.event_detector.low_limit = v;
Some(Ok(()))
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::DEADBAND => {
if let bacnet_types::primitives::PropertyValue::Real(v) = $value {
if v < 0.0 || !v.is_finite() {
Some(Err($crate::common::value_out_of_range_error()))
} else {
$self.event_detector.deadband = v;
Some(Ok(()))
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::LIMIT_ENABLE => {
if let bacnet_types::primitives::PropertyValue::BitString { unused_bits, data } =
&$value
{
match $crate::common::check_fixed_width_bit_string(*unused_bits, data, 2) {
Ok(byte) => {
$self.event_detector.limit_enable =
bacnet_types::bitstring::LimitEnable::from_bacnet(&[byte]);
Some(Ok(()))
}
Err(e) => Some(Err(e)),
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
_ => None,
}
};
}
pub(crate) use write_analog_event_properties;
macro_rules! write_generic_event_properties {
($self:expr, $property:expr, $value:expr) => {
match $property {
p if p == bacnet_types::enums::PropertyIdentifier::EVENT_ENABLE => {
if let bacnet_types::primitives::PropertyValue::BitString { unused_bits, data } =
&$value
{
match $crate::common::check_fixed_width_bit_string(*unused_bits, data, 3) {
Ok(byte) => {
$self.event_detector.event_enable =
bacnet_types::bitstring::EventTransitionBits::from_bacnet(&[byte]);
Some(Ok(()))
}
Err(e) => Some(Err(e)),
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::NOTIFICATION_CLASS => {
if let bacnet_types::primitives::PropertyValue::Unsigned(v) = $value {
match $crate::common::u64_to_u32(v) {
Ok(v32) => {
$self.event_detector.notification_class = v32;
Some(Ok(()))
}
Err(e) => Some(Err(e)),
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::NOTIFY_TYPE => {
if let bacnet_types::primitives::PropertyValue::Enumerated(v) = $value {
let notify_type = bacnet_types::enums::NotifyType::from_raw(v);
let named = bacnet_types::enums::NotifyType::ALL_NAMED
.iter()
.any(|&(_, n)| n == notify_type);
if !named {
Some(Err($crate::common::value_out_of_range_error()))
} else {
$self.event_detector.notify_type = notify_type;
Some(Ok(()))
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::TIME_DELAY => {
if let bacnet_types::primitives::PropertyValue::Unsigned(v) = $value {
match $crate::common::u64_to_u32(v) {
Ok(v32) => {
$self.event_detector.time_delay = v32;
Some(Ok(()))
}
Err(e) => Some(Err(e)),
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::TIME_DELAY_NORMAL => {
if let bacnet_types::primitives::PropertyValue::Unsigned(v) = $value {
match $crate::common::u64_to_u32(v) {
Ok(v32) => {
$self.event_detector.time_delay_normal = Some(v32);
Some(Ok(()))
}
Err(e) => Some(Err(e)),
}
} else {
Some(Err($crate::common::invalid_data_type_error()))
}
}
p if p == bacnet_types::enums::PropertyIdentifier::ACKED_TRANSITIONS => {
Some(Err($crate::common::write_access_denied_error()))
}
_ => None,
}
};
}
pub(crate) use write_generic_event_properties;
macro_rules! read_priority_array {
($self:expr, $array_index:expr, $wrap:expr) => {{
let wrap_fn = $wrap;
match $array_index {
None => {
let elements = $self
.priority_array
.iter()
.map(|slot| match slot {
Some(v) => wrap_fn(*v),
None => bacnet_types::primitives::PropertyValue::Null,
})
.collect();
Ok(bacnet_types::primitives::PropertyValue::List(elements))
}
Some(0) => Ok(bacnet_types::primitives::PropertyValue::Unsigned(16)),
Some(idx) if (1..=16).contains(&idx) => {
match $self.priority_array[(idx - 1) as usize] {
Some(v) => Ok(wrap_fn(v)),
None => Ok(bacnet_types::primitives::PropertyValue::Null),
}
}
_ => Err($crate::common::invalid_array_index_error()),
}
}};
}
pub(crate) use read_priority_array;
#[inline]
pub(crate) fn write_cov_increment(
cov_increment: &mut f32,
property: bacnet_types::enums::PropertyIdentifier,
value: &bacnet_types::primitives::PropertyValue,
) -> Option<Result<(), bacnet_types::error::Error>> {
if property == bacnet_types::enums::PropertyIdentifier::COV_INCREMENT {
if let bacnet_types::primitives::PropertyValue::Real(v) = value {
if *v < 0.0 || !v.is_finite() {
Some(Err(value_out_of_range_error()))
} else {
*cov_increment = *v;
Some(Ok(()))
}
} else {
Some(Err(invalid_data_type_error()))
}
} else {
None
}
}
#[cfg(test)]
#[inline]
pub(crate) fn is_commandable_property_writable(
property: bacnet_types::enums::PropertyIdentifier,
) -> bool {
matches!(
property,
bacnet_types::enums::PropertyIdentifier::PRESENT_VALUE
| bacnet_types::enums::PropertyIdentifier::RELINQUISH_DEFAULT
)
}
#[cfg(test)]
#[inline]
pub(crate) fn is_common_writable(property: bacnet_types::enums::PropertyIdentifier) -> bool {
matches!(
property,
bacnet_types::enums::PropertyIdentifier::OUT_OF_SERVICE
| bacnet_types::enums::PropertyIdentifier::OBJECT_NAME
| bacnet_types::enums::PropertyIdentifier::DESCRIPTION
)
}