1use bacnet_types::constructed::{
25 BACnetDeviceObjectPropertyReference, BACnetExtendedPropertyState, BACnetPropertyStates,
26 BACnetProprietaryPropertyState,
27};
28use bacnet_types::error::Error;
29use bacnet_types::primitives::ObjectIdentifier;
30use bytes::BytesMut;
31
32use crate::primitives;
33use crate::tags::{self, TagClass};
34
35mod audit_notification;
36mod audit_record;
37pub mod cov_subscription;
38pub mod event_parameter;
39pub mod fault_parameter;
40pub mod object_property_reference;
41pub mod recipient;
42pub mod staging;
43
44pub use audit_notification::{decode_audit_notification_at, encode_audit_notification};
45pub use audit_record::{
46 decode_audit_log_record, decode_audit_log_record_result_at, encode_audit_log_record,
47 encode_audit_log_record_result,
48};
49pub use cov_subscription::{encode_cov_subscription, encode_cov_subscription_list};
50pub use event_parameter::{decode_event_parameter, encode_event_parameter};
51pub use fault_parameter::{decode_fault_parameters, encode_fault_parameters};
52pub use object_property_reference::{
53 decode_object_property_reference, decode_setpoint_reference, encode_object_property_reference,
54 encode_setpoint_reference,
55};
56pub use recipient::{
57 decode_destination, decode_destination_list, decode_recipient, encode_destination,
58 encode_destination_list, encode_recipient,
59};
60pub use staging::{
61 decode_device_object_reference, decode_stage_limit_value, encode_device_object_reference,
62 encode_stage_limit_value,
63};
64
65const MAX_FRAMED_ITEMS: usize = 10_000;
69
70fn expect_opening(data: &[u8], offset: usize, tag: u8, what: &str) -> Result<usize, Error> {
77 let (t, pos) = tags::decode_tag(data, offset)?;
78 if !t.is_opening_tag(tag) {
79 return Err(Error::decoding(
80 offset,
81 format!("{what}: expected opening tag [{tag}]"),
82 ));
83 }
84 Ok(pos)
85}
86
87fn expect_closing(data: &[u8], offset: usize, tag: u8, what: &str) -> Result<usize, Error> {
89 let (t, pos) = tags::decode_tag(data, offset)?;
90 if !t.is_closing_tag(tag) {
91 return Err(Error::decoding(
92 offset,
93 format!("{what}: expected closing tag [{tag}]"),
94 ));
95 }
96 Ok(pos)
97}
98
99fn decode_ctx_unsigned(
101 data: &[u8],
102 offset: usize,
103 tag: u8,
104 what: &str,
105) -> Result<(u64, usize), Error> {
106 let (t, pos) = tags::decode_tag(data, offset)?;
107 if !t.is_context(tag) {
108 return Err(Error::decoding(
109 offset,
110 format!("{what}: expected context tag [{tag}]"),
111 ));
112 }
113 let end = pos
114 .checked_add(t.length as usize)
115 .ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
116 if end > data.len() {
117 return Err(Error::buffer_too_short(end, data.len()));
118 }
119 Ok((primitives::decode_unsigned(&data[pos..end])?, end))
120}
121
122fn decode_ctx_real(data: &[u8], offset: usize, tag: u8, what: &str) -> Result<(f32, usize), Error> {
124 let (t, pos) = tags::decode_tag(data, offset)?;
125 if !t.is_context(tag) || t.length != 4 {
126 return Err(Error::decoding(
127 offset,
128 format!("{what}: expected context tag [{tag}] REAL (4 octets)"),
129 ));
130 }
131 let end = pos + 4;
132 if end > data.len() {
133 return Err(Error::buffer_too_short(end, data.len()));
134 }
135 Ok((primitives::decode_real(&data[pos..end])?, end))
136}
137
138fn decode_ctx_bit_string(
141 data: &[u8],
142 offset: usize,
143 tag: u8,
144 what: &str,
145) -> Result<((u8, Vec<u8>), usize), Error> {
146 let (t, pos) = tags::decode_tag(data, offset)?;
147 if !t.is_context(tag) {
148 return Err(Error::decoding(
149 offset,
150 format!("{what}: expected context tag [{tag}] BIT STRING"),
151 ));
152 }
153 let end = pos
154 .checked_add(t.length as usize)
155 .ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
156 if end > data.len() {
157 return Err(Error::buffer_too_short(end, data.len()));
158 }
159 let (unused_bits, bits) = primitives::decode_bit_string(&data[pos..end])?;
160 Ok(((unused_bits, bits), end))
161}
162
163fn decode_app_bit_string(
165 data: &[u8],
166 offset: usize,
167 what: &str,
168) -> Result<((u8, Vec<u8>), usize), Error> {
169 let (t, pos) = tags::decode_tag(data, offset)?;
170 if t.class != TagClass::Application || t.number != tags::app_tag::BIT_STRING {
171 return Err(Error::decoding(
172 offset,
173 format!("{what}: expected application-tagged BIT STRING"),
174 ));
175 }
176 let end = pos
177 .checked_add(t.length as usize)
178 .ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
179 if end > data.len() {
180 return Err(Error::buffer_too_short(end, data.len()));
181 }
182 let (unused_bits, bits) = primitives::decode_bit_string(&data[pos..end])?;
183 Ok(((unused_bits, bits), end))
184}
185
186fn decode_app_enumerated(data: &[u8], offset: usize, what: &str) -> Result<(u32, usize), Error> {
188 let (t, pos) = tags::decode_tag(data, offset)?;
189 if t.class != TagClass::Application || t.number != tags::app_tag::ENUMERATED {
190 return Err(Error::decoding(
191 offset,
192 format!("{what}: expected application-tagged ENUMERATED"),
193 ));
194 }
195 let end = pos
196 .checked_add(t.length as usize)
197 .ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
198 if end > data.len() {
199 return Err(Error::buffer_too_short(end, data.len()));
200 }
201 let value = u32::try_from(primitives::decode_unsigned(&data[pos..end])?)
202 .map_err(|_| Error::decoding(pos, format!("{what}: ENUMERATED exceeds u32")))?;
203 Ok((value, end))
204}
205
206fn decode_app_character_string(
208 data: &[u8],
209 offset: usize,
210 what: &str,
211) -> Result<(String, usize), Error> {
212 let (t, pos) = tags::decode_tag(data, offset)?;
213 if t.class != TagClass::Application || t.number != tags::app_tag::CHARACTER_STRING {
214 return Err(Error::decoding(
215 offset,
216 format!("{what}: expected application-tagged CharacterString"),
217 ));
218 }
219 let end = pos
220 .checked_add(t.length as usize)
221 .ok_or_else(|| Error::decoding(pos, format!("{what}: length overflow")))?;
222 if end > data.len() {
223 return Err(Error::buffer_too_short(end, data.len()));
224 }
225 Ok((primitives::decode_character_string(&data[pos..end])?, end))
226}
227
228pub fn encode_property_state(
237 buf: &mut BytesMut,
238 state: &BACnetPropertyStates,
239) -> Result<(), Error> {
240 use BACnetPropertyStates as S;
241 if let S::Other(value) = state {
242 if value.is_constructed() {
243 validate_tlv_sequence(value.data(), "proprietary property-state body")?;
244 let mut framed = BytesMut::new();
245 tags::encode_opening_tag(&mut framed, value.tag());
246 let (_, body_start) = tags::decode_tag(&framed, 0)?;
247 framed.extend_from_slice(value.data());
248 tags::encode_closing_tag(&mut framed, value.tag());
249 let (_, end) = tags::extract_context_value(&framed, body_start, value.tag())?;
250 if end != framed.len() {
251 return Err(Error::decoding(
252 end,
253 "proprietary property-state body has trailing data",
254 ));
255 }
256 }
257 }
258 match state {
259 S::BooleanValue(v) => primitives::encode_ctx_boolean(buf, 0, *v),
260 S::BinaryValue(v) => primitives::encode_ctx_enumerated(buf, 1, *v),
261 S::EventType(v) => primitives::encode_ctx_enumerated(buf, 2, *v),
262 S::Polarity(v) => primitives::encode_ctx_enumerated(buf, 3, *v),
263 S::ProgramChange(v) => primitives::encode_ctx_enumerated(buf, 4, *v),
264 S::ProgramState(v) => primitives::encode_ctx_enumerated(buf, 5, *v),
265 S::ReasonForHalt(v) => primitives::encode_ctx_enumerated(buf, 6, *v),
266 S::Reliability(v) => primitives::encode_ctx_enumerated(buf, 7, *v),
267 S::State(v) => primitives::encode_ctx_enumerated(buf, 8, *v),
268 S::SystemStatus(v) => primitives::encode_ctx_enumerated(buf, 9, *v),
269 S::Units(v) => primitives::encode_ctx_enumerated(buf, 10, *v),
270 S::UnsignedValue(v) => primitives::encode_ctx_unsigned(buf, 11, *v as u64),
271 S::LifeSafetyMode(v) => primitives::encode_ctx_enumerated(buf, 12, *v),
272 S::LifeSafetyState(v) => primitives::encode_ctx_enumerated(buf, 13, *v),
273 S::RestartReason(v) => primitives::encode_ctx_enumerated(buf, 14, *v),
274 S::DoorAlarmState(v) => primitives::encode_ctx_enumerated(buf, 15, *v),
275 S::Action(v) => primitives::encode_ctx_enumerated(buf, 16, *v),
276 S::DoorSecuredStatus(v) => primitives::encode_ctx_enumerated(buf, 17, *v),
277 S::DoorStatus(v) => primitives::encode_ctx_enumerated(buf, 18, *v),
278 S::DoorValue(v) => primitives::encode_ctx_enumerated(buf, 19, *v),
279 S::FileAccessMethod(v) => primitives::encode_ctx_enumerated(buf, 20, *v),
280 S::LockStatus(v) => primitives::encode_ctx_enumerated(buf, 21, *v),
281 S::LifeSafetyOperation(v) => primitives::encode_ctx_enumerated(buf, 22, *v),
282 S::Maintenance(v) => primitives::encode_ctx_enumerated(buf, 23, *v),
283 S::NodeType(v) => primitives::encode_ctx_enumerated(buf, 24, *v),
284 S::NotifyType(v) => primitives::encode_ctx_enumerated(buf, 25, *v),
285 S::ShedState(v) => primitives::encode_ctx_enumerated(buf, 27, *v),
286 S::SilencedState(v) => primitives::encode_ctx_enumerated(buf, 28, *v),
287 S::AccessEvent(v) => primitives::encode_ctx_enumerated(buf, 30, *v),
288 S::ZoneOccupancyState(v) => primitives::encode_ctx_enumerated(buf, 31, *v),
289 S::AccessCredentialDisableReason(v) => primitives::encode_ctx_enumerated(buf, 32, *v),
290 S::AccessCredentialDisable(v) => primitives::encode_ctx_enumerated(buf, 33, *v),
291 S::AuthenticationStatus(v) => primitives::encode_ctx_enumerated(buf, 34, *v),
292 S::BackupState(v) => primitives::encode_ctx_enumerated(buf, 36, *v),
293 S::WriteStatus(v) => primitives::encode_ctx_enumerated(buf, 37, *v),
294 S::LightingInProgress(v) => primitives::encode_ctx_enumerated(buf, 38, *v),
295 S::LightingOperation(v) => primitives::encode_ctx_enumerated(buf, 39, *v),
296 S::LightingTransition(v) => primitives::encode_ctx_enumerated(buf, 40, *v),
297 S::IntegerValue(v) => primitives::encode_ctx_signed(buf, 41, *v),
298 S::BinaryLightingValue(v) => primitives::encode_ctx_enumerated(buf, 42, *v),
299 S::TimerState(v) => primitives::encode_ctx_enumerated(buf, 43, *v),
300 S::TimerTransition(v) => primitives::encode_ctx_enumerated(buf, 44, *v),
301 S::BacnetIpMode(v) => primitives::encode_ctx_enumerated(buf, 45, *v),
302 S::NetworkPortCommand(v) => primitives::encode_ctx_enumerated(buf, 46, *v),
303 S::NetworkType(v) => primitives::encode_ctx_enumerated(buf, 47, *v),
304 S::NetworkNumberQuality(v) => primitives::encode_ctx_enumerated(buf, 48, *v),
305 S::EscalatorOperationDirection(v) => primitives::encode_ctx_enumerated(buf, 49, *v),
306 S::EscalatorFault(v) => primitives::encode_ctx_enumerated(buf, 50, *v),
307 S::EscalatorMode(v) => primitives::encode_ctx_enumerated(buf, 51, *v),
308 S::LiftCarDirection(v) => primitives::encode_ctx_enumerated(buf, 52, *v),
309 S::LiftCarDoorCommand(v) => primitives::encode_ctx_enumerated(buf, 53, *v),
310 S::LiftCarDriveStatus(v) => primitives::encode_ctx_enumerated(buf, 54, *v),
311 S::LiftCarMode(v) => primitives::encode_ctx_enumerated(buf, 55, *v),
312 S::LiftGroupMode(v) => primitives::encode_ctx_enumerated(buf, 56, *v),
313 S::LiftFault(v) => primitives::encode_ctx_enumerated(buf, 57, *v),
314 S::ProtocolLevel(v) => primitives::encode_ctx_enumerated(buf, 58, *v),
315 S::AuditLevel(v) => primitives::encode_ctx_enumerated(buf, 59, *v),
316 S::AuditOperation(v) => primitives::encode_ctx_enumerated(buf, 60, *v),
317 S::ExtendedValue(v) => primitives::encode_ctx_unsigned(buf, 63, v.encoded() as u64),
318 S::Other(v) if v.is_constructed() => {
319 tags::encode_opening_tag(buf, v.tag());
320 buf.extend_from_slice(v.data());
321 tags::encode_closing_tag(buf, v.tag());
322 }
323 S::Other(v) => primitives::encode_ctx_octet_string(buf, v.tag(), v.data()),
324 }
325 Ok(())
326}
327
328pub fn decode_property_state(
333 data: &[u8],
334 offset: usize,
335) -> Result<(BACnetPropertyStates, usize), Error> {
336 use BACnetPropertyStates as S;
337 let (tag, pos) = tags::decode_tag(data, offset)?;
338 if tag.class != TagClass::Context || tag.is_closing {
339 return Err(Error::decoding(
340 offset,
341 "BACnetPropertyStates: expected a context tag",
342 ));
343 }
344 if tag.is_opening {
345 if !(64..=254).contains(&tag.number) {
346 return Err(Error::decoding(
347 offset,
348 "BACnetPropertyStates: constructed form requires a proprietary tag",
349 ));
350 }
351 let (content, end) = tags::extract_context_value(data, pos, tag.number)?;
352 validate_tlv_sequence(content, "proprietary property-state body")?;
353 return Ok((
354 S::Other(BACnetProprietaryPropertyState::constructed(
355 tag.number,
356 content.to_vec(),
357 )?),
358 end,
359 ));
360 }
361 let end = pos
362 .checked_add(tag.length as usize)
363 .ok_or_else(|| Error::decoding(pos, "BACnetPropertyStates: length overflow"))?;
364 if end > data.len() {
365 return Err(Error::buffer_too_short(end, data.len()));
366 }
367 let content = &data[pos..end];
368 let unsigned = || -> Result<u32, Error> {
369 u32::try_from(primitives::decode_unsigned(content)?)
370 .map_err(|_| Error::decoding(pos, "BACnetPropertyStates: contents exceed u32"))
371 };
372 let state = match tag.number {
373 0 => {
374 if content.len() != 1 {
375 return Err(Error::decoding(
376 offset,
377 format!(
378 "BACnetPropertyStates boolean-value: expected 1 contents octet, got {}",
379 content.len()
380 ),
381 ));
382 }
383 match content[0] {
384 0 => S::BooleanValue(false),
385 1 => S::BooleanValue(true),
386 value => {
387 return Err(Error::decoding(
388 pos,
389 format!("BACnetPropertyStates boolean-value must be 0 or 1, got {value}"),
390 ));
391 }
392 }
393 }
394 1 => S::BinaryValue(unsigned()?),
395 2 => S::EventType(unsigned()?),
396 3 => S::Polarity(unsigned()?),
397 4 => S::ProgramChange(unsigned()?),
398 5 => S::ProgramState(unsigned()?),
399 6 => S::ReasonForHalt(unsigned()?),
400 7 => S::Reliability(unsigned()?),
401 8 => S::State(unsigned()?),
402 9 => S::SystemStatus(unsigned()?),
403 10 => S::Units(unsigned()?),
404 11 => S::UnsignedValue(unsigned()?),
405 12 => S::LifeSafetyMode(unsigned()?),
406 13 => S::LifeSafetyState(unsigned()?),
407 14 => S::RestartReason(unsigned()?),
408 15 => S::DoorAlarmState(unsigned()?),
409 16 => S::Action(unsigned()?),
410 17 => S::DoorSecuredStatus(unsigned()?),
411 18 => S::DoorStatus(unsigned()?),
412 19 => S::DoorValue(unsigned()?),
413 20 => S::FileAccessMethod(unsigned()?),
414 21 => S::LockStatus(unsigned()?),
415 22 => S::LifeSafetyOperation(unsigned()?),
416 23 => S::Maintenance(unsigned()?),
417 24 => S::NodeType(unsigned()?),
418 25 => S::NotifyType(unsigned()?),
419 27 => S::ShedState(unsigned()?),
420 28 => S::SilencedState(unsigned()?),
421 30 => S::AccessEvent(unsigned()?),
422 31 => S::ZoneOccupancyState(unsigned()?),
423 32 => S::AccessCredentialDisableReason(unsigned()?),
424 33 => S::AccessCredentialDisable(unsigned()?),
425 34 => S::AuthenticationStatus(unsigned()?),
426 36 => S::BackupState(unsigned()?),
427 37 => S::WriteStatus(unsigned()?),
428 38 => S::LightingInProgress(unsigned()?),
429 39 => S::LightingOperation(unsigned()?),
430 40 => S::LightingTransition(unsigned()?),
431 41 => S::IntegerValue(primitives::decode_signed_canonical(content)?),
432 42 => S::BinaryLightingValue(unsigned()?),
433 43 => S::TimerState(unsigned()?),
434 44 => S::TimerTransition(unsigned()?),
435 45 => S::BacnetIpMode(unsigned()?),
436 46 => S::NetworkPortCommand(unsigned()?),
437 47 => S::NetworkType(unsigned()?),
438 48 => S::NetworkNumberQuality(unsigned()?),
439 49 => S::EscalatorOperationDirection(unsigned()?),
440 50 => S::EscalatorFault(unsigned()?),
441 51 => S::EscalatorMode(unsigned()?),
442 52 => S::LiftCarDirection(unsigned()?),
443 53 => S::LiftCarDoorCommand(unsigned()?),
444 54 => S::LiftCarDriveStatus(unsigned()?),
445 55 => S::LiftCarMode(unsigned()?),
446 56 => S::LiftGroupMode(unsigned()?),
447 57 => S::LiftFault(unsigned()?),
448 58 => S::ProtocolLevel(unsigned()?),
449 59 => S::AuditLevel(unsigned()?),
450 60 => S::AuditOperation(unsigned()?),
451 63 => S::ExtendedValue(BACnetExtendedPropertyState::from_encoded(unsigned()?)?),
452 other @ 64..=254 => S::Other(BACnetProprietaryPropertyState::primitive(
453 other,
454 content.to_vec(),
455 )?),
456 reserved => {
457 return Err(Error::decoding(
458 offset,
459 format!("BACnetPropertyStates context tag {reserved} is reserved"),
460 ));
461 }
462 };
463 Ok((state, end))
464}
465
466pub(crate) fn encode_dopr_body(buf: &mut BytesMut, r: &BACnetDeviceObjectPropertyReference) {
475 primitives::encode_ctx_object_id(buf, 0, &r.object_identifier);
476 primitives::encode_ctx_unsigned(buf, 1, r.property_identifier as u64);
477 if let Some(index) = r.property_array_index {
478 primitives::encode_ctx_unsigned(buf, 2, index as u64);
479 }
480 if let Some(ref device) = r.device_identifier {
481 primitives::encode_ctx_object_id(buf, 3, device);
482 }
483}
484
485pub(crate) fn decode_dopr_body(
488 data: &[u8],
489 offset: usize,
490 what: &str,
491) -> Result<(BACnetDeviceObjectPropertyReference, usize), Error> {
492 let (t, pos) = tags::decode_tag(data, offset)?;
494 if !t.is_context(0) || t.length != 4 {
495 return Err(Error::decoding(
496 offset,
497 format!("{what}: expected [0] object-identifier (4 octets)"),
498 ));
499 }
500 let end = pos + 4;
501 if end > data.len() {
502 return Err(Error::buffer_too_short(end, data.len()));
503 }
504 let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
505 let mut offset = end;
506
507 let (raw, new_offset) = decode_ctx_unsigned(data, offset, 1, what)?;
509 let property_identifier = u32::try_from(raw)
510 .map_err(|_| Error::decoding(offset, format!("{what}: property-identifier exceeds u32")))?;
511 offset = new_offset;
512
513 let mut property_array_index = None;
515 if offset < data.len() {
516 let (peek, peek_pos) = tags::decode_tag(data, offset)?;
517 if peek.is_context(2) {
518 let end = peek_pos
519 .checked_add(peek.length as usize)
520 .ok_or_else(|| Error::decoding(peek_pos, format!("{what}: length overflow")))?;
521 if end > data.len() {
522 return Err(Error::buffer_too_short(end, data.len()));
523 }
524 let index = primitives::decode_unsigned(&data[peek_pos..end])?;
525 property_array_index = Some(u32::try_from(index).map_err(|_| {
526 Error::decoding(offset, format!("{what}: property-array-index exceeds u32"))
527 })?);
528 offset = end;
529 }
530 }
531
532 let mut device_identifier = None;
534 if offset < data.len() {
535 let (peek, peek_pos) = tags::decode_tag(data, offset)?;
536 if peek.is_context(3) {
537 if peek.length != 4 {
538 return Err(Error::decoding(
539 offset,
540 format!("{what}: expected [3] device-identifier (4 octets)"),
541 ));
542 }
543 let end = peek_pos + 4;
544 if end > data.len() {
545 return Err(Error::buffer_too_short(end, data.len()));
546 }
547 device_identifier = Some(ObjectIdentifier::decode(&data[peek_pos..end])?);
548 offset = end;
549 }
550 }
551
552 Ok((
553 BACnetDeviceObjectPropertyReference {
554 object_identifier,
555 property_identifier,
556 property_array_index,
557 device_identifier,
558 },
559 offset,
560 ))
561}
562
563pub fn validate_tlv_sequence(data: &[u8], what: &str) -> Result<(), Error> {
568 let mut offset = 0;
569 let mut count = 0;
570 while offset < data.len() {
571 if count >= MAX_FRAMED_ITEMS {
572 return Err(Error::decoding(
573 offset,
574 format!("{what}: sequence exceeds item limit"),
575 ));
576 }
577 let (tag, content) = tags::decode_tag(data, offset)?;
578 if tag.is_opening {
579 let (inner, next) = tags::extract_context_value(data, content, tag.number)?;
580 validate_tlv_sequence(inner, what)?;
581 offset = next;
582 } else if tag.class == TagClass::Application {
583 offset = primitives::validate_application_value(data, offset)?;
584 } else {
585 let (_, next) = primitives::decode_application_value(data, offset)?;
586 offset = next;
587 }
588 count += 1;
589 }
590 Ok(())
591}
592
593pub(crate) fn validate_extended_parameters(data: &[u8], what: &str) -> Result<(), Error> {
598 let mut offset = 0;
599 let mut count = 0;
600 while offset < data.len() {
601 if count >= MAX_FRAMED_ITEMS {
602 return Err(Error::decoding(
603 offset,
604 format!("{what}: parameters exceed item limit"),
605 ));
606 }
607 let (tag, content) = tags::decode_tag(data, offset)?;
608 if tag.class == TagClass::Context {
609 if !tag.is_opening_tag(0) {
610 return Err(Error::decoding(
611 offset,
612 format!("{what}: expected reference opening tag [0]"),
613 ));
614 }
615 let (_, after_reference) = decode_dopr_body(data, content, what)?;
616 offset = expect_closing(data, after_reference, 0, what)?;
617 } else {
618 offset = primitives::validate_application_value(data, offset)?;
619 }
620 count += 1;
621 }
622 Ok(())
623}
624
625#[cfg(test)]
626mod tests;