#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use crate::bitstring::{AuditOperationFlags, LogStatus};
use crate::enums::{
AuditOperation, BACnetSuccessFilter, ErrorClass, ErrorCode, ObjectType, PropertyIdentifier,
};
use crate::primitives::{BACnetTimeStamp, Date, ObjectIdentifier, PropertyValue, Time};
use super::{BACnetAddress, BACnetRecipient};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BACnetObjectSelector {
None,
Object(ObjectIdentifier),
ObjectType(ObjectType),
}
impl BACnetObjectSelector {
pub fn encode_property_value(&self) -> PropertyValue {
match self {
Self::None => PropertyValue::Null,
Self::Object(object) => PropertyValue::ObjectIdentifier(*object),
Self::ObjectType(kind) => PropertyValue::Enumerated(kind.to_raw()),
}
}
pub fn decode_property_value(value: &PropertyValue) -> Result<Self, crate::error::Error> {
match value {
PropertyValue::Null => Ok(Self::None),
PropertyValue::ObjectIdentifier(object) => Ok(Self::Object(*object)),
PropertyValue::Enumerated(kind) => Ok(Self::ObjectType(ObjectType::from_raw(*kind))),
_ => Err(crate::error::Error::Encoding(
"BACnetObjectSelector requires NULL, ObjectIdentifier, or Enumerated".into(),
)),
}
}
}
#[cfg(test)]
mod selector_tests {
use super::*;
#[test]
fn audit_object_selector_preserves_choice_and_extensible_type() {
let object = ObjectIdentifier::new(ObjectType::BINARY_VALUE, 42).unwrap();
for (selector, value) in [
(BACnetObjectSelector::None, PropertyValue::Null),
(
BACnetObjectSelector::Object(object),
PropertyValue::ObjectIdentifier(object),
),
(
BACnetObjectSelector::ObjectType(ObjectType::BINARY_VALUE),
PropertyValue::Enumerated(5),
),
(
BACnetObjectSelector::ObjectType(ObjectType::from_raw(128)),
PropertyValue::Enumerated(128),
),
] {
assert_eq!(selector.encode_property_value(), value);
assert_eq!(
BACnetObjectSelector::decode_property_value(&value).unwrap(),
selector
);
}
for invalid in [
PropertyValue::Unsigned(5),
PropertyValue::Boolean(false),
PropertyValue::List(vec![]),
] {
assert!(matches!(
BACnetObjectSelector::decode_property_value(&invalid),
Err(crate::error::Error::Encoding(_))
));
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BACnetAuditNotification {
pub source_timestamp: Option<BACnetTimeStamp>,
pub target_timestamp: Option<BACnetTimeStamp>,
pub source_device: BACnetRecipient,
pub source_object: Option<ObjectIdentifier>,
pub operation: AuditOperation,
pub source_comment: Option<String>,
pub target_comment: Option<String>,
pub invoke_id: Option<u8>,
pub source_user_id: Option<u16>,
pub source_user_role: Option<u8>,
pub target_device: BACnetRecipient,
pub target_object: Option<ObjectIdentifier>,
pub target_property: Option<AuditPropertyReference>,
pub target_priority: Option<u8>,
pub target_value: Option<Vec<u8>>,
pub current_value: Option<Vec<u8>>,
pub result: Option<(ErrorClass, ErrorCode)>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BACnetAuditLogRecordResult {
pub sequence_number: u64,
pub record: BACnetAuditLogRecord,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BACnetAuditLogRecord {
pub timestamp: (Date, Time),
pub datum: BACnetAuditLogDatum,
}
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::large_enum_variant)] pub enum BACnetAuditLogDatum {
LogStatus(LogStatus),
AuditNotification(BACnetAuditNotification),
TimeChange(f32),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BACnetAuditLogQueryParameters {
ByTarget {
target_device_identifier: ObjectIdentifier,
target_device_address: Option<BACnetAddress>,
target_object_identifier: Option<ObjectIdentifier>,
target_property_identifier: Option<PropertyIdentifier>,
target_array_index: Option<u64>,
target_priority: Option<u8>,
operations: Option<AuditOperationFlags>,
successful_actions_only: BACnetSuccessFilter,
},
BySource {
source_device_identifier: ObjectIdentifier,
source_device_address: Option<BACnetAddress>,
source_object_identifier: Option<ObjectIdentifier>,
operations: Option<AuditOperationFlags>,
successful_actions_only: BACnetSuccessFilter,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuditPropertyReference {
pub property_identifier: PropertyIdentifier,
pub property_array_index: Option<u64>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn audit_record_models_are_dependency_neutral_and_exactly_typed() {
let record = BACnetAuditLogRecordResult {
sequence_number: u64::MAX,
record: BACnetAuditLogRecord {
timestamp: (
Date {
year: 124,
month: 2,
day: 29,
day_of_week: 4,
},
Time {
hour: 12,
minute: 0,
second: 0,
hundredths: 0,
},
),
datum: BACnetAuditLogDatum::TimeChange(-1.5),
},
};
assert_eq!(record.clone(), record);
}
#[test]
#[allow(deprecated)]
fn legacy_bool_helper_maps_old_meaning_to_named_filter() {
assert_eq!(
BACnetSuccessFilter::from_legacy_bool(true),
BACnetSuccessFilter::SUCCESSES_ONLY
);
assert_eq!(
BACnetSuccessFilter::from_legacy_bool(false),
BACnetSuccessFilter::ALL
);
}
}