#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use bacnet_types::bitstring::{DaysOfWeek, EventTransitionBits};
use bacnet_types::constructed::{BACnetAddress, BACnetDestination, BACnetRecipient};
use bacnet_types::error::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_tag,
};
use super::MAX_FRAMED_ITEMS;
fn mac_len_excess(what: &str, length: usize) -> Option<String> {
(length > BACnetAddress::MAX_MAC_LEN).then(|| {
format!(
"{what}: mac-address of {length} octets exceeds the {}-octet limit",
BACnetAddress::MAX_MAC_LEN
)
})
}
pub fn check_decoded_mac_len(length: usize, offset: usize, what: &str) -> Result<(), Error> {
mac_len_excess(what, length).map_or(Ok(()), |message| Err(Error::overflow(offset, message)))
}
pub fn check_encoded_mac_len(mac: &[u8], what: &str) -> Result<(), Error> {
mac_len_excess(what, mac.len()).map_or(Ok(()), |message| Err(Error::Encoding(message)))
}
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(|_| {
Error::out_of_range(pos, format!("{what}: network-number exceeds Unsigned16"))
})?;
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,
));
}
Err(misplaced_tag(
data,
&tag,
None,
offset,
format!(
"{what}: expected [0] (device) or [1] (address), got {}",
if tag.is_opening {
format!("opening tag [{}]", tag.number)
} else if tag.is_closing {
format!("closing tag [{}]", tag.number)
} else {
match tag.class {
TagClass::Context => format!("primitive context tag [{}]", tag.number),
TagClass::Application => format!("application tag {}", tag.number),
}
}
),
))
}
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(|_| {
Error::out_of_range(
pos,
format!("{what}: process-identifier exceeds Unsigned32"),
)
})?;
let (tag, pos) = tags::decode_tag(data, pos)?;
if tag.class != TagClass::Application || tag.number != tags::app_tag::BOOLEAN {
return Err(misplaced_tag(
data,
&tag,
None,
pos,
format!("{what}: expected application-tagged BOOLEAN"),
));
}
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: &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_tag(
data,
&tag,
None,
offset,
format!("{what}: expected application-tagged OCTET STRING"),
));
}
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: &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_tag(
data,
&tag,
None,
offset,
format!("{what}: expected application-tagged Time"),
));
}
if tag.length != 4 {
return Err(Error::decoding(
offset,
format!(
"{what}: Time has {} contents octets, expected 4",
tag.length
),
));
}
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: &str,
) -> Result<(), Error> {
if data.len() != 1 || unused_bits != expected_unused {
return Err(Error::decoding(
0,
format!(
"{name}: expected 1 content octet with unused_bits {expected_unused} \
(got {} content octet(s), unused_bits {unused_bits})",
data.len()
),
));
}
Ok(())
}