use super::*;
use bacnet_types::constructed::{BACnetAddress, BACnetDestination, BACnetRecipient};
use bacnet_types::primitives::Time;
fn t(h: u8, m: u8, s: u8, cs: u8) -> Time {
Time {
hour: h,
minute: m,
second: s,
hundredths: cs,
}
}
fn device_destination() -> BACnetDestination {
BACnetDestination {
valid_days: 0b0111_1111,
from_time: t(0, 0, 0, 0),
to_time: t(23, 59, 59, 99),
recipient: BACnetRecipient::Device(ObjectIdentifier::new(ObjectType::DEVICE, 99).unwrap()),
process_identifier: 1,
issue_confirmed_notifications: true,
transitions: 0b0000_0111,
}
}
#[test]
fn destination_device_form_golden() {
let dest = device_destination();
let mut buf = BytesMut::new();
encode_destination(&mut buf, &dest);
assert_eq!(
buf.as_ref(),
&[
0x82, 0x01, 0xFE, 0xB4, 0x00, 0x00, 0x00, 0x00, 0xB4, 0x17, 0x3B, 0x3B, 0x63, 0x0C, 0x02, 0x00, 0x00, 0x63, 0x21, 0x01, 0x11, 0x82, 0x05, 0xE0, ]
);
let (decoded, end) = decode_destination(&buf, 0).unwrap();
assert_eq!(decoded, dest);
assert_eq!(end, buf.len());
}
#[test]
fn destination_address_form_golden() {
let dest = BACnetDestination {
valid_days: 0b0011_1110, from_time: t(8, 0, 0, 0),
to_time: t(17, 0, 0, 0),
recipient: BACnetRecipient::Address(BACnetAddress {
network_number: 0xBAC0,
mac_address: bacnet_types::MacAddr::from_slice(&[192, 168, 1, 100, 0xBA, 0xC0]),
}),
process_identifier: 42,
issue_confirmed_notifications: false,
transitions: 0b0000_0001, };
let mut buf = BytesMut::new();
encode_destination(&mut buf, &dest);
assert_eq!(
buf.as_ref(),
&[
0x82, 0x01, 0x7C, 0xB4, 0x08, 0x00, 0x00, 0x00, 0xB4, 0x11, 0x00, 0x00, 0x00, 0x1E, 0x22, 0xBA, 0xC0, 0x65, 0x06, 0xC0, 0xA8, 0x01, 0x64, 0xBA, 0xC0, 0x1F, 0x21, 0x2A, 0x10, 0x82, 0x05, 0x80, ]
);
let (decoded, end) = decode_destination(&buf, 0).unwrap();
assert_eq!(decoded, dest);
assert_eq!(end, buf.len());
}
#[test]
fn destination_broadcast_address_golden() {
let dest = BACnetDestination {
valid_days: 0b0111_1111,
from_time: t(0, 0, 0, 0),
to_time: t(23, 59, 59, 99),
recipient: BACnetRecipient::Address(BACnetAddress {
network_number: 0xFFFF,
mac_address: bacnet_types::MacAddr::new(),
}),
process_identifier: 0,
issue_confirmed_notifications: false,
transitions: 0b0000_0111,
};
let mut buf = BytesMut::new();
encode_destination(&mut buf, &dest);
assert_eq!(
buf.as_ref(),
&[
0x82, 0x01, 0xFE, 0xB4, 0x00, 0x00, 0x00, 0x00, 0xB4, 0x17, 0x3B, 0x3B, 0x63,
0x1E, 0x22, 0xFF, 0xFF, 0x60, 0x1F, 0x21, 0x00, 0x10, 0x82, 0x05, 0xE0,
]
);
let (decoded, end) = decode_destination(&buf, 0).unwrap();
assert_eq!(decoded, dest);
assert_eq!(end, buf.len());
}
#[test]
fn destination_list_eight_entries_round_trip() {
let entries: Vec<BACnetDestination> = (0..8u32)
.map(|i| {
let mut d = device_destination();
d.process_identifier = i;
d.recipient = if i % 2 == 0 {
BACnetRecipient::Device(ObjectIdentifier::new(ObjectType::DEVICE, 100 + i).unwrap())
} else {
BACnetRecipient::Address(BACnetAddress {
network_number: (1000 + i) as u16,
mac_address: bacnet_types::MacAddr::from_slice(&[
10, 0, i as u8, 1, 0xBA, 0xC0,
]),
})
};
d
})
.collect();
let mut buf = BytesMut::new();
encode_destination_list(&mut buf, &entries);
assert_eq!(buf.len(), 4 * 24 + 4 * 32);
let decoded = decode_destination_list(&buf).unwrap();
assert_eq!(decoded, entries);
}
#[test]
fn destination_list_empty_encodes_to_nothing() {
let mut buf = BytesMut::new();
encode_destination_list(&mut buf, &[]);
assert!(buf.is_empty());
assert!(decode_destination_list(&buf).unwrap().is_empty());
}
#[test]
fn destination_recipient_tag_2_rejected() {
let base = device_destination();
let mut buf = BytesMut::new();
primitives::encode_app_bit_string(&mut buf, 1, &[0xFE]);
primitives::encode_app_time(&mut buf, &base.from_time);
primitives::encode_app_time(&mut buf, &base.to_time);
tags::encode_opening_tag(&mut buf, 2);
primitives::encode_app_unsigned(&mut buf, 0);
primitives::encode_app_octet_string(&mut buf, &[1, 2, 3]);
tags::encode_closing_tag(&mut buf, 2);
assert!(decode_destination(&buf, 0).is_err());
let mut buf = BytesMut::new();
primitives::encode_app_bit_string(&mut buf, 1, &[0xFE]);
primitives::encode_app_time(&mut buf, &base.from_time);
primitives::encode_app_time(&mut buf, &base.to_time);
primitives::encode_ctx_unsigned(&mut buf, 2, 1);
assert!(decode_destination(&buf, 0).is_err());
}
#[test]
fn destination_address_opening_without_closing_rejected() {
let base = device_destination();
let mut buf = BytesMut::new();
primitives::encode_app_bit_string(&mut buf, 1, &[0xFE]);
primitives::encode_app_time(&mut buf, &base.from_time);
primitives::encode_app_time(&mut buf, &base.to_time);
tags::encode_opening_tag(&mut buf, 1);
primitives::encode_app_unsigned(&mut buf, 0xBAC0);
primitives::encode_app_octet_string(&mut buf, &[192, 168, 1, 100, 0xBA, 0xC0]);
primitives::encode_app_unsigned(&mut buf, 1);
assert!(decode_destination(&buf, 0).is_err());
}
#[test]
fn destination_truncated_members_rejected() {
let dest = device_destination();
let mut buf = BytesMut::new();
encode_destination(&mut buf, &dest);
for cut in 1..buf.len() {
assert!(
decode_destination(&buf[..cut], 0).is_err(),
"truncated at {cut} bytes must fail"
);
}
}
#[test]
fn destination_wrong_member_type_rejected() {
let mut buf = BytesMut::new();
primitives::encode_app_bit_string(&mut buf, 1, &[0xFE]);
primitives::encode_app_unsigned(&mut buf, 3600);
assert!(decode_destination(&buf, 0).is_err());
}
#[test]
fn destination_wrong_width_valid_days_rejected() {
let base = device_destination();
let tail = {
let mut tail = BytesMut::new();
primitives::encode_app_time(&mut tail, &base.from_time);
primitives::encode_app_time(&mut tail, &base.to_time);
encode_recipient(&mut tail, &base.recipient);
primitives::encode_app_unsigned(&mut tail, base.process_identifier as u64);
primitives::encode_app_boolean(&mut tail, base.issue_confirmed_notifications);
primitives::encode_app_bit_string(&mut tail, 5, &[0xE0]);
tail.to_vec()
};
for bad_days in [
{
let mut b = BytesMut::new();
primitives::encode_app_bit_string(&mut b, 0, &[]);
b.to_vec()
},
{
let mut b = BytesMut::new();
primitives::encode_app_bit_string(&mut b, 6, &[0xFE, 0x00]);
b.to_vec()
},
{
let mut b = BytesMut::new();
primitives::encode_app_bit_string(&mut b, 0, &[0xFE]);
b.to_vec()
},
{
let mut b = BytesMut::new();
primitives::encode_app_bit_string(&mut b, 5, &[0xFE]);
b.to_vec()
},
] {
let mut buf = bad_days.clone();
buf.extend_from_slice(&tail);
assert!(
decode_destination(&buf, 0).is_err(),
"valid-days {bad_days:?} must be rejected"
);
}
}
#[test]
fn destination_wrong_width_transitions_rejected() {
let base = device_destination();
let mut head = BytesMut::new();
primitives::encode_app_bit_string(&mut head, 1, &[0xFE]);
primitives::encode_app_time(&mut head, &base.from_time);
primitives::encode_app_time(&mut head, &base.to_time);
encode_recipient(&mut head, &base.recipient);
primitives::encode_app_unsigned(&mut head, base.process_identifier as u64);
primitives::encode_app_boolean(&mut head, base.issue_confirmed_notifications);
for bad_transitions in [
{
let mut b = BytesMut::new();
primitives::encode_app_bit_string(&mut b, 0, &[]);
b.to_vec()
},
{
let mut b = BytesMut::new();
primitives::encode_app_bit_string(&mut b, -5i8 as u8 & 0xFF, &[0xE0, 0x00]);
b.to_vec()
},
{
let mut b = BytesMut::new();
primitives::encode_app_bit_string(&mut b, 1, &[0xE0]);
b.to_vec()
},
] {
let mut buf = head.clone();
buf.extend_from_slice(&bad_transitions);
assert!(
decode_destination(&buf, 0).is_err(),
"transitions {bad_transitions:?} must be rejected"
);
}
}
#[test]
fn destination_network_number_over_unsigned16_rejected() {
let base = device_destination();
let mut buf = BytesMut::new();
primitives::encode_app_bit_string(&mut buf, 1, &[0xFE]);
primitives::encode_app_time(&mut buf, &base.from_time);
primitives::encode_app_time(&mut buf, &base.to_time);
tags::encode_opening_tag(&mut buf, 1);
primitives::encode_app_unsigned(&mut buf, 65536); primitives::encode_app_octet_string(&mut buf, &[1, 2, 3]);
tags::encode_closing_tag(&mut buf, 1);
assert!(decode_destination(&buf, 0).is_err());
}