use super::*;
use bacnet_objects::traits::BACnetObject;
struct DisabledAlarmingObject {
oid: ObjectIdentifier,
detection_enable: Option<bool>,
}
impl BACnetObject for DisabledAlarmingObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
"MOCK-1"
}
fn read_property(
&self,
property: PropertyIdentifier,
_array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
match property {
p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
Ok(PropertyValue::ObjectIdentifier(self.oid))
}
p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(
bacnet_types::enums::EventState::HIGH_LIMIT.to_raw(),
)),
p if p == PropertyIdentifier::EVENT_DETECTION_ENABLE => self
.detection_enable
.map(PropertyValue::Boolean)
.ok_or(Error::protocol(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::UNKNOWN_PROPERTY,
)),
p if p == PropertyIdentifier::EVENT_TYPE => Ok(PropertyValue::Enumerated(
bacnet_types::enums::EventType::OUT_OF_RANGE.to_raw(),
)),
p if p == PropertyIdentifier::NOTIFICATION_CLASS => Ok(PropertyValue::Unsigned(0)),
p if p == PropertyIdentifier::EVENT_ENABLE => Ok(PropertyValue::BitString {
unused_bits: 5,
data: vec![0xe0],
}),
p if p == PropertyIdentifier::ACKED_TRANSITIONS => Ok(PropertyValue::BitString {
unused_bits: 5,
data: vec![0xe0],
}),
p if p == PropertyIdentifier::NOTIFY_TYPE => Ok(PropertyValue::Enumerated(0)),
p if p == PropertyIdentifier::EVENT_TIME_STAMPS => Ok(PropertyValue::List(vec![
PropertyValue::Unsigned(0),
PropertyValue::Unsigned(0),
PropertyValue::Unsigned(0),
])),
_ => Err(Error::protocol(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::UNKNOWN_PROPERTY,
)),
}
}
fn write_property(
&mut self,
_property: PropertyIdentifier,
_array_index: Option<u32>,
_value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
Err(Error::protocol(
bacnet_types::enums::ErrorClass::PROPERTY,
bacnet_types::enums::ErrorCode::WRITE_ACCESS_DENIED,
))
}
fn property_list(&self) -> std::borrow::Cow<'static, [PropertyIdentifier]> {
let mut properties = vec![
PropertyIdentifier::EVENT_STATE,
PropertyIdentifier::EVENT_TYPE,
PropertyIdentifier::NOTIFICATION_CLASS,
PropertyIdentifier::EVENT_ENABLE,
PropertyIdentifier::ACKED_TRANSITIONS,
PropertyIdentifier::NOTIFY_TYPE,
PropertyIdentifier::EVENT_TIME_STAMPS,
];
if self.detection_enable.is_some() {
properties.push(PropertyIdentifier::EVENT_DETECTION_ENABLE);
}
std::borrow::Cow::Owned(properties)
}
fn enrollment_summary_capability_internal(
&self,
) -> Option<bacnet_objects::event::EnrollmentSummaryCapability> {
Some(bacnet_objects::event::EnrollmentSummaryCapability {
event_type: bacnet_types::enums::EventType::OUT_OF_RANGE,
last_transition: Some(bacnet_objects::event::EventTransition::ToOffnormal),
})
}
}
fn db_with_enrollment(detection_enabled: bool) -> ObjectDatabase {
let mut db = ObjectDatabase::new();
db.add(Box::new(DisabledAlarmingObject {
oid: ObjectIdentifier::new(bacnet_types::enums::ObjectType::EVENT_ENROLLMENT, 1).unwrap(),
detection_enable: Some(detection_enabled),
}))
.unwrap();
db.add(Box::new(
bacnet_objects::notification_class::NotificationClass::new(0, "NC-0").unwrap(),
))
.unwrap();
db
}
fn alarm_summary_entry_count(db: &ObjectDatabase) -> usize {
use bacnet_services::alarm_summary::GetAlarmSummaryAck;
let mut buf = BytesMut::new();
handle_get_alarm_summary(db, &mut buf).unwrap();
GetAlarmSummaryAck::decode(&buf).unwrap().entries.len()
}
#[test]
fn get_alarm_summary_excludes_detection_disabled_object() {
assert_eq!(
alarm_summary_entry_count(&db_with_enrollment(true)),
1,
"control: an alarming enrollment is reported when detection is enabled"
);
assert_eq!(
alarm_summary_entry_count(&db_with_enrollment(false)),
0,
"an object with Event_Detection_Enable FALSE shall be ignored"
);
}
#[test]
fn summarization_includes_objects_without_the_property() {
let mut db = ObjectDatabase::new();
let object = DisabledAlarmingObject {
oid: ObjectIdentifier::new(bacnet_types::enums::ObjectType::EVENT_ENROLLMENT, 2).unwrap(),
detection_enable: None,
};
assert!(
object
.read_property(PropertyIdentifier::EVENT_DETECTION_ENABLE, None)
.is_err(),
"fixture must not model Event_Detection_Enable"
);
db.add(Box::new(object)).unwrap();
assert_eq!(
alarm_summary_entry_count(&db),
1,
"an object with no Event_Detection_Enable property must still be reported"
);
}
#[test]
fn get_enrollment_summary_excludes_detection_disabled_object() {
use bacnet_services::enrollment_summary::{
GetEnrollmentSummaryAck, GetEnrollmentSummaryRequest,
};
let count = |db: &ObjectDatabase| {
let request = GetEnrollmentSummaryRequest {
acknowledgment_filter: bacnet_types::enums::AcknowledgmentFilter::ALL,
enrollment_filter: None,
event_state_filter: None,
event_type_filter: None,
priority_filter: None,
notification_class_filter: None,
};
let mut buf = BytesMut::new();
request.encode(&mut buf);
let mut ack = BytesMut::new();
handle_get_enrollment_summary(db, &buf, &mut ack).unwrap();
GetEnrollmentSummaryAck::decode(&ack).unwrap().entries.len()
};
assert_eq!(
count(&db_with_enrollment(true)),
1,
"control: the enrollment is summarized when detection is enabled"
);
assert_eq!(
count(&db_with_enrollment(false)),
0,
"Event_Detection_Enable FALSE excludes the object from the summary"
);
}
#[test]
fn get_event_information_excludes_detection_disabled_object() {
use bacnet_services::alarm_event::GetEventInformationRequest;
let responses = [true, false].map(|enabled| {
let mut db = db_with_enrollment(enabled);
db.add(Box::new(
bacnet_objects::notification_class::NotificationClass::new(0, "NC-0").unwrap(),
))
.unwrap();
let request = GetEventInformationRequest {
last_received_object_identifier: None,
};
let mut buf = BytesMut::new();
request.encode(&mut buf);
let mut ack = BytesMut::new();
handle_get_event_information(&db, &buf, &mut ack).unwrap();
ack.to_vec()
});
assert_ne!(
responses[0], responses[1],
"disabling detection must change the GetEventInformation response"
);
assert!(
responses[1].len() < responses[0].len(),
"the disabled object must be absent from the response, got {} vs {} bytes",
responses[1].len(),
responses[0].len()
);
}