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 ResolvedEnum {
53 pub const fn name(&self) -> Option<&'static str> {
57 match self {
58 $( Self::$variant(v) => v.name(), )+
59 Self::Unknown(_) => None,
60 }
61 }
62
63 pub fn enumeration(&self) -> Option<(&'static str, Vec<(&'static str, u32)>)> {
68 match self {
69 $( Self::$variant(_) => Some((
70 stringify!($variant),
71 <$ty>::ALL_NAMED
72 .iter()
73 .map(|&(name, value)| (name, u32::from(value.to_raw())))
74 .collect(),
75 )), )+
76 Self::Unknown(_) => None,
77 }
78 }
79 }
80
81 impl core::fmt::Display for ResolvedEnum {
82 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83 match self {
84 $( Self::$variant(v) => core::fmt::Display::fmt(v, f), )+
85 Self::Unknown(n) => write!(f, "{n}"),
86 }
87 }
88 }
89 };
90}
91
92resolved_enum! {
93 ObjectType(ObjectType),
94 EventState(EventState),
95 EventType(EventType),
96 FaultType(FaultType),
97 NotifyType(NotifyType),
98 Reliability(Reliability),
99 DeviceStatus(DeviceStatus),
100 Segmentation(Segmentation),
101 EngineeringUnits(EngineeringUnits),
102 Polarity(Polarity),
103 BinaryPV(BinaryPV),
104 ProgramState(ProgramState),
105 ProgramChange(ProgramChange),
106 ProgramError(ProgramError),
107 NodeType(NodeType),
108 LoggingType(LoggingType),
109 FileAccessMethod(FileAccessMethod),
110 BackupAndRestoreState(BackupAndRestoreState),
111 RestartReason(RestartReason),
112 LifeSafetyMode(LifeSafetyMode),
113 LifeSafetyState(LifeSafetyState),
114 LifeSafetyOperation(LifeSafetyOperation),
115 SilencedState(SilencedState),
116 Maintenance(Maintenance),
117 DoorStatus(DoorStatus),
118 DoorAlarmState(DoorAlarmState),
119 DoorSecuredStatus(DoorSecuredStatus),
120 LockStatus(LockStatus),
121 AccessEvent(AccessEvent),
122 AuthorizationMode(AuthorizationMode),
123 AccessPassbackMode(AccessPassbackMode),
124 AccessUserType(AccessUserType),
125 AccessCredentialDisable(AccessCredentialDisable),
126 AccessCredentialDisableReason(AccessCredentialDisableReason),
127 AuthenticationStatus(AuthenticationStatus),
128 AuthorizationExemption(AuthorizationExemption),
129 AccessZoneOccupancyState(AccessZoneOccupancyState),
130 Relationship(Relationship),
131 NetworkType(NetworkType),
132 NetworkNumberQuality(NetworkNumberQuality),
133 IPMode(IPMode),
134 NetworkPortCommand(NetworkPortCommand),
135 ProtocolLevel(ProtocolLevel),
136 LightingInProgress(LightingInProgress),
137 LightingTransition(LightingTransition),
138 TimerState(TimerState),
139 TimerTransition(TimerTransition),
140 WriteStatus(WriteStatus),
141 LiftCarMode(LiftCarMode),
142 LiftGroupMode(LiftGroupMode),
143 EscalatorMode(EscalatorMode),
144 EscalatorOperationDirection(EscalatorOperationDirection),
145 LiftCarDriveStatus(LiftCarDriveStatus),
146 LiftCarDirection(LiftCarDirection),
147 LiftCarDoorCommand(LiftCarDoorCommand),
148 AuditLevel(AuditLevel),
149}
150
151impl ResolvedEnum {
152 pub fn from_property(property: PropertyIdentifier, value: u32) -> Self {
160 match property.to_raw() {
161 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 => {
172 Self::Segmentation(Segmentation::from_raw(value as u8))
173 }
174
175 20 | 27 | 50 | 82 | 94 | 117 | 455 => {
177 Self::EngineeringUnits(EngineeringUnits::from_raw(value))
178 }
179
180 84 => Self::Polarity(Polarity::from_raw(value)), 6 => Self::BinaryPV(BinaryPV::from_raw(value)),
183 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)),
197 161 => Self::LifeSafetyOperation(LifeSafetyOperation::from_raw(value)), 163 => Self::SilencedState(SilencedState::from_raw(value)), 158 => Self::Maintenance(Maintenance::from_raw(value)),
202
203 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)),
218 303 => {
219 Self::AccessCredentialDisableReason(AccessCredentialDisableReason::from_raw(value))
220 } 260 => Self::AuthenticationStatus(AuthenticationStatus::from_raw(value)), 296 => Self::AccessZoneOccupancyState(AccessZoneOccupancyState::from_raw(value)), 364 => Self::AuthorizationExemption(AuthorizationExemption::from_raw(value)),
226
227 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)),
240
241 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)),
260
261 498 => Self::AuditLevel(AuditLevel::from_raw(value)), _ => Self::Unknown(value),
265 }
266 }
267}
268
269pub fn resolve_enum(property: PropertyIdentifier, value: &PropertyValue) -> Option<ResolvedEnum> {
294 match value {
295 PropertyValue::Enumerated(n) => Some(ResolvedEnum::from_property(property, *n)),
296 _ => None,
297 }
298}
299
300#[derive(Clone, PartialEq, Eq, Debug)]
308pub enum ResolvedBits {
309 StatusFlags(StatusFlags),
311 EventTransitionBits(EventTransitionBits),
313 LimitEnable(LimitEnable),
315 ServicesSupported(ServicesSupported),
317 ObjectTypesSupported(ObjectTypesSupported),
319 Unknown {
321 unused_bits: u8,
323 data: Vec<u8>,
325 },
326}
327
328impl ResolvedBits {
329 pub fn from_property(property: PropertyIdentifier, unused_bits: u8, data: &[u8]) -> Self {
337 match property.to_raw() {
338 111 | 347 => Self::StatusFlags(crate::bitstring::status_flags_from_bacnet(data)),
340
341 0 | 1 | 35 => Self::EventTransitionBits(EventTransitionBits::from_bacnet(data)),
343
344 52 => Self::LimitEnable(LimitEnable::from_bacnet(data)),
346
347 97 => Self::ServicesSupported(ServicesSupported::from_bacnet(data)),
349
350 96 => Self::ObjectTypesSupported(ObjectTypesSupported::from_bacnet(data)),
352
353 _ => Self::Unknown {
354 unused_bits,
355 data: data.to_vec(),
356 },
357 }
358 }
359}
360
361impl core::fmt::Display for ResolvedBits {
362 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
363 match self {
364 Self::StatusFlags(v) => core::fmt::Display::fmt(v, f),
365 Self::EventTransitionBits(v) => core::fmt::Display::fmt(v, f),
366 Self::LimitEnable(v) => core::fmt::Display::fmt(v, f),
367 Self::ServicesSupported(v) => core::fmt::Display::fmt(v, f),
368 Self::ObjectTypesSupported(v) => core::fmt::Display::fmt(v, f),
369 Self::Unknown { data, .. } => {
370 f.write_str("0x")?;
371 for byte in data {
372 write!(f, "{byte:02X}")?;
373 }
374 Ok(())
375 }
376 }
377 }
378}
379
380pub fn resolve_bits(property: PropertyIdentifier, value: &PropertyValue) -> Option<ResolvedBits> {
383 match value {
384 PropertyValue::BitString { unused_bits, data } => {
385 Some(ResolvedBits::from_property(property, *unused_bits, data))
386 }
387 _ => None,
388 }
389}
390
391#[derive(Debug, Clone, PartialEq)]
399pub enum ResolvedValue {
400 Null,
402 Boolean(bool),
404 Unsigned(u64),
406 Signed(i32),
408 Real(f32),
410 Double(f64),
412 OctetString(Vec<u8>),
414 CharacterString(String),
416 BitString(ResolvedBits),
418 Enumerated(ResolvedEnum),
420 Date(Date),
422 Time(Time),
424 ObjectIdentifier(ObjectIdentifier),
426 List(Vec<ResolvedValue>),
428 ApplicationData(Vec<u8>),
433}
434
435pub fn resolve_value(property: PropertyIdentifier, value: PropertyValue) -> ResolvedValue {
459 match value {
460 PropertyValue::Null => ResolvedValue::Null,
461 PropertyValue::Boolean(v) => ResolvedValue::Boolean(v),
462 PropertyValue::Unsigned(v) => ResolvedValue::Unsigned(v),
463 PropertyValue::Signed(v) => ResolvedValue::Signed(v),
464 PropertyValue::Real(v) => ResolvedValue::Real(v),
465 PropertyValue::Double(v) => ResolvedValue::Double(v),
466 PropertyValue::OctetString(v) => ResolvedValue::OctetString(v),
467 PropertyValue::CharacterString(v) => ResolvedValue::CharacterString(v),
468 PropertyValue::BitString { unused_bits, data } => {
469 ResolvedValue::BitString(ResolvedBits::from_property(property, unused_bits, &data))
470 }
471 PropertyValue::Enumerated(n) => {
472 ResolvedValue::Enumerated(ResolvedEnum::from_property(property, n))
473 }
474 PropertyValue::Date(v) => ResolvedValue::Date(v),
475 PropertyValue::Time(v) => ResolvedValue::Time(v),
476 PropertyValue::ObjectIdentifier(v) => ResolvedValue::ObjectIdentifier(v),
477 PropertyValue::List(values) => ResolvedValue::List(
478 values
479 .into_iter()
480 .map(|v| resolve_value(property, v))
481 .collect(),
482 ),
483 PropertyValue::ApplicationData(bytes) => ResolvedValue::ApplicationData(bytes),
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 #[test]
492 fn resolves_object_type() {
493 let r = ResolvedEnum::from_property(PropertyIdentifier::OBJECT_TYPE, 19);
494 assert_eq!(r, ResolvedEnum::ObjectType(ObjectType::MULTI_STATE_VALUE));
495 assert_eq!(r.to_string(), "MULTI_STATE_VALUE");
496 }
497
498 #[test]
499 fn resolves_units_across_aliases() {
500 for prop in [
501 PropertyIdentifier::UNITS,
502 PropertyIdentifier::OUTPUT_UNITS,
503 PropertyIdentifier::CAR_LOAD_UNITS,
504 ] {
505 assert!(matches!(
506 ResolvedEnum::from_property(prop, 0),
507 ResolvedEnum::EngineeringUnits(_)
508 ));
509 }
510 }
511
512 #[test]
513 fn unmapped_property_is_unknown() {
514 let r = ResolvedEnum::from_property(PropertyIdentifier::PRESENT_VALUE, 7);
516 assert_eq!(r, ResolvedEnum::Unknown(7));
517 assert_eq!(r.to_string(), "7");
518 }
519
520 #[test]
521 fn alarm_value_resolves_to_binary_pv() {
522 assert_eq!(
523 ResolvedEnum::from_property(PropertyIdentifier::ALARM_VALUE, 1),
524 ResolvedEnum::BinaryPV(BinaryPV::ACTIVE),
525 );
526 }
527
528 #[test]
529 fn credential_status_resolves_to_binary_pv() {
530 assert_eq!(
531 ResolvedEnum::from_property(PropertyIdentifier::CREDENTIAL_STATUS, 1),
532 ResolvedEnum::BinaryPV(BinaryPV::ACTIVE),
533 );
534 assert_eq!(
535 ResolvedEnum::from_property(PropertyIdentifier::CREDENTIAL_STATUS, 0).to_string(),
536 "INACTIVE"
537 );
538 }
539
540 #[test]
541 fn plural_alarm_and_fault_values_remain_unknown() {
542 assert_eq!(
543 ResolvedEnum::from_property(PropertyIdentifier::ALARM_VALUES, 1),
544 ResolvedEnum::Unknown(1)
545 );
546 assert_eq!(
547 ResolvedEnum::from_property(PropertyIdentifier::FAULT_VALUES, 1),
548 ResolvedEnum::Unknown(1)
549 );
550 }
551
552 #[test]
553 fn vendor_object_type_passes_through() {
554 let r = ResolvedEnum::from_property(PropertyIdentifier::OBJECT_TYPE, 128);
556 assert_eq!(r, ResolvedEnum::ObjectType(ObjectType::from_raw(128)));
557 assert_eq!(r.to_string(), "128");
558 }
559
560 #[test]
561 fn resolve_value_promotes_enum_and_keeps_rest() {
562 assert_eq!(
564 resolve_value(
565 PropertyIdentifier::EVENT_STATE,
566 PropertyValue::Enumerated(0)
567 ),
568 ResolvedValue::Enumerated(ResolvedEnum::EventState(EventState::from_raw(0))),
569 );
570 assert_eq!(
572 resolve_value(
573 PropertyIdentifier::OBJECT_NAME,
574 PropertyValue::CharacterString("AI-1".into())
575 ),
576 ResolvedValue::CharacterString("AI-1".into()),
577 );
578 }
579
580 #[test]
581 fn resolve_value_recurses_into_lists() {
582 let list = PropertyValue::List(vec![
583 PropertyValue::Enumerated(0),
584 PropertyValue::Enumerated(1),
585 ]);
586 let ResolvedValue::List(items) = resolve_value(PropertyIdentifier::ACCEPTED_MODES, list)
587 else {
588 panic!("expected list");
589 };
590 assert!(items.iter().all(|i| matches!(
591 i,
592 ResolvedValue::Enumerated(ResolvedEnum::LifeSafetyMode(_))
593 )));
594 }
595
596 #[test]
597 fn resolves_status_flags_bitstring() {
598 let bits = ResolvedBits::from_property(PropertyIdentifier::STATUS_FLAGS, 4, &[0x80]);
600 assert_eq!(bits, ResolvedBits::StatusFlags(StatusFlags::IN_ALARM));
601 assert_eq!(bits.to_string(), "IN_ALARM");
602 }
603
604 #[test]
605 fn status_flags_decode_ignores_declared_length() {
606 for unused in [0, 4, 5] {
609 assert_eq!(
610 ResolvedBits::from_property(PropertyIdentifier::STATUS_FLAGS, unused, &[0xC0]),
611 ResolvedBits::StatusFlags(StatusFlags::IN_ALARM | StatusFlags::FAULT),
612 );
613 }
614 }
615
616 #[test]
617 fn segmentation_out_of_range_stays_unknown() {
618 assert_eq!(
621 ResolvedEnum::from_property(PropertyIdentifier::SEGMENTATION_SUPPORTED, 259),
622 ResolvedEnum::Unknown(259),
623 );
624 assert_eq!(
625 ResolvedEnum::from_property(PropertyIdentifier::SEGMENTATION_SUPPORTED, 3),
626 ResolvedEnum::Segmentation(Segmentation::NONE),
627 );
628 }
629
630 #[test]
631 fn car_door_status_resolves_to_door_status() {
632 let r = ResolvedEnum::from_property(PropertyIdentifier::CAR_DOOR_STATUS, 3);
635 assert_eq!(r, ResolvedEnum::DoorStatus(DoorStatus::DOOR_FAULT));
636 assert_eq!(r.to_string(), "DOOR_FAULT");
637 assert_eq!(
638 ResolvedEnum::from_property(PropertyIdentifier::CAR_DOOR_STATUS, 0),
639 ResolvedEnum::DoorStatus(DoorStatus::CLOSED),
640 );
641 }
642
643 #[test]
644 fn backup_and_restore_state_failure_states_resolve_by_name() {
645 let r = ResolvedEnum::from_property(PropertyIdentifier::BACKUP_AND_RESTORE_STATE, 5);
649 assert_eq!(
650 r,
651 ResolvedEnum::BackupAndRestoreState(BackupAndRestoreState::BACKUP_FAILURE)
652 );
653 assert_eq!(r.to_string(), "BACKUP_FAILURE");
654 assert_eq!(
655 ResolvedEnum::from_property(PropertyIdentifier::BACKUP_AND_RESTORE_STATE, 6)
656 .to_string(),
657 "RESTORE_FAILURE"
658 );
659 }
660
661 #[test]
662 fn resolves_limit_enable_and_event_enable() {
663 assert!(matches!(
664 ResolvedBits::from_property(PropertyIdentifier::LIMIT_ENABLE, 6, &[0xC0]),
665 ResolvedBits::LimitEnable(_)
666 ));
667 assert!(matches!(
668 ResolvedBits::from_property(PropertyIdentifier::EVENT_ENABLE, 5, &[0xE0]),
669 ResolvedBits::EventTransitionBits(_)
670 ));
671 }
672
673 #[test]
674 fn resolves_object_types_supported() {
675 let mut data = vec![0u8; 9];
676 data[8] = 0x80; let bits = ResolvedBits::from_property(
678 PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED,
679 7,
680 &data,
681 );
682 match bits {
683 ResolvedBits::ObjectTypesSupported(ots) => {
684 assert!(ots.contains(ObjectType::COLOR_TEMPERATURE));
685 }
686 _ => panic!("expected ObjectTypesSupported"),
687 }
688 }
689
690 #[test]
691 fn unmapped_bitstring_is_unknown() {
692 let bits = ResolvedBits::from_property(PropertyIdentifier::PROPERTY_LIST, 0, &[0xAB]);
693 assert_eq!(
694 bits,
695 ResolvedBits::Unknown {
696 unused_bits: 0,
697 data: vec![0xAB],
698 }
699 );
700 assert_eq!(bits.to_string(), "0xAB");
701 }
702
703 #[test]
704 fn resolve_value_promotes_bitstring() {
705 let v = resolve_value(
706 PropertyIdentifier::STATUS_FLAGS,
707 PropertyValue::BitString {
708 unused_bits: 4,
709 data: vec![0x40], },
711 );
712 assert_eq!(
713 v,
714 ResolvedValue::BitString(ResolvedBits::StatusFlags(StatusFlags::FAULT)),
715 );
716 }
717
718 #[test]
719 fn resolve_bits_ignores_non_bitstring() {
720 assert!(
721 resolve_bits(
722 PropertyIdentifier::STATUS_FLAGS,
723 &PropertyValue::Unsigned(1)
724 )
725 .is_none()
726 );
727 }
728
729 #[test]
730 fn resolve_enum_ignores_non_enumerated() {
731 assert!(
732 resolve_enum(
733 PropertyIdentifier::OBJECT_TYPE,
734 &PropertyValue::Unsigned(19)
735 )
736 .is_none()
737 );
738 assert!(matches!(
739 resolve_enum(
740 PropertyIdentifier::EVENT_STATE,
741 &PropertyValue::Enumerated(0)
742 ),
743 Some(ResolvedEnum::EventState(_))
744 ));
745 }
746}