#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use crate::enums::ObjectType;
use crate::error::Error;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ObjectIdentifier {
object_type: ObjectType,
instance_number: u32,
}
impl ObjectIdentifier {
pub const MAX_INSTANCE: u32 = 0x3F_FFFF;
pub const WILDCARD_INSTANCE: u32 = Self::MAX_INSTANCE;
pub fn new(object_type: ObjectType, instance_number: u32) -> Result<Self, Error> {
if instance_number > Self::MAX_INSTANCE {
return Err(Error::OutOfRange(alloc_or_std_format!(
"instance number {} exceeds max {}",
instance_number,
Self::MAX_INSTANCE
)));
}
Ok(Self {
object_type,
instance_number,
})
}
pub const fn new_unchecked(object_type: ObjectType, instance_number: u32) -> Self {
Self {
object_type,
instance_number,
}
}
pub const fn object_type(&self) -> ObjectType {
self.object_type
}
pub const fn instance_number(&self) -> u32 {
self.instance_number
}
pub fn encode(&self) -> [u8; 4] {
debug_assert!(
self.object_type.to_raw() <= 0x3FF,
"ObjectType {} exceeds 10-bit field",
self.object_type.to_raw()
);
debug_assert!(
self.instance_number <= Self::MAX_INSTANCE,
"Instance {} exceeds MAX_INSTANCE",
self.instance_number
);
let value = ((self.object_type.to_raw() & 0x3FF) << 22)
| (self.instance_number & Self::MAX_INSTANCE);
value.to_be_bytes()
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
if data.len() != 4 {
return Err(Error::decoding(
0,
format!(
"ObjectIdentifier expects exactly 4 bytes, got {}",
data.len()
),
));
}
let value = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let type_raw = (value >> 22) & 0x3FF;
let instance = value & Self::MAX_INSTANCE;
Ok(Self {
object_type: ObjectType::from_raw(type_raw),
instance_number: instance,
})
}
}
impl core::fmt::Debug for ObjectIdentifier {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"ObjectIdentifier({:?}, {})",
self.object_type, self.instance_number
)
}
}
impl core::fmt::Display for ObjectIdentifier {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{},{}", self.object_type, self.instance_number)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Date {
pub year: u8,
pub month: u8,
pub day: u8,
pub day_of_week: u8,
}
impl Date {
pub const UNSPECIFIED: u8 = 0xFF;
pub fn encode(&self) -> [u8; 4] {
[self.year, self.month, self.day, self.day_of_week]
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
if data.len() != 4 {
return Err(Error::decoding(
0,
format!("Date expects exactly 4 bytes, got {}", data.len()),
));
}
Ok(Self {
year: data[0],
month: data[1],
day: data[2],
day_of_week: data[3],
})
}
pub fn actual_year(&self) -> Option<u16> {
if self.year == Self::UNSPECIFIED {
None
} else {
Some(1900 + self.year as u16)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Time {
pub hour: u8,
pub minute: u8,
pub second: u8,
pub hundredths: u8,
}
impl Time {
pub const UNSPECIFIED: u8 = 0xFF;
pub fn encode(&self) -> [u8; 4] {
[self.hour, self.minute, self.second, self.hundredths]
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
if data.len() != 4 {
return Err(Error::decoding(
0,
format!("Time expects exactly 4 bytes, got {}", data.len()),
));
}
Ok(Self {
hour: data[0],
minute: data[1],
second: data[2],
hundredths: data[3],
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BACnetTimeStamp {
Time(Time),
SequenceNumber(u64),
DateTime { date: Date, time: Time },
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StatusFlags: u8 {
const IN_ALARM = 0b1000;
const FAULT = 0b0100;
const OVERRIDDEN = 0b0010;
const OUT_OF_SERVICE = 0b0001;
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DaysOfWeek: u8 {
const MONDAY = 0b0100_0000;
const TUESDAY = 0b0010_0000;
const WEDNESDAY = 0b0001_0000;
const THURSDAY = 0b0000_1000;
const FRIDAY = 0b0000_0100;
const SATURDAY = 0b0000_0010;
const SUNDAY = 0b0000_0001;
const ALL = 0b0111_1111;
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PropertyValue {
Null,
Boolean(bool),
Unsigned(u64),
Signed(i32),
Real(f32),
Double(f64),
OctetString(Vec<u8>),
CharacterString(String),
BitString {
unused_bits: u8,
data: Vec<u8>,
},
Enumerated(u32),
Date(Date),
Time(Time),
ObjectIdentifier(ObjectIdentifier),
List(Vec<PropertyValue>),
}
#[cfg(feature = "std")]
macro_rules! alloc_or_std_format {
($($arg:tt)*) => { format!($($arg)*) }
}
#[cfg(not(feature = "std"))]
macro_rules! alloc_or_std_format {
($($arg:tt)*) => { alloc::format!($($arg)*) }
}
use alloc_or_std_format;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn object_identifier_encode_decode_round_trip() {
let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
let bytes = oid.encode();
let decoded = ObjectIdentifier::decode(&bytes).unwrap();
assert_eq!(oid, decoded);
}
#[test]
fn object_identifier_wire_format() {
let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
assert_eq!(oid.encode(), [0x00, 0x00, 0x00, 0x01]);
let oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
let expected = ((8u32 << 22) | 1234u32).to_be_bytes();
assert_eq!(oid.encode(), expected);
}
#[test]
fn object_identifier_max_instance() {
let oid =
ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE).unwrap();
assert_eq!(oid.instance_number(), 0x3F_FFFF);
let bytes = oid.encode();
let decoded = ObjectIdentifier::decode(&bytes).unwrap();
assert_eq!(decoded.instance_number(), 0x3F_FFFF);
}
#[test]
fn object_identifier_invalid_instance() {
let result = ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE + 1);
assert!(result.is_err());
}
#[test]
fn object_identifier_buffer_too_short() {
let result = ObjectIdentifier::decode(&[0x00, 0x00]);
assert!(result.is_err());
}
#[test]
fn object_identifier_overlong_errors() {
assert!(ObjectIdentifier::decode(&[0x00, 0x00, 0x00, 0x01, 0xFF]).is_err());
}
#[test]
fn date_encode_decode_round_trip() {
let date = Date {
year: 124, month: 6,
day: 15,
day_of_week: 6, };
let bytes = date.encode();
let decoded = Date::decode(&bytes).unwrap();
assert_eq!(date, decoded);
assert_eq!(decoded.actual_year(), Some(2024));
}
#[test]
fn date_unspecified_year() {
let date = Date {
year: Date::UNSPECIFIED,
month: 1,
day: 1,
day_of_week: Date::UNSPECIFIED,
};
assert_eq!(date.actual_year(), None);
}
#[test]
fn date_overlong_errors() {
assert!(Date::decode(&[124, 6, 15, 6, 0]).is_err());
}
#[test]
fn time_encode_decode_round_trip() {
let time = Time {
hour: 14,
minute: 30,
second: 45,
hundredths: 50,
};
let bytes = time.encode();
let decoded = Time::decode(&bytes).unwrap();
assert_eq!(time, decoded);
}
#[test]
fn time_overlong_errors() {
assert!(Time::decode(&[14, 30, 45, 50, 0]).is_err());
}
#[test]
fn status_flags_operations() {
let flags = StatusFlags::IN_ALARM | StatusFlags::OUT_OF_SERVICE;
assert!(flags.contains(StatusFlags::IN_ALARM));
assert!(flags.contains(StatusFlags::OUT_OF_SERVICE));
assert!(!flags.contains(StatusFlags::FAULT));
assert!(!flags.contains(StatusFlags::OVERRIDDEN));
}
#[test]
fn object_identifier_instance_zero() {
let oid = ObjectIdentifier::new(ObjectType::DEVICE, 0).unwrap();
assert_eq!(oid.instance_number(), 0);
let bytes = oid.encode();
let decoded = ObjectIdentifier::decode(&bytes).unwrap();
assert_eq!(decoded.instance_number(), 0);
assert_eq!(decoded.object_type(), ObjectType::DEVICE);
}
#[test]
fn object_identifier_all_types_instance_zero() {
for type_raw in [0u32, 1, 2, 3, 4, 5, 6, 8, 10, 13, 14, 17, 19] {
let obj_type = ObjectType::from_raw(type_raw);
let oid = ObjectIdentifier::new(obj_type, 0).unwrap();
let bytes = oid.encode();
let decoded = ObjectIdentifier::decode(&bytes).unwrap();
assert_eq!(decoded.object_type(), obj_type, "type {type_raw} failed");
assert_eq!(
decoded.instance_number(),
0,
"type {type_raw} instance failed"
);
}
}
#[test]
fn object_identifier_wildcard_instance() {
let oid =
ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::WILDCARD_INSTANCE).unwrap();
assert_eq!(oid.instance_number(), ObjectIdentifier::MAX_INSTANCE);
let bytes = oid.encode();
let decoded = ObjectIdentifier::decode(&bytes).unwrap();
assert_eq!(decoded.instance_number(), ObjectIdentifier::MAX_INSTANCE);
}
#[test]
fn object_identifier_decode_extra_bytes_errors() {
let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 42).unwrap();
let mut bytes = oid.encode().to_vec();
bytes.extend_from_slice(&[0xFF, 0xFF]);
assert!(ObjectIdentifier::decode(&bytes).is_err());
}
#[test]
#[cfg_attr(debug_assertions, should_panic(expected = "exceeds 10-bit field"))]
fn object_identifier_type_overflow_round_trip() {
let oid = ObjectIdentifier::new_unchecked(ObjectType::from_raw(1024), 0);
let bytes = oid.encode();
let decoded = ObjectIdentifier::decode(&bytes).unwrap();
assert_eq!(decoded.object_type(), ObjectType::from_raw(0));
}
#[test]
fn property_value_variants() {
let null = PropertyValue::Null;
let boolean = PropertyValue::Boolean(true);
let real = PropertyValue::Real(72.5);
let string = PropertyValue::CharacterString("test".into());
assert_eq!(null, PropertyValue::Null);
assert_eq!(boolean, PropertyValue::Boolean(true));
assert_ne!(real, PropertyValue::Real(73.0));
assert_eq!(string, PropertyValue::CharacterString("test".into()));
}
}