use bacnet_encoding::primitives;
use bacnet_encoding::tags;
use bacnet_types::enums::{EventState, EventType};
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use bytes::BytesMut;
use crate::common::MAX_DECODED_ITEMS;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PriorityFilter {
pub min_priority: u8,
pub max_priority: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetEnrollmentSummaryRequest {
pub acknowledgment_filter: u32,
pub event_state_filter: Option<EventState>,
pub event_type_filter: Option<EventType>,
pub priority_filter: Option<PriorityFilter>,
pub notification_class_filter: Option<u16>,
}
impl GetEnrollmentSummaryRequest {
pub fn encode(&self, buf: &mut BytesMut) {
primitives::encode_ctx_enumerated(buf, 0, self.acknowledgment_filter);
if let Some(es) = self.event_state_filter {
primitives::encode_ctx_enumerated(buf, 2, es.to_raw());
}
if let Some(et) = self.event_type_filter {
primitives::encode_ctx_enumerated(buf, 3, et.to_raw());
}
if let Some(pf) = self.priority_filter {
tags::encode_opening_tag(buf, 4);
primitives::encode_ctx_unsigned(buf, 0, pf.min_priority as u64);
primitives::encode_ctx_unsigned(buf, 1, pf.max_priority as u64);
tags::encode_closing_tag(buf, 4);
}
if let Some(nc) = self.notification_class_filter {
primitives::encode_ctx_unsigned(buf, 5, nc as u64);
}
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let mut offset = 0;
let (tag, pos) = tags::decode_tag(data, offset)?;
let end = pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
pos,
"EnrollmentSummary truncated at acknowledgmentFilter",
));
}
let acknowledgment_filter = primitives::decode_unsigned(&data[pos..end])? as u32;
offset = end;
if offset < data.len() {
let (tag, tag_end) = tags::decode_tag(data, offset)?;
if tag.is_opening_tag(1) {
let (_, new_offset) = tags::extract_context_value(data, tag_end, 1)?;
offset = new_offset;
}
}
let mut event_state_filter = None;
let (opt_data, new_offset) = tags::decode_optional_context(data, offset, 2)?;
if let Some(content) = opt_data {
event_state_filter = Some(EventState::from_raw(
primitives::decode_unsigned(content)? as u32
));
offset = new_offset;
}
let mut event_type_filter = None;
let (opt_data, new_offset) = tags::decode_optional_context(data, offset, 3)?;
if let Some(content) = opt_data {
event_type_filter = Some(EventType::from_raw(
primitives::decode_unsigned(content)? as u32
));
offset = new_offset;
}
let mut priority_filter = None;
if offset < data.len() {
let (tag, tag_end) = tags::decode_tag(data, offset)?;
if tag.is_opening_tag(4) {
let (inner_tag, inner_pos) = tags::decode_tag(data, tag_end)?;
let inner_end = inner_pos + inner_tag.length as usize;
if inner_end > data.len() {
return Err(Error::decoding(
inner_pos,
"EnrollmentSummary truncated at minPriority",
));
}
let min_priority = primitives::decode_unsigned(&data[inner_pos..inner_end])? as u8;
let (inner_tag, inner_pos) = tags::decode_tag(data, inner_end)?;
let inner_end = inner_pos + inner_tag.length as usize;
if inner_end > data.len() {
return Err(Error::decoding(
inner_pos,
"EnrollmentSummary truncated at maxPriority",
));
}
let max_priority = primitives::decode_unsigned(&data[inner_pos..inner_end])? as u8;
let (close_tag, close_end) = tags::decode_tag(data, inner_end)?;
if !close_tag.is_closing_tag(4) {
return Err(Error::decoding(
inner_end,
"EnrollmentSummary expected closing tag 4",
));
}
priority_filter = Some(PriorityFilter {
min_priority,
max_priority,
});
offset = close_end;
}
}
let mut notification_class_filter = None;
if offset < data.len() {
let (opt_data, _new_offset) = tags::decode_optional_context(data, offset, 5)?;
if let Some(content) = opt_data {
notification_class_filter = Some(primitives::decode_unsigned(content)? as u16);
}
}
Ok(Self {
acknowledgment_filter,
event_state_filter,
event_type_filter,
priority_filter,
notification_class_filter,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnrollmentSummaryEntry {
pub object_identifier: ObjectIdentifier,
pub event_type: EventType,
pub event_state: EventState,
pub priority: u8,
pub notification_class: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetEnrollmentSummaryAck {
pub entries: Vec<EnrollmentSummaryEntry>,
}
impl GetEnrollmentSummaryAck {
pub fn encode(&self, buf: &mut BytesMut) {
for entry in &self.entries {
primitives::encode_app_object_id(buf, &entry.object_identifier);
primitives::encode_app_enumerated(buf, entry.event_type.to_raw());
primitives::encode_app_enumerated(buf, entry.event_state.to_raw());
primitives::encode_app_unsigned(buf, entry.priority as u64);
primitives::encode_app_unsigned(buf, entry.notification_class as u64);
}
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let mut entries = Vec::new();
let mut offset = 0;
while offset < data.len() {
if entries.len() >= MAX_DECODED_ITEMS {
return Err(Error::decoding(
offset,
"EnrollmentSummaryAck too many entries",
));
}
let (tag, pos) = tags::decode_tag(data, offset)?;
let end = pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
pos,
"EnrollmentSummaryAck truncated at object-id",
));
}
let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
offset = end;
let (tag, pos) = tags::decode_tag(data, offset)?;
let end = pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
pos,
"EnrollmentSummaryAck truncated at eventType",
));
}
let event_type =
EventType::from_raw(primitives::decode_unsigned(&data[pos..end])? as u32);
offset = end;
let (tag, pos) = tags::decode_tag(data, offset)?;
let end = pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
pos,
"EnrollmentSummaryAck truncated at eventState",
));
}
let event_state =
EventState::from_raw(primitives::decode_unsigned(&data[pos..end])? as u32);
offset = end;
let (tag, pos) = tags::decode_tag(data, offset)?;
let end = pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
pos,
"EnrollmentSummaryAck truncated at priority",
));
}
let priority = primitives::decode_unsigned(&data[pos..end])? as u8;
offset = end;
let (tag, pos) = tags::decode_tag(data, offset)?;
let end = pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
pos,
"EnrollmentSummaryAck truncated at notificationClass",
));
}
let notification_class = primitives::decode_unsigned(&data[pos..end])? as u16;
offset = end;
entries.push(EnrollmentSummaryEntry {
object_identifier,
event_type,
event_state,
priority,
notification_class,
});
}
Ok(Self { entries })
}
}
#[cfg(test)]
mod tests {
use super::*;
use bacnet_types::enums::ObjectType;
#[test]
fn request_round_trip() {
let req = GetEnrollmentSummaryRequest {
acknowledgment_filter: 0, event_state_filter: Some(EventState::OFFNORMAL),
event_type_filter: None,
priority_filter: Some(PriorityFilter {
min_priority: 1,
max_priority: 10,
}),
notification_class_filter: Some(5),
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
let decoded = GetEnrollmentSummaryRequest::decode(&buf).unwrap();
assert_eq!(req, decoded);
}
#[test]
fn request_minimal_round_trip() {
let req = GetEnrollmentSummaryRequest {
acknowledgment_filter: 2, event_state_filter: None,
event_type_filter: None,
priority_filter: None,
notification_class_filter: None,
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
let decoded = GetEnrollmentSummaryRequest::decode(&buf).unwrap();
assert_eq!(req, decoded);
}
#[test]
fn ack_round_trip() {
let ack = GetEnrollmentSummaryAck {
entries: vec![
EnrollmentSummaryEntry {
object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
event_type: EventType::OUT_OF_RANGE,
event_state: EventState::HIGH_LIMIT,
priority: 3,
notification_class: 10,
},
EnrollmentSummaryEntry {
object_identifier: ObjectIdentifier::new(ObjectType::BINARY_INPUT, 5).unwrap(),
event_type: EventType::CHANGE_OF_STATE,
event_state: EventState::NORMAL,
priority: 7,
notification_class: 20,
},
],
};
let mut buf = BytesMut::new();
ack.encode(&mut buf);
let decoded = GetEnrollmentSummaryAck::decode(&buf).unwrap();
assert_eq!(ack, decoded);
}
#[test]
fn ack_empty_round_trip() {
let ack = GetEnrollmentSummaryAck { entries: vec![] };
let mut buf = BytesMut::new();
ack.encode(&mut buf);
let decoded = GetEnrollmentSummaryAck::decode(&buf).unwrap();
assert_eq!(ack, decoded);
}
#[test]
fn test_decode_request_empty_input() {
assert!(GetEnrollmentSummaryRequest::decode(&[]).is_err());
}
#[test]
fn test_decode_request_truncated_1_byte() {
let req = GetEnrollmentSummaryRequest {
acknowledgment_filter: 0,
event_state_filter: Some(EventState::FAULT),
event_type_filter: None,
priority_filter: None,
notification_class_filter: None,
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
assert!(GetEnrollmentSummaryRequest::decode(&buf[..1]).is_err());
}
#[test]
fn test_decode_request_invalid_tag() {
assert!(GetEnrollmentSummaryRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
}
#[test]
fn test_decode_ack_truncated_1_byte() {
let ack = GetEnrollmentSummaryAck {
entries: vec![EnrollmentSummaryEntry {
object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
event_type: EventType::OUT_OF_RANGE,
event_state: EventState::HIGH_LIMIT,
priority: 3,
notification_class: 10,
}],
};
let mut buf = BytesMut::new();
ack.encode(&mut buf);
assert!(GetEnrollmentSummaryAck::decode(&buf[..1]).is_err());
}
#[test]
fn test_decode_ack_truncated_half() {
let ack = GetEnrollmentSummaryAck {
entries: vec![EnrollmentSummaryEntry {
object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
event_type: EventType::OUT_OF_RANGE,
event_state: EventState::HIGH_LIMIT,
priority: 3,
notification_class: 10,
}],
};
let mut buf = BytesMut::new();
ack.encode(&mut buf);
let half = buf.len() / 2;
assert!(GetEnrollmentSummaryAck::decode(&buf[..half]).is_err());
}
#[test]
fn test_decode_ack_invalid_tag() {
assert!(GetEnrollmentSummaryAck::decode(&[0xFF, 0xFF, 0xFF]).is_err());
}
}