1use std::collections::BTreeMap;
7use std::fmt;
8
9use bacnet_encoding::primitives::charset;
10use bacnet_objects::database::ObjectDatabase;
11use bacnet_types::bitstring::ServicesSupported;
12use bacnet_types::enums::{ObjectType, PropertyIdentifier, ServiceSupported};
13use bacnet_types::primitives::PropertyValue;
14
15use crate::server::ServerConfig;
16
17const PROPERTY_CAPABILITIES_EXPLANATION: &str = "Property rows aggregate configured instances: a row or access flag means at least one instance supports it. Actual availability and access depend on the concrete object. Optional is the metadata conformance classification; a required declaration wins, and absent rows do not vote.";
18
19const CREATION_ONLY_LABEL: &str = "Whole value set only by CreateObject";
23
24fn access_text(row: &PropertySupport) -> String {
29 match row.written_through {
30 Some(through) => format!(
31 "{} (resized through {through}: a whole write or its size at index 0)",
32 row.access
33 ),
34 None => row.access.to_string(),
35 }
36}
37
38fn creation_only_names(support: &ObjectTypeSupport) -> Option<String> {
41 let names: Vec<_> = support
42 .creation_only_properties
43 .iter()
44 .map(ToString::to_string)
45 .collect();
46 (!names.is_empty()).then(|| names.join(", "))
47}
48
49#[derive(Debug, Clone)]
53pub struct Pics {
54 pub vendor_info: VendorInfo,
56 pub device_profile: DeviceProfile,
58 pub supported_object_types: Vec<ObjectTypeSupport>,
60 pub supported_services: Vec<ServiceSupport>,
62 pub data_link_layers: Vec<DataLinkSupport>,
64 pub network_layer: NetworkLayerSupport,
66 pub character_sets: Vec<CharacterSet>,
68 pub special_functionality: Vec<String>,
70}
71
72#[derive(Debug, Clone)]
74pub struct VendorInfo {
75 pub vendor_id: u16,
77 pub vendor_name: String,
79 pub model_name: String,
81 pub firmware_revision: String,
83 pub application_software_version: String,
85 pub protocol_version: u16,
87 pub protocol_revision: u16,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum DeviceProfile {
94 BAac,
96 BAsc,
98 BOws,
100 BBc,
102 BOp,
104 BRouter,
106 BGw,
108 BSc,
110 Custom(String),
112}
113
114impl fmt::Display for DeviceProfile {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self {
117 Self::BAac => f.write_str("B-AAC"),
118 Self::BAsc => f.write_str("B-ASC"),
119 Self::BOws => f.write_str("B-OWS"),
120 Self::BBc => f.write_str("B-BC"),
121 Self::BOp => f.write_str("B-OP"),
122 Self::BRouter => f.write_str("B-ROUTER"),
123 Self::BGw => f.write_str("B-GW"),
124 Self::BSc => f.write_str("B-SC"),
125 Self::Custom(s) => f.write_str(s),
126 }
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub struct PropertyAccess {
135 pub readable: bool,
137 pub writable: bool,
139 pub optional: bool,
141}
142
143impl fmt::Display for PropertyAccess {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 let r = if self.readable { "R" } else { "" };
146 let w = if self.writable { "W" } else { "" };
147 let o = if self.optional { "O" } else { "" };
148 write!(f, "{r}{w}{o}")
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct PropertySupport {
155 pub property_id: PropertyIdentifier,
157 pub access: PropertyAccess,
159 pub written_through: Option<PropertyIdentifier>,
163}
164
165#[derive(Debug, Clone)]
167pub struct ObjectTypeSupport {
168 pub object_type: ObjectType,
170 pub createable: bool,
172 pub creation_only_properties: Vec<PropertyIdentifier>,
178 pub deleteable: bool,
180 pub supported_properties: Vec<PropertySupport>,
182}
183
184#[derive(Debug, Clone)]
186pub struct ServiceSupport {
187 pub service_name: String,
189 pub initiator: bool,
191 pub executor: bool,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
200#[non_exhaustive]
201pub enum DataLinkSupport {
202 BipV4,
204 BipV4NatTraversal,
208 BipV6,
210 Mstp,
212 Ethernet,
214 BacnetSc,
216}
217
218impl fmt::Display for DataLinkSupport {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 match self {
221 Self::BipV4 => f.write_str("BACnet/IP (Annex J)"),
222 Self::BipV4NatTraversal => f.write_str("BACnet/IP with NAT traversal (Annex J.7.5)"),
223 Self::BipV6 => f.write_str("BACnet/IPv6 (Annex U)"),
224 Self::Mstp => f.write_str("MS/TP (Clause 9)"),
225 Self::Ethernet => f.write_str("BACnet Ethernet (Clause 7)"),
226 Self::BacnetSc => f.write_str("BACnet/SC (Annex AB)"),
227 }
228 }
229}
230
231#[derive(Debug, Clone)]
233pub struct NetworkLayerSupport {
234 pub router: bool,
236 pub bbmd: bool,
238 pub foreign_device: bool,
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
249#[repr(u8)]
250pub enum CharacterSet {
251 Utf8 = charset::UTF8,
253 IbmMicrosoftDbcs = charset::IBM_MICROSOFT_DBCS,
255 JisX0208 = charset::JIS_X_0208,
257 Ucs4 = charset::UCS4,
259 Ucs2 = charset::UCS2,
261 Iso8859_1 = charset::ISO_8859_1,
263}
264
265impl CharacterSet {
266 pub const ALL: [Self; 6] = [
268 Self::Utf8,
269 Self::IbmMicrosoftDbcs,
270 Self::JisX0208,
271 Self::Ucs4,
272 Self::Ucs2,
273 Self::Iso8859_1,
274 ];
275
276 pub const fn code(self) -> u8 {
278 self as u8
279 }
280}
281
282impl fmt::Display for CharacterSet {
283 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284 f.write_str(match self {
285 Self::Utf8 => "ISO 10646 (UTF-8)",
286 Self::IbmMicrosoftDbcs => "IBM/Microsoft DBCS",
287 Self::JisX0208 => "JIS X 0208",
288 Self::Ucs4 => "ISO 10646 (UCS-4)",
289 Self::Ucs2 => "ISO 10646 (UCS-2)",
290 Self::Iso8859_1 => "ISO 8859-1",
291 })
292 }
293}
294
295#[derive(Debug, Clone)]
299pub struct PicsConfig {
300 pub vendor_name: String,
302 pub model_name: String,
304 pub firmware_revision: String,
306 pub application_software_version: String,
308 pub protocol_version: u16,
310 pub protocol_revision: u16,
312 pub device_profile: DeviceProfile,
314 pub data_link_layers: Vec<DataLinkSupport>,
316 pub network_layer: NetworkLayerSupport,
318 pub character_sets: Vec<CharacterSet>,
320 pub special_functionality: Vec<String>,
322}
323
324impl Default for PicsConfig {
325 fn default() -> Self {
326 Self {
327 vendor_name: String::new(),
328 model_name: String::new(),
329 firmware_revision: String::new(),
330 application_software_version: String::new(),
331 protocol_version: 1,
332 protocol_revision: 24,
333 device_profile: DeviceProfile::BAsc,
334 data_link_layers: vec![DataLinkSupport::BipV4],
335 network_layer: NetworkLayerSupport {
336 router: false,
337 bbmd: false,
338 foreign_device: false,
339 },
340 character_sets: vec![CharacterSet::Utf8],
341 special_functionality: Vec::new(),
342 }
343 }
344}
345
346pub struct PicsGenerator<'a> {
352 db: &'a ObjectDatabase,
353 server_config: &'a ServerConfig,
354 pics_config: &'a PicsConfig,
355 served: bool,
356}
357
358impl<'a> PicsGenerator<'a> {
359 pub fn new(
361 db: &'a ObjectDatabase,
362 server_config: &'a ServerConfig,
363 pics_config: &'a PicsConfig,
364 ) -> Self {
365 Self {
366 db,
367 server_config,
368 pics_config,
369 served: false,
370 }
371 }
372
373 pub(crate) fn for_server(mut self) -> Self {
374 self.served = true;
375 self
376 }
377
378 pub fn generate(&self) -> Pics {
380 Pics {
381 vendor_info: self.build_vendor_info(),
382 device_profile: self.pics_config.device_profile.clone(),
383 supported_object_types: self.build_object_types(),
384 supported_services: self.build_services(),
385 data_link_layers: self.pics_config.data_link_layers.clone(),
386 network_layer: self.pics_config.network_layer.clone(),
387 character_sets: self.pics_config.character_sets.clone(),
388 special_functionality: self.pics_config.special_functionality.clone(),
389 }
390 }
391
392 fn build_vendor_info(&self) -> VendorInfo {
393 VendorInfo {
394 vendor_id: self.server_config.device.vendor_id(),
395 vendor_name: self.pics_config.vendor_name.clone(),
396 model_name: self.pics_config.model_name.clone(),
397 firmware_revision: self.pics_config.firmware_revision.clone(),
398 application_software_version: self.pics_config.application_software_version.clone(),
399 protocol_version: self.pics_config.protocol_version,
400 protocol_revision: self.pics_config.protocol_revision,
401 }
402 }
403
404 fn build_object_types(&self) -> Vec<ObjectTypeSupport> {
405 let mut by_type: BTreeMap<u32, Vec<&dyn bacnet_objects::traits::BACnetObject>> =
406 BTreeMap::new();
407 for (_oid, obj) in self.db.iter_objects() {
408 by_type
409 .entry(obj.object_identifier().object_type().to_raw())
410 .or_default()
411 .push(obj);
412 }
413
414 let mut result = Vec::with_capacity(by_type.len());
415 for (raw_type, objects) in &by_type {
416 let object_type = ObjectType::from_raw(*raw_type);
417 let representative = objects[0];
418 let view = (self.served && object_type == ObjectType::DEVICE).then(|| {
419 crate::device_view::DeviceReadContext::new(
420 self.db,
421 crate::device_view::DeviceExecution::FullServer,
422 )
423 .with_identity(Some(&self.server_config.device))
424 });
425 let supported_properties =
426 Self::union_property_support(objects.iter().flat_map(|object| {
427 if let Some(view) = &view {
428 Self::object_property_support(&view.object(*object))
429 } else {
430 Self::object_property_support(*object)
431 }
432 }));
433
434 let createable = representative.is_createable();
438 let deleteable = representative.is_deleteable();
439 let written_through = |property: &PropertyIdentifier| {
440 supported_properties
441 .iter()
442 .any(|row| row.property_id == *property && row.written_through.is_some())
443 };
444 let creation_only_properties = if createable {
445 let mut properties = representative.creation_only_properties().to_vec();
446 properties.retain(|property| !written_through(property));
447 properties
448 } else {
449 Vec::new()
450 };
451
452 result.push(ObjectTypeSupport {
453 object_type,
454 createable,
455 creation_only_properties,
456 deleteable,
457 supported_properties,
458 });
459 }
460 result
461 }
462
463 fn object_property_support(
464 object: &dyn bacnet_objects::traits::BACnetObject,
465 ) -> Vec<PropertySupport> {
466 let metadata = object.property_metadata();
467 if metadata.is_empty() {
468 let all_props = object.property_list();
469 let required = object.required_properties();
470 all_props
471 .iter()
472 .map(|&property_id| PropertySupport {
473 property_id,
474 access: PropertyAccess {
475 readable: true,
476 writable: object.is_writable_property(property_id),
477 optional: !required.contains(&property_id),
478 },
479 written_through: None,
480 })
481 .collect()
482 } else {
483 metadata
484 .iter()
485 .map(|row| PropertySupport {
486 property_id: row.property_identifier,
487 access: PropertyAccess {
488 readable: true,
489 writable: row.write_capability.is_writable(),
490 optional: !row.is_required(),
491 },
492 written_through: row.write_capability.written_through(),
493 })
494 .collect()
495 }
496 }
497
498 fn union_property_support(
499 rows: impl IntoIterator<Item = PropertySupport>,
500 ) -> Vec<PropertySupport> {
501 let mut properties: BTreeMap<u32, PropertySupport> = BTreeMap::new();
505 for row in rows {
506 properties
507 .entry(row.property_id.to_raw())
508 .and_modify(|existing| {
509 existing.access.readable |= row.access.readable;
510 existing.access.writable |= row.access.writable;
511 existing.access.optional &= row.access.optional;
512 existing.written_through = existing.written_through.or(row.written_through);
513 })
514 .or_insert(row);
515 }
516 properties.into_values().collect()
517 }
518
519 const INITIATED_SERVICES: &'static [ServiceSupported] = &[
524 ServiceSupported::I_AM,
525 ServiceSupported::I_HAVE,
526 ServiceSupported::CONFIRMED_COV_NOTIFICATION,
527 ServiceSupported::CONFIRMED_EVENT_NOTIFICATION,
528 ServiceSupported::UNCONFIRMED_COV_NOTIFICATION,
529 ServiceSupported::UNCONFIRMED_EVENT_NOTIFICATION,
530 ServiceSupported::CONFIRMED_COV_NOTIFICATION_MULTIPLE,
531 ServiceSupported::UNCONFIRMED_COV_NOTIFICATION_MULTIPLE,
532 ];
533
534 fn build_services(&self) -> Vec<ServiceSupport> {
535 let effective_executed = (!self.served)
540 .then(|| {
541 let device = self.db.get(&self.db.selected_device()?)?;
542 match device
543 .read_property(PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED, None)
544 .ok()?
545 {
546 PropertyValue::BitString { data, .. } => {
547 Some(ServicesSupported::from_bacnet(&data))
548 }
549 _ => None,
550 }
551 })
552 .flatten();
553 let mut service_map: BTreeMap<&'static str, (bool, bool)> = BTreeMap::new();
554 let clock_available = self.db.clock_frame().is_some();
555 let executed: Box<dyn Iterator<Item = ServiceSupported> + '_> = match &effective_executed {
556 Some(services) => Box::new(services.iter()),
557 None => {
558 Box::new(crate::device_view::DeviceExecution::FullServer.services(clock_available))
559 }
560 };
561 for service in executed {
562 service_map
563 .entry(service_display_name(service))
564 .or_default()
565 .1 = true;
566 }
567 for service in Self::INITIATED_SERVICES {
568 service_map
569 .entry(service_display_name(*service))
570 .or_default()
571 .0 = true;
572 }
573
574 service_map
575 .into_iter()
576 .map(|(name, (initiator, executor))| ServiceSupport {
577 service_name: name.to_string(),
578 initiator,
579 executor,
580 })
581 .collect()
582 }
583}
584
585impl Pics {
588 pub fn generate_text(&self) -> String {
590 let mut out = String::with_capacity(4096);
591
592 out.push_str("=== BACnet Protocol Implementation Conformance Statement (PICS) ===\n");
593 out.push_str(" Per ASHRAE 135-2020 Annex A\n\n");
594
595 out.push_str("--- Vendor Information ---\n");
596 out.push_str(&format!(
597 "Vendor ID: {}\n",
598 self.vendor_info.vendor_id
599 ));
600 out.push_str(&format!(
601 "Vendor Name: {}\n",
602 self.vendor_info.vendor_name
603 ));
604 out.push_str(&format!(
605 "Model Name: {}\n",
606 self.vendor_info.model_name
607 ));
608 out.push_str(&format!(
609 "Firmware Revision: {}\n",
610 self.vendor_info.firmware_revision
611 ));
612 out.push_str(&format!(
613 "Application Software Version: {}\n",
614 self.vendor_info.application_software_version
615 ));
616 out.push_str(&format!(
617 "Protocol Version: {}\n",
618 self.vendor_info.protocol_version
619 ));
620 out.push_str(&format!(
621 "Protocol Revision: {}\n\n",
622 self.vendor_info.protocol_revision
623 ));
624
625 out.push_str("--- BACnet Device Profile ---\n");
626 out.push_str(&format!("Profile: {}\n\n", self.device_profile));
627
628 out.push_str("--- Supported Object Types ---\n");
629 out.push_str(PROPERTY_CAPABILITIES_EXPLANATION);
630 out.push('\n');
631 for ot in &self.supported_object_types {
632 out.push_str(&format!(
633 "\n Object Type: {} (createable={}, deleteable={})\n",
634 ot.object_type, ot.createable, ot.deleteable
635 ));
636 if let Some(names) = creation_only_names(ot) {
637 out.push_str(&format!(" {CREATION_ONLY_LABEL}: {names}\n"));
638 }
639 out.push_str(" Properties:\n");
640 for prop in &ot.supported_properties {
641 out.push_str(&format!(
642 " {:<40} {}\n",
643 prop.property_id.to_string(),
644 access_text(prop)
645 ));
646 }
647 }
648 out.push('\n');
649
650 out.push_str("--- Supported Services ---\n");
651 out.push_str(&format!(
652 " {:<45} {:>9} {:>9}\n",
653 "Service", "Initiator", "Executor"
654 ));
655 out.push_str(&format!(" {:-<45} {:-<9} {:-<9}\n", "", "", ""));
656 for svc in &self.supported_services {
657 let init = if svc.initiator { "Yes" } else { "No" };
658 let exec = if svc.executor { "Yes" } else { "No" };
659 out.push_str(&format!(
660 " {:<45} {:>9} {:>9}\n",
661 svc.service_name, init, exec
662 ));
663 }
664 out.push('\n');
665
666 out.push_str("--- Data Link Layer Support ---\n");
667 for dl in &self.data_link_layers {
668 out.push_str(&format!(" {dl}\n"));
669 }
670 out.push('\n');
671
672 out.push_str("--- Network Layer Options ---\n");
673 out.push_str(&format!(
674 " Router: {}\n",
675 self.network_layer.router
676 ));
677 out.push_str(&format!(" BBMD: {}\n", self.network_layer.bbmd));
678 out.push_str(&format!(
679 " Foreign Device: {}\n\n",
680 self.network_layer.foreign_device
681 ));
682
683 out.push_str("--- Character Sets Supported ---\n");
684 for cs in &self.character_sets {
685 out.push_str(&format!(" {cs}\n"));
686 }
687 out.push('\n');
688
689 if !self.special_functionality.is_empty() {
690 out.push_str("--- Special Functionality ---\n");
691 for sf in &self.special_functionality {
692 out.push_str(&format!(" {sf}\n"));
693 }
694 out.push('\n');
695 }
696
697 out
698 }
699
700 pub fn generate_markdown(&self) -> String {
702 let mut out = String::with_capacity(4096);
703
704 out.push_str("# BACnet Protocol Implementation Conformance Statement (PICS)\n\n");
705 out.push_str("*Per ASHRAE 135-2020 Annex A*\n\n");
706
707 out.push_str("## Vendor Information\n\n");
708 out.push_str("| Field | Value |\n");
709 out.push_str("|-------|-------|\n");
710 out.push_str(&format!("| Vendor ID | {} |\n", self.vendor_info.vendor_id));
711 out.push_str(&format!(
712 "| Vendor Name | {} |\n",
713 self.vendor_info.vendor_name
714 ));
715 out.push_str(&format!(
716 "| Model Name | {} |\n",
717 self.vendor_info.model_name
718 ));
719 out.push_str(&format!(
720 "| Firmware Revision | {} |\n",
721 self.vendor_info.firmware_revision
722 ));
723 out.push_str(&format!(
724 "| Application Software Version | {} |\n",
725 self.vendor_info.application_software_version
726 ));
727 out.push_str(&format!(
728 "| Protocol Version | {} |\n",
729 self.vendor_info.protocol_version
730 ));
731 out.push_str(&format!(
732 "| Protocol Revision | {} |\n\n",
733 self.vendor_info.protocol_revision
734 ));
735
736 out.push_str("## BACnet Device Profile\n\n");
737 out.push_str(&format!("**{}**\n\n", self.device_profile));
738
739 out.push_str("## Supported Object Types\n\n");
740 out.push_str(PROPERTY_CAPABILITIES_EXPLANATION);
741 out.push_str("\n\n");
742 for ot in &self.supported_object_types {
743 out.push_str(&format!(
744 "### {}\n\n- Createable: {}\n- Deleteable: {}\n",
745 ot.object_type, ot.createable, ot.deleteable
746 ));
747 if let Some(names) = creation_only_names(ot) {
748 out.push_str(&format!("- {CREATION_ONLY_LABEL}: {names}\n"));
749 }
750 out.push('\n');
751 out.push_str("| Property | Access |\n");
752 out.push_str("|----------|--------|\n");
753 for prop in &ot.supported_properties {
754 out.push_str(&format!(
755 "| {} | {} |\n",
756 prop.property_id,
757 access_text(prop)
758 ));
759 }
760 out.push('\n');
761 }
762
763 out.push_str("## Supported Services\n\n");
764 out.push_str("| Service | Initiator | Executor |\n");
765 out.push_str("|---------|-----------|----------|\n");
766 for svc in &self.supported_services {
767 let init = if svc.initiator { "Yes" } else { "No" };
768 let exec = if svc.executor { "Yes" } else { "No" };
769 out.push_str(&format!("| {} | {} | {} |\n", svc.service_name, init, exec));
770 }
771 out.push('\n');
772
773 out.push_str("## Data Link Layer Support\n\n");
774 for dl in &self.data_link_layers {
775 out.push_str(&format!("- {dl}\n"));
776 }
777 out.push('\n');
778
779 out.push_str("## Network Layer Options\n\n");
780 out.push_str("| Feature | Supported |\n");
781 out.push_str("|---------|-----------|\n");
782 out.push_str(&format!("| Router | {} |\n", self.network_layer.router));
783 out.push_str(&format!("| BBMD | {} |\n", self.network_layer.bbmd));
784 out.push_str(&format!(
785 "| Foreign Device | {} |\n\n",
786 self.network_layer.foreign_device
787 ));
788
789 out.push_str("## Character Sets Supported\n\n");
790 for cs in &self.character_sets {
791 out.push_str(&format!("- {cs}\n"));
792 }
793 out.push('\n');
794
795 if !self.special_functionality.is_empty() {
796 out.push_str("## Special Functionality\n\n");
797 for sf in &self.special_functionality {
798 out.push_str(&format!("- {sf}\n"));
799 }
800 out.push('\n');
801 }
802
803 out
804 }
805}
806
807pub fn generate_pics(
813 db: &ObjectDatabase,
814 server_config: &ServerConfig,
815 pics_config: &PicsConfig,
816) -> Pics {
817 PicsGenerator::new(db, server_config, pics_config).generate()
818}
819
820fn service_display_name(service: ServiceSupported) -> &'static str {
824 match service.to_raw() {
825 0 => "AcknowledgeAlarm",
826 1 => "ConfirmedCOVNotification",
827 2 => "ConfirmedEventNotification",
828 3 => "GetAlarmSummary",
829 4 => "GetEnrollmentSummary",
830 5 => "SubscribeCOV",
831 6 => "AtomicReadFile",
832 7 => "AtomicWriteFile",
833 8 => "AddListElement",
834 9 => "RemoveListElement",
835 10 => "CreateObject",
836 11 => "DeleteObject",
837 12 => "ReadProperty",
838 14 => "ReadPropertyMultiple",
839 15 => "WriteProperty",
840 16 => "WritePropertyMultiple",
841 17 => "DeviceCommunicationControl",
842 18 => "ConfirmedPrivateTransfer",
843 19 => "ConfirmedTextMessage",
844 20 => "ReinitializeDevice",
845 21 => "VT-Open",
846 22 => "VT-Close",
847 23 => "VT-Data",
848 26 => "I-Am",
849 27 => "I-Have",
850 28 => "UnconfirmedCOVNotification",
851 29 => "UnconfirmedEventNotification",
852 30 => "UnconfirmedPrivateTransfer",
853 31 => "UnconfirmedTextMessage",
854 32 => "TimeSynchronization",
855 33 => "WhoHas",
856 34 => "WhoIs",
857 35 => "ReadRange",
858 36 => "UTCTimeSynchronization",
859 37 => "LifeSafetyOperation",
860 38 => "SubscribeCOVProperty",
861 39 => "GetEventInformation",
862 40 => "WriteGroup",
863 41 => "SubscribeCOVPropertyMultiple",
864 42 => "ConfirmedCOVNotificationMultiple",
865 43 => "UnconfirmedCOVNotificationMultiple",
866 44 => "ConfirmedAuditNotification",
867 45 => "AuditLogQuery",
868 46 => "UnconfirmedAuditNotification",
869 47 => "Who-Am-I",
870 48 => "You-Are",
871 _ => "Unknown",
872 }
873}
874
875#[cfg(test)]
876mod tests;
877
878#[cfg(test)]
879mod property_metadata_tests;
880
881#[cfg(test)]
882mod acked_transitions_policy_tests;
883
884#[cfg(test)]
885mod truth_source_tests;
886
887#[cfg(test)]
888mod property_union_tests;
889
890#[cfg(test)]
891mod character_set_tests;
892
893#[cfg(test)]
894mod selected_device_tests;
895
896#[cfg(test)]
897mod creation_only_tests;
898
899#[cfg(test)]
900mod data_link_tests;