use bacnet_encoding::primitives;
use bacnet_encoding::tags;
use bacnet_types::error::Error;
use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier};
use bytes::BytesMut;
use crate::common::PropertyReference;
#[derive(Debug, Clone, PartialEq)]
pub struct AuditNotificationRequest {
pub source_timestamp: BACnetTimeStamp,
pub target_timestamp: Option<BACnetTimeStamp>,
pub source_device: Vec<u8>,
pub source_object: Option<ObjectIdentifier>,
pub operation: u32,
pub source_comment: Option<String>,
pub target_comment: Option<String>,
pub invoke_id: Option<u8>,
pub source_user_info: Option<Vec<u8>>,
pub target_device: Vec<u8>,
pub target_object: Option<ObjectIdentifier>,
pub target_property: Option<PropertyReference>,
pub target_priority: Option<u8>,
pub target_value: Option<Vec<u8>>,
pub current_value: Option<Vec<u8>>,
pub result: Option<Vec<u8>>,
}
impl AuditNotificationRequest {
pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
primitives::encode_timestamp(buf, 0, &self.source_timestamp);
if let Some(ref ts) = self.target_timestamp {
primitives::encode_timestamp(buf, 1, ts);
}
tags::encode_opening_tag(buf, 2);
buf.extend_from_slice(&self.source_device);
tags::encode_closing_tag(buf, 2);
if let Some(ref oid) = self.source_object {
primitives::encode_ctx_object_id(buf, 3, oid);
}
primitives::encode_ctx_enumerated(buf, 4, self.operation);
if let Some(ref s) = self.source_comment {
primitives::encode_ctx_character_string(buf, 5, s)?;
}
if let Some(ref s) = self.target_comment {
primitives::encode_ctx_character_string(buf, 6, s)?;
}
if let Some(id) = self.invoke_id {
primitives::encode_ctx_unsigned(buf, 7, id as u64);
}
if let Some(ref raw) = self.source_user_info {
tags::encode_opening_tag(buf, 8);
buf.extend_from_slice(raw);
tags::encode_closing_tag(buf, 8);
}
tags::encode_opening_tag(buf, 9);
buf.extend_from_slice(&self.target_device);
tags::encode_closing_tag(buf, 9);
if let Some(ref oid) = self.target_object {
primitives::encode_ctx_object_id(buf, 10, oid);
}
if let Some(ref pr) = self.target_property {
tags::encode_opening_tag(buf, 11);
pr.encode(buf);
tags::encode_closing_tag(buf, 11);
}
if let Some(prio) = self.target_priority {
primitives::encode_ctx_unsigned(buf, 12, prio as u64);
}
if let Some(ref raw) = self.target_value {
tags::encode_opening_tag(buf, 13);
buf.extend_from_slice(raw);
tags::encode_closing_tag(buf, 13);
}
if let Some(ref raw) = self.current_value {
tags::encode_opening_tag(buf, 14);
buf.extend_from_slice(raw);
tags::encode_closing_tag(buf, 14);
}
if let Some(ref raw) = self.result {
tags::encode_opening_tag(buf, 15);
buf.extend_from_slice(raw);
tags::encode_closing_tag(buf, 15);
}
Ok(())
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let mut offset = 0;
let (source_timestamp, new_off) = primitives::decode_timestamp(data, offset, 0)?;
offset = new_off;
let mut target_timestamp = None;
if offset < data.len() {
let (peek, _) = tags::decode_tag(data, offset)?;
if peek.is_opening_tag(1) {
let (ts, new_off) = primitives::decode_timestamp(data, offset, 1)?;
target_timestamp = Some(ts);
offset = new_off;
}
}
let (tag, tag_end) = tags::decode_tag(data, offset)?;
if !tag.is_opening_tag(2) {
return Err(Error::decoding(
offset,
"AuditNotification expected opening tag 2 for source-device",
));
}
let (raw, new_off) = tags::extract_context_value(data, tag_end, 2)?;
let source_device = raw.to_vec();
offset = new_off;
let mut source_object = None;
if offset < data.len() {
let (opt, new_off) = tags::decode_optional_context(data, offset, 3)?;
if let Some(content) = opt {
source_object = Some(ObjectIdentifier::decode(content)?);
offset = new_off;
}
}
let (tag, pos) = tags::decode_tag(data, offset)?;
let end = pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
pos,
"AuditNotification truncated at operation",
));
}
let operation = primitives::decode_unsigned(&data[pos..end])? as u32;
offset = end;
let mut source_comment = None;
if offset < data.len() {
let (opt, new_off) = tags::decode_optional_context(data, offset, 5)?;
if let Some(content) = opt {
source_comment = Some(primitives::decode_character_string(content)?);
offset = new_off;
}
}
let mut target_comment = None;
if offset < data.len() {
let (opt, new_off) = tags::decode_optional_context(data, offset, 6)?;
if let Some(content) = opt {
target_comment = Some(primitives::decode_character_string(content)?);
offset = new_off;
}
}
let mut invoke_id = None;
if offset < data.len() {
let (opt, new_off) = tags::decode_optional_context(data, offset, 7)?;
if let Some(content) = opt {
invoke_id = Some(primitives::decode_unsigned(content)? as u8);
offset = new_off;
}
}
let mut source_user_info = None;
if offset < data.len() {
let (peek, _) = tags::decode_tag(data, offset)?;
if peek.is_opening_tag(8) {
let (_, inner_start) = tags::decode_tag(data, offset)?;
let (raw, new_off) = tags::extract_context_value(data, inner_start, 8)?;
source_user_info = Some(raw.to_vec());
offset = new_off;
}
}
let (tag, tag_end) = tags::decode_tag(data, offset)?;
if !tag.is_opening_tag(9) {
return Err(Error::decoding(
offset,
"AuditNotification expected opening tag 9 for target-device",
));
}
let (raw, new_off) = tags::extract_context_value(data, tag_end, 9)?;
let target_device = raw.to_vec();
offset = new_off;
let mut target_object = None;
if offset < data.len() {
let (opt, new_off) = tags::decode_optional_context(data, offset, 10)?;
if let Some(content) = opt {
target_object = Some(ObjectIdentifier::decode(content)?);
offset = new_off;
}
}
let mut target_property = None;
if offset < data.len() {
let (peek, _) = tags::decode_tag(data, offset)?;
if peek.is_opening_tag(11) {
let (_, inner_start) = tags::decode_tag(data, offset)?;
let (pr, pr_end) = PropertyReference::decode(data, inner_start)?;
target_property = Some(pr);
let (_tag, tag_end) = tags::decode_tag(data, pr_end)?;
offset = tag_end;
}
}
let mut target_priority = None;
if offset < data.len() {
let (opt, new_off) = tags::decode_optional_context(data, offset, 12)?;
if let Some(content) = opt {
target_priority = Some(primitives::decode_unsigned(content)? as u8);
offset = new_off;
}
}
let mut target_value = None;
if offset < data.len() {
let (peek, _) = tags::decode_tag(data, offset)?;
if peek.is_opening_tag(13) {
let (_, inner_start) = tags::decode_tag(data, offset)?;
let (raw, new_off) = tags::extract_context_value(data, inner_start, 13)?;
target_value = Some(raw.to_vec());
offset = new_off;
}
}
let mut current_value = None;
if offset < data.len() {
let (peek, _) = tags::decode_tag(data, offset)?;
if peek.is_opening_tag(14) {
let (_, inner_start) = tags::decode_tag(data, offset)?;
let (raw, new_off) = tags::extract_context_value(data, inner_start, 14)?;
current_value = Some(raw.to_vec());
offset = new_off;
}
}
let mut result = None;
if offset < data.len() {
let (peek, _) = tags::decode_tag(data, offset)?;
if peek.is_opening_tag(15) {
let (_, inner_start) = tags::decode_tag(data, offset)?;
let (raw, new_off) = tags::extract_context_value(data, inner_start, 15)?;
result = Some(raw.to_vec());
offset = new_off;
}
}
let _ = offset;
Ok(Self {
source_timestamp,
target_timestamp,
source_device,
source_object,
operation,
source_comment,
target_comment,
invoke_id,
source_user_info,
target_device,
target_object,
target_property,
target_priority,
target_value,
current_value,
result,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuditLogQueryRequest {
pub acknowledgment_filter: u32,
pub query_options_raw: Vec<u8>,
}
impl AuditLogQueryRequest {
pub fn encode(&self, buf: &mut BytesMut) {
primitives::encode_ctx_enumerated(buf, 0, self.acknowledgment_filter);
buf.extend_from_slice(&self.query_options_raw);
}
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,
"AuditLogQuery truncated at acknowledgment-filter",
));
}
let acknowledgment_filter = primitives::decode_unsigned(&data[pos..end])? as u32;
offset = end;
let query_options_raw = data[offset..].to_vec();
Ok(Self {
acknowledgment_filter,
query_options_raw,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use bacnet_types::enums::{ObjectType, PropertyIdentifier};
use bacnet_types::primitives::Time;
#[test]
fn audit_notification_round_trip() {
let req = AuditNotificationRequest {
source_timestamp: BACnetTimeStamp::SequenceNumber(100),
target_timestamp: None,
source_device: vec![0x09, 0x01], source_object: Some(ObjectIdentifier::new(ObjectType::DEVICE, 1).unwrap()),
operation: 3,
source_comment: Some("test audit".to_string()),
target_comment: None,
invoke_id: Some(5),
source_user_info: None,
target_device: vec![0x09, 0x02], target_object: Some(ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap()),
target_property: Some(PropertyReference {
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: None,
}),
target_priority: Some(8),
target_value: Some(vec![0x44, 0x42, 0x90, 0x00, 0x00]),
current_value: Some(vec![0x44, 0x00, 0x00, 0x00, 0x00]),
result: None,
};
let mut buf = BytesMut::new();
req.encode(&mut buf).unwrap();
let decoded = AuditNotificationRequest::decode(&buf).unwrap();
assert_eq!(req, decoded);
}
#[test]
fn audit_notification_minimal() {
let req = AuditNotificationRequest {
source_timestamp: BACnetTimeStamp::Time(Time {
hour: 10,
minute: 0,
second: 0,
hundredths: 0,
}),
target_timestamp: None,
source_device: vec![0x09, 0x01],
source_object: None,
operation: 0,
source_comment: None,
target_comment: None,
invoke_id: None,
source_user_info: None,
target_device: vec![0x09, 0x02],
target_object: None,
target_property: None,
target_priority: None,
target_value: None,
current_value: None,
result: None,
};
let mut buf = BytesMut::new();
req.encode(&mut buf).unwrap();
let decoded = AuditNotificationRequest::decode(&buf).unwrap();
assert_eq!(req, decoded);
}
#[test]
fn audit_notification_empty_input() {
assert!(AuditNotificationRequest::decode(&[]).is_err());
}
#[test]
fn audit_log_query_round_trip() {
let req = AuditLogQueryRequest {
acknowledgment_filter: 1,
query_options_raw: vec![0x19, 0x05, 0x29, 0x0A],
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
let decoded = AuditLogQueryRequest::decode(&buf).unwrap();
assert_eq!(req, decoded);
}
#[test]
fn audit_log_query_no_options() {
let req = AuditLogQueryRequest {
acknowledgment_filter: 0,
query_options_raw: vec![],
};
let mut buf = BytesMut::new();
req.encode(&mut buf);
let decoded = AuditLogQueryRequest::decode(&buf).unwrap();
assert_eq!(req, decoded);
}
#[test]
fn audit_log_query_empty_input() {
assert!(AuditLogQueryRequest::decode(&[]).is_err());
}
}