#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use bacnet_types::bitstring::{DaysOfWeek, EventTransitionBits};
use bacnet_types::constructed::{BACnetAddress, BACnetDestination, BACnetRecipient};
use bacnet_types::error::{DecodeError, DecodingKind, EncodeError, Error};
use bacnet_types::primitives::Time;
use bacnet_types::MacAddr;
use bytes::BytesMut;
use crate::primitives;
use crate::tags::{self, TagClass};
use super::tagged::{
contents, decode_app_bit_string, decode_app_unsigned, decode_ctx_object_id, expect_closing,
misplaced,
};
use super::MAX_FRAMED_ITEMS;
fn mac_len_excess(length: usize) -> bool {
length > BACnetAddress::MAX_MAC_LEN
}
pub fn check_decoded_mac_len(length: usize, offset: usize, what: &'static str) -> Result<(), Error> {
if !mac_len_excess(length) {
return Ok(());
}
Err(DecodeError::new(
DecodingKind::Overflow,
offset,
"mac-address of {} octets exceeds the {}-octet limit",
)
.within(what)
.with_values(length as u64, BACnetAddress::MAX_MAC_LEN as u64)
.into())
}
pub fn check_encoded_mac_len(mac: &[u8], what: &'static str) -> Result<(), Error> {
if !mac_len_excess(mac.len()) {
return Ok(());
}
Err(
EncodeError::new("mac-address of {} octets exceeds the {}-octet limit")
.within(what)
.with_values(mac.len() as u32, BACnetAddress::MAX_MAC_LEN as u32)
.into(),
)
}
pub(super) fn check_encoded_recipient(recipient: &BACnetRecipient) -> Result<(), Error> {
match recipient {
BACnetRecipient::Device(_) => Ok(()),
BACnetRecipient::Address(address) => {
check_encoded_mac_len(&address.mac_address, "BACnetRecipient")
}
}
}
pub fn encode_destination(buf: &mut BytesMut, dest: &BACnetDestination) -> Result<(), Error> {
check_encoded_recipient(&dest.recipient)?;
write_destination(buf, dest);
Ok(())
}
fn write_destination(buf: &mut BytesMut, dest: &BACnetDestination) {
primitives::encode_app_bit_string(buf, 1, &[dest.valid_days.to_bacnet()]);
primitives::encode_app_time(buf, &dest.from_time);
primitives::encode_app_time(buf, &dest.to_time);
write_recipient(buf, &dest.recipient);
primitives::encode_app_unsigned(buf, dest.process_identifier as u64);
primitives::encode_app_boolean(buf, dest.issue_confirmed_notifications);
primitives::encode_app_bit_string(buf, 5, &[dest.transitions.to_bacnet()]);
}
pub fn encode_destination_list(
buf: &mut BytesMut,
destinations: &[BACnetDestination],
) -> Result<(), Error> {
for dest in destinations {
check_encoded_recipient(&dest.recipient)?;
}
for dest in destinations {
write_destination(buf, dest);
}
Ok(())
}
pub fn encode_recipient(buf: &mut BytesMut, recipient: &BACnetRecipient) -> Result<(), Error> {
check_encoded_recipient(recipient)?;
write_recipient(buf, recipient);
Ok(())
}
pub(super) fn write_recipient(buf: &mut BytesMut, recipient: &BACnetRecipient) {
match recipient {
BACnetRecipient::Device(oid) => {
tags::encode_tag(buf, 0, TagClass::Context, 4);
buf.extend_from_slice(&oid.encode());
}
BACnetRecipient::Address(address) => {
tags::encode_opening_tag(buf, 1);
primitives::encode_app_unsigned(buf, address.network_number as u64);
primitives::encode_app_octet_string(buf, &address.mac_address);
tags::encode_closing_tag(buf, 1);
}
}
}
pub fn decode_recipient(data: &[u8], offset: usize) -> Result<(BACnetRecipient, usize), Error> {
let what = "BACnetRecipient";
let (tag, pos) = tags::decode_tag(data, offset)?;
if tag.is_context(0) {
let (device, end) = decode_ctx_object_id(data, offset, 0, "BACnetRecipient device")?;
return Ok((BACnetRecipient::Device(device), end));
}
if tag.is_opening_tag(1) {
let (network_number, pos) = decode_app_unsigned::<u64>(data, pos, what)?;
let network_number = u16::try_from(network_number).map_err(|_| {
DecodeError::new(
DecodingKind::OutOfRange,
pos,
"network-number exceeds Unsigned16",
)
.within(what)
})?;
let (mac_address, pos) = decode_app_mac_address(data, pos, what)?;
let end = expect_closing(data, pos, 1, "BACnetRecipient address")?;
return Ok((
BACnetRecipient::Address(BACnetAddress {
network_number,
mac_address,
}),
end,
));
}
let label = if tag.is_opening {
"expected [0] (device) or [1] (address), got opening tag [{}]"
} else if tag.is_closing {
"expected [0] (device) or [1] (address), got closing tag [{}]"
} else {
match tag.class {
TagClass::Context => "expected [0] (device) or [1] (address), got primitive context tag [{}]",
TagClass::Application => "expected [0] (device) or [1] (address), got application tag {}",
}
};
Err(misplaced(data, &tag, None, offset, label)
.within(what)
.with_value(tag.number.into())
.into())
}
pub fn decode_destination(data: &[u8], offset: usize) -> Result<(BACnetDestination, usize), Error> {
let what = "BACnetDestination";
let ((days_unused, days_data), pos) = decode_app_bit_string(data, offset, what)?;
check_fixed_bit_string(
days_unused,
&days_data,
1,
"valid-days (BACnetDaysOfWeek: 7 bits)",
)?;
let valid_days = DaysOfWeek::from_bacnet(&days_data);
let (from_time, pos) = decode_app_time(data, pos, what)?;
let (to_time, pos) = decode_app_time(data, pos, what)?;
let (recipient, pos) = decode_recipient(data, pos)?;
let (process_identifier, pos) = decode_app_unsigned::<u64>(data, pos, what)?;
let process_identifier = u32::try_from(process_identifier).map_err(|_| {
DecodeError::new(
DecodingKind::OutOfRange,
pos,
"process-identifier exceeds Unsigned32",
)
.within(what)
})?;
let (tag, pos) = tags::decode_tag(data, pos)?;
if tag.class != TagClass::Application || tag.number != tags::app_tag::BOOLEAN {
return Err(
misplaced(data, &tag, None, pos, "expected application-tagged BOOLEAN")
.within(what)
.into(),
);
}
let issue_confirmed_notifications = tag.length != 0;
let ((transitions_unused, transitions_data), pos) = decode_app_bit_string(data, pos, what)?;
check_fixed_bit_string(
transitions_unused,
&transitions_data,
5,
"transitions (BACnetEventTransitionBits: 3 bits)",
)?;
let transitions = EventTransitionBits::from_bacnet(&transitions_data);
Ok((
BACnetDestination {
valid_days,
from_time,
to_time,
recipient,
process_identifier,
issue_confirmed_notifications,
transitions,
},
pos,
))
}
pub fn decode_destination_list(data: &[u8]) -> Result<Vec<BACnetDestination>, Error> {
let mut destinations = Vec::new();
let mut pos = 0;
while pos < data.len() {
if destinations.len() >= MAX_FRAMED_ITEMS {
return Err(Error::overflow(
pos,
"Recipient_List: destination count exceeds limit",
));
}
let (dest, new_pos) = decode_destination(data, pos)?;
destinations.push(dest);
pos = new_pos;
}
Ok(destinations)
}
pub(super) fn decode_app_mac_address(
data: &[u8],
offset: usize,
what: &'static str,
) -> Result<(MacAddr, usize), Error> {
let (tag, pos) = tags::decode_tag(data, offset)?;
if tag.class != TagClass::Application || tag.number != tags::app_tag::OCTET_STRING {
return Err(misplaced(
data,
&tag,
None,
offset,
"expected application-tagged OCTET STRING",
)
.within(what)
.into());
}
check_decoded_mac_len(tag.length as usize, offset, what)?;
let (mac, end) = contents(data, pos, tag.length)?;
Ok((MacAddr::from_slice(mac), end))
}
fn decode_app_time(data: &[u8], offset: usize, what: &'static str) -> Result<(Time, usize), Error> {
let (tag, pos) = tags::decode_tag(data, offset)?;
if tag.class != TagClass::Application || tag.number != tags::app_tag::TIME {
return Err(
misplaced(data, &tag, None, offset, "expected application-tagged Time")
.within(what)
.into(),
);
}
if tag.length != 4 {
return Err(DecodeError::new(
DecodingKind::InvalidEncoding,
offset,
"Time has {} contents octets, expected 4",
)
.within(what)
.with_value(tag.length.into())
.into());
}
let (octets, end) = contents(data, pos, 4)?;
Ok((Time::decode(octets)?, end))
}
fn check_fixed_bit_string(
unused_bits: u8,
data: &[u8],
expected_unused: u8,
name: &'static str,
) -> Result<(), Error> {
if data.len() != 1 {
return Err(DecodeError::new(
DecodingKind::InvalidEncoding,
0,
"expected 1 content octet, got {}",
)
.within(name)
.with_value(data.len() as u64)
.into());
}
if unused_bits != expected_unused {
return Err(DecodeError::new(
DecodingKind::InvalidEncoding,
0,
"expected unused_bits {}, got {}",
)
.within(name)
.with_values(expected_unused.into(), unused_bits.into())
.into());
}
Ok(())
}