use bacnet_types::constructed::{BACnetAddress, BACnetDestination, BACnetRecipient};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, Time};
use bacnet_types::MacAddr;
use bytes::BytesMut;
use crate::primitives;
use crate::tags::{self, TagClass};
use super::MAX_FRAMED_ITEMS;
use bacnet_types::bitstring::{pack_octet, unpack_octet};
pub fn encode_destination(buf: &mut BytesMut, dest: &BACnetDestination) {
primitives::encode_app_bit_string(buf, 1, &[pack_octet(dest.valid_days)]);
primitives::encode_app_time(buf, &dest.from_time);
primitives::encode_app_time(buf, &dest.to_time);
encode_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, &[pack_octet(dest.transitions)]);
}
pub fn encode_destination_list(buf: &mut BytesMut, destinations: &[BACnetDestination]) {
for dest in destinations {
encode_destination(buf, dest);
}
}
pub fn encode_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) {
if tag.length != 4 {
return Err(Error::decoding(
offset,
format!(
"{what}: device [0] expects 4 contents octets, got {}",
tag.length
),
));
}
let end = pos + 4;
if end > data.len() {
return Err(Error::buffer_too_short(end, data.len()));
}
return Ok((
BACnetRecipient::Device(ObjectIdentifier::decode(&data[pos..end])?),
end,
));
}
if tag.is_opening_tag(1) {
let (network_number, pos) = decode_app_unsigned(data, pos, what)?;
let network_number = u16::try_from(network_number).map_err(|_| {
Error::decoding(pos, format!("{what}: network-number exceeds Unsigned16"))
})?;
let (mac_address, pos) = decode_app_octet_string(data, pos, what)?;
let (close, close_pos) = tags::decode_tag(data, pos)?;
if !close.is_closing_tag(1) {
return Err(Error::decoding(
pos,
format!("{what}: missing closing tag [1] for BACnetAddress"),
));
}
return Ok((
BACnetRecipient::Address(BACnetAddress {
network_number,
mac_address,
}),
close_pos,
));
}
Err(Error::decoding(
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 = unpack_octet(&days_data, 7);
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(data, pos, what)?;
let process_identifier = u32::try_from(process_identifier).map_err(|_| {
Error::decoding(
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(Error::decoding(
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 = unpack_octet(&transitions_data, 3);
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::decoding(
pos,
"Recipient_List: destination count exceeds limit",
));
}
let (dest, new_pos) = decode_destination(data, pos)?;
destinations.push(dest);
pos = new_pos;
}
Ok(destinations)
}
fn decode_app_unsigned(data: &[u8], offset: usize, what: &str) -> Result<(u64, usize), Error> {
let (tag, pos) = tags::decode_tag(data, offset)?;
if tag.class != TagClass::Application || tag.number != tags::app_tag::UNSIGNED {
return Err(Error::decoding(
offset,
format!("{what}: expected application-tagged Unsigned"),
));
}
let end = pos
.checked_add(tag.length as usize)
.ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
if end > data.len() {
return Err(Error::buffer_too_short(end, data.len()));
}
Ok((primitives::decode_unsigned(&data[pos..end])?, end))
}
fn decode_app_octet_string(
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(Error::decoding(
offset,
format!("{what}: expected application-tagged OCTET STRING"),
));
}
let end = pos
.checked_add(tag.length as usize)
.ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
if end > data.len() {
return Err(Error::buffer_too_short(end, data.len()));
}
Ok((MacAddr::from_slice(&data[pos..end]), 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 || tag.length != 4 {
return Err(Error::decoding(
offset,
format!("{what}: expected application-tagged Time (4 octets)"),
));
}
let end = pos + 4;
if end > data.len() {
return Err(Error::buffer_too_short(end, data.len()));
}
Ok((Time::decode(&data[pos..end])?, 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(())
}
fn decode_app_bit_string(
data: &[u8],
offset: usize,
what: &str,
) -> Result<((u8, Vec<u8>), usize), Error> {
let (tag, pos) = tags::decode_tag(data, offset)?;
if tag.class != TagClass::Application || tag.number != tags::app_tag::BIT_STRING {
return Err(Error::decoding(
offset,
format!("{what}: expected application-tagged BIT STRING"),
));
}
let end = pos
.checked_add(tag.length as usize)
.ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
if end > data.len() {
return Err(Error::buffer_too_short(end, data.len()));
}
let (unused_bits, bits) = primitives::decode_bit_string(&data[pos..end])?;
Ok(((unused_bits, bits), end))
}