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)]
30pub struct ObjectIdentifier {
31 object_type: ObjectType,
32 instance_number: u32,
33}
34
35impl ObjectIdentifier {
36 pub const MAX_INSTANCE: u32 = 0x3F_FFFF;
44
45 pub const WILDCARD_INSTANCE: u32 = Self::MAX_INSTANCE;
52
53 pub const MAX_ADDRESSABLE_INSTANCE: u32 = Self::WILDCARD_INSTANCE - 1;
57
58 pub fn new(object_type: ObjectType, instance_number: u32) -> Result<Self, Error> {
69 if instance_number > Self::MAX_INSTANCE {
70 return Err(Error::OutOfRange(format!(
71 "instance number {} exceeds max {}",
72 instance_number,
73 Self::MAX_INSTANCE
74 )));
75 }
76 if object_type.to_raw() > 0x3FF {
77 return Err(Error::OutOfRange(format!(
78 "object type {} exceeds max 1023",
79 object_type.to_raw()
80 )));
81 }
82 Ok(Self {
83 object_type,
84 instance_number,
85 })
86 }
87
88 pub fn new_addressable(object_type: ObjectType, instance_number: u32) -> Result<Self, Error> {
99 let identifier = Self::new(object_type, instance_number)?;
100 if instance_number == Self::WILDCARD_INSTANCE {
101 return Err(Error::OutOfRange(format!(
102 "instance number {} is reserved as the wildcard value; maximum addressable instance is {}",
103 instance_number,
104 Self::MAX_ADDRESSABLE_INSTANCE
105 )));
106 }
107 Ok(identifier)
108 }
109
110 pub const fn object_type(&self) -> ObjectType {
112 self.object_type
113 }
114
115 pub const fn instance_number(&self) -> u32 {
122 self.instance_number
123 }
124
125 pub fn encode(&self) -> [u8; 4] {
127 assert!(
128 self.object_type.to_raw() <= 0x3FF,
129 "ObjectType {} exceeds 10-bit field",
130 self.object_type.to_raw()
131 );
132 assert!(
133 self.instance_number <= Self::MAX_INSTANCE,
134 "Instance {} exceeds MAX_INSTANCE",
135 self.instance_number
136 );
137 let value = (self.object_type.to_raw() << 22) | self.instance_number;
138 value.to_be_bytes()
139 }
140
141 pub fn decode(data: &[u8]) -> Result<Self, Error> {
143 if data.len() != 4 {
144 return Err(Error::decoding(
145 0,
146 format!(
147 "ObjectIdentifier expects exactly 4 bytes, got {}",
148 data.len()
149 ),
150 ));
151 }
152 let value = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
153 let type_raw = (value >> 22) & 0x3FF;
154 let instance = value & Self::MAX_INSTANCE;
155 Ok(Self {
156 object_type: ObjectType::from_raw(type_raw),
157 instance_number: instance,
158 })
159 }
160}
161
162impl core::fmt::Debug for ObjectIdentifier {
163 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
164 write!(
165 f,
166 "ObjectIdentifier({:?}, {})",
167 self.object_type, self.instance_number
168 )
169 }
170}
171
172impl core::fmt::Display for ObjectIdentifier {
173 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
174 write!(f, "{},{}", self.object_type, self.instance_number)
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
189pub struct Date {
190 pub year: u8,
192 pub month: u8,
194 pub day: u8,
196 pub day_of_week: u8,
198}
199
200impl Date {
201 pub const UNSPECIFIED: u8 = 0xFF;
203
204 pub fn encode(&self) -> [u8; 4] {
206 [self.year, self.month, self.day, self.day_of_week]
207 }
208
209 pub fn decode(data: &[u8]) -> Result<Self, Error> {
211 if data.len() != 4 {
212 return Err(Error::decoding(
213 0,
214 format!("Date expects exactly 4 bytes, got {}", data.len()),
215 ));
216 }
217 Ok(Self {
218 year: data[0],
219 month: data[1],
220 day: data[2],
221 day_of_week: data[3],
222 })
223 }
224
225 pub fn actual_year(&self) -> Option<u16> {
227 if self.year == Self::UNSPECIFIED {
228 None
229 } else {
230 Some(1900 + self.year as u16)
231 }
232 }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
243pub struct Time {
244 pub hour: u8,
246 pub minute: u8,
248 pub second: u8,
250 pub hundredths: u8,
252}
253
254impl Time {
255 pub const UNSPECIFIED: u8 = 0xFF;
257
258 pub fn encode(&self) -> [u8; 4] {
260 [self.hour, self.minute, self.second, self.hundredths]
261 }
262
263 pub fn decode(data: &[u8]) -> Result<Self, Error> {
265 if data.len() != 4 {
266 return Err(Error::decoding(
267 0,
268 format!("Time expects exactly 4 bytes, got {}", data.len()),
269 ));
270 }
271 Ok(Self {
272 hour: data[0],
273 minute: data[1],
274 second: data[2],
275 hundredths: data[3],
276 })
277 }
278}
279
280#[derive(Debug, Clone, PartialEq)]
286pub enum BACnetTimeStamp {
287 Time(Time),
289 SequenceNumber(u16),
291 DateTime {
293 date: Date,
295 time: Time,
297 },
298}
299
300bitflags::bitflags! {
305 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
307 pub struct StatusFlags: u8 {
308 const IN_ALARM = 0b1000;
310 const FAULT = 0b0100;
312 const OVERRIDDEN = 0b0010;
314 const OUT_OF_SERVICE = 0b0001;
316 }
317}
318
319impl core::fmt::Display for StatusFlags {
320 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
323 let mut first = true;
324 for (name, _) in self.iter_names() {
325 if !first {
326 f.write_str(" | ")?;
327 }
328 f.write_str(name)?;
329 first = false;
330 }
331 if first {
332 f.write_str("()")?;
333 }
334 Ok(())
335 }
336}
337
338impl core::fmt::Debug for StatusFlags {
339 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
340 core::fmt::Display::fmt(self, f)
341 }
342}
343
344#[derive(Debug, Clone, PartialEq)]
354pub enum PropertyValue {
355 Null,
357 Boolean(bool),
359 Unsigned(u64),
361 Signed(i32),
363 Real(f32),
365 Double(f64),
367 OctetString(Vec<u8>),
369 CharacterString(String),
371 BitString {
373 unused_bits: u8,
375 data: Vec<u8>,
377 },
378 Enumerated(u32),
380 Date(Date),
382 Time(Time),
384 ObjectIdentifier(ObjectIdentifier),
386 List(Vec<PropertyValue>),
392 ApplicationData(Vec<u8>),
406}
407
408impl PropertyValue {
409 pub fn is_primitive(&self) -> bool {
412 match self {
413 Self::Null
414 | Self::Boolean(_)
415 | Self::Unsigned(_)
416 | Self::Signed(_)
417 | Self::Real(_)
418 | Self::Double(_)
419 | Self::OctetString(_)
420 | Self::CharacterString(_)
421 | Self::BitString { .. }
422 | Self::Enumerated(_)
423 | Self::Date(_)
424 | Self::Time(_)
425 | Self::ObjectIdentifier(_) => true,
426 Self::List(_) | Self::ApplicationData(_) => false,
427 }
428 }
429}
430
431#[cfg(test)]
436mod tests {
437 use super::*;
438
439 #[test]
440 fn object_identifier_encode_decode_round_trip() {
441 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
442 let bytes = oid.encode();
443 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
444 assert_eq!(oid, decoded);
445 }
446
447 #[test]
448 fn object_identifier_wire_format() {
449 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
451 assert_eq!(oid.encode(), [0x00, 0x00, 0x00, 0x01]);
452
453 let oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
455 let expected = ((8u32 << 22) | 1234u32).to_be_bytes();
456 assert_eq!(oid.encode(), expected);
457 }
458
459 #[test]
460 fn object_identifier_max_instance() {
461 let oid =
462 ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE).unwrap();
463 assert_eq!(oid.instance_number(), 0x3F_FFFF);
464 let bytes = oid.encode();
465 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
466 assert_eq!(decoded.instance_number(), 0x3F_FFFF);
467 }
468
469 #[test]
470 fn object_identifier_invalid_instance() {
471 let result = ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE + 1);
472 assert!(result.is_err());
473 }
474
475 #[test]
476 fn object_identifier_buffer_too_short() {
477 let result = ObjectIdentifier::decode(&[0x00, 0x00]);
478 assert!(result.is_err());
479 }
480
481 #[test]
482 fn object_identifier_overlong_errors() {
483 assert!(ObjectIdentifier::decode(&[0x00, 0x00, 0x00, 0x01, 0xFF]).is_err());
484 }
485
486 #[test]
487 fn date_encode_decode_round_trip() {
488 let date = Date {
489 year: 124, month: 6,
491 day: 15,
492 day_of_week: 6, };
494 let bytes = date.encode();
495 let decoded = Date::decode(&bytes).unwrap();
496 assert_eq!(date, decoded);
497 assert_eq!(decoded.actual_year(), Some(2024));
498 }
499
500 #[test]
501 fn date_unspecified_year() {
502 let date = Date {
503 year: Date::UNSPECIFIED,
504 month: 1,
505 day: 1,
506 day_of_week: Date::UNSPECIFIED,
507 };
508 assert_eq!(date.actual_year(), None);
509 }
510
511 #[test]
512 fn date_overlong_errors() {
513 assert!(Date::decode(&[124, 6, 15, 6, 0]).is_err());
514 }
515
516 #[test]
517 fn time_encode_decode_round_trip() {
518 let time = Time {
519 hour: 14,
520 minute: 30,
521 second: 45,
522 hundredths: 50,
523 };
524 let bytes = time.encode();
525 let decoded = Time::decode(&bytes).unwrap();
526 assert_eq!(time, decoded);
527 }
528
529 #[test]
530 fn time_overlong_errors() {
531 assert!(Time::decode(&[14, 30, 45, 50, 0]).is_err());
532 }
533
534 #[test]
535 fn status_flags_operations() {
536 let flags = StatusFlags::IN_ALARM | StatusFlags::OUT_OF_SERVICE;
537 assert!(flags.contains(StatusFlags::IN_ALARM));
538 assert!(flags.contains(StatusFlags::OUT_OF_SERVICE));
539 assert!(!flags.contains(StatusFlags::FAULT));
540 assert!(!flags.contains(StatusFlags::OVERRIDDEN));
541 }
542
543 #[test]
546 fn object_identifier_instance_zero() {
547 let oid = ObjectIdentifier::new(ObjectType::DEVICE, 0).unwrap();
549 assert_eq!(oid.instance_number(), 0);
550 let bytes = oid.encode();
551 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
552 assert_eq!(decoded.instance_number(), 0);
553 assert_eq!(decoded.object_type(), ObjectType::DEVICE);
554 }
555
556 #[test]
557 fn object_identifier_all_types_instance_zero() {
558 for type_raw in [0u32, 1, 2, 3, 4, 5, 6, 8, 10, 13, 14, 17, 19] {
560 let obj_type = ObjectType::from_raw(type_raw);
561 let oid = ObjectIdentifier::new(obj_type, 0).unwrap();
562 let bytes = oid.encode();
563 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
564 assert_eq!(decoded.object_type(), obj_type, "type {type_raw} failed");
565 assert_eq!(
566 decoded.instance_number(),
567 0,
568 "type {type_raw} instance failed"
569 );
570 }
571 }
572
573 #[test]
574 fn object_identifier_wildcard_instance() {
575 let oid =
576 ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::WILDCARD_INSTANCE).unwrap();
577 assert_eq!(oid.instance_number(), ObjectIdentifier::MAX_INSTANCE);
578 let bytes = oid.encode();
579 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
580 assert_eq!(decoded.instance_number(), ObjectIdentifier::MAX_INSTANCE);
581
582 let raw_wire_value = ((ObjectType::DEVICE.to_raw() << 22)
583 | ObjectIdentifier::WILDCARD_INSTANCE)
584 .to_be_bytes();
585 let decoded = ObjectIdentifier::decode(&raw_wire_value).unwrap();
586 assert_eq!(decoded.object_type(), ObjectType::DEVICE);
587 assert_eq!(
588 decoded.instance_number(),
589 ObjectIdentifier::WILDCARD_INSTANCE
590 );
591 }
592
593 #[test]
594 fn object_identifier_new_addressable_rejects_wildcard() {
595 assert_eq!(
596 ObjectIdentifier::MAX_ADDRESSABLE_INSTANCE,
597 ObjectIdentifier::WILDCARD_INSTANCE - 1
598 );
599
600 let oid = ObjectIdentifier::new_addressable(
601 ObjectType::DEVICE,
602 ObjectIdentifier::MAX_ADDRESSABLE_INSTANCE,
603 )
604 .unwrap();
605 assert_eq!(
606 oid.instance_number(),
607 ObjectIdentifier::MAX_ADDRESSABLE_INSTANCE
608 );
609
610 let analog_input = ObjectIdentifier::new_addressable(
611 ObjectType::ANALOG_INPUT,
612 ObjectIdentifier::MAX_ADDRESSABLE_INSTANCE,
613 )
614 .unwrap();
615 assert_eq!(analog_input.object_type(), ObjectType::ANALOG_INPUT);
616 assert_eq!(
617 analog_input.instance_number(),
618 ObjectIdentifier::MAX_ADDRESSABLE_INSTANCE
619 );
620
621 let wildcard =
622 ObjectIdentifier::new_addressable(ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE)
623 .unwrap_err();
624 assert!(wildcard.to_string().contains("wildcard"));
625
626 let too_large = ObjectIdentifier::new_addressable(
627 ObjectType::DEVICE,
628 ObjectIdentifier::MAX_INSTANCE + 1,
629 );
630 assert!(too_large.is_err());
631 }
632
633 #[test]
634 fn object_identifier_decode_extra_bytes_errors() {
635 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 42).unwrap();
636 let mut bytes = oid.encode().to_vec();
637 bytes.extend_from_slice(&[0xFF, 0xFF]);
638 assert!(ObjectIdentifier::decode(&bytes).is_err());
639 }
640
641 #[test]
642 fn object_identifier_new_rejects_type_overflow() {
643 for raw in [1024, u32::MAX] {
644 assert!(matches!(
645 ObjectIdentifier::new(ObjectType::from_raw(raw), 0),
646 Err(Error::OutOfRange(_))
647 ));
648 }
649 }
650
651 #[test]
652 fn object_identifier_addressable_rejects_type_overflow() {
653 for raw in [1024, u32::MAX] {
654 assert!(matches!(
655 ObjectIdentifier::new_addressable(ObjectType::from_raw(raw), 0),
656 Err(Error::OutOfRange(_))
657 ));
658 }
659 }
660
661 #[test]
662 fn object_identifier_checked_instance_boundaries() {
663 for instance in [ObjectIdentifier::MAX_INSTANCE + 1, u32::MAX] {
664 for constructor in [ObjectIdentifier::new, ObjectIdentifier::new_addressable] {
665 assert!(matches!(
666 constructor(ObjectType::from_raw(1023), instance),
667 Err(Error::OutOfRange(_))
668 ));
669 }
670 }
671 }
672
673 #[test]
674 fn object_identifier_wire_boundaries_preserve_identity() {
675 for (raw_type, instance, wire) in [
676 (0, 0, [0, 0, 0, 0]),
677 (1023, 0, [0xff, 0xc0, 0, 0]),
678 (
679 1023,
680 ObjectIdentifier::MAX_ADDRESSABLE_INSTANCE,
681 [0xff, 0xff, 0xff, 0xfe],
682 ),
683 (
684 1023,
685 ObjectIdentifier::MAX_INSTANCE,
686 [0xff, 0xff, 0xff, 0xff],
687 ),
688 ] {
689 let object_type = ObjectType::from_raw(raw_type);
690 let oid = ObjectIdentifier::new(object_type, instance).unwrap();
691 assert_eq!(oid.encode(), wire);
692 assert_eq!(ObjectIdentifier::decode(&wire).unwrap(), oid);
693 if instance == ObjectIdentifier::WILDCARD_INSTANCE {
694 assert!(ObjectIdentifier::new_addressable(object_type, instance).is_err());
695 } else {
696 assert_eq!(
697 ObjectIdentifier::new_addressable(object_type, instance).unwrap(),
698 oid
699 );
700 }
701 }
702 assert_eq!(ObjectType::from_raw(u32::MAX).to_raw(), u32::MAX);
704 }
705
706 #[test]
707 fn property_value_variants() {
708 let null = PropertyValue::Null;
709 let boolean = PropertyValue::Boolean(true);
710 let real = PropertyValue::Real(72.5);
711 let string = PropertyValue::CharacterString("test".into());
712
713 assert_eq!(null, PropertyValue::Null);
714 assert_eq!(boolean, PropertyValue::Boolean(true));
715 assert_ne!(real, PropertyValue::Real(73.0));
716 assert_eq!(string, PropertyValue::CharacterString("test".into()));
717 }
718}