#[cfg(not(feature = "std"))]
use alloc::{string::ToString, vec::Vec};
use bacnet_types::constructed::{BACnetEventParameter, ChangeOfValueCriteria};
use bacnet_types::error::Error;
use bytes::BytesMut;
use crate::primitives;
use crate::tags;
use super::tagged::{
contents, decode_app_bit_string, decode_ctx_bit_string, decode_ctx_constructed,
decode_ctx_real, decode_ctx_unsigned, expect_closing, expect_opening, next_is_closing,
};
use super::{
decode_dopr_body, decode_property_state, encode_dopr_body, encode_property_state,
validate_extended_parameters, validate_tlv_sequence, MAX_FRAMED_ITEMS,
};
const RESERVED_EVENT_TAGS: [u8; 4] = [6, 7, 12, 19];
const MODELED_EVENT_TAGS: [u8; 6] = [0, 1, 2, 4, 5, 9];
pub fn encode_event_parameter(
buf: &mut BytesMut,
value: &BACnetEventParameter,
) -> Result<(), Error> {
let mut encoded = BytesMut::new();
encode_event_parameter_into(&mut encoded, value)?;
validate_tlv_sequence(&encoded, "BACnetEventParameter")
.map_err(|error| Error::Encoding(error.to_string()))?;
buf.extend_from_slice(&encoded);
Ok(())
}
fn encode_event_parameter_into(
buf: &mut BytesMut,
value: &BACnetEventParameter,
) -> Result<(), Error> {
match value {
BACnetEventParameter::ChangeOfBitstring {
time_delay,
bitmask,
list_of_values,
} => {
tags::encode_opening_tag(buf, 0);
primitives::encode_ctx_unsigned(buf, 0, *time_delay as u64);
primitives::encode_ctx_bit_string(buf, 1, bitmask.0, &bitmask.1);
tags::encode_opening_tag(buf, 2);
for (unused_bits, data) in list_of_values {
primitives::encode_app_bit_string(buf, *unused_bits, data);
}
tags::encode_closing_tag(buf, 2);
tags::encode_closing_tag(buf, 0);
}
BACnetEventParameter::ChangeOfState {
time_delay,
list_of_values,
} => {
let mut encoded_states = BytesMut::new();
for state in list_of_values {
encode_property_state(&mut encoded_states, state)?;
}
tags::encode_opening_tag(buf, 1);
primitives::encode_ctx_unsigned(buf, 0, *time_delay as u64);
tags::encode_opening_tag(buf, 1);
buf.extend_from_slice(&encoded_states);
tags::encode_closing_tag(buf, 1);
tags::encode_closing_tag(buf, 1);
}
BACnetEventParameter::ChangeOfValue {
time_delay,
criteria,
} => {
tags::encode_opening_tag(buf, 2);
primitives::encode_ctx_unsigned(buf, 0, *time_delay as u64);
tags::encode_opening_tag(buf, 1);
match criteria {
ChangeOfValueCriteria::Bitmask { unused_bits, data } => {
primitives::encode_ctx_bit_string(buf, 0, *unused_bits, data);
}
ChangeOfValueCriteria::ReferencedPropertyIncrement(increment) => {
primitives::encode_ctx_real(buf, 1, *increment);
}
}
tags::encode_closing_tag(buf, 1);
tags::encode_closing_tag(buf, 2);
}
BACnetEventParameter::FloatingLimit {
time_delay,
setpoint_reference,
low_diff_limit,
high_diff_limit,
deadband,
} => {
tags::encode_opening_tag(buf, 4);
primitives::encode_ctx_unsigned(buf, 0, *time_delay as u64);
tags::encode_opening_tag(buf, 1);
encode_dopr_body(buf, setpoint_reference);
tags::encode_closing_tag(buf, 1);
primitives::encode_ctx_real(buf, 2, *low_diff_limit);
primitives::encode_ctx_real(buf, 3, *high_diff_limit);
primitives::encode_ctx_real(buf, 4, *deadband);
tags::encode_closing_tag(buf, 4);
}
BACnetEventParameter::OutOfRange {
time_delay,
low_limit,
high_limit,
deadband,
} => {
tags::encode_opening_tag(buf, 5);
primitives::encode_ctx_unsigned(buf, 0, *time_delay as u64);
primitives::encode_ctx_real(buf, 1, *low_limit);
primitives::encode_ctx_real(buf, 2, *high_limit);
primitives::encode_ctx_real(buf, 3, *deadband);
tags::encode_closing_tag(buf, 5);
}
BACnetEventParameter::Extended {
vendor_id,
extended_event_type,
parameters,
} => {
validate_extended_parameters(parameters, "extended")
.map_err(|error| Error::Encoding(error.to_string()))?;
tags::encode_opening_tag(buf, 9);
primitives::encode_ctx_unsigned(buf, 0, *vendor_id as u64);
primitives::encode_ctx_unsigned(buf, 1, *extended_event_type as u64);
tags::encode_opening_tag(buf, 2);
buf.extend_from_slice(parameters);
tags::encode_closing_tag(buf, 2);
tags::encode_closing_tag(buf, 9);
}
BACnetEventParameter::Opaque { tag, data } if *tag == u8::MAX => {
primitives::encode_app_octet_string(buf, data);
}
BACnetEventParameter::Opaque { tag: 20, data } if data.is_empty() => {
tags::encode_tag(buf, 20, tags::TagClass::Context, 0);
}
BACnetEventParameter::Opaque { tag, .. }
if *tag == 20
|| MODELED_EVENT_TAGS.contains(tag)
|| RESERVED_EVENT_TAGS.contains(tag) =>
{
return Err(Error::Encoding(format!(
"BACnetEventParameter: tag [{tag}] cannot use an opaque constructed body"
)));
}
BACnetEventParameter::Opaque { tag, data } => {
validate_tlv_sequence(data, "opaque event parameters")
.map_err(|error| Error::Encoding(error.to_string()))?;
tags::encode_opening_tag(buf, *tag);
buf.extend_from_slice(data);
tags::encode_closing_tag(buf, *tag);
}
}
Ok(())
}
pub fn decode_event_parameter(
data: &[u8],
offset: usize,
) -> Result<(BACnetEventParameter, usize), Error> {
use BACnetEventParameter as EP;
if data.get(offset..offset.saturating_add(2)) == Some(&[0xFE, 0xFF]) {
let body_start = offset + 2;
if data.len() < body_start + 2 {
return Err(Error::buffer_too_short(body_start + 2, data.len()));
}
let body_end = data.len() - 2;
if data.get(body_end..) != Some(&[0xFF, 0xFF]) {
return Err(Error::decoding(
offset,
"BACnetEventParameter: missing legacy closing tag",
));
}
return Ok((
EP::Opaque {
tag: u8::MAX,
data: data[body_start..body_end].to_vec(),
},
data.len(),
));
}
let (tag, pos) = tags::decode_tag(data, offset)?;
let what = "BACnetEventParameter";
if tag.class == tags::TagClass::Application && tag.number == tags::app_tag::OCTET_STRING {
let (octets, end) = contents(data, pos, tag.length)?;
return Ok((
EP::Opaque {
tag: u8::MAX,
data: octets.to_vec(),
},
end,
));
}
if tag.class != tags::TagClass::Context {
return Err(Error::decoding(
offset,
"BACnetEventParameter: expected a context tag",
));
}
if RESERVED_EVENT_TAGS.contains(&tag.number) {
return Err(Error::decoding(
offset,
format!(
"BACnetEventParameter: context tag [{}] is omitted/deprecated/reserved in Clause 21",
tag.number
),
));
}
if !tag.is_opening && !tag.is_closing {
if tag.number == 20 && tag.length == 0 {
return Ok((
EP::Opaque {
tag: 20,
data: Vec::new(),
},
pos,
));
}
return Err(Error::decoding(
offset,
"BACnetEventParameter: invalid primitive alternative",
));
}
if !tag.is_opening {
return Err(Error::decoding(
offset,
"BACnetEventParameter: unexpected closing tag",
));
}
if tag.number == 20 {
return Err(Error::decoding(
offset,
"BACnetEventParameter: none [20] must use primitive NULL form",
));
}
let value = match tag.number {
0 => {
let what = "BACnetEventParameter change-of-bitstring";
let (time_delay, pos) = decode_ctx_unsigned::<u32>(data, pos, 0, what)?;
let (bitmask, pos) = decode_ctx_bit_string(data, pos, 1, what)?;
let mut pos = expect_opening(data, pos, 2, what)?;
let mut list_of_values = Vec::new();
while !next_is_closing(data, pos, 2)? {
if list_of_values.len() >= MAX_FRAMED_ITEMS {
return Err(Error::overflow(
pos,
"change-of-bitstring: list-of-bitstring-values exceeds limit",
));
}
let (bits, p) = decode_app_bit_string(data, pos, what)?;
list_of_values.push(bits);
pos = p;
}
pos = expect_closing(data, pos, 2, what)?;
pos = expect_closing(data, pos, 0, what)?;
(
EP::ChangeOfBitstring {
time_delay,
bitmask,
list_of_values,
},
pos,
)
}
1 => {
let what = "BACnetEventParameter change-of-state";
let (time_delay, pos) = decode_ctx_unsigned::<u32>(data, pos, 0, what)?;
let mut pos = expect_opening(data, pos, 1, what)?;
let mut list_of_values = Vec::new();
while !next_is_closing(data, pos, 1)? {
if list_of_values.len() >= MAX_FRAMED_ITEMS {
return Err(Error::overflow(
pos,
"change-of-state: list-of-values exceeds limit",
));
}
let (state, p) = decode_property_state(data, pos)?;
list_of_values.push(state);
pos = p;
}
pos = expect_closing(data, pos, 1, what)?;
pos = expect_closing(data, pos, 1, what)?;
(
EP::ChangeOfState {
time_delay,
list_of_values,
},
pos,
)
}
2 => {
let what = "BACnetEventParameter change-of-value";
let (time_delay, pos) = decode_ctx_unsigned::<u32>(data, pos, 0, what)?;
let mut pos = expect_opening(data, pos, 1, what)?;
let (inner, _) = tags::decode_tag(data, pos)?;
let criteria = if inner.is_context(0) {
let (bits, p) = decode_ctx_bit_string(data, pos, 0, what)?;
pos = p;
ChangeOfValueCriteria::Bitmask {
unused_bits: bits.0,
data: bits.1,
}
} else if inner.is_context(1) {
let (increment, p) = decode_ctx_real(data, pos, 1, what)?;
pos = p;
ChangeOfValueCriteria::ReferencedPropertyIncrement(increment)
} else {
return Err(Error::decoding(
pos,
format!(
"change-of-value: unexpected cov-criteria tag [{}]",
inner.number
),
));
};
pos = expect_closing(data, pos, 1, what)?;
pos = expect_closing(data, pos, 2, what)?;
(
EP::ChangeOfValue {
time_delay,
criteria,
},
pos,
)
}
4 => {
let what = "BACnetEventParameter floating-limit";
let (time_delay, pos) = decode_ctx_unsigned::<u32>(data, pos, 0, what)?;
let pos = expect_opening(data, pos, 1, what)?;
let (setpoint_reference, pos) = decode_dopr_body(data, pos, what)?;
let pos = expect_closing(data, pos, 1, what)?;
let (low_diff_limit, pos) = decode_ctx_real(data, pos, 2, what)?;
let (high_diff_limit, pos) = decode_ctx_real(data, pos, 3, what)?;
let (deadband, pos) = decode_ctx_real(data, pos, 4, what)?;
let pos = expect_closing(data, pos, 4, what)?;
(
EP::FloatingLimit {
time_delay,
setpoint_reference,
low_diff_limit,
high_diff_limit,
deadband,
},
pos,
)
}
5 => {
let what = "BACnetEventParameter out-of-range";
let (time_delay, pos) = decode_ctx_unsigned::<u32>(data, pos, 0, what)?;
let (low_limit, pos) = decode_ctx_real(data, pos, 1, what)?;
let (high_limit, pos) = decode_ctx_real(data, pos, 2, what)?;
let (deadband, pos) = decode_ctx_real(data, pos, 3, what)?;
let pos = expect_closing(data, pos, 5, what)?;
(
EP::OutOfRange {
time_delay,
low_limit,
high_limit,
deadband,
},
pos,
)
}
9 => {
let what = "BACnetEventParameter extended";
let (vendor_id, pos) = decode_ctx_unsigned::<u16>(data, pos, 0, what)?;
let (extended_event_type, pos) = decode_ctx_unsigned::<u32>(data, pos, 1, what)?;
let (raw_params, pos) = decode_ctx_constructed(data, pos, 2, what)?;
validate_extended_parameters(raw_params, "extended")?;
let pos = expect_closing(data, pos, 9, what)?;
(
EP::Extended {
vendor_id,
extended_event_type,
parameters: raw_params.to_vec(),
},
pos,
)
}
n => {
let (body, p) = tags::extract_context_value(data, pos, n)?;
validate_tlv_sequence(body, "opaque event parameters")?;
(
EP::Opaque {
tag: n,
data: body.to_vec(),
},
p,
)
}
};
let (value, end) = value;
validate_tlv_sequence(&data[offset..end], what)?;
Ok((value, end))
}