use crate::apdu::encoding::{ApduDecoder, ApduEncoder};
use crate::apdu::types::{ConfirmedService, ErrorClass, ErrorCode};
use crate::object::event_enrollment::{EventEnrollment, EventTransitionBits};
use crate::object::property::{BACnetValue, EventState, PropertyId};
use crate::object::traits::BACnetObject;
use crate::object::types::{ObjectId, ObjectType};
use super::handler::{ConfirmedServiceHandler, ServiceContext, ServiceResult};
#[derive(Debug)]
pub struct AcknowledgeAlarmRequest {
pub acknowledging_process_id: u32,
pub event_object_id: ObjectId,
pub event_state_acknowledged: EventState,
pub time_stamp: u64,
pub acknowledgment_source: String,
pub time_of_acknowledgment: u64,
}
impl AcknowledgeAlarmRequest {
pub fn decode(data: &[u8]) -> Result<Self, &'static str> {
let mut decoder = ApduDecoder::new(data);
let (_, _, len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode tag")?;
let process_id = decoder
.decode_unsigned(len)
.map_err(|_| "Failed to decode process_id")?;
let (_, _, len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode tag")?;
if len != 4 {
return Err("Invalid object identifier length");
}
let event_object_id = decoder
.decode_object_identifier()
.map_err(|_| "Failed to decode object_id")?;
let (_, _, len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode tag")?;
let state_val = decoder
.decode_unsigned(len)
.map_err(|_| "Failed to decode state")?;
let event_state = event_state_from_u32(state_val);
let (_, _, len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode tag")?;
let time_stamp = decoder
.decode_unsigned(len)
.map_err(|_| "Failed to decode timestamp")? as u64;
let (_, _, len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode tag")?;
let ack_source = decoder
.decode_character_string(len)
.map_err(|_| "Failed to decode source")?;
let time_of_ack = if !decoder.is_empty() {
let (_, _, len) = decoder
.decode_tag_info()
.map_err(|_| "Failed to decode tag")?;
decoder
.decode_unsigned(len)
.map_err(|_| "Failed to decode ack_time")? as u64
} else {
0
};
Ok(Self {
acknowledging_process_id: process_id,
event_object_id,
event_state_acknowledged: event_state,
time_stamp,
acknowledgment_source: ack_source,
time_of_acknowledgment: time_of_ack,
})
}
}
pub struct AcknowledgeAlarmHandler;
impl AcknowledgeAlarmHandler {
pub fn new() -> Self {
Self
}
}
impl ConfirmedServiceHandler for AcknowledgeAlarmHandler {
fn service_choice(&self) -> ConfirmedService {
ConfirmedService::AcknowledgeAlarm
}
fn handle(&self, data: &[u8], ctx: &ServiceContext) -> ServiceResult {
let request = match AcknowledgeAlarmRequest::decode(data) {
Ok(r) => r,
Err(_) => return ServiceResult::invalid_tag(),
};
let obj = match ctx.objects.get(&request.event_object_id) {
Some(o) => o,
None => return ServiceResult::unknown_object(),
};
if let Some(ee) = obj.as_any().downcast_ref::<EventEnrollment>() {
let transition_name = match request.event_state_acknowledged {
EventState::Normal => "to-normal",
EventState::Fault => "to-fault",
_ => "to-offnormal",
};
match ee.acknowledge(transition_name) {
Ok(()) => ServiceResult::SimpleAck,
Err(_) => ServiceResult::service_request_denied(),
}
} else {
ServiceResult::Error {
error_class: ErrorClass::Object,
error_code: ErrorCode::UnsupportedObjectType,
}
}
}
fn name(&self) -> &'static str {
"AcknowledgeAlarm"
}
fn min_data_length(&self) -> usize {
8
}
}
#[derive(Debug, Clone)]
pub struct AlarmSummaryEntry {
pub object_id: ObjectId,
pub alarm_state: EventState,
pub acked_transitions: EventTransitionBits,
}
pub struct GetAlarmSummaryHandler;
impl GetAlarmSummaryHandler {
pub fn new() -> Self {
Self
}
}
impl ConfirmedServiceHandler for GetAlarmSummaryHandler {
fn service_choice(&self) -> ConfirmedService {
ConfirmedService::GetAlarmSummary
}
fn handle(&self, _data: &[u8], ctx: &ServiceContext) -> ServiceResult {
let mut encoder = ApduEncoder::new();
let mut count = 0u32;
for obj in ctx.objects.iter() {
let flags = obj.status_flags();
if flags.in_alarm {
let obj_id = obj.object_identifier();
let event_state = obj
.read_property(PropertyId::EventState)
.ok()
.and_then(|v| v.as_unsigned())
.map(event_state_from_u32)
.unwrap_or(EventState::Offnormal);
let acked = obj
.read_property(PropertyId::AckedTransitions)
.ok()
.and_then(|v| {
if let BACnetValue::BitString(bits) = v {
Some(EventTransitionBits::from_bits(&bits))
} else {
None
}
})
.unwrap_or(EventTransitionBits::all());
encoder.encode_context_object_identifier(0, obj_id);
encoder.encode_context_enumerated(1, event_state as u32);
encoder.encode_context_bit_string(2, &acked.to_bits());
count += 1;
}
}
if count == 0 {
ServiceResult::ComplexAck(encoder.into_bytes())
} else {
ServiceResult::ComplexAck(encoder.into_bytes())
}
}
fn name(&self) -> &'static str {
"GetAlarmSummary"
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum AcknowledgmentFilter {
All = 0,
Acked = 1,
NotAcked = 2,
}
pub struct GetEnrollmentSummaryHandler;
impl GetEnrollmentSummaryHandler {
pub fn new() -> Self {
Self
}
}
impl ConfirmedServiceHandler for GetEnrollmentSummaryHandler {
fn service_choice(&self) -> ConfirmedService {
ConfirmedService::GetEnrollmentSummary
}
fn handle(&self, data: &[u8], ctx: &ServiceContext) -> ServiceResult {
let ack_filter = if !data.is_empty() {
let mut decoder = ApduDecoder::new(data);
if let Ok((_, _, len)) = decoder.decode_tag_info() {
let val = decoder.decode_unsigned(len).unwrap_or(0);
match val {
1 => AcknowledgmentFilter::Acked,
2 => AcknowledgmentFilter::NotAcked,
_ => AcknowledgmentFilter::All,
}
} else {
AcknowledgmentFilter::All
}
} else {
AcknowledgmentFilter::All
};
let mut encoder = ApduEncoder::new();
for obj in ctx.objects.iter() {
if obj.object_identifier().object_type != ObjectType::EventEnrollment {
continue;
}
if let Some(ee) = obj.as_any().downcast_ref::<EventEnrollment>() {
let acked = ee.acked_transitions();
let all_acked = acked.to_offnormal && acked.to_fault && acked.to_normal;
let any_unacked = !acked.to_offnormal || !acked.to_fault || !acked.to_normal;
let include = match ack_filter {
AcknowledgmentFilter::All => true,
AcknowledgmentFilter::Acked => all_acked,
AcknowledgmentFilter::NotAcked => any_unacked,
};
if include {
let obj_id = ee.object_identifier();
let event_type = ee.event_type();
let event_state = ee.event_state();
let nc = ee.notification_class();
encoder.encode_context_object_identifier(0, obj_id);
encoder.encode_context_enumerated(1, event_type as u32);
encoder.encode_context_enumerated(2, event_state as u32);
encoder.encode_context_unsigned(3, 0); encoder.encode_context_unsigned(4, nc);
}
}
}
ServiceResult::ComplexAck(encoder.into_bytes())
}
fn name(&self) -> &'static str {
"GetEnrollmentSummary"
}
}
pub struct GetEventInformationHandler;
impl GetEventInformationHandler {
pub fn new() -> Self {
Self
}
}
impl ConfirmedServiceHandler for GetEventInformationHandler {
fn service_choice(&self) -> ConfirmedService {
ConfirmedService::GetEventInformation
}
fn handle(&self, data: &[u8], ctx: &ServiceContext) -> ServiceResult {
let last_obj = if !data.is_empty() {
let mut decoder = ApduDecoder::new(data);
if let Ok((_, _, len)) = decoder.decode_tag_info() {
if len == 4 {
decoder.decode_object_identifier().ok()
} else {
None
}
} else {
None
}
} else {
None
};
let mut encoder = ApduEncoder::new();
encoder.encode_opening_tag(0);
let mut found_last = last_obj.is_none();
let mut count = 0u32;
for obj in ctx.objects.iter() {
let obj_id = obj.object_identifier();
if !found_last {
if Some(obj_id) == last_obj {
found_last = true;
}
continue;
}
let event_state = obj
.read_property(PropertyId::EventState)
.ok()
.and_then(|v| v.as_unsigned())
.map(event_state_from_u32)
.unwrap_or(EventState::Normal);
if event_state == EventState::Normal {
continue;
}
encoder.encode_context_object_identifier(0, obj_id);
encoder.encode_context_enumerated(1, event_state as u32);
let acked = obj
.read_property(PropertyId::AckedTransitions)
.ok()
.and_then(|v| {
if let BACnetValue::BitString(bits) = v {
Some(bits)
} else {
None
}
})
.unwrap_or_else(|| vec![true, true, true]);
encoder.encode_context_bit_string(2, &acked);
encoder.encode_opening_tag(3);
if let Ok(BACnetValue::Array(ts)) = obj.read_property(PropertyId::EventTimeStamps) {
for t in &ts {
if let Some(v) = t.as_unsigned() {
encoder.encode_unsigned(v);
} else if let BACnetValue::Unsigned64(v) = t {
encoder.encode_unsigned(*v as u32);
} else {
encoder.encode_unsigned(0);
}
}
}
encoder.encode_closing_tag(3);
let notify_type = obj
.read_property(PropertyId::NotifyType)
.ok()
.and_then(|v| v.as_unsigned())
.unwrap_or(0);
encoder.encode_context_enumerated(4, notify_type);
let event_enable = obj
.read_property(PropertyId::EventEnable)
.ok()
.and_then(|v| {
if let BACnetValue::BitString(bits) = v {
Some(bits)
} else {
None
}
})
.unwrap_or_else(|| vec![true, true, true]);
encoder.encode_context_bit_string(5, &event_enable);
count += 1;
if count >= 20 {
break;
}
}
encoder.encode_closing_tag(0);
let more_events = count >= 20;
encoder.encode_context_unsigned(1, if more_events { 1 } else { 0 });
ServiceResult::ComplexAck(encoder.into_bytes())
}
fn name(&self) -> &'static str {
"GetEventInformation"
}
}
pub struct ConfirmedEventNotificationHandler;
impl ConfirmedEventNotificationHandler {
pub fn new() -> Self {
Self
}
}
impl ConfirmedServiceHandler for ConfirmedEventNotificationHandler {
fn service_choice(&self) -> ConfirmedService {
ConfirmedService::ConfirmedEventNotification
}
fn handle(&self, _data: &[u8], _ctx: &ServiceContext) -> ServiceResult {
ServiceResult::SimpleAck
}
fn name(&self) -> &'static str {
"ConfirmedEventNotification"
}
}
fn event_state_from_u32(v: u32) -> EventState {
match v {
0 => EventState::Normal,
1 => EventState::Fault,
2 => EventState::Offnormal,
3 => EventState::HighLimit,
4 => EventState::LowLimit,
5 => EventState::LifeSafetyAlarm,
_ => EventState::Offnormal,
}
}
pub fn encode_event_notification(
notification: &crate::object::event_enrollment::EventNotification,
) -> Vec<u8> {
let mut encoder = ApduEncoder::new();
encoder.encode_context_unsigned(0, notification.process_id);
encoder.encode_context_object_identifier(1, notification.initiating_device);
encoder.encode_context_object_identifier(2, notification.event_object);
encoder.encode_context_unsigned(3, notification.time_stamp as u32);
encoder.encode_context_unsigned(4, notification.notification_class);
encoder.encode_context_unsigned(5, notification.priority as u32);
encoder.encode_context_enumerated(6, notification.event_type as u32);
if let Some(ref text) = notification.message_text {
encoder.encode_context_character_string(7, text);
}
encoder.encode_context_enumerated(8, notification.notify_type as u32);
encoder.encode_context_unsigned(9, if notification.ack_required { 1 } else { 0 });
encoder.encode_context_enumerated(10, notification.from_state as u32);
encoder.encode_context_enumerated(11, notification.to_state as u32);
encoder.into_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::object::event_enrollment::{EventEnrollment, EventType};
use crate::object::registry::ObjectRegistry;
use std::sync::Arc;
fn make_ctx(registry: &Arc<ObjectRegistry>) -> ServiceContext {
ServiceContext {
objects: registry.clone(),
device_instance: 1234,
invoke_id: Some(1),
max_apdu_length: 1476,
source_address: None,
}
}
#[test]
fn test_handler_creation() {
assert_eq!(
AcknowledgeAlarmHandler::new().service_choice(),
ConfirmedService::AcknowledgeAlarm
);
assert_eq!(
GetAlarmSummaryHandler::new().service_choice(),
ConfirmedService::GetAlarmSummary
);
assert_eq!(
GetEnrollmentSummaryHandler::new().service_choice(),
ConfirmedService::GetEnrollmentSummary
);
assert_eq!(
GetEventInformationHandler::new().service_choice(),
ConfirmedService::GetEventInformation
);
assert_eq!(
ConfirmedEventNotificationHandler::new().service_choice(),
ConfirmedService::ConfirmedEventNotification
);
}
#[test]
fn test_get_alarm_summary_empty() {
let registry = Arc::new(ObjectRegistry::new());
let ctx = make_ctx(®istry);
let handler = GetAlarmSummaryHandler::new();
match handler.handle(&[], &ctx) {
ServiceResult::ComplexAck(data) => {
assert!(data.is_empty()); }
other => panic!("Expected ComplexAck, got {:?}", other),
}
}
#[test]
fn test_get_alarm_summary_with_alarm() {
let registry = Arc::new(ObjectRegistry::new());
let ee = EventEnrollment::new(1, "TempAlarm", EventType::OutOfRange)
.with_high_limit(80.0)
.with_low_limit(20.0);
ee.transition_to(EventState::HighLimit, 1000);
registry.register(Arc::new(ee));
let ctx = make_ctx(®istry);
let handler = GetAlarmSummaryHandler::new();
match handler.handle(&[], &ctx) {
ServiceResult::ComplexAck(data) => {
assert!(!data.is_empty());
}
other => panic!("Expected ComplexAck, got {:?}", other),
}
}
#[test]
fn test_get_enrollment_summary() {
let registry = Arc::new(ObjectRegistry::new());
let ee1 = EventEnrollment::new(1, "Alarm1", EventType::OutOfRange);
let ee2 = EventEnrollment::new(2, "Alarm2", EventType::ChangeOfState);
registry.register(Arc::new(ee1));
registry.register(Arc::new(ee2));
let ctx = make_ctx(®istry);
let handler = GetEnrollmentSummaryHandler::new();
match handler.handle(&[], &ctx) {
ServiceResult::ComplexAck(data) => {
assert!(!data.is_empty());
}
other => panic!("Expected ComplexAck, got {:?}", other),
}
}
#[test]
fn test_get_event_information_no_alarms() {
let registry = Arc::new(ObjectRegistry::new());
let ee = EventEnrollment::new(1, "NormalAlarm", EventType::OutOfRange);
registry.register(Arc::new(ee));
let ctx = make_ctx(®istry);
let handler = GetEventInformationHandler::new();
match handler.handle(&[], &ctx) {
ServiceResult::ComplexAck(data) => {
assert!(!data.is_empty());
}
other => panic!("Expected ComplexAck, got {:?}", other),
}
}
#[test]
fn test_get_event_information_with_alarm() {
let registry = Arc::new(ObjectRegistry::new());
let ee = EventEnrollment::new(1, "ActiveAlarm", EventType::OutOfRange);
ee.transition_to(EventState::HighLimit, 5000);
registry.register(Arc::new(ee));
let ctx = make_ctx(®istry);
let handler = GetEventInformationHandler::new();
match handler.handle(&[], &ctx) {
ServiceResult::ComplexAck(data) => {
assert!(!data.is_empty());
}
other => panic!("Expected ComplexAck, got {:?}", other),
}
}
#[test]
fn test_confirmed_event_notification_ack() {
let registry = Arc::new(ObjectRegistry::new());
let ctx = make_ctx(®istry);
let handler = ConfirmedEventNotificationHandler::new();
match handler.handle(&[], &ctx) {
ServiceResult::SimpleAck => {} other => panic!("Expected SimpleAck, got {:?}", other),
}
}
#[test]
fn test_encode_event_notification() {
use crate::object::event_enrollment::{EventNotification, NotifyType};
let notification = EventNotification {
destination: "192.168.1.100:47808".parse().unwrap(),
process_id: 1,
initiating_device: ObjectId::new(ObjectType::Device, 1234),
event_object: ObjectId::new(ObjectType::EventEnrollment, 1),
time_stamp: 5000,
notification_class: 1,
priority: 3,
event_type: EventType::OutOfRange,
message_text: Some("Temperature too high".to_string()),
notify_type: NotifyType::Alarm,
ack_required: true,
from_state: EventState::Normal,
to_state: EventState::HighLimit,
confirmed: false,
};
let encoded = encode_event_notification(¬ification);
assert!(!encoded.is_empty());
}
#[test]
fn test_event_state_from_u32() {
assert_eq!(event_state_from_u32(0), EventState::Normal);
assert_eq!(event_state_from_u32(1), EventState::Fault);
assert_eq!(event_state_from_u32(2), EventState::Offnormal);
assert_eq!(event_state_from_u32(3), EventState::HighLimit);
assert_eq!(event_state_from_u32(4), EventState::LowLimit);
assert_eq!(event_state_from_u32(5), EventState::LifeSafetyAlarm);
assert_eq!(event_state_from_u32(99), EventState::Offnormal); }
}