1#[cfg(not(feature = "std"))]
26use alloc::{string::String, vec::Vec};
27
28use crate::bitstring::{EventTransitionBits, LimitEnable, ObjectTypesSupported, ServicesSupported};
29use crate::primitives::{Date, ObjectIdentifier, PropertyValue, StatusFlags, Time};
30
31use super::*;
32
33macro_rules! resolved_enum {
34 ($($variant:ident($ty:ty)),+ $(,)?) => {
35 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
43 pub enum ResolvedEnum {
44 $(
45 #[doc = concat!("A [`", stringify!($ty), "`] value.")]
46 $variant($ty),
47 )+
48 Unknown(u32),
50 }
51
52 impl core::fmt::Display for ResolvedEnum {
53 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54 match self {
55 $( Self::$variant(v) => core::fmt::Display::fmt(v, f), )+
56 Self::Unknown(n) => write!(f, "{n}"),
57 }
58 }
59 }
60 };
61}
62
63resolved_enum! {
64 ObjectType(ObjectType),
65 EventState(EventState),
66 EventType(EventType),
67 FaultType(FaultType),
68 NotifyType(NotifyType),
69 Reliability(Reliability),
70 DeviceStatus(DeviceStatus),
71 Segmentation(Segmentation),
72 EngineeringUnits(EngineeringUnits),
73 Polarity(Polarity),
74 BinaryPV(BinaryPV),
75 ProgramState(ProgramState),
76 ProgramChange(ProgramChange),
77 ProgramError(ProgramError),
78 NodeType(NodeType),
79 LoggingType(LoggingType),
80 FileAccessMethod(FileAccessMethod),
81 BackupAndRestoreState(BackupAndRestoreState),
82 RestartReason(RestartReason),
83 LifeSafetyMode(LifeSafetyMode),
84 LifeSafetyState(LifeSafetyState),
85 LifeSafetyOperation(LifeSafetyOperation),
86 SilencedState(SilencedState),
87 Maintenance(Maintenance),
88 DoorStatus(DoorStatus),
89 DoorAlarmState(DoorAlarmState),
90 DoorSecuredStatus(DoorSecuredStatus),
91 LockStatus(LockStatus),
92 AccessEvent(AccessEvent),
93 AuthorizationMode(AuthorizationMode),
94 AccessPassbackMode(AccessPassbackMode),
95 AccessUserType(AccessUserType),
96 AccessCredentialDisable(AccessCredentialDisable),
97 AccessCredentialDisableReason(AccessCredentialDisableReason),
98 AuthenticationStatus(AuthenticationStatus),
99 AuthorizationExemption(AuthorizationExemption),
100 AccessZoneOccupancyState(AccessZoneOccupancyState),
101 Relationship(Relationship),
102 NetworkType(NetworkType),
103 NetworkNumberQuality(NetworkNumberQuality),
104 IPMode(IPMode),
105 NetworkPortCommand(NetworkPortCommand),
106 ProtocolLevel(ProtocolLevel),
107 LightingInProgress(LightingInProgress),
108 LightingTransition(LightingTransition),
109 TimerState(TimerState),
110 TimerTransition(TimerTransition),
111 WriteStatus(WriteStatus),
112 LiftCarMode(LiftCarMode),
113 LiftGroupMode(LiftGroupMode),
114 EscalatorMode(EscalatorMode),
115 EscalatorOperationDirection(EscalatorOperationDirection),
116 LiftCarDriveStatus(LiftCarDriveStatus),
117 LiftCarDirection(LiftCarDirection),
118 LiftCarDoorCommand(LiftCarDoorCommand),
119 AuditLevel(AuditLevel),
120}
121
122impl ResolvedEnum {
123 pub fn from_property(property: PropertyIdentifier, value: u32) -> Self {
131 match property.to_raw() {
132 79 => Self::ObjectType(ObjectType::from_raw(value)), 36 => Self::EventState(EventState::from_raw(value)), 37 => Self::EventType(EventType::from_raw(value)), 359 => Self::FaultType(FaultType::from_raw(value)), 72 => Self::NotifyType(NotifyType::from_raw(value)), 103 => Self::Reliability(Reliability::from_raw(value)), 112 => Self::DeviceStatus(DeviceStatus::from_raw(value)), 107 if value <= u8::MAX as u32 => {
143 Self::Segmentation(Segmentation::from_raw(value as u8))
144 }
145
146 20 | 27 | 50 | 82 | 94 | 117 | 455 => {
148 Self::EngineeringUnits(EngineeringUnits::from_raw(value))
149 }
150
151 84 => Self::Polarity(Polarity::from_raw(value)), 6 => Self::BinaryPV(BinaryPV::from_raw(value)),
154 92 => Self::ProgramState(ProgramState::from_raw(value)), 90 => Self::ProgramChange(ProgramChange::from_raw(value)), 100 => Self::ProgramError(ProgramError::from_raw(value)), 208 => Self::NodeType(NodeType::from_raw(value)), 197 => Self::LoggingType(LoggingType::from_raw(value)), 41 => Self::FileAccessMethod(FileAccessMethod::from_raw(value)), 338 => Self::BackupAndRestoreState(BackupAndRestoreState::from_raw(value)), 196 => Self::RestartReason(RestartReason::from_raw(value)), 160 | 175 => Self::LifeSafetyMode(LifeSafetyMode::from_raw(value)), 164 => Self::LifeSafetyState(LifeSafetyState::from_raw(value)),
168 161 => Self::LifeSafetyOperation(LifeSafetyOperation::from_raw(value)), 163 => Self::SilencedState(SilencedState::from_raw(value)), 158 => Self::Maintenance(Maintenance::from_raw(value)),
173
174 231 => Self::DoorStatus(DoorStatus::from_raw(value)), 226 => Self::DoorAlarmState(DoorAlarmState::from_raw(value)), 233 => Self::LockStatus(LockStatus::from_raw(value)), 235 => Self::DoorSecuredStatus(DoorSecuredStatus::from_raw(value)), 247 | 275 => Self::AccessEvent(AccessEvent::from_raw(value)), 261 => Self::AuthorizationMode(AuthorizationMode::from_raw(value)), 300 => Self::AccessPassbackMode(AccessPassbackMode::from_raw(value)), 318 => Self::AccessUserType(AccessUserType::from_raw(value)), 263 => Self::AccessCredentialDisable(AccessCredentialDisable::from_raw(value)), 264 => Self::BinaryPV(BinaryPV::from_raw(value)),
189 303 => {
190 Self::AccessCredentialDisableReason(AccessCredentialDisableReason::from_raw(value))
191 } 260 => Self::AuthenticationStatus(AuthenticationStatus::from_raw(value)), 296 => Self::AccessZoneOccupancyState(AccessZoneOccupancyState::from_raw(value)), 364 => Self::AuthorizationExemption(AuthorizationExemption::from_raw(value)),
197
198 427 => Self::NetworkType(NetworkType::from_raw(value)), 426 => Self::NetworkNumberQuality(NetworkNumberQuality::from_raw(value)), 408 | 435 => Self::IPMode(IPMode::from_raw(value)), 417 => Self::NetworkPortCommand(NetworkPortCommand::from_raw(value)), 482 => Self::ProtocolLevel(ProtocolLevel::from_raw(value)), 489 | 490 => Self::Relationship(Relationship::from_raw(value)),
211
212 378 => Self::LightingInProgress(LightingInProgress::from_raw(value)), 385 => Self::LightingTransition(LightingTransition::from_raw(value)), 398 => Self::TimerState(TimerState::from_raw(value)), 395 => Self::TimerTransition(TimerTransition::from_raw(value)), 370 => Self::WriteStatus(WriteStatus::from_raw(value)), 456 => Self::LiftCarMode(LiftCarMode::from_raw(value)), 467 => Self::LiftGroupMode(LiftGroupMode::from_raw(value)), 462 => Self::EscalatorMode(EscalatorMode::from_raw(value)), 477 => Self::EscalatorOperationDirection(EscalatorOperationDirection::from_raw(value)), 453 => Self::LiftCarDriveStatus(LiftCarDriveStatus::from_raw(value)), 448 | 457 => Self::LiftCarDirection(LiftCarDirection::from_raw(value)), 449 => Self::LiftCarDoorCommand(LiftCarDoorCommand::from_raw(value)), 450 => Self::DoorStatus(DoorStatus::from_raw(value)),
231
232 498 => Self::AuditLevel(AuditLevel::from_raw(value)), _ => Self::Unknown(value),
236 }
237 }
238}
239
240pub fn resolve_enum(property: PropertyIdentifier, value: &PropertyValue) -> Option<ResolvedEnum> {
265 match value {
266 PropertyValue::Enumerated(n) => Some(ResolvedEnum::from_property(property, *n)),
267 _ => None,
268 }
269}
270
271#[derive(Clone, PartialEq, Eq, Debug)]
279pub enum ResolvedBits {
280 StatusFlags(StatusFlags),
282 EventTransitionBits(EventTransitionBits),
284 LimitEnable(LimitEnable),
286 ServicesSupported(ServicesSupported),
288 ObjectTypesSupported(ObjectTypesSupported),
290 Unknown {
292 unused_bits: u8,
294 data: Vec<u8>,
296 },
297}
298
299impl ResolvedBits {
300 pub fn from_property(property: PropertyIdentifier, unused_bits: u8, data: &[u8]) -> Self {
308 match property.to_raw() {
309 111 | 347 => Self::StatusFlags(crate::bitstring::status_flags_from_bacnet(data)),
311
312 0 | 1 | 35 => Self::EventTransitionBits(EventTransitionBits::from_bacnet(data)),
314
315 52 => Self::LimitEnable(LimitEnable::from_bacnet(data)),
317
318 97 => Self::ServicesSupported(ServicesSupported::from_bacnet(data)),
320
321 96 => Self::ObjectTypesSupported(ObjectTypesSupported::from_bacnet(data)),
323
324 _ => Self::Unknown {
325 unused_bits,
326 data: data.to_vec(),
327 },
328 }
329 }
330}
331
332impl core::fmt::Display for ResolvedBits {
333 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
334 match self {
335 Self::StatusFlags(v) => core::fmt::Display::fmt(v, f),
336 Self::EventTransitionBits(v) => core::fmt::Display::fmt(v, f),
337 Self::LimitEnable(v) => core::fmt::Display::fmt(v, f),
338 Self::ServicesSupported(v) => core::fmt::Display::fmt(v, f),
339 Self::ObjectTypesSupported(v) => core::fmt::Display::fmt(v, f),
340 Self::Unknown { data, .. } => {
341 f.write_str("0x")?;
342 for byte in data {
343 write!(f, "{byte:02X}")?;
344 }
345 Ok(())
346 }
347 }
348 }
349}
350
351pub fn resolve_bits(property: PropertyIdentifier, value: &PropertyValue) -> Option<ResolvedBits> {
354 match value {
355 PropertyValue::BitString { unused_bits, data } => {
356 Some(ResolvedBits::from_property(property, *unused_bits, data))
357 }
358 _ => None,
359 }
360}
361
362#[derive(Debug, Clone, PartialEq)]
370pub enum ResolvedValue {
371 Null,
373 Boolean(bool),
375 Unsigned(u64),
377 Signed(i32),
379 Real(f32),
381 Double(f64),
383 OctetString(Vec<u8>),
385 CharacterString(String),
387 BitString(ResolvedBits),
389 Enumerated(ResolvedEnum),
391 Date(Date),
393 Time(Time),
395 ObjectIdentifier(ObjectIdentifier),
397 List(Vec<ResolvedValue>),
399 ApplicationData(Vec<u8>),
404}
405
406pub fn resolve_value(property: PropertyIdentifier, value: PropertyValue) -> ResolvedValue {
430 match value {
431 PropertyValue::Null => ResolvedValue::Null,
432 PropertyValue::Boolean(v) => ResolvedValue::Boolean(v),
433 PropertyValue::Unsigned(v) => ResolvedValue::Unsigned(v),
434 PropertyValue::Signed(v) => ResolvedValue::Signed(v),
435 PropertyValue::Real(v) => ResolvedValue::Real(v),
436 PropertyValue::Double(v) => ResolvedValue::Double(v),
437 PropertyValue::OctetString(v) => ResolvedValue::OctetString(v),
438 PropertyValue::CharacterString(v) => ResolvedValue::CharacterString(v),
439 PropertyValue::BitString { unused_bits, data } => {
440 ResolvedValue::BitString(ResolvedBits::from_property(property, unused_bits, &data))
441 }
442 PropertyValue::Enumerated(n) => {
443 ResolvedValue::Enumerated(ResolvedEnum::from_property(property, n))
444 }
445 PropertyValue::Date(v) => ResolvedValue::Date(v),
446 PropertyValue::Time(v) => ResolvedValue::Time(v),
447 PropertyValue::ObjectIdentifier(v) => ResolvedValue::ObjectIdentifier(v),
448 PropertyValue::List(values) => ResolvedValue::List(
449 values
450 .into_iter()
451 .map(|v| resolve_value(property, v))
452 .collect(),
453 ),
454 PropertyValue::ApplicationData(bytes) => ResolvedValue::ApplicationData(bytes),
455 }
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn resolves_object_type() {
464 let r = ResolvedEnum::from_property(PropertyIdentifier::OBJECT_TYPE, 19);
465 assert_eq!(r, ResolvedEnum::ObjectType(ObjectType::MULTI_STATE_VALUE));
466 assert_eq!(r.to_string(), "MULTI_STATE_VALUE");
467 }
468
469 #[test]
470 fn resolves_units_across_aliases() {
471 for prop in [
472 PropertyIdentifier::UNITS,
473 PropertyIdentifier::OUTPUT_UNITS,
474 PropertyIdentifier::CAR_LOAD_UNITS,
475 ] {
476 assert!(matches!(
477 ResolvedEnum::from_property(prop, 0),
478 ResolvedEnum::EngineeringUnits(_)
479 ));
480 }
481 }
482
483 #[test]
484 fn unmapped_property_is_unknown() {
485 let r = ResolvedEnum::from_property(PropertyIdentifier::PRESENT_VALUE, 7);
487 assert_eq!(r, ResolvedEnum::Unknown(7));
488 assert_eq!(r.to_string(), "7");
489 }
490
491 #[test]
492 fn alarm_value_resolves_to_binary_pv() {
493 assert_eq!(
494 ResolvedEnum::from_property(PropertyIdentifier::ALARM_VALUE, 1),
495 ResolvedEnum::BinaryPV(BinaryPV::ACTIVE),
496 );
497 }
498
499 #[test]
500 fn credential_status_resolves_to_binary_pv() {
501 assert_eq!(
502 ResolvedEnum::from_property(PropertyIdentifier::CREDENTIAL_STATUS, 1),
503 ResolvedEnum::BinaryPV(BinaryPV::ACTIVE),
504 );
505 assert_eq!(
506 ResolvedEnum::from_property(PropertyIdentifier::CREDENTIAL_STATUS, 0).to_string(),
507 "INACTIVE"
508 );
509 }
510
511 #[test]
512 fn plural_alarm_and_fault_values_remain_unknown() {
513 assert_eq!(
514 ResolvedEnum::from_property(PropertyIdentifier::ALARM_VALUES, 1),
515 ResolvedEnum::Unknown(1)
516 );
517 assert_eq!(
518 ResolvedEnum::from_property(PropertyIdentifier::FAULT_VALUES, 1),
519 ResolvedEnum::Unknown(1)
520 );
521 }
522
523 #[test]
524 fn vendor_object_type_passes_through() {
525 let r = ResolvedEnum::from_property(PropertyIdentifier::OBJECT_TYPE, 128);
527 assert_eq!(r, ResolvedEnum::ObjectType(ObjectType::from_raw(128)));
528 assert_eq!(r.to_string(), "128");
529 }
530
531 #[test]
532 fn resolve_value_promotes_enum_and_keeps_rest() {
533 assert_eq!(
535 resolve_value(
536 PropertyIdentifier::EVENT_STATE,
537 PropertyValue::Enumerated(0)
538 ),
539 ResolvedValue::Enumerated(ResolvedEnum::EventState(EventState::from_raw(0))),
540 );
541 assert_eq!(
543 resolve_value(
544 PropertyIdentifier::OBJECT_NAME,
545 PropertyValue::CharacterString("AI-1".into())
546 ),
547 ResolvedValue::CharacterString("AI-1".into()),
548 );
549 }
550
551 #[test]
552 fn resolve_value_recurses_into_lists() {
553 let list = PropertyValue::List(vec![
554 PropertyValue::Enumerated(0),
555 PropertyValue::Enumerated(1),
556 ]);
557 let ResolvedValue::List(items) = resolve_value(PropertyIdentifier::ACCEPTED_MODES, list)
558 else {
559 panic!("expected list");
560 };
561 assert!(items.iter().all(|i| matches!(
562 i,
563 ResolvedValue::Enumerated(ResolvedEnum::LifeSafetyMode(_))
564 )));
565 }
566
567 #[test]
568 fn resolves_status_flags_bitstring() {
569 let bits = ResolvedBits::from_property(PropertyIdentifier::STATUS_FLAGS, 4, &[0x80]);
571 assert_eq!(bits, ResolvedBits::StatusFlags(StatusFlags::IN_ALARM));
572 assert_eq!(bits.to_string(), "IN_ALARM");
573 }
574
575 #[test]
576 fn status_flags_decode_ignores_declared_length() {
577 for unused in [0, 4, 5] {
580 assert_eq!(
581 ResolvedBits::from_property(PropertyIdentifier::STATUS_FLAGS, unused, &[0xC0]),
582 ResolvedBits::StatusFlags(StatusFlags::IN_ALARM | StatusFlags::FAULT),
583 );
584 }
585 }
586
587 #[test]
588 fn segmentation_out_of_range_stays_unknown() {
589 assert_eq!(
592 ResolvedEnum::from_property(PropertyIdentifier::SEGMENTATION_SUPPORTED, 259),
593 ResolvedEnum::Unknown(259),
594 );
595 assert_eq!(
596 ResolvedEnum::from_property(PropertyIdentifier::SEGMENTATION_SUPPORTED, 3),
597 ResolvedEnum::Segmentation(Segmentation::NONE),
598 );
599 }
600
601 #[test]
602 fn car_door_status_resolves_to_door_status() {
603 let r = ResolvedEnum::from_property(PropertyIdentifier::CAR_DOOR_STATUS, 3);
606 assert_eq!(r, ResolvedEnum::DoorStatus(DoorStatus::DOOR_FAULT));
607 assert_eq!(r.to_string(), "DOOR_FAULT");
608 assert_eq!(
609 ResolvedEnum::from_property(PropertyIdentifier::CAR_DOOR_STATUS, 0),
610 ResolvedEnum::DoorStatus(DoorStatus::CLOSED),
611 );
612 }
613
614 #[test]
615 fn backup_and_restore_state_failure_states_resolve_by_name() {
616 let r = ResolvedEnum::from_property(PropertyIdentifier::BACKUP_AND_RESTORE_STATE, 5);
620 assert_eq!(
621 r,
622 ResolvedEnum::BackupAndRestoreState(BackupAndRestoreState::BACKUP_FAILURE)
623 );
624 assert_eq!(r.to_string(), "BACKUP_FAILURE");
625 assert_eq!(
626 ResolvedEnum::from_property(PropertyIdentifier::BACKUP_AND_RESTORE_STATE, 6)
627 .to_string(),
628 "RESTORE_FAILURE"
629 );
630 }
631
632 #[test]
633 fn resolves_limit_enable_and_event_enable() {
634 assert!(matches!(
635 ResolvedBits::from_property(PropertyIdentifier::LIMIT_ENABLE, 6, &[0xC0]),
636 ResolvedBits::LimitEnable(_)
637 ));
638 assert!(matches!(
639 ResolvedBits::from_property(PropertyIdentifier::EVENT_ENABLE, 5, &[0xE0]),
640 ResolvedBits::EventTransitionBits(_)
641 ));
642 }
643
644 #[test]
645 fn resolves_object_types_supported() {
646 let mut data = vec![0u8; 9];
647 data[8] = 0x80; let bits = ResolvedBits::from_property(
649 PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED,
650 7,
651 &data,
652 );
653 match bits {
654 ResolvedBits::ObjectTypesSupported(ots) => {
655 assert!(ots.contains(ObjectType::COLOR_TEMPERATURE));
656 }
657 _ => panic!("expected ObjectTypesSupported"),
658 }
659 }
660
661 #[test]
662 fn unmapped_bitstring_is_unknown() {
663 let bits = ResolvedBits::from_property(PropertyIdentifier::PROPERTY_LIST, 0, &[0xAB]);
664 assert_eq!(
665 bits,
666 ResolvedBits::Unknown {
667 unused_bits: 0,
668 data: vec![0xAB],
669 }
670 );
671 assert_eq!(bits.to_string(), "0xAB");
672 }
673
674 #[test]
675 fn resolve_value_promotes_bitstring() {
676 let v = resolve_value(
677 PropertyIdentifier::STATUS_FLAGS,
678 PropertyValue::BitString {
679 unused_bits: 4,
680 data: vec![0x40], },
682 );
683 assert_eq!(
684 v,
685 ResolvedValue::BitString(ResolvedBits::StatusFlags(StatusFlags::FAULT)),
686 );
687 }
688
689 #[test]
690 fn resolve_bits_ignores_non_bitstring() {
691 assert!(resolve_bits(
692 PropertyIdentifier::STATUS_FLAGS,
693 &PropertyValue::Unsigned(1)
694 )
695 .is_none());
696 }
697
698 #[test]
699 fn resolve_enum_ignores_non_enumerated() {
700 assert!(resolve_enum(
701 PropertyIdentifier::OBJECT_TYPE,
702 &PropertyValue::Unsigned(19)
703 )
704 .is_none());
705 assert!(matches!(
706 resolve_enum(
707 PropertyIdentifier::EVENT_STATE,
708 &PropertyValue::Enumerated(0)
709 ),
710 Some(ResolvedEnum::EventState(_))
711 ));
712 }
713}