1#[cfg(not(feature = "std"))]
7use alloc::{string::String, vec::Vec};
8
9use crate::enums::ObjectType;
10use crate::error::Error;
11
12#[derive(Clone, Copy, PartialEq, Eq, Hash)]
21pub struct ObjectIdentifier {
22 object_type: ObjectType,
23 instance_number: u32,
24}
25
26impl ObjectIdentifier {
27 pub const MAX_INSTANCE: u32 = 0x3F_FFFF;
29
30 pub const WILDCARD_INSTANCE: u32 = Self::MAX_INSTANCE;
32
33 pub fn new(object_type: ObjectType, instance_number: u32) -> Result<Self, Error> {
38 if instance_number > Self::MAX_INSTANCE {
39 return Err(Error::OutOfRange(alloc_or_std_format!(
40 "instance number {} exceeds max {}",
41 instance_number,
42 Self::MAX_INSTANCE
43 )));
44 }
45 Ok(Self {
46 object_type,
47 instance_number,
48 })
49 }
50
51 pub const fn new_unchecked(object_type: ObjectType, instance_number: u32) -> Self {
53 Self {
54 object_type,
55 instance_number,
56 }
57 }
58
59 pub const fn object_type(&self) -> ObjectType {
61 self.object_type
62 }
63
64 pub const fn instance_number(&self) -> u32 {
66 self.instance_number
67 }
68
69 pub fn encode(&self) -> [u8; 4] {
71 debug_assert!(
72 self.object_type.to_raw() <= 0x3FF,
73 "ObjectType {} exceeds 10-bit field",
74 self.object_type.to_raw()
75 );
76 debug_assert!(
77 self.instance_number <= Self::MAX_INSTANCE,
78 "Instance {} exceeds MAX_INSTANCE",
79 self.instance_number
80 );
81 let value = ((self.object_type.to_raw() & 0x3FF) << 22)
82 | (self.instance_number & Self::MAX_INSTANCE);
83 value.to_be_bytes()
84 }
85
86 pub fn decode(data: &[u8]) -> Result<Self, Error> {
88 if data.len() != 4 {
89 return Err(Error::decoding(
90 0,
91 format!(
92 "ObjectIdentifier expects exactly 4 bytes, got {}",
93 data.len()
94 ),
95 ));
96 }
97 let value = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
98 let type_raw = (value >> 22) & 0x3FF;
99 let instance = value & Self::MAX_INSTANCE;
100 Ok(Self {
101 object_type: ObjectType::from_raw(type_raw),
102 instance_number: instance,
103 })
104 }
105}
106
107impl core::fmt::Debug for ObjectIdentifier {
108 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
109 write!(
110 f,
111 "ObjectIdentifier({:?}, {})",
112 self.object_type, self.instance_number
113 )
114 }
115}
116
117impl core::fmt::Display for ObjectIdentifier {
118 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119 write!(f, "{},{}", self.object_type, self.instance_number)
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
134pub struct Date {
135 pub year: u8,
137 pub month: u8,
139 pub day: u8,
141 pub day_of_week: u8,
143}
144
145impl Date {
146 pub const UNSPECIFIED: u8 = 0xFF;
148
149 pub fn encode(&self) -> [u8; 4] {
151 [self.year, self.month, self.day, self.day_of_week]
152 }
153
154 pub fn decode(data: &[u8]) -> Result<Self, Error> {
156 if data.len() != 4 {
157 return Err(Error::decoding(
158 0,
159 format!("Date expects exactly 4 bytes, got {}", data.len()),
160 ));
161 }
162 Ok(Self {
163 year: data[0],
164 month: data[1],
165 day: data[2],
166 day_of_week: data[3],
167 })
168 }
169
170 pub fn actual_year(&self) -> Option<u16> {
172 if self.year == Self::UNSPECIFIED {
173 None
174 } else {
175 Some(1900 + self.year as u16)
176 }
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188pub struct Time {
189 pub hour: u8,
191 pub minute: u8,
193 pub second: u8,
195 pub hundredths: u8,
197}
198
199impl Time {
200 pub const UNSPECIFIED: u8 = 0xFF;
202
203 pub fn encode(&self) -> [u8; 4] {
205 [self.hour, self.minute, self.second, self.hundredths]
206 }
207
208 pub fn decode(data: &[u8]) -> Result<Self, Error> {
210 if data.len() != 4 {
211 return Err(Error::decoding(
212 0,
213 format!("Time expects exactly 4 bytes, got {}", data.len()),
214 ));
215 }
216 Ok(Self {
217 hour: data[0],
218 minute: data[1],
219 second: data[2],
220 hundredths: data[3],
221 })
222 }
223}
224
225#[derive(Debug, Clone, PartialEq)]
231pub enum BACnetTimeStamp {
232 Time(Time),
234 SequenceNumber(u64),
236 DateTime { date: Date, time: Time },
238}
239
240bitflags::bitflags! {
245 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
247 pub struct StatusFlags: u8 {
248 const IN_ALARM = 0b1000;
249 const FAULT = 0b0100;
250 const OVERRIDDEN = 0b0010;
251 const OUT_OF_SERVICE = 0b0001;
252 }
253}
254
255bitflags::bitflags! {
256 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
260 pub struct DaysOfWeek: u8 {
261 const MONDAY = 0b0100_0000;
262 const TUESDAY = 0b0010_0000;
263 const WEDNESDAY = 0b0001_0000;
264 const THURSDAY = 0b0000_1000;
265 const FRIDAY = 0b0000_0100;
266 const SATURDAY = 0b0000_0010;
267 const SUNDAY = 0b0000_0001;
268 const ALL = 0b0111_1111;
269 }
270}
271
272#[derive(Debug, Clone, PartialEq)]
282pub enum PropertyValue {
283 Null,
285 Boolean(bool),
287 Unsigned(u64),
289 Signed(i32),
291 Real(f32),
293 Double(f64),
295 OctetString(Vec<u8>),
297 CharacterString(String),
299 BitString {
301 unused_bits: u8,
303 data: Vec<u8>,
305 },
306 Enumerated(u32),
308 Date(Date),
310 Time(Time),
312 ObjectIdentifier(ObjectIdentifier),
314 List(Vec<PropertyValue>),
320}
321
322#[cfg(feature = "std")]
328macro_rules! alloc_or_std_format {
329 ($($arg:tt)*) => { format!($($arg)*) }
330}
331
332#[cfg(not(feature = "std"))]
333macro_rules! alloc_or_std_format {
334 ($($arg:tt)*) => { alloc::format!($($arg)*) }
335}
336
337use alloc_or_std_format;
338
339#[cfg(test)]
344mod tests {
345 use super::*;
346
347 #[test]
348 fn object_identifier_encode_decode_round_trip() {
349 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
350 let bytes = oid.encode();
351 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
352 assert_eq!(oid, decoded);
353 }
354
355 #[test]
356 fn object_identifier_wire_format() {
357 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
359 assert_eq!(oid.encode(), [0x00, 0x00, 0x00, 0x01]);
360
361 let oid = ObjectIdentifier::new(ObjectType::DEVICE, 1234).unwrap();
363 let expected = ((8u32 << 22) | 1234u32).to_be_bytes();
364 assert_eq!(oid.encode(), expected);
365 }
366
367 #[test]
368 fn object_identifier_max_instance() {
369 let oid =
370 ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE).unwrap();
371 assert_eq!(oid.instance_number(), 0x3F_FFFF);
372 let bytes = oid.encode();
373 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
374 assert_eq!(decoded.instance_number(), 0x3F_FFFF);
375 }
376
377 #[test]
378 fn object_identifier_invalid_instance() {
379 let result = ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE + 1);
380 assert!(result.is_err());
381 }
382
383 #[test]
384 fn object_identifier_buffer_too_short() {
385 let result = ObjectIdentifier::decode(&[0x00, 0x00]);
386 assert!(result.is_err());
387 }
388
389 #[test]
390 fn object_identifier_overlong_errors() {
391 assert!(ObjectIdentifier::decode(&[0x00, 0x00, 0x00, 0x01, 0xFF]).is_err());
392 }
393
394 #[test]
395 fn date_encode_decode_round_trip() {
396 let date = Date {
397 year: 124, month: 6,
399 day: 15,
400 day_of_week: 6, };
402 let bytes = date.encode();
403 let decoded = Date::decode(&bytes).unwrap();
404 assert_eq!(date, decoded);
405 assert_eq!(decoded.actual_year(), Some(2024));
406 }
407
408 #[test]
409 fn date_unspecified_year() {
410 let date = Date {
411 year: Date::UNSPECIFIED,
412 month: 1,
413 day: 1,
414 day_of_week: Date::UNSPECIFIED,
415 };
416 assert_eq!(date.actual_year(), None);
417 }
418
419 #[test]
420 fn date_overlong_errors() {
421 assert!(Date::decode(&[124, 6, 15, 6, 0]).is_err());
422 }
423
424 #[test]
425 fn time_encode_decode_round_trip() {
426 let time = Time {
427 hour: 14,
428 minute: 30,
429 second: 45,
430 hundredths: 50,
431 };
432 let bytes = time.encode();
433 let decoded = Time::decode(&bytes).unwrap();
434 assert_eq!(time, decoded);
435 }
436
437 #[test]
438 fn time_overlong_errors() {
439 assert!(Time::decode(&[14, 30, 45, 50, 0]).is_err());
440 }
441
442 #[test]
443 fn status_flags_operations() {
444 let flags = StatusFlags::IN_ALARM | StatusFlags::OUT_OF_SERVICE;
445 assert!(flags.contains(StatusFlags::IN_ALARM));
446 assert!(flags.contains(StatusFlags::OUT_OF_SERVICE));
447 assert!(!flags.contains(StatusFlags::FAULT));
448 assert!(!flags.contains(StatusFlags::OVERRIDDEN));
449 }
450
451 #[test]
454 fn object_identifier_instance_zero() {
455 let oid = ObjectIdentifier::new(ObjectType::DEVICE, 0).unwrap();
457 assert_eq!(oid.instance_number(), 0);
458 let bytes = oid.encode();
459 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
460 assert_eq!(decoded.instance_number(), 0);
461 assert_eq!(decoded.object_type(), ObjectType::DEVICE);
462 }
463
464 #[test]
465 fn object_identifier_all_types_instance_zero() {
466 for type_raw in [0u32, 1, 2, 3, 4, 5, 6, 8, 10, 13, 14, 17, 19] {
468 let obj_type = ObjectType::from_raw(type_raw);
469 let oid = ObjectIdentifier::new(obj_type, 0).unwrap();
470 let bytes = oid.encode();
471 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
472 assert_eq!(decoded.object_type(), obj_type, "type {type_raw} failed");
473 assert_eq!(
474 decoded.instance_number(),
475 0,
476 "type {type_raw} instance failed"
477 );
478 }
479 }
480
481 #[test]
482 fn object_identifier_wildcard_instance() {
483 let oid =
484 ObjectIdentifier::new(ObjectType::DEVICE, ObjectIdentifier::WILDCARD_INSTANCE).unwrap();
485 assert_eq!(oid.instance_number(), ObjectIdentifier::MAX_INSTANCE);
486 let bytes = oid.encode();
487 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
488 assert_eq!(decoded.instance_number(), ObjectIdentifier::MAX_INSTANCE);
489 }
490
491 #[test]
492 fn object_identifier_decode_extra_bytes_errors() {
493 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 42).unwrap();
494 let mut bytes = oid.encode().to_vec();
495 bytes.extend_from_slice(&[0xFF, 0xFF]);
496 assert!(ObjectIdentifier::decode(&bytes).is_err());
497 }
498
499 #[test]
500 #[cfg_attr(debug_assertions, should_panic(expected = "exceeds 10-bit field"))]
501 fn object_identifier_type_overflow_round_trip() {
502 let oid = ObjectIdentifier::new_unchecked(ObjectType::from_raw(1024), 0);
505 let bytes = oid.encode();
506 let decoded = ObjectIdentifier::decode(&bytes).unwrap();
507 assert_eq!(decoded.object_type(), ObjectType::from_raw(0));
508 }
509
510 #[test]
511 fn property_value_variants() {
512 let null = PropertyValue::Null;
513 let boolean = PropertyValue::Boolean(true);
514 let real = PropertyValue::Real(72.5);
515 let string = PropertyValue::CharacterString("test".into());
516
517 assert_eq!(null, PropertyValue::Null);
518 assert_eq!(boolean, PropertyValue::Boolean(true));
519 assert_ne!(real, PropertyValue::Real(73.0));
520 assert_eq!(string, PropertyValue::CharacterString("test".into()));
521 }
522}