1#[cfg(all(feature = "alloc", not(feature = "std")))]
22use alloc::vec::Vec;
23
24use crate::enums::{AuditOperation, ObjectType, ServiceSupported};
25use crate::error::{DecodeError, DecodingKind, Error, RangeError, 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(RangeError::new(
362 "BACnetAuditOperationFlags: reserved bits 16-31 must be zero",
363 )
364 .into());
365 }
366 Ok(Self { bits })
367 }
368
369 #[inline]
371 pub const fn bits(self) -> u64 {
372 self.bits
373 }
374
375 #[inline]
377 pub const fn is_empty(self) -> bool {
378 self.bits == 0
379 }
380
381 #[inline]
386 pub const fn contains(self, operation: AuditOperation) -> bool {
387 let bit = operation.to_raw();
388 Self::valid_bit(bit) && self.bits & (1u64 << bit) != 0
389 }
390
391 #[inline]
394 pub fn insert(&mut self, operation: AuditOperation) -> bool {
395 let bit = operation.to_raw();
396 if !Self::valid_bit(bit) {
397 return false;
398 }
399 self.bits |= 1u64 << bit;
400 true
401 }
402
403 #[inline]
406 pub fn remove(&mut self, operation: AuditOperation) -> bool {
407 let bit = operation.to_raw();
408 if !Self::valid_bit(bit) {
409 return false;
410 }
411 self.bits &= !(1u64 << bit);
412 true
413 }
414
415 pub fn iter(self) -> impl Iterator<Item = AuditOperation> {
418 let bits = self.bits;
419 (0..64)
420 .filter(move |&bit| bits & (1u64 << bit) != 0)
421 .map(AuditOperation::from_raw)
422 }
423
424 const fn valid_bit(bit: u32) -> bool {
425 bit <= 15 || (bit >= 32 && bit <= 63)
426 }
427
428 pub fn from_bacnet(unused_bits: u8, data: &[u8]) -> Result<Self> {
435 if unused_bits > 7 {
436 return Err(DecodeError::new(
437 DecodingKind::InvalidEncoding,
438 0,
439 "BACnetAuditOperationFlags: unused_bits must be 0-7, got {}",
440 )
441 .with_value(u64::from(unused_bits))
442 .into());
443 }
444 if data.is_empty() {
445 if unused_bits != 0 {
446 return Err(Error::decoding(
447 0,
448 "BACnetAuditOperationFlags: an empty bit string must have zero unused bits",
449 ));
450 }
451 return Ok(Self::empty());
452 }
453
454 let meaningful_bits = data.len() * 8 - usize::from(unused_bits);
455 if meaningful_bits > 64 {
456 return Err(DecodeError::new(
457 DecodingKind::InvalidEncoding,
458 0,
459 "BACnetAuditOperationFlags: {} bits exceeds the 64-bit limit",
460 )
461 .with_value(meaningful_bits as u64)
462 .into());
463 }
464 if unused_bits != 0 {
465 let padding_mask = (1u8 << unused_bits) - 1;
466 if data.last().is_some_and(|last| last & padding_mask != 0) {
467 return Err(Error::decoding(
468 0,
469 "BACnetAuditOperationFlags: final-octet padding bits must be zero",
470 ));
471 }
472 }
473
474 let mut bits = 0u64;
475 for (index, &byte) in data.iter().enumerate() {
476 bits |= u64::from(byte.reverse_bits()) << (index * 8);
477 }
478 if bits & Self::RESERVED_BITS != 0 {
479 return Err(Error::decoding(
480 0,
481 "BACnetAuditOperationFlags: reserved bits 16-31 must be zero",
482 ));
483 }
484 Ok(Self { bits })
485 }
486
487 #[cfg(feature = "alloc")]
493 pub fn to_bacnet(self) -> (u8, Vec<u8>) {
494 if self.bits == 0 {
495 return (0, Vec::new());
496 }
497
498 let bit_len = (u64::BITS - self.bits.leading_zeros()) as usize;
499 let byte_len = bit_len.div_ceil(8);
500 let unused_bits = (byte_len * 8 - bit_len) as u8;
501 let mut data = Vec::with_capacity(byte_len);
502 for index in 0..byte_len {
503 data.push(((self.bits >> (index * 8)) as u8).reverse_bits());
504 }
505 (unused_bits, data)
506 }
507}
508
509impl core::fmt::Display for AuditOperationFlags {
510 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
511 write_joined(f, self.iter())
512 }
513}
514
515impl core::fmt::Debug for AuditOperationFlags {
516 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
517 core::fmt::Display::fmt(self, f)
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524 use crate::primitives::StatusFlags;
525
526 #[test]
527 fn pack_unpack_octet_spec_vectors() {
528 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);
535 assert_eq!(unpack_octet(&[0x20], 3), 0b100);
536 assert_eq!(unpack_octet(&[0xFE], 7), 0x7F); assert_eq!(unpack_octet(&[], 3), 0);
538 assert_eq!(unpack_octet(&[0xFF], 3), 0b111);
540 assert_eq!(
542 EventTransitionBits::from_bacnet(&[pack_octet(0b001)]),
543 EventTransitionBits::TO_OFFNORMAL
544 );
545 assert_eq!(EventTransitionBits::TO_OFFNORMAL.to_bacnet(), 0x80);
546 assert_eq!(LimitEnable::LOW_LIMIT_ENABLE.to_bacnet(), 0x80);
547 }
548
549 #[test]
550 fn log_status_follows_the_bit0_first_wire_order() {
551 for (status, octet) in [
553 (LogStatus::LOG_DISABLED, 0x80),
554 (LogStatus::BUFFER_PURGED, 0x40),
555 (LogStatus::LOG_INTERRUPTED, 0x20),
556 (LogStatus::LOG_DISABLED | LogStatus::BUFFER_PURGED, 0xC0),
557 (LogStatus::empty(), 0x00),
558 ] {
559 assert_eq!(status.to_bacnet(), octet, "{status}");
560 assert_eq!(LogStatus::from_bacnet(&[octet]), status);
561 }
562 assert_eq!(LogStatus::from_bits_retain(0xF9).to_bacnet(), 0x80);
564 assert_eq!(LogStatus::from_bacnet(&[0x9F]), LogStatus::LOG_DISABLED);
565 assert_eq!(LogStatus::from_bacnet(&[]), LogStatus::empty());
566 assert_eq!(
567 format!("{}", LogStatus::LOG_DISABLED | LogStatus::LOG_INTERRUPTED),
568 "LOG_DISABLED | LOG_INTERRUPTED"
569 );
570 }
571
572 #[test]
573 fn event_transition_bits_msb_first() {
574 let bits = EventTransitionBits::from_bacnet(&[0b1010_0000]);
576 assert_eq!(
577 bits,
578 EventTransitionBits::TO_OFFNORMAL | EventTransitionBits::TO_NORMAL
579 );
580 assert_eq!(bits.to_string(), "TO_OFFNORMAL | TO_NORMAL");
581 }
582
583 #[test]
584 fn limit_enable_msb_first() {
585 assert_eq!(
586 LimitEnable::from_bacnet(&[0x80]),
587 LimitEnable::LOW_LIMIT_ENABLE
588 );
589 assert_eq!(
590 LimitEnable::from_bacnet(&[0xC0]),
591 LimitEnable::LOW_LIMIT_ENABLE | LimitEnable::HIGH_LIMIT_ENABLE
592 );
593 assert_eq!(LimitEnable::from_bacnet(&[]).to_string(), "()");
594 }
595
596 #[test]
597 fn object_types_supported_matches_device_layout() {
598 let mut data = vec![0u8; 9];
600 data[8] = 0x80;
601 let ots = ObjectTypesSupported::from_bacnet(&data);
602 assert!(ots.contains(ObjectType::COLOR_TEMPERATURE));
603 assert!(!ots.contains(ObjectType::ANALOG_INPUT));
604 assert_eq!(
605 ots.iter().collect::<Vec<_>>(),
606 vec![ObjectType::COLOR_TEMPERATURE]
607 );
608
609 let ss = ObjectTypesSupported::from_bacnet(&[0, 0, 0, 0, 0xFD]);
611 assert!(ss.contains(ObjectType::from_raw(32)));
612 assert!(!ss.contains(ObjectType::NETWORK_SECURITY)); assert!(ss.contains(ObjectType::from_raw(39)));
614 }
615
616 #[test]
617 fn resolved_value_holds_names_not_bytes() {
618 let ots = ObjectTypesSupported::from_bacnet(&[0b1110_0000]); assert_eq!(
622 ots.iter().collect::<Vec<_>>(),
623 [
624 ObjectType::ANALOG_INPUT,
625 ObjectType::ANALOG_OUTPUT,
626 ObjectType::ANALOG_VALUE
627 ]
628 );
629 let dbg = format!("{ots:?}");
630 assert!(
631 dbg.contains("ANALOG_INPUT"),
632 "Debug should name types: {dbg}"
633 );
634 assert!(
635 !dbg.contains("224"),
636 "Debug must not show raw byte 0xE0: {dbg}"
637 );
638 }
639
640 #[test]
641 fn services_supported_follows_clause_21() {
642 let ss = ServicesSupported::from_bacnet(&[0xA4, 0x0B, 0x80, 0x35, 0x80, 0x00]);
645 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)); }
652
653 #[test]
654 fn status_flags_render_names_not_numbers() {
655 assert_eq!(
656 (StatusFlags::IN_ALARM | StatusFlags::FAULT).to_string(),
657 "IN_ALARM | FAULT"
658 );
659 assert_eq!(StatusFlags::empty().to_string(), "()");
661 assert_eq!(format!("{:?}", StatusFlags::empty()), "()");
662 assert_eq!(format!("{:?}", StatusFlags::IN_ALARM), "IN_ALARM");
663 }
664
665 #[test]
666 fn small_types_debug_is_named_never_a_number() {
667 assert_eq!(format!("{:?}", EventTransitionBits::empty()), "()");
669 assert_eq!(format!("{:?}", LimitEnable::empty()), "()");
670 assert_eq!(format!("{:?}", EventTransitionBits::TO_FAULT), "TO_FAULT");
671 }
672
673 #[cfg(feature = "alloc")]
674 #[test]
675 fn audit_operation_flags_empty_and_named_bits_use_msb_first_wire_order() {
676 let empty = AuditOperationFlags::empty();
677 assert!(empty.is_empty());
678 assert_eq!(empty.to_bacnet(), (0, Vec::new()));
679 assert_eq!(AuditOperationFlags::from_bacnet(0, &[]).unwrap(), empty);
680
681 let mut flags = AuditOperationFlags::empty();
682 assert!(flags.insert(AuditOperation::READ));
683 assert!(flags.insert(AuditOperation::GENERAL));
684 assert!(flags.contains(AuditOperation::READ));
685 assert!(flags.contains(AuditOperation::GENERAL));
686 assert_eq!(flags.bits(), (1 << 0) | (1 << 15));
687 assert_eq!(flags.to_bacnet(), (0, vec![0x80, 0x01]));
688 assert_eq!(
689 AuditOperationFlags::from_bacnet(0, &[0x80, 0x01]).unwrap(),
690 flags
691 );
692 assert_eq!(flags.to_string(), "READ | GENERAL");
693 }
694
695 #[cfg(feature = "alloc")]
696 #[test]
697 fn audit_operation_flags_preserve_proprietary_bit_63() {
698 let proprietary_32 = AuditOperation::from_raw(32);
699 let proprietary = AuditOperation::from_raw(63);
700 let mut flags = AuditOperationFlags::from_bits(1u64 << 63).unwrap();
701 assert!(flags.insert(proprietary_32));
702 assert!(flags.contains(proprietary_32));
703 assert!(flags.contains(proprietary));
704 assert_eq!(flags.to_bacnet(), (0, vec![0, 0, 0, 0, 0x80, 0, 0, 1]));
705 assert_eq!(
706 AuditOperationFlags::from_bacnet(0, &[0, 0, 0, 0, 0x80, 0, 0, 1]).unwrap(),
707 flags
708 );
709 assert!(!flags.insert(AuditOperation::from_raw(64)));
710 assert!(!flags.contains(AuditOperation::from_raw(64)));
711 assert!(flags.remove(proprietary_32));
712 assert!(flags.remove(proprietary));
713 assert!(flags.is_empty());
714 }
715
716 #[test]
717 fn audit_operation_flags_reject_overlong_and_nonzero_padding() {
718 assert!(AuditOperationFlags::from_bacnet(7, &[0; 9]).is_err());
719 assert!(AuditOperationFlags::from_bacnet(1, &[0x01]).is_err());
720 assert!(AuditOperationFlags::from_bacnet(1, &[]).is_err());
721 assert!(AuditOperationFlags::from_bacnet(8, &[0]).is_err());
722 assert!(AuditOperationFlags::from_bits(1 << 16).is_err());
723 assert!(AuditOperationFlags::from_bits(1 << 31).is_err());
724 assert!(AuditOperationFlags::from_bacnet(7, &[0, 0, 0x80]).is_err());
725
726 let mut flags = AuditOperationFlags::empty();
727 assert!(!flags.insert(AuditOperation::from_raw(16)));
728 assert!(!flags.insert(AuditOperation::from_raw(31)));
729 assert!(flags.is_empty());
730
731 assert_eq!(
733 AuditOperationFlags::from_bacnet(7, &[0x80]).unwrap(),
734 AuditOperationFlags::from_bits(1).unwrap()
735 );
736 }
737}