1#[cfg(not(feature = "std"))]
7use alloc::{string::String, vec::Vec};
8
9use crate::enums::ObjectType;
10use crate::error::Error;
11
12#[derive(Clone, Copy, PartialEq, Eq, Hash)]
21pub struct ObjectIdentifier {
22 object_type: ObjectType,
23 instance_number: u32,
24}
25
26impl ObjectIdentifier {
27 pub const MAX_INSTANCE: u32 = 0x3F_FFFF;
29
30 pub const WILDCARD_INSTANCE: u32 = Self::MAX_INSTANCE;
32
33 pub fn new(object_type: ObjectType, instance_number: u32) -> Result<Self, Error> {
38 if instance_number > Self::MAX_INSTANCE {
39 return Err(Error::OutOfRange(alloc_or_std_format!(
40 "instance number {} exceeds max {}",
41 instance_number,
42 Self::MAX_INSTANCE
43 )));
44 }
45 Ok(Self {
46 object_type,
47 instance_number,
48 })
49 }
50
51 pub const fn new_unchecked(object_type: ObjectType, instance_number: u32) -> Self {
53 Self {
54 object_type,
55 instance_number,
56 }
57 }
58
59 pub const fn object_type(&self) -> ObjectType {
61 self.object_type
62 }
63
64 pub const fn instance_number(&self) -> u32 {
66 self.instance_number
67 }
68
69 pub fn encode(&self) -> [u8; 4] {
71 debug_assert!(
72 self.object_type.to_raw() <= 0x3FF,
73 "ObjectType {} exceeds 10-bit field",
74 self.object_type.to_raw()
75 );
76 debug_assert!(
77 self.instance_number <= Self::MAX_INSTANCE,
78 "Instance {} exceeds MAX_INSTANCE",
79 self.instance_number
80 );
81 let value = ((self.object_type.to_raw() & 0x3FF) << 22)
82 | (self.instance_number & Self::MAX_INSTANCE);
83 value.to_be_bytes()
84 }
85
86 pub fn decode(data: &[u8]) -> Result<Self, Error> {
88 if data.len() < 4 {
89 return Err(Error::buffer_too_short(4, data.len()));
90 }
91 let value = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
92 let type_raw = (value >> 22) & 0x3FF;
93 let instance = value & Self::MAX_INSTANCE;
94 Ok(Self {
95 object_type: ObjectType::from_raw(type_raw),
96 instance_number: instance,
97 })
98 }
99}
100
101impl core::fmt::Debug for ObjectIdentifier {
102 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
103 write!(
104 f,
105 "ObjectIdentifier({:?}, {})",
106 self.object_type, self.instance_number
107 )
108 }
109}
110
111impl core::fmt::Display for ObjectIdentifier {
112 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
113 write!(f, "{},{}", self.object_type, self.instance_number)
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
128pub struct Date {
129 pub year: u8,
131 pub month: u8,
133 pub day: u8,
135 pub day_of_week: u8,
137}
138
139impl Date {
140 pub const UNSPECIFIED: u8 = 0xFF;
142
143 pub fn encode(&self) -> [u8; 4] {
145 [self.year, self.month, self.day, self.day_of_week]
146 }
147
148 pub fn decode(data: &[u8]) -> Result<Self, Error> {
150 if data.len() < 4 {
151 return Err(Error::buffer_too_short(4, data.len()));
152 }
153 Ok(Self {
154 year: data[0],
155 month: data[1],
156 day: data[2],
157 day_of_week: data[3],
158 })
159 }
160
161 pub fn actual_year(&self) -> Option<u16> {
163 if self.year == Self::UNSPECIFIED {
164 None
165 } else {
166 Some(1900 + self.year as u16)
167 }
168 }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
179pub struct Time {
180 pub hour: u8,
182 pub minute: u8,
184 pub second: u8,
186 pub hundredths: u8,
188}
189
190impl Time {
191 pub const UNSPECIFIED: u8 = 0xFF;
193
194 pub fn encode(&self) -> [u8; 4] {
196 [self.hour, self.minute, self.second, self.hundredths]
197 }
198
199 pub fn decode(data: &[u8]) -> Result<Self, Error> {
201 if data.len() < 4 {
202 return Err(Error::buffer_too_short(4, data.len()));
203 }
204 Ok(Self {
205 hour: data[0],
206 minute: data[1],
207 second: data[2],
208 hundredths: data[3],
209 })
210 }
211}
212
213#[derive(Debug, Clone, PartialEq)]
219pub enum BACnetTimeStamp {
220 Time(Time),
222 SequenceNumber(u64),
224 DateTime { date: Date, time: Time },
226}
227
228bitflags::bitflags! {
233 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
235 pub struct StatusFlags: u8 {
236 const IN_ALARM = 0b1000;
237 const FAULT = 0b0100;
238 const OVERRIDDEN = 0b0010;
239 const OUT_OF_SERVICE = 0b0001;
240 }
241}
242
243bitflags::bitflags! {
244 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
248 pub struct DaysOfWeek: u8 {
249 const MONDAY = 0b0100_0000;
250 const TUESDAY = 0b0010_0000;
251 const WEDNESDAY = 0b0001_0000;
252 const THURSDAY = 0b0000_1000;
253 const FRIDAY = 0b0000_0100;
254 const SATURDAY = 0b0000_0010;
255 const SUNDAY = 0b0000_0001;
256 const ALL = 0b0111_1111;
257 }
258}
259
260#[derive(Debug, Clone, PartialEq)]
270pub enum PropertyValue {
271 Null,
273 Boolean(bool),
275 Unsigned(u64),
277 Signed(i32),
279 Real(f32),
281 Double(f64),
283 OctetString(Vec<u8>),
285 CharacterString(String),
287 BitString {
289 unused_bits: u8,
291 data: Vec<u8>,
293 },
294 Enumerated(u32),
296 Date(Date),
298 Time(Time),
300 ObjectIdentifier(ObjectIdentifier),
302 List(Vec<PropertyValue>),
308}
309
310#[cfg(feature = "std")]
316macro_rules! alloc_or_std_format {
317 ($($arg:tt)*) => { format!($($arg)*) }
318}
319
320#[cfg(not(feature = "std"))]
321macro_rules! alloc_or_std_format {
322 ($($arg:tt)*) => { alloc::format!($($arg)*) }
323}
324
325use alloc_or_std_format;
326
327#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn object_identifier_encode_decode_round_trip() {
337 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
338 let bytes = oid.encode();
339 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
340 assert_eq!(oid, decoded);
341 }
342
343 #[test]
344 fn object_identifier_wire_format() {
345 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
347 assert_eq!(oid.encode(), [0x00, 0x00, 0x00, 0x01]);
348
349 let oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
351 let expected = ((8u32 << 22) | 1234u32).to_be_bytes();
352 assert_eq!(oid.encode(), expected);
353 }
354
355 #[test]
356 fn object_identifier_max_instance() {
357 let oid =
358 ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE).unwrap();
359 assert_eq!(oid.instance_number(), 0x3F_FFFF);
360 let bytes = oid.encode();
361 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
362 assert_eq!(decoded.instance_number(), 0x3F_FFFF);
363 }
364
365 #[test]
366 fn object_identifier_invalid_instance() {
367 let result = ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE + 1);
368 assert!(result.is_err());
369 }
370
371 #[test]
372 fn object_identifier_buffer_too_short() {
373 let result = ObjectIdentifier::decode(&[0x00, 0x00]);
374 assert!(result.is_err());
375 }
376
377 #[test]
378 fn date_encode_decode_round_trip() {
379 let date = Date {
380 year: 124, month: 6,
382 day: 15,
383 day_of_week: 6, };
385 let bytes = date.encode();
386 let decoded = Date::decode(&bytes).unwrap();
387 assert_eq!(date, decoded);
388 assert_eq!(decoded.actual_year(), Some(2024));
389 }
390
391 #[test]
392 fn date_unspecified_year() {
393 let date = Date {
394 year: Date::UNSPECIFIED,
395 month: 1,
396 day: 1,
397 day_of_week: Date::UNSPECIFIED,
398 };
399 assert_eq!(date.actual_year(), None);
400 }
401
402 #[test]
403 fn time_encode_decode_round_trip() {
404 let time = Time {
405 hour: 14,
406 minute: 30,
407 second: 45,
408 hundredths: 50,
409 };
410 let bytes = time.encode();
411 let decoded = Time::decode(&bytes).unwrap();
412 assert_eq!(time, decoded);
413 }
414
415 #[test]
416 fn status_flags_operations() {
417 let flags = StatusFlags::IN_ALARM | StatusFlags::OUT_OF_SERVICE;
418 assert!(flags.contains(StatusFlags::IN_ALARM));
419 assert!(flags.contains(StatusFlags::OUT_OF_SERVICE));
420 assert!(!flags.contains(StatusFlags::FAULT));
421 assert!(!flags.contains(StatusFlags::OVERRIDDEN));
422 }
423
424 #[test]
427 fn object_identifier_instance_zero() {
428 let oid = ObjectIdentifier::new(ObjectType::DEVICE, 0).unwrap();
430 assert_eq!(oid.instance_number(), 0);
431 let bytes = oid.encode();
432 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
433 assert_eq!(decoded.instance_number(), 0);
434 assert_eq!(decoded.object_type(), ObjectType::DEVICE);
435 }
436
437 #[test]
438 fn object_identifier_all_types_instance_zero() {
439 for type_raw in [0u32, 1, 2, 3, 4, 5, 6, 8, 10, 13, 14, 17, 19] {
441 let obj_type = ObjectType::from_raw(type_raw);
442 let oid = ObjectIdentifier::new(obj_type, 0).unwrap();
443 let bytes = oid.encode();
444 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
445 assert_eq!(decoded.object_type(), obj_type, "type {type_raw} failed");
446 assert_eq!(
447 decoded.instance_number(),
448 0,
449 "type {type_raw} instance failed"
450 );
451 }
452 }
453
454 #[test]
455 fn object_identifier_wildcard_instance() {
456 let oid =
457 ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::WILDCARD_INSTANCE).unwrap();
458 assert_eq!(oid.instance_number(), ObjectIdentifier::MAX_INSTANCE);
459 let bytes = oid.encode();
460 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
461 assert_eq!(decoded.instance_number(), ObjectIdentifier::MAX_INSTANCE);
462 }
463
464 #[test]
465 fn object_identifier_decode_extra_bytes_ignored() {
466 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 42).unwrap();
468 let mut bytes = oid.encode().to_vec();
469 bytes.extend_from_slice(&[0xFF, 0xFF]); let decoded = ObjectIdentifier::decode(&bytes).unwrap();
471 assert_eq!(decoded, oid);
472 }
473
474 #[test]
475 #[cfg_attr(debug_assertions, should_panic(expected = "exceeds 10-bit field"))]
476 fn object_identifier_type_overflow_round_trip() {
477 let oid = ObjectIdentifier::new_unchecked(ObjectType::from_raw(1024), 0);
480 let bytes = oid.encode();
481 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
482 assert_eq!(decoded.object_type(), ObjectType::from_raw(0));
483 }
484
485 #[test]
486 fn property_value_variants() {
487 let null = PropertyValue::Null;
488 let boolean = PropertyValue::Boolean(true);
489 let real = PropertyValue::Real(72.5);
490 let string = PropertyValue::CharacterString("test".into());
491
492 assert_eq!(null, PropertyValue::Null);
493 assert_eq!(boolean, PropertyValue::Boolean(true));
494 assert_ne!(real, PropertyValue::Real(73.0));
495 assert_eq!(string, PropertyValue::CharacterString("test".into()));
496 }
497}