use super::*;
use bacnet_types::primitives::{Date, Time};
fn ts_time() -> BACnetTimeStamp {
BACnetTimeStamp::Time(Time {
hour: 14,
minute: 30,
second: 45,
hundredths: 50,
})
}
fn ts_datetime() -> BACnetTimeStamp {
BACnetTimeStamp::DateTime {
date: Date {
year: 126, month: 2,
day: 28,
day_of_week: 6,
},
time: Time {
hour: 10,
minute: 15,
second: 0,
hundredths: 0,
},
}
}
fn ack_with_timestamps(event_timestamps: [BACnetTimeStamp; 3]) -> GetEventInformationAck {
GetEventInformationAck {
list_of_event_summaries: vec![EventSummary {
object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
event_state: EventState::HIGH_LIMIT,
acknowledged_transitions: EventTransitionBits::TO_OFFNORMAL
| EventTransitionBits::TO_NORMAL,
event_timestamps,
notify_type: NotifyType::ALARM,
event_enable: EventTransitionBits::all(),
event_priorities: [3, 3, 3],
}],
more_events: true,
}
}
#[test]
fn ack_timestamps_all_three_alternatives_golden() {
let ack = ack_with_timestamps([
ts_time(),
BACnetTimeStamp::SequenceNumber(42),
ts_datetime(),
]);
let mut buf = BytesMut::new();
ack.encode(&mut buf).unwrap();
assert_eq!(
buf.as_ref(),
&[
0x0E, 0x0C, 0x00, 0x00, 0x00, 0x01, 0x19, 0x03, 0x2A, 0x05, 0xA0, 0x3E, 0x0C, 0x0E, 0x1E, 0x2D, 0x32, 0x19, 0x2A, 0x2E, 0xA4, 0x7E, 0x02, 0x1C, 0x06, 0xB4, 0x0A, 0x0F, 0x00, 0x00, 0x2F, 0x3F, 0x49, 0x00, 0x5A, 0x05, 0xE0, 0x6E, 0x21, 0x03, 0x21, 0x03, 0x21, 0x03, 0x6F, 0x0F, 0x19, 0x01, ]
);
let decoded = GetEventInformationAck::decode(&buf).unwrap();
let s = &decoded.list_of_event_summaries[0];
assert_eq!(s.event_timestamps[0], ts_time());
assert_eq!(s.event_timestamps[1], BACnetTimeStamp::SequenceNumber(42));
assert_eq!(s.event_timestamps[2], ts_datetime());
assert!(decoded.more_events);
}
#[test]
fn ack_timestamps_round_trip_each_alternative() {
for ts in [ts_time(), BACnetTimeStamp::SequenceNumber(0), ts_datetime()] {
let ack = ack_with_timestamps([ts.clone(), ts.clone(), ts]);
let mut buf = BytesMut::new();
ack.encode(&mut buf).unwrap();
let decoded = GetEventInformationAck::decode(&buf).unwrap();
assert_eq!(
decoded.list_of_event_summaries[0].event_timestamps,
ack.list_of_event_summaries[0].event_timestamps
);
}
}
#[test]
fn ack_timestamps_cross_codec_matrix_primitives_into_ack() {
let stamps = [ts_time(), BACnetTimeStamp::SequenceNumber(7), ts_datetime()];
let mut stamp_bytes = BytesMut::new();
bacnet_encoding::tags::encode_opening_tag(&mut stamp_bytes, 3);
for ts in &stamps {
bacnet_encoding::primitives::encode_timestamp_choice(&mut stamp_bytes, ts).unwrap();
}
bacnet_encoding::tags::encode_closing_tag(&mut stamp_bytes, 3);
let ack = ack_with_timestamps([
BACnetTimeStamp::SequenceNumber(1),
BACnetTimeStamp::SequenceNumber(2),
BACnetTimeStamp::SequenceNumber(3),
]);
let mut buf = BytesMut::new();
ack.encode(&mut buf).unwrap();
let start = buf
.windows(1)
.position(|w| w == [0x3E])
.expect("ACK contains [3] opening");
let end = buf
.windows(1)
.rposition(|w| w == [0x3F])
.expect("ACK contains [3] closing");
let mut spliced = Vec::from(&buf[..start]);
spliced.extend_from_slice(&stamp_bytes);
spliced.extend_from_slice(&buf[end + 1..]);
let decoded = GetEventInformationAck::decode(&spliced).unwrap();
assert_eq!(decoded.list_of_event_summaries[0].event_timestamps, stamps);
}
#[test]
fn ack_timestamps_cross_codec_matrix_ack_into_primitives() {
let stamps = [
ts_time(),
BACnetTimeStamp::SequenceNumber(42),
ts_datetime(),
];
let ack = ack_with_timestamps(stamps.clone());
let mut buf = BytesMut::new();
ack.encode(&mut buf).unwrap();
let mut offset = buf
.windows(1)
.position(|w| w == [0x3E])
.expect("ACK contains [3] opening")
+ 1;
for expected in &stamps {
let (decoded, new_offset) =
bacnet_encoding::primitives::decode_timestamp_choice(&buf, offset).unwrap();
assert_eq!(&decoded, expected);
offset = new_offset;
}
assert_eq!(buf[offset], 0x3F, "primitive scans land on [3] closing");
}
#[test]
fn ack_timestamps_non_conformant_time_form_rejected() {
let ack = ack_with_timestamps([
BACnetTimeStamp::SequenceNumber(1),
BACnetTimeStamp::SequenceNumber(2),
BACnetTimeStamp::SequenceNumber(3),
]);
let mut buf = BytesMut::new();
ack.encode(&mut buf).unwrap();
let start = buf.windows(1).position(|w| w == [0x3E]).unwrap() + 1;
let end = buf.windows(1).rposition(|w| w == [0x3F]).unwrap();
let mut spliced = Vec::from(&buf[..start]);
spliced.extend_from_slice(&[0x0E, 0xB4, 14, 30, 45, 50, 0x0F]); spliced.extend_from_slice(&buf[start..end]); spliced.extend_from_slice(&buf[end..]);
assert!(
GetEventInformationAck::decode(&spliced).is_err(),
"opening-tag-0 time form must be rejected"
);
}
#[test]
fn ack_timestamps_wrong_section_tag_rejected() {
let ack = ack_with_timestamps([
BACnetTimeStamp::SequenceNumber(1),
BACnetTimeStamp::SequenceNumber(2),
BACnetTimeStamp::SequenceNumber(3),
]);
let mut buf = BytesMut::new();
ack.encode(&mut buf).unwrap();
let pos = buf.windows(1).position(|w| w == [0x3E]).unwrap();
let mut wrong = buf.to_vec();
wrong[pos] = 0x4E; assert!(GetEventInformationAck::decode(&wrong).is_err());
}
#[test]
fn ack_decode_truncated_in_timestamps_rejected() {
let ack = ack_with_timestamps([
BACnetTimeStamp::SequenceNumber(42),
BACnetTimeStamp::SequenceNumber(43),
BACnetTimeStamp::SequenceNumber(44),
]);
let mut buf = BytesMut::new();
ack.encode(&mut buf).unwrap();
for cut in 1..buf.len() {
assert!(
GetEventInformationAck::decode(&buf[..cut]).is_err(),
"truncated at {cut} bytes must fail"
);
}
}
#[test]
fn ack_timestamps_sequence_number_over_65535_rejected() {
let ack = ack_with_timestamps([
BACnetTimeStamp::SequenceNumber(1),
BACnetTimeStamp::SequenceNumber(2),
BACnetTimeStamp::SequenceNumber(3),
]);
let mut buf = BytesMut::new();
ack.encode(&mut buf).unwrap();
let start = buf.windows(1).position(|w| w == [0x3E]).unwrap() + 1;
let end = buf.windows(1).rposition(|w| w == [0x3F]).unwrap();
let mut spliced = Vec::from(&buf[..start]);
spliced.extend_from_slice(&[0x1B, 0x01, 0x00, 0x00]); spliced.extend_from_slice(&buf[start + 2..end]); spliced.extend_from_slice(&buf[end..]);
assert!(GetEventInformationAck::decode(&spliced).is_err());
}