#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use bacnet_types::error::{DecodingKind, Error};
use bacnet_types::primitives::{BACnetTimeStamp, Date, ObjectIdentifier, PropertyValue, Time};
use bytes::{BufMut, BytesMut};
use crate::constructed::tagged::{misplaced_tag, unclosed_kind};
use crate::tags::{self, app_tag, TagClass};
pub fn encode_unsigned(buf: &mut BytesMut, value: u64) {
if value <= 0xFF {
buf.put_u8(value as u8);
} else if value <= 0xFFFF {
buf.put_u16(value as u16);
} else if value <= 0xFF_FFFF {
buf.put_u8((value >> 16) as u8);
buf.put_u16(value as u16);
} else if value <= 0xFFFF_FFFF {
buf.put_u32(value as u32);
} else if value <= 0xFF_FFFF_FFFF {
buf.put_u8((value >> 32) as u8);
buf.put_u32(value as u32);
} else if value <= 0xFFFF_FFFF_FFFF {
buf.put_u16((value >> 32) as u16);
buf.put_u32(value as u32);
} else if value <= 0xFF_FFFF_FFFF_FFFF {
buf.put_u8((value >> 48) as u8);
buf.put_u16((value >> 32) as u16);
buf.put_u32(value as u32);
} else {
buf.put_u64(value);
}
}
pub fn unsigned_len(value: u64) -> u32 {
if value <= 0xFF {
1
} else if value <= 0xFFFF {
2
} else if value <= 0xFF_FFFF {
3
} else if value <= 0xFFFF_FFFF {
4
} else if value <= 0xFF_FFFF_FFFF {
5
} else if value <= 0xFFFF_FFFF_FFFF {
6
} else if value <= 0xFF_FFFF_FFFF_FFFF {
7
} else {
8
}
}
pub fn decode_unsigned(data: &[u8]) -> Result<u64, Error> {
if data.is_empty() || data.len() > 8 {
return Err(Error::decoding(
0,
format!("unsigned requires 1-8 bytes, got {}", data.len()),
));
}
let mut value: u64 = 0;
for &b in data {
value = (value << 8) | b as u64;
}
Ok(value)
}
pub fn decode_unsigned_u8(data: &[u8]) -> Result<u8, Error> {
let value = decode_unsigned(data)?;
u8::try_from(value)
.map_err(|_| Error::out_of_range(0, format!("unsigned value {value} exceeds u8")))
}
pub fn decode_unsigned_u16(data: &[u8]) -> Result<u16, Error> {
let value = decode_unsigned(data)?;
u16::try_from(value)
.map_err(|_| Error::out_of_range(0, format!("unsigned value {value} exceeds u16")))
}
pub fn decode_unsigned_u32(data: &[u8]) -> Result<u32, Error> {
let value = decode_unsigned(data)?;
u32::try_from(value)
.map_err(|_| Error::out_of_range(0, format!("unsigned value {value} exceeds u32")))
}
pub fn encode_signed(buf: &mut BytesMut, value: i32) {
let n = signed_len(value);
let bytes = value.to_be_bytes();
buf.put_slice(&bytes[4 - n as usize..]);
}
pub fn signed_len(value: i32) -> u32 {
if (-128..=127).contains(&value) {
1
} else if (-32768..=32767).contains(&value) {
2
} else if (-8_388_608..=8_388_607).contains(&value) {
3
} else {
4
}
}
pub fn decode_signed(data: &[u8]) -> Result<i32, Error> {
if data.is_empty() || data.len() > 4 {
return Err(Error::decoding(
0,
format!("signed requires 1-4 bytes, got {}", data.len()),
));
}
let sign_extend = if data[0] & 0x80 != 0 { 0xFF } else { 0x00 };
let mut bytes = [sign_extend; 4];
bytes[4 - data.len()..].copy_from_slice(data);
Ok(i32::from_be_bytes(bytes))
}
pub(crate) fn decode_signed_canonical(data: &[u8]) -> Result<i32, Error> {
if data.len() > 1
&& ((data[0] == 0 && data[1] & 0x80 == 0) || (data[0] == 0xFF && data[1] & 0x80 != 0))
{
return Err(Error::decoding(0, "signed contains a redundant sign octet"));
}
decode_signed(data)
}
pub fn encode_real(buf: &mut BytesMut, value: f32) {
buf.put_f32(value);
}
pub fn decode_real(data: &[u8]) -> Result<f32, Error> {
if data.len() != 4 {
return Err(Error::decoding(
0,
format!("Real expects exactly 4 bytes, got {}", data.len()),
));
}
Ok(f32::from_be_bytes([data[0], data[1], data[2], data[3]]))
}
pub fn encode_double(buf: &mut BytesMut, value: f64) {
buf.put_f64(value);
}
pub fn decode_double(data: &[u8]) -> Result<f64, Error> {
if data.len() != 8 {
return Err(Error::decoding(
0,
format!("Double expects exactly 8 bytes, got {}", data.len()),
));
}
let bytes: [u8; 8] = [
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
];
Ok(f64::from_be_bytes(bytes))
}
pub mod charset {
pub const UTF8: u8 = 0;
pub const IBM_MICROSOFT_DBCS: u8 = 1;
pub const JIS_X_0208: u8 = 2;
pub const UCS4: u8 = 3;
pub const UCS2: u8 = 4;
pub const ISO_8859_1: u8 = 5;
}
pub fn encode_character_string(buf: &mut BytesMut, value: &str) {
buf.put_u8(charset::UTF8);
buf.put_slice(value.as_bytes());
}
pub fn character_string_len(value: &str) -> Result<u32, Error> {
u32::try_from(value.len())
.ok()
.and_then(|n| n.checked_add(1))
.ok_or_else(|| Error::Encoding("CharacterString too long for BACnet encoding".into()))
}
pub fn decode_character_string(data: &[u8]) -> Result<String, Error> {
if data.is_empty() {
return Err(Error::decoding(
0,
"CharacterString requires at least 1 byte for charset",
));
}
let charset_id = data[0];
let payload = &data[1..];
match charset_id {
charset::UTF8 => String::from_utf8(payload.to_vec())
.map_err(|e| Error::decoding(1, format!("invalid UTF-8: {e}"))),
charset::UCS2 => {
if !payload.len().is_multiple_of(2) {
return Err(Error::decoding(1, "UCS-2 data must have even length"));
}
let mut s = String::new();
for (i, chunk) in payload.as_chunks::<2>().0.iter().enumerate() {
let code_point = u16::from_be_bytes(*chunk);
if let Some(c) = char::from_u32(code_point as u32) {
s.push(c);
} else {
return Err(Error::decoding(1 + i * 2, "invalid UCS-2 code point"));
}
}
Ok(s)
}
charset::ISO_8859_1 => Ok(payload.iter().map(|&b| b as char).collect()),
charset::IBM_MICROSOFT_DBCS | charset::JIS_X_0208 | charset::UCS4 => {
Err(Error::decoding_kind(
DecodingKind::Unsupported,
0,
format!("unsupported charset: {charset_id}"),
))
}
other => Err(Error::decoding(0, format!("unknown charset: {other}"))),
}
}
pub fn encode_bit_string(buf: &mut BytesMut, unused_bits: u8, data: &[u8]) {
buf.put_u8(unused_bits);
buf.put_slice(data);
}
pub fn decode_bit_string(data: &[u8]) -> Result<(u8, Vec<u8>), Error> {
if data.is_empty() {
return Err(Error::decoding(
0,
"BitString requires at least 1 byte for unused-bits count",
));
}
let unused = data[0];
if unused > 7 {
return Err(Error::decoding(
0,
format!("BitString unused_bits must be 0-7, got {unused}"),
));
}
Ok((unused, data[1..].to_vec()))
}
pub fn encode_app_null(buf: &mut BytesMut) {
tags::encode_tag(buf, app_tag::NULL, TagClass::Application, 0);
}
pub fn encode_app_boolean(buf: &mut BytesMut, value: bool) {
tags::encode_tag(
buf,
app_tag::BOOLEAN,
TagClass::Application,
if value { 1 } else { 0 },
);
}
pub fn encode_app_unsigned(buf: &mut BytesMut, value: u64) {
let len = unsigned_len(value);
tags::encode_tag(buf, app_tag::UNSIGNED, TagClass::Application, len);
encode_unsigned(buf, value);
}
pub fn encode_app_signed(buf: &mut BytesMut, value: i32) {
let len = signed_len(value);
tags::encode_tag(buf, app_tag::SIGNED, TagClass::Application, len);
encode_signed(buf, value);
}
pub fn encode_app_real(buf: &mut BytesMut, value: f32) {
tags::encode_tag(buf, app_tag::REAL, TagClass::Application, 4);
encode_real(buf, value);
}
pub fn encode_app_double(buf: &mut BytesMut, value: f64) {
tags::encode_tag(buf, app_tag::DOUBLE, TagClass::Application, 8);
encode_double(buf, value);
}
pub fn encode_app_octet_string(buf: &mut BytesMut, data: &[u8]) {
let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
tags::encode_tag(buf, app_tag::OCTET_STRING, TagClass::Application, data_len);
buf.put_slice(data);
}
pub fn encode_app_character_string(buf: &mut BytesMut, value: &str) -> Result<(), Error> {
let len = character_string_len(value)?;
tags::encode_tag(buf, app_tag::CHARACTER_STRING, TagClass::Application, len);
encode_character_string(buf, value);
Ok(())
}
pub fn encode_app_bit_string(buf: &mut BytesMut, unused_bits: u8, data: &[u8]) {
let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
let len = data_len.saturating_add(1);
tags::encode_tag(buf, app_tag::BIT_STRING, TagClass::Application, len);
encode_bit_string(buf, unused_bits, data);
}
pub fn encode_app_enumerated(buf: &mut BytesMut, value: u32) {
let len = unsigned_len(value as u64);
tags::encode_tag(buf, app_tag::ENUMERATED, TagClass::Application, len);
encode_unsigned(buf, value as u64);
}
pub fn encode_app_date(buf: &mut BytesMut, date: &Date) {
tags::encode_tag(buf, app_tag::DATE, TagClass::Application, 4);
buf.put_slice(&date.encode());
}
pub fn encode_app_time(buf: &mut BytesMut, time: &Time) {
tags::encode_tag(buf, app_tag::TIME, TagClass::Application, 4);
buf.put_slice(&time.encode());
}
pub fn encode_app_object_id(buf: &mut BytesMut, oid: &ObjectIdentifier) {
tags::encode_tag(buf, app_tag::OBJECT_IDENTIFIER, TagClass::Application, 4);
buf.put_slice(&oid.encode());
}
pub fn encode_ctx_unsigned(buf: &mut BytesMut, tag: u8, value: u64) {
let len = unsigned_len(value);
tags::encode_tag(buf, tag, TagClass::Context, len);
encode_unsigned(buf, value);
}
pub fn encode_ctx_signed(buf: &mut BytesMut, tag: u8, value: i32) {
let len = signed_len(value);
tags::encode_tag(buf, tag, TagClass::Context, len);
encode_signed(buf, value);
}
pub fn encode_ctx_real(buf: &mut BytesMut, tag: u8, value: f32) {
tags::encode_tag(buf, tag, TagClass::Context, 4);
encode_real(buf, value);
}
pub fn encode_ctx_double(buf: &mut BytesMut, tag: u8, value: f64) {
tags::encode_tag(buf, tag, TagClass::Context, 8);
encode_double(buf, value);
}
pub fn encode_ctx_enumerated(buf: &mut BytesMut, tag: u8, value: u32) {
let len = unsigned_len(value as u64);
tags::encode_tag(buf, tag, TagClass::Context, len);
encode_unsigned(buf, value as u64);
}
pub fn encode_ctx_boolean(buf: &mut BytesMut, tag: u8, value: bool) {
tags::encode_tag(buf, tag, TagClass::Context, 1);
buf.put_u8(if value { 1 } else { 0 });
}
pub fn encode_ctx_object_id(buf: &mut BytesMut, tag: u8, oid: &ObjectIdentifier) {
tags::encode_tag(buf, tag, TagClass::Context, 4);
buf.put_slice(&oid.encode());
}
pub fn encode_ctx_octet_string(buf: &mut BytesMut, tag: u8, data: &[u8]) {
let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
tags::encode_tag(buf, tag, TagClass::Context, data_len);
buf.put_slice(data);
}
pub fn encode_ctx_character_string(buf: &mut BytesMut, tag: u8, value: &str) -> Result<(), Error> {
let len = character_string_len(value)?;
tags::encode_tag(buf, tag, TagClass::Context, len);
encode_character_string(buf, value);
Ok(())
}
pub fn encode_ctx_date(buf: &mut BytesMut, tag: u8, date: &Date) {
tags::encode_tag(buf, tag, TagClass::Context, 4);
buf.put_slice(&date.encode());
}
pub fn encode_ctx_bit_string(buf: &mut BytesMut, tag: u8, unused_bits: u8, data: &[u8]) {
let data_len = u32::try_from(data.len()).unwrap_or(u32::MAX);
let len = data_len.saturating_add(1);
tags::encode_tag(buf, tag, TagClass::Context, len);
encode_bit_string(buf, unused_bits, data);
}
pub fn decode_application_value(
data: &[u8],
offset: usize,
) -> Result<(PropertyValue, usize), Error> {
let (tag, new_offset) = tags::decode_tag(data, offset)?;
if tag.class == TagClass::Context {
if tag.is_opening {
let (_content, after) = tags::extract_context_value(data, new_offset, tag.number)?;
return Ok((
PropertyValue::ApplicationData(data[offset..after].to_vec()),
after,
));
}
if tag.is_closing {
return Err(Error::invalid_tag(offset, "unexpected closing tag"));
}
let content_end = new_offset
.checked_add(tag.length as usize)
.ok_or_else(|| Error::decoding(new_offset, "length overflow"))?;
if data.len() < content_end {
return Err(Error::buffer_too_short(content_end, data.len()));
}
return Ok((
PropertyValue::ApplicationData(data[offset..content_end].to_vec()),
content_end,
));
}
if tag.is_opening || tag.is_closing {
return Err(Error::invalid_tag(offset, "unexpected opening/closing tag"));
}
if tag.number == app_tag::NULL && tag.length != 0 {
return Err(Error::decoding(
offset,
"application NULL must have no contents",
));
}
if tag.number == app_tag::BOOLEAN && tag.length > 1 {
return Err(Error::decoding(
offset,
"application BOOLEAN L/V/T must be 0 or 1",
));
}
let content_start = new_offset;
let content_len = tag.length as usize;
let content_end = content_start
.checked_add(content_len)
.ok_or_else(|| Error::decoding(content_start, "length overflow"))?;
if tag.number == app_tag::BOOLEAN {
return Ok((PropertyValue::Boolean(tag.length != 0), content_start));
}
let fixed = match tag.number {
app_tag::REAL => Some(("REAL", 4)),
app_tag::DOUBLE => Some(("Double", 8)),
app_tag::DATE => Some(("Date", 4)),
app_tag::TIME => Some(("Time", 4)),
app_tag::OBJECT_IDENTIFIER => Some(("BACnetObjectIdentifier", 4)),
_ => None,
};
if let Some((kind, octets)) = fixed {
if tag.length != octets {
return Err(Error::decoding(
offset,
format!(
"application {kind} has {} contents octets, expected {octets}",
tag.length
),
));
}
}
if data.len() < content_end {
return Err(Error::buffer_too_short(content_end, data.len()));
}
let content = &data[content_start..content_end];
let value = match tag.number {
app_tag::NULL => PropertyValue::Null,
app_tag::UNSIGNED => PropertyValue::Unsigned(decode_unsigned(content)?),
app_tag::SIGNED => PropertyValue::Signed(decode_signed(content)?),
app_tag::REAL => PropertyValue::Real(decode_real(content)?),
app_tag::DOUBLE => PropertyValue::Double(decode_double(content)?),
app_tag::OCTET_STRING => PropertyValue::OctetString(content.to_vec()),
app_tag::CHARACTER_STRING => {
PropertyValue::CharacterString(decode_character_string(content)?)
}
app_tag::BIT_STRING => {
let (unused, bits) = decode_bit_string(content)?;
PropertyValue::BitString {
unused_bits: unused,
data: bits,
}
}
app_tag::ENUMERATED => {
let value = u32::try_from(decode_unsigned(content)?)
.map_err(|_| Error::out_of_range(content_start, "ENUMERATED exceeds u32"))?;
PropertyValue::Enumerated(value)
}
app_tag::DATE => PropertyValue::Date(Date::decode(content)?),
app_tag::TIME => PropertyValue::Time(Time::decode(content)?),
app_tag::OBJECT_IDENTIFIER => {
PropertyValue::ObjectIdentifier(ObjectIdentifier::decode(content)?)
}
other => {
return Err(Error::invalid_tag(
offset,
format!("unknown application tag number {other}"),
));
}
};
Ok((value, content_end))
}
pub fn validate_application_value(data: &[u8], offset: usize) -> Result<usize, Error> {
let (tag, content_start) = tags::decode_tag(data, offset)?;
if tag.class != TagClass::Application || tag.is_opening || tag.is_closing {
return Err(misplaced_tag(
data,
&tag,
None,
offset,
"expected an application-tagged value",
));
}
if tag.number != app_tag::CHARACTER_STRING && tag.number != app_tag::SIGNED {
return decode_application_value(data, offset).map(|(_, end)| end);
}
let content_end = content_start
.checked_add(tag.length as usize)
.ok_or_else(|| Error::decoding(content_start, "length overflow"))?;
if content_end > data.len() {
return Err(Error::buffer_too_short(content_end, data.len()));
}
let content = &data[content_start..content_end];
if tag.number == app_tag::SIGNED {
decode_signed_canonical(content)?;
return Ok(content_end);
}
let Some((&charset_id, payload)) = content.split_first() else {
return Err(Error::decoding(
content_start,
"CharacterString requires a character-set octet",
));
};
match charset_id {
charset::UTF8 | charset::UCS2 | charset::ISO_8859_1 => {
decode_character_string(content)?;
}
charset::IBM_MICROSOFT_DBCS if payload.len() < 2 => {
return Err(Error::decoding(
content_start + 1,
"DBCS CharacterString requires a two-octet code page",
));
}
charset::IBM_MICROSOFT_DBCS | charset::JIS_X_0208 => {}
charset::UCS4 => {
if !payload.len().is_multiple_of(4) {
return Err(Error::decoding(
content_start + 1,
"UCS-4 CharacterString length must be a multiple of four",
));
}
for (index, encoded) in payload.as_chunks::<4>().0.iter().enumerate() {
let code_point = u32::from_be_bytes(*encoded);
if char::from_u32(code_point).is_none() {
return Err(Error::decoding(
content_start + 1 + index * 4,
"invalid UCS-4 code point",
));
}
}
}
_ => {
return Err(Error::decoding(
content_start,
format!("reserved character set: {charset_id}"),
));
}
}
Ok(content_end)
}
pub fn encode_property_value(buf: &mut BytesMut, value: &PropertyValue) -> Result<(), Error> {
match value {
PropertyValue::Null => encode_app_null(buf),
PropertyValue::Boolean(v) => encode_app_boolean(buf, *v),
PropertyValue::Unsigned(v) => encode_app_unsigned(buf, *v),
PropertyValue::Signed(v) => encode_app_signed(buf, *v),
PropertyValue::Real(v) => encode_app_real(buf, *v),
PropertyValue::Double(v) => encode_app_double(buf, *v),
PropertyValue::OctetString(v) => encode_app_octet_string(buf, v),
PropertyValue::CharacterString(v) => encode_app_character_string(buf, v)?,
PropertyValue::BitString { unused_bits, data } => {
encode_app_bit_string(buf, *unused_bits, data)
}
PropertyValue::Enumerated(v) => encode_app_enumerated(buf, *v),
PropertyValue::Date(v) => encode_app_date(buf, v),
PropertyValue::Time(v) => encode_app_time(buf, v),
PropertyValue::ObjectIdentifier(v) => encode_app_object_id(buf, v),
PropertyValue::List(values) => {
for v in values {
encode_property_value(buf, v)?;
}
}
PropertyValue::ApplicationData(bytes) => {
buf.put_slice(bytes);
}
}
Ok(())
}
pub const MAX_TIMESTAMP_SEQUENCE_NUMBER: u64 = 65535;
pub fn encode_timestamp_choice(buf: &mut BytesMut, ts: &BACnetTimeStamp) -> Result<(), Error> {
match ts {
BACnetTimeStamp::Time(t) => {
tags::encode_tag(buf, 0, TagClass::Context, 4);
buf.put_slice(&t.encode());
}
BACnetTimeStamp::SequenceNumber(n) => {
encode_ctx_unsigned(buf, 1, u64::from(*n));
}
BACnetTimeStamp::DateTime { date, time } => {
tags::encode_opening_tag(buf, 2);
encode_app_date(buf, date);
encode_app_time(buf, time);
tags::encode_closing_tag(buf, 2);
}
}
Ok(())
}
pub fn decode_timestamp_choice(
data: &[u8],
offset: usize,
) -> Result<(BACnetTimeStamp, usize), Error> {
use crate::constructed::tagged;
const WHAT: &str = "BACnetTimeStamp";
let (inner_tag, inner_pos) = tags::decode_tag(data, offset)?;
if inner_tag.is_context(0) {
let (octets, end) = tagged::decode_ctx_fixed(data, offset, 0, 4, "Time", WHAT)?;
return Ok((BACnetTimeStamp::Time(Time::decode(octets)?), end));
}
if inner_tag.is_context(1) {
let (octets, end) = tagged::decode_ctx_primitive(data, offset, 1, WHAT)?;
let n = decode_unsigned(octets)?;
if n > MAX_TIMESTAMP_SEQUENCE_NUMBER {
return Err(Error::out_of_range(
inner_pos,
format!("BACnetTimeStamp sequence-number {n} exceeds 65535"),
));
}
let n = u16::try_from(n).expect("BACnetTimeStamp sequence range checked above");
return Ok((BACnetTimeStamp::SequenceNumber(n), end));
}
if inner_tag.is_opening_tag(2) {
const DATE_TIME: &str = "BACnetTimeStamp DateTime";
let (date, date_end) =
tagged::decode_app_fixed(data, inner_pos, app_tag::DATE, 4, DATE_TIME)?;
let (time, time_end) =
tagged::decode_app_fixed(data, date_end, app_tag::TIME, 4, DATE_TIME)?;
let end = tagged::expect_closing(data, time_end, 2, DATE_TIME)?;
let date_time = BACnetTimeStamp::DateTime {
date: Date::decode(date)?,
time: Time::decode(time)?,
};
return Ok((date_time, end));
}
Err(misplaced_tag(
data,
&inner_tag,
None,
offset,
"BACnetTimeStamp: unexpected inner choice tag",
))
}
pub fn encode_timestamp(
buf: &mut BytesMut,
tag_number: u8,
ts: &BACnetTimeStamp,
) -> Result<(), Error> {
tags::encode_opening_tag(buf, tag_number);
encode_timestamp_choice(buf, ts)?;
tags::encode_closing_tag(buf, tag_number);
Ok(())
}
pub fn decode_timestamp(
data: &[u8],
offset: usize,
tag_number: u8,
) -> Result<(BACnetTimeStamp, usize), Error> {
let (tag, pos) = tags::decode_tag(data, offset)?;
if !tag.is_opening_tag(tag_number) {
return Err(misplaced_tag(
data,
&tag,
Some(tag_number),
offset,
format!("expected opening tag {tag_number} for BACnetTimeStamp"),
));
}
let (ts, after_inner) = decode_timestamp_choice(data, pos)?;
let (close, final_pos) = tags::decode_tag(data, after_inner)?;
if !close.is_closing_tag(tag_number) {
return Err(Error::decoding_kind(
unclosed_kind(&close),
after_inner,
format!("expected closing tag {tag_number} for BACnetTimeStamp"),
));
}
Ok((ts, final_pos))
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod length_first_tests;