1#[cfg(not(feature = "std"))]
22use alloc::vec::Vec;
23
24use crate::enums::{AuditOperation, ObjectType, ServiceSupported};
25use crate::error::{Error, Result};
26
27pub use crate::primitives::StatusFlags;
28pub use days_of_week::DaysOfWeek;
29pub use priority_filter::BACnetPriorityFilter;
30
31mod priority_filter;
32
33fn wire_bit(data: &[u8], n: usize) -> bool {
38 let mask = 0x80u8 >> (n % 8);
39 data.get(n / 8).is_some_and(|b| b & mask != 0)
40}
41
42fn pack_octet(bits_lsb0: u8) -> u8 {
50 bits_lsb0.reverse_bits()
51}
52
53fn unpack_octet(data: &[u8], defined_bits: u32) -> u8 {
59 let mask = if defined_bits >= 8 {
60 u8::MAX
61 } else {
62 (1u8 << defined_bits) - 1
63 };
64 data.first().copied().unwrap_or(0).reverse_bits() & mask
65}
66
67pub fn status_flags_from_bacnet(data: &[u8]) -> StatusFlags {
74 let mut flags = StatusFlags::empty();
75 flags.set(StatusFlags::IN_ALARM, wire_bit(data, 0));
76 flags.set(StatusFlags::FAULT, wire_bit(data, 1));
77 flags.set(StatusFlags::OVERRIDDEN, wire_bit(data, 2));
78 flags.set(StatusFlags::OUT_OF_SERVICE, wire_bit(data, 3));
79 flags
80}
81
82macro_rules! impl_named_bit_display {
85 ($t:ty) => {
86 impl core::fmt::Display for $t {
87 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
88 let mut first = true;
89 for (name, _) in self.iter_names() {
90 if !first {
91 f.write_str(" | ")?;
92 }
93 f.write_str(name)?;
94 first = false;
95 }
96 if first {
97 f.write_str("()")?;
98 }
99 Ok(())
100 }
101 }
102
103 impl core::fmt::Debug for $t {
104 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105 core::fmt::Display::fmt(self, f)
106 }
107 }
108 };
109}
110
111mod days_of_week;
113
114fn write_joined<T: core::fmt::Display>(
116 f: &mut core::fmt::Formatter<'_>,
117 items: impl Iterator<Item = T>,
118) -> core::fmt::Result {
119 let mut first = true;
120 for item in items {
121 if !first {
122 f.write_str(" | ")?;
123 }
124 write!(f, "{item}")?;
125 first = false;
126 }
127 if first {
128 f.write_str("()")?;
129 }
130 Ok(())
131}
132
133bitflags::bitflags! {
134 #[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
137 pub struct EventTransitionBits: u8 {
138 const TO_OFFNORMAL = 1 << 0;
140 const TO_FAULT = 1 << 1;
142 const TO_NORMAL = 1 << 2;
144 }
145}
146
147impl EventTransitionBits {
148 pub fn from_bacnet(data: &[u8]) -> Self {
150 let mut bits = Self::empty();
151 bits.set(Self::TO_OFFNORMAL, wire_bit(data, 0));
152 bits.set(Self::TO_FAULT, wire_bit(data, 1));
153 bits.set(Self::TO_NORMAL, wire_bit(data, 2));
154 bits
155 }
156
157 pub fn to_bacnet(self) -> u8 {
160 pack_octet(self.bits())
161 }
162}
163
164impl_named_bit_display!(EventTransitionBits);
165
166bitflags::bitflags! {
167 #[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
170 pub struct LimitEnable: u8 {
171 const LOW_LIMIT_ENABLE = 1 << 0;
173 const HIGH_LIMIT_ENABLE = 1 << 1;
175 }
176}
177
178impl LimitEnable {
179 pub fn from_bacnet(data: &[u8]) -> Self {
181 let mut bits = Self::empty();
182 bits.set(Self::LOW_LIMIT_ENABLE, wire_bit(data, 0));
183 bits.set(Self::HIGH_LIMIT_ENABLE, wire_bit(data, 1));
184 bits
185 }
186
187 pub fn to_bacnet(self) -> u8 {
190 pack_octet(self.bits())
191 }
192}
193
194impl_named_bit_display!(LimitEnable);
195
196bitflags::bitflags! {
197 #[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
200 pub struct LogStatus: u8 {
201 const LOG_DISABLED = 1 << 0;
203 const BUFFER_PURGED = 1 << 1;
205 const LOG_INTERRUPTED = 1 << 2;
207 }
208}
209
210impl LogStatus {
211 pub fn from_bacnet(data: &[u8]) -> Self {
214 Self::from_bits_truncate(unpack_octet(data, 3))
215 }
216
217 pub fn to_bacnet(self) -> u8 {
221 pack_octet(self.bits() & Self::all().bits())
222 }
223}
224
225impl_named_bit_display!(LogStatus);
226
227#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
237pub struct ServicesSupported {
238 bits: u64,
239}
240
241impl ServicesSupported {
242 pub fn from_bacnet(data: &[u8]) -> Self {
245 let mut bits = 0u64;
246 for (i, &byte) in data.iter().take(8).enumerate() {
247 bits |= (byte.reverse_bits() as u64) << (i * 8);
248 }
249 Self { bits }
250 }
251
252 pub fn contains(&self, service: ServiceSupported) -> bool {
254 let n = service.to_raw() as u32;
255 n < 64 && self.bits & (1u64 << n) != 0
256 }
257
258 pub fn iter(&self) -> impl Iterator<Item = ServiceSupported> + '_ {
260 let bits = self.bits;
261 (0..64)
262 .filter(move |&n| bits & (1u64 << n) != 0)
263 .map(|n| ServiceSupported::from_raw(n as u8))
264 }
265}
266
267impl core::fmt::Display for ServicesSupported {
268 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
269 write_joined(f, self.iter())
270 }
271}
272
273impl core::fmt::Debug for ServicesSupported {
274 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
275 core::fmt::Display::fmt(self, f)
276 }
277}
278
279#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
292pub struct ObjectTypesSupported {
293 bits: [u64; 16],
294}
295
296impl ObjectTypesSupported {
297 pub fn from_bacnet(data: &[u8]) -> Self {
300 let mut bits = [0u64; 16];
301 for (i, &byte) in data.iter().take(128).enumerate() {
302 bits[i / 8] |= (byte.reverse_bits() as u64) << ((i % 8) * 8);
303 }
304 Self { bits }
305 }
306
307 pub fn contains(&self, object_type: ObjectType) -> bool {
309 let n = object_type.to_raw();
310 n < 1024 && self.bits[(n / 64) as usize] & (1u64 << (n % 64)) != 0
311 }
312
313 pub fn iter(&self) -> impl Iterator<Item = ObjectType> + '_ {
315 (0..1024u32)
316 .filter(move |&n| self.bits[(n / 64) as usize] & (1u64 << (n % 64)) != 0)
317 .map(ObjectType::from_raw)
318 }
319}
320
321impl core::fmt::Display for ObjectTypesSupported {
322 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
323 write_joined(f, self.iter())
324 }
325}
326
327impl core::fmt::Debug for ObjectTypesSupported {
328 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
329 core::fmt::Display::fmt(self, f)
330 }
331}
332
333#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
341pub struct AuditOperationFlags {
342 bits: u64,
343}
344
345impl AuditOperationFlags {
346 const RESERVED_BITS: u64 = 0x0000_0000_ffff_0000;
347
348 #[inline]
350 pub const fn empty() -> Self {
351 Self { bits: 0 }
352 }
353
354 #[inline]
359 pub fn from_bits(bits: u64) -> Result<Self> {
360 if bits & Self::RESERVED_BITS != 0 {
361 return Err(Error::OutOfRange(
362 "BACnetAuditOperationFlags: reserved bits 16-31 must be zero".into(),
363 ));
364 }
365 Ok(Self { bits })
366 }
367
368 #[inline]
370 pub const fn bits(self) -> u64 {
371 self.bits
372 }
373
374 #[inline]
376 pub const fn is_empty(self) -> bool {
377 self.bits == 0
378 }
379
380 #[inline]
385 pub const fn contains(self, operation: AuditOperation) -> bool {
386 let bit = operation.to_raw();
387 Self::valid_bit(bit) && self.bits & (1u64 << bit) != 0
388 }
389
390 #[inline]
393 pub fn insert(&mut self, operation: AuditOperation) -> bool {
394 let bit = operation.to_raw();
395 if !Self::valid_bit(bit) {
396 return false;
397 }
398 self.bits |= 1u64 << bit;
399 true
400 }
401
402 #[inline]
405 pub fn remove(&mut self, operation: AuditOperation) -> bool {
406 let bit = operation.to_raw();
407 if !Self::valid_bit(bit) {
408 return false;
409 }
410 self.bits &= !(1u64 << bit);
411 true
412 }
413
414 pub fn iter(self) -> impl Iterator<Item = AuditOperation> {
417 let bits = self.bits;
418 (0..64)
419 .filter(move |&bit| bits & (1u64 << bit) != 0)
420 .map(AuditOperation::from_raw)
421 }
422
423 const fn valid_bit(bit: u32) -> bool {
424 bit <= 15 || (bit >= 32 && bit <= 63)
425 }
426
427 pub fn from_bacnet(unused_bits: u8, data: &[u8]) -> Result<Self> {
434 if unused_bits > 7 {
435 return Err(Error::decoding(
436 0,
437 format!("BACnetAuditOperationFlags: unused_bits must be 0-7, got {unused_bits}"),
438 ));
439 }
440 if data.is_empty() {
441 if unused_bits != 0 {
442 return Err(Error::decoding(
443 0,
444 "BACnetAuditOperationFlags: an empty bit string must have zero unused bits",
445 ));
446 }
447 return Ok(Self::empty());
448 }
449
450 let meaningful_bits = data.len() * 8 - usize::from(unused_bits);
451 if meaningful_bits > 64 {
452 return Err(Error::decoding(
453 0,
454 format!(
455 "BACnetAuditOperationFlags: {meaningful_bits} bits exceeds the 64-bit limit"
456 ),
457 ));
458 }
459 if unused_bits != 0 {
460 let padding_mask = (1u8 << unused_bits) - 1;
461 if data.last().is_some_and(|last| last & padding_mask != 0) {
462 return Err(Error::decoding(
463 0,
464 "BACnetAuditOperationFlags: final-octet padding bits must be zero",
465 ));
466 }
467 }
468
469 let mut bits = 0u64;
470 for (index, &byte) in data.iter().enumerate() {
471 bits |= u64::from(byte.reverse_bits()) << (index * 8);
472 }
473 if bits & Self::RESERVED_BITS != 0 {
474 return Err(Error::decoding(
475 0,
476 "BACnetAuditOperationFlags: reserved bits 16-31 must be zero",
477 ));
478 }
479 Ok(Self { bits })
480 }
481
482 pub fn to_bacnet(self) -> (u8, Vec<u8>) {
487 if self.bits == 0 {
488 return (0, Vec::new());
489 }
490
491 let bit_len = (u64::BITS - self.bits.leading_zeros()) as usize;
492 let byte_len = bit_len.div_ceil(8);
493 let unused_bits = (byte_len * 8 - bit_len) as u8;
494 let mut data = Vec::with_capacity(byte_len);
495 for index in 0..byte_len {
496 data.push(((self.bits >> (index * 8)) as u8).reverse_bits());
497 }
498 (unused_bits, data)
499 }
500}
501
502impl core::fmt::Display for AuditOperationFlags {
503 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
504 write_joined(f, self.iter())
505 }
506}
507
508impl core::fmt::Debug for AuditOperationFlags {
509 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
510 core::fmt::Display::fmt(self, f)
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517 use crate::primitives::StatusFlags;
518
519 #[test]
520 fn pack_unpack_octet_spec_vectors() {
521 assert_eq!(pack_octet(0b001), 0x80); assert_eq!(pack_octet(0b100), 0x20); assert_eq!(pack_octet(0b0100_0000), 0x02); assert_eq!(unpack_octet(&[0x80], 3), 0b001);
528 assert_eq!(unpack_octet(&[0x20], 3), 0b100);
529 assert_eq!(unpack_octet(&[0xFE], 7), 0x7F); assert_eq!(unpack_octet(&[], 3), 0);
531 assert_eq!(unpack_octet(&[0xFF], 3), 0b111);
533 assert_eq!(
535 EventTransitionBits::from_bacnet(&[pack_octet(0b001)]),
536 EventTransitionBits::TO_OFFNORMAL
537 );
538 assert_eq!(EventTransitionBits::TO_OFFNORMAL.to_bacnet(), 0x80);
539 assert_eq!(LimitEnable::LOW_LIMIT_ENABLE.to_bacnet(), 0x80);
540 }
541
542 #[test]
543 fn log_status_follows_the_bit0_first_wire_order() {
544 for (status, octet) in [
546 (LogStatus::LOG_DISABLED, 0x80),
547 (LogStatus::BUFFER_PURGED, 0x40),
548 (LogStatus::LOG_INTERRUPTED, 0x20),
549 (LogStatus::LOG_DISABLED | LogStatus::BUFFER_PURGED, 0xC0),
550 (LogStatus::empty(), 0x00),
551 ] {
552 assert_eq!(status.to_bacnet(), octet, "{status}");
553 assert_eq!(LogStatus::from_bacnet(&[octet]), status);
554 }
555 assert_eq!(LogStatus::from_bits_retain(0xF9).to_bacnet(), 0x80);
557 assert_eq!(LogStatus::from_bacnet(&[0x9F]), LogStatus::LOG_DISABLED);
558 assert_eq!(LogStatus::from_bacnet(&[]), LogStatus::empty());
559 assert_eq!(
560 format!("{}", LogStatus::LOG_DISABLED | LogStatus::LOG_INTERRUPTED),
561 "LOG_DISABLED | LOG_INTERRUPTED"
562 );
563 }
564
565 #[test]
566 fn event_transition_bits_msb_first() {
567 let bits = EventTransitionBits::from_bacnet(&[0b1010_0000]);
569 assert_eq!(
570 bits,
571 EventTransitionBits::TO_OFFNORMAL | EventTransitionBits::TO_NORMAL
572 );
573 assert_eq!(bits.to_string(), "TO_OFFNORMAL | TO_NORMAL");
574 }
575
576 #[test]
577 fn limit_enable_msb_first() {
578 assert_eq!(
579 LimitEnable::from_bacnet(&[0x80]),
580 LimitEnable::LOW_LIMIT_ENABLE
581 );
582 assert_eq!(
583 LimitEnable::from_bacnet(&[0xC0]),
584 LimitEnable::LOW_LIMIT_ENABLE | LimitEnable::HIGH_LIMIT_ENABLE
585 );
586 assert_eq!(LimitEnable::from_bacnet(&[]).to_string(), "()");
587 }
588
589 #[test]
590 fn object_types_supported_matches_device_layout() {
591 let mut data = vec![0u8; 9];
593 data[8] = 0x80;
594 let ots = ObjectTypesSupported::from_bacnet(&data);
595 assert!(ots.contains(ObjectType::COLOR_TEMPERATURE));
596 assert!(!ots.contains(ObjectType::ANALOG_INPUT));
597 assert_eq!(
598 ots.iter().collect::<Vec<_>>(),
599 vec![ObjectType::COLOR_TEMPERATURE]
600 );
601
602 let ss = ObjectTypesSupported::from_bacnet(&[0, 0, 0, 0, 0xFD]);
604 assert!(ss.contains(ObjectType::from_raw(32)));
605 assert!(!ss.contains(ObjectType::NETWORK_SECURITY)); assert!(ss.contains(ObjectType::from_raw(39)));
607 }
608
609 #[test]
610 fn resolved_value_holds_names_not_bytes() {
611 let ots = ObjectTypesSupported::from_bacnet(&[0b1110_0000]); assert_eq!(
615 ots.iter().collect::<Vec<_>>(),
616 [
617 ObjectType::ANALOG_INPUT,
618 ObjectType::ANALOG_OUTPUT,
619 ObjectType::ANALOG_VALUE
620 ]
621 );
622 let dbg = format!("{ots:?}");
623 assert!(
624 dbg.contains("ANALOG_INPUT"),
625 "Debug should name types: {dbg}"
626 );
627 assert!(
628 !dbg.contains("224"),
629 "Debug must not show raw byte 0xE0: {dbg}"
630 );
631 }
632
633 #[test]
634 fn services_supported_follows_clause_21() {
635 let ss = ServicesSupported::from_bacnet(&[0xA4, 0x0B, 0x80, 0x35, 0x80, 0x00]);
638 assert!(ss.contains(ServiceSupported::ACKNOWLEDGE_ALARM)); assert!(ss.contains(ServiceSupported::SUBSCRIBE_COV)); assert!(ss.contains(ServiceSupported::READ_PROPERTY)); assert!(ss.contains(ServiceSupported::I_AM)); assert!(ss.contains(ServiceSupported::TIME_SYNCHRONIZATION)); assert!(!ss.contains(ServiceSupported::WHO_IS)); }
645
646 #[test]
647 fn status_flags_render_names_not_numbers() {
648 assert_eq!(
649 (StatusFlags::IN_ALARM | StatusFlags::FAULT).to_string(),
650 "IN_ALARM | FAULT"
651 );
652 assert_eq!(StatusFlags::empty().to_string(), "()");
654 assert_eq!(format!("{:?}", StatusFlags::empty()), "()");
655 assert_eq!(format!("{:?}", StatusFlags::IN_ALARM), "IN_ALARM");
656 }
657
658 #[test]
659 fn small_types_debug_is_named_never_a_number() {
660 assert_eq!(format!("{:?}", EventTransitionBits::empty()), "()");
662 assert_eq!(format!("{:?}", LimitEnable::empty()), "()");
663 assert_eq!(format!("{:?}", EventTransitionBits::TO_FAULT), "TO_FAULT");
664 }
665
666 #[test]
667 fn audit_operation_flags_empty_and_named_bits_use_msb_first_wire_order() {
668 let empty = AuditOperationFlags::empty();
669 assert!(empty.is_empty());
670 assert_eq!(empty.to_bacnet(), (0, Vec::new()));
671 assert_eq!(AuditOperationFlags::from_bacnet(0, &[]).unwrap(), empty);
672
673 let mut flags = AuditOperationFlags::empty();
674 assert!(flags.insert(AuditOperation::READ));
675 assert!(flags.insert(AuditOperation::GENERAL));
676 assert!(flags.contains(AuditOperation::READ));
677 assert!(flags.contains(AuditOperation::GENERAL));
678 assert_eq!(flags.bits(), (1 << 0) | (1 << 15));
679 assert_eq!(flags.to_bacnet(), (0, vec![0x80, 0x01]));
680 assert_eq!(
681 AuditOperationFlags::from_bacnet(0, &[0x80, 0x01]).unwrap(),
682 flags
683 );
684 assert_eq!(flags.to_string(), "READ | GENERAL");
685 }
686
687 #[test]
688 fn audit_operation_flags_preserve_proprietary_bit_63() {
689 let proprietary_32 = AuditOperation::from_raw(32);
690 let proprietary = AuditOperation::from_raw(63);
691 let mut flags = AuditOperationFlags::from_bits(1u64 << 63).unwrap();
692 assert!(flags.insert(proprietary_32));
693 assert!(flags.contains(proprietary_32));
694 assert!(flags.contains(proprietary));
695 assert_eq!(flags.to_bacnet(), (0, vec![0, 0, 0, 0, 0x80, 0, 0, 1]));
696 assert_eq!(
697 AuditOperationFlags::from_bacnet(0, &[0, 0, 0, 0, 0x80, 0, 0, 1]).unwrap(),
698 flags
699 );
700 assert!(!flags.insert(AuditOperation::from_raw(64)));
701 assert!(!flags.contains(AuditOperation::from_raw(64)));
702 assert!(flags.remove(proprietary_32));
703 assert!(flags.remove(proprietary));
704 assert!(flags.is_empty());
705 }
706
707 #[test]
708 fn audit_operation_flags_reject_overlong_and_nonzero_padding() {
709 assert!(AuditOperationFlags::from_bacnet(7, &[0; 9]).is_err());
710 assert!(AuditOperationFlags::from_bacnet(1, &[0x01]).is_err());
711 assert!(AuditOperationFlags::from_bacnet(1, &[]).is_err());
712 assert!(AuditOperationFlags::from_bacnet(8, &[0]).is_err());
713 assert!(AuditOperationFlags::from_bits(1 << 16).is_err());
714 assert!(AuditOperationFlags::from_bits(1 << 31).is_err());
715 assert!(AuditOperationFlags::from_bacnet(7, &[0, 0, 0x80]).is_err());
716
717 let mut flags = AuditOperationFlags::empty();
718 assert!(!flags.insert(AuditOperation::from_raw(16)));
719 assert!(!flags.insert(AuditOperation::from_raw(31)));
720 assert!(flags.is_empty());
721
722 assert_eq!(
724 AuditOperationFlags::from_bacnet(7, &[0x80]).unwrap(),
725 AuditOperationFlags::from_bits(1).unwrap()
726 );
727 }
728}