use super::*;
use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType};
use bacnet_types::primitives::ObjectIdentifier;
const FRAMES: [ReferenceFrame; 2] = [ReferenceFrame::Bare, ReferenceFrame::Setpoint];
fn ai_ref(instance: u32, property: u32) -> BACnetObjectPropertyReference {
BACnetObjectPropertyReference::new(
ObjectIdentifier::new(ObjectType::ANALOG_INPUT, instance).unwrap(),
property,
)
}
fn framed(r: &BACnetObjectPropertyReference) -> Vec<u8> {
let mut buf = bytes::BytesMut::new();
bacnet_encoding::constructed::encode_object_property_reference(&mut buf, r);
buf.to_vec()
}
fn framed_wrapped(r: &BACnetObjectPropertyReference) -> Vec<u8> {
let mut buf = bytes::BytesMut::new();
bacnet_encoding::constructed::encode_setpoint_reference(&mut buf, r);
buf.to_vec()
}
fn framed_split(r: &BACnetObjectPropertyReference) -> PropertyValue {
let bytes = framed(r);
let mut values = Vec::new();
let mut offset = 0;
while offset < bytes.len() {
let (value, new_offset) =
bacnet_encoding::primitives::decode_application_value(&bytes, offset).unwrap();
values.push(value);
offset = new_offset;
}
PropertyValue::List(values)
}
fn expect_protocol(
result: Result<Option<BACnetObjectPropertyReference>, Error>,
expected_code: ErrorCode,
context: &str,
) {
match result {
Err(Error::Protocol { class, code }) => {
assert_eq!(
class,
ErrorClass::PROPERTY.to_raw() as u32,
"{context}: wrong error class"
);
assert_eq!(code, expected_code.to_raw() as u32, "{context}: wrong code");
}
other => panic!("{context}: expected PROPERTY/{expected_code:?}, got {other:?}"),
}
}
#[test]
fn object_property_reference_reads_as_its_members_or_the_unset_form() {
assert_eq!(
object_property_reference_value(Some(&ai_ref(5, 85)), ObjectType::ANALOG_INPUT),
PropertyValue::ApplicationData(vec![0x0C, 0x00, 0x00, 0x00, 0x05, 0x19, 0x55])
);
let indexed = BACnetObjectPropertyReference::new_indexed(
ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 3).unwrap(),
104,
2,
);
assert_eq!(
object_property_reference_value(Some(&indexed), ObjectType::ANALOG_INPUT),
PropertyValue::ApplicationData(vec![0x0C, 0x00, 0x40, 0x00, 0x03, 0x19, 0x68, 0x29, 0x02])
);
assert_eq!(
object_property_reference_value(None, ObjectType::ANALOG_INPUT),
PropertyValue::ApplicationData(vec![0x0C, 0x00, 0x3F, 0xFF, 0xFF, 0x19, 0x55])
);
assert_eq!(
object_property_reference_value(None, ObjectType::ACCUMULATOR),
PropertyValue::ApplicationData(vec![0x0C, 0x05, 0xFF, 0xFF, 0xFF, 0x19, 0x55])
);
}
#[test]
fn setpoint_reference_reads_framed_or_empty() {
let reference = BACnetObjectPropertyReference::new(
ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 10).unwrap(),
85,
);
assert_eq!(
setpoint_reference_value(Some(&reference)),
PropertyValue::ApplicationData(vec![0x0E, 0x0C, 0x00, 0x80, 0x00, 0x0A, 0x19, 0x55, 0x0F])
);
assert_eq!(
setpoint_reference_value(None),
PropertyValue::ApplicationData(Vec::new())
);
}
#[test]
fn read_values_write_back_unchanged() {
let indexed =
BACnetObjectPropertyReference::new_indexed(ai_ref(7, 88).object_identifier, 88, 12);
for reference in [None, Some(ai_ref(5, 85)), Some(indexed)] {
let bare = object_property_reference_value(reference.as_ref(), ObjectType::ANALOG_OUTPUT);
assert_eq!(
decode_reference_write(&bare, ReferenceFrame::Bare).unwrap(),
reference
);
let setpoint = setpoint_reference_value(reference.as_ref());
assert_eq!(
decode_reference_write(&setpoint, ReferenceFrame::Setpoint).unwrap(),
reference
);
}
}
#[test]
fn null_is_another_datatype_on_every_reference() {
for frame in [ReferenceFrame::Bare, ReferenceFrame::Setpoint] {
expect_protocol(
decode_reference_write(&PropertyValue::Null, frame),
ErrorCode::INVALID_DATA_TYPE,
&format!("Null on {frame:?}"),
);
}
}
#[test]
fn a_bare_reference_to_the_reserved_instance_clears() {
for octets in [
vec![0x0C, 0x00, 0x3F, 0xFF, 0xFF, 0x19, 0x55],
vec![0x0C, 0x05, 0xFF, 0xFF, 0xFF, 0x19, 0x57, 0x29, 0x02],
] {
let value = PropertyValue::ApplicationData(octets);
assert_eq!(
decode_reference_write(&value, ReferenceFrame::Bare).unwrap(),
None,
"{value:?}"
);
}
let value = PropertyValue::ApplicationData(vec![0x0C, 0x00, 0x3F, 0xFF, 0xFE, 0x19, 0x55]);
assert!(decode_reference_write(&value, ReferenceFrame::Bare)
.unwrap()
.is_some());
}
#[test]
fn empty_value_clears_only_setpoint_reference() {
for empty in [
PropertyValue::ApplicationData(Vec::new()),
PropertyValue::List(Vec::new()),
PropertyValue::List(vec![PropertyValue::ApplicationData(Vec::new())]),
] {
assert_eq!(
decode_reference_write(&empty, ReferenceFrame::Setpoint).unwrap(),
None,
"{empty:?}"
);
expect_protocol(
decode_reference_write(&empty, ReferenceFrame::Bare),
ErrorCode::INVALID_DATA_ENCODING,
&format!("{empty:?} on a bare reference"),
);
}
}
#[test]
fn framed_form_decodes_from_one_or_split_application_data() {
let reference =
BACnetObjectPropertyReference::new_indexed(ai_ref(7, 88).object_identifier, 88, 12);
let frame = ReferenceFrame::Bare;
assert_eq!(
decode_reference_write(&PropertyValue::ApplicationData(framed(&reference)), frame).unwrap(),
Some(reference.clone())
);
assert_eq!(
decode_reference_write(&framed_split(&reference), frame).unwrap(),
Some(reference.clone())
);
assert_eq!(
decode_reference_write(
&PropertyValue::ApplicationData(framed_wrapped(&reference)),
ReferenceFrame::Setpoint
)
.unwrap(),
Some(reference)
);
}
#[test]
fn flat_list_is_another_datatype_in_every_frame() {
let oid = ai_ref(5, 85).object_identifier;
let flat_forms = [
vec![
PropertyValue::ObjectIdentifier(oid),
PropertyValue::Enumerated(85),
],
vec![
PropertyValue::ObjectIdentifier(oid),
PropertyValue::Unsigned(85),
PropertyValue::Unsigned(3),
],
];
for frame in FRAMES {
for items in &flat_forms {
expect_protocol(
decode_reference_write(&PropertyValue::List(items.clone()), frame),
ErrorCode::INVALID_DATA_TYPE,
&format!("flat list {items:?} under {frame:?}"),
);
}
expect_protocol(
decode_reference_write(
&PropertyValue::ApplicationData(vec![0xC4, 0x00, 0x00, 0x00, 0x05, 0x91, 0x55]),
frame,
),
ErrorCode::INVALID_DATA_TYPE,
&format!("flat octets under {frame:?}"),
);
}
}
#[test]
fn opening_of_another_datatype_is_invalid_data_type() {
let reference = ai_ref(10, 85);
expect_protocol(
decode_reference_write(
&PropertyValue::ApplicationData(framed_wrapped(&reference)),
ReferenceFrame::Bare,
),
ErrorCode::INVALID_DATA_TYPE,
"[0]-framed reference on a bare-reference property",
);
expect_protocol(
decode_reference_write(
&PropertyValue::ApplicationData(framed(&reference)),
ReferenceFrame::Setpoint,
),
ErrorCode::INVALID_DATA_TYPE,
"bare members on Setpoint_Reference",
);
for frame in FRAMES {
for bytes in [vec![0x00], vec![0x19, 0x55]] {
expect_protocol(
decode_reference_write(&PropertyValue::ApplicationData(bytes.clone()), frame),
ErrorCode::INVALID_DATA_TYPE,
&format!("{bytes:02X?} under {frame:?}"),
);
}
}
}
#[test]
fn framed_malformed_is_invalid_data_encoding() {
let good = framed(&ai_ref(5, 85));
let cases: Vec<(Vec<u8>, &str)> = vec![
(good[..5].to_vec(), "object id only (partial members)"),
(
[good.clone(), vec![0x29, 0x02, 0x3C, 0x00, 0x00, 0x00, 0x4D]].concat(),
"device-qualified member [3]",
),
(
[good.clone(), vec![0x49, 0x01]].concat(),
"unknown trailing context tag [4]",
),
(
[good.clone(), vec![0x21, 0x00]].concat(),
"application tag trailing the members",
),
([good.clone(), good.clone()].concat(), "two references"),
(vec![0x0C, 0x00], "truncated object identifier"),
];
for (bytes, context) in cases {
expect_protocol(
decode_reference_write(
&PropertyValue::ApplicationData(bytes.clone()),
ReferenceFrame::Bare,
),
ErrorCode::INVALID_DATA_ENCODING,
context,
);
let mut wrapped = vec![0x0E];
wrapped.extend_from_slice(&bytes);
wrapped.push(0x0F);
expect_protocol(
decode_reference_write(
&PropertyValue::ApplicationData(wrapped),
ReferenceFrame::Setpoint,
),
ErrorCode::INVALID_DATA_ENCODING,
&format!("{context}, framed"),
);
}
}
#[test]
fn setpoint_frame_must_hold_one_whole_reference() {
let wrapped = framed_wrapped(&ai_ref(10, 85));
for (bytes, context) in [
(vec![0x0E, 0x0F], "empty frame"),
(wrapped[..wrapped.len() - 1].to_vec(), "unbalanced frame"),
(
[wrapped.clone(), vec![0x21, 0x01]].concat(),
"trailing octets",
),
([wrapped.clone(), wrapped.clone()].concat(), "two frames"),
] {
expect_protocol(
decode_reference_write(
&PropertyValue::ApplicationData(bytes),
ReferenceFrame::Setpoint,
),
ErrorCode::INVALID_DATA_ENCODING,
context,
);
}
}
#[test]
fn list_mixing_chunks_and_decoded_values_is_invalid_data_type() {
let value = PropertyValue::List(vec![
PropertyValue::ApplicationData(framed(&ai_ref(5, 85))[..5].to_vec()),
PropertyValue::Enumerated(85),
]);
for frame in FRAMES {
expect_protocol(
decode_reference_write(&value, frame),
ErrorCode::INVALID_DATA_TYPE,
&format!("mixed framed + flat members under {frame:?}"),
);
}
}
#[test]
fn wrong_value_datatypes_are_invalid_data_type() {
for frame in FRAMES {
for value in [
PropertyValue::Unsigned(42),
PropertyValue::Real(1.0),
PropertyValue::ObjectIdentifier(ai_ref(5, 85).object_identifier),
PropertyValue::List(vec![PropertyValue::Unsigned(1), PropertyValue::Unsigned(2)]),
] {
expect_protocol(
decode_reference_write(&value, frame),
ErrorCode::INVALID_DATA_TYPE,
&format!("{value:?} under {frame:?}"),
);
}
}
}