1use bacnet_types::constructed::{
25 BACnetDeviceObjectPropertyReference, BACnetExtendedPropertyState, BACnetPropertyStates,
26 BACnetProprietaryPropertyState,
27};
28use bacnet_types::error::Error;
29use bytes::BytesMut;
30
31use crate::primitives;
32use crate::tags::{self, TagClass};
33use tagged::{
34 contents, decode_ctx_object_id, decode_ctx_unsigned, decode_optional_ctx, expect_closing,
35 expect_end, expect_opening,
36};
37
38pub mod access_credential;
39pub mod access_rule;
40mod action_list;
41pub mod assigned_landing_calls;
42mod audit_notification;
43mod audit_record;
44pub mod authentication_policy;
45pub mod calendar;
46mod channel_value;
47mod color_command;
48pub mod cov_subscription;
49mod event_log_record;
50mod event_notification;
51pub mod event_notification_subscription;
52pub mod event_parameter;
53pub mod fault_parameter;
54mod floor_pairs;
55pub mod landing_call_status;
56pub mod landing_door_status;
57pub mod lift_car_call_list;
58mod lighting_command;
59mod log_fields;
60mod log_multiple_record;
61mod log_record;
62mod members;
63mod name_value;
64pub mod network_port;
65pub mod object_property_reference;
66pub mod port_permission;
67mod property_access_result;
68mod property_value;
69mod read_access;
70pub mod recipient;
71mod scale;
72pub mod schedule;
73mod shed_level;
74pub mod staging;
75pub mod tagged;
76mod value_source;
77
78pub use access_credential::{
79 decode_assigned_access_rights, decode_authentication_factor,
80 decode_authentication_factor_format, decode_credential_authentication_factor,
81 encode_assigned_access_rights, encode_authentication_factor,
82 encode_authentication_factor_format, encode_credential_authentication_factor,
83};
84pub use access_rule::{decode_access_rule, encode_access_rule};
85pub use action_list::{
86 decode_action_command, decode_action_list, encode_action_command, encode_action_list,
87};
88pub use assigned_landing_calls::{decode_assigned_landing_calls, encode_assigned_landing_calls};
89pub use audit_notification::{decode_audit_notification_at, encode_audit_notification};
90pub use audit_record::{
91 decode_audit_log_record, decode_audit_log_record_at, decode_audit_log_record_result_at,
92 encode_audit_log_record, encode_audit_log_record_result,
93};
94pub use authentication_policy::{decode_authentication_policy, encode_authentication_policy};
95pub use calendar::{
96 decode_calendar_entry, decode_calendar_entry_list, decode_date_range, encode_calendar_entry,
97 encode_calendar_entry_list, encode_date_range,
98};
99pub use channel_value::{channel_value_end, constructed_channel_value, ConstructedChannelValue};
100pub use color_command::{
101 decode_color_command, decode_color_command_value, decode_xy_color, encode_color_command,
102 encode_xy_color,
103};
104pub use cov_subscription::{
105 decode_cov_multiple_subscription, decode_cov_subscription, encode_cov_multiple_subscription,
106 encode_cov_multiple_subscription_list, encode_cov_subscription, encode_cov_subscription_list,
107};
108pub use event_log_record::{decode_event_log_record, encode_event_log_record};
109pub use event_notification::{
110 decode_event_notification, decode_event_notification_tolerant, decode_notification_parameters,
111 encode_event_notification, encode_notification_parameters,
112};
113pub use event_notification_subscription::{
114 decode_event_notification_subscription, encode_event_notification_subscription,
115 encode_event_notification_subscription_list,
116};
117pub use event_parameter::{decode_event_parameter, encode_event_parameter};
118pub use fault_parameter::{decode_fault_parameters, encode_fault_parameters};
119pub use landing_call_status::{
120 decode_landing_call_status, decode_landing_call_status_list, encode_landing_call_status,
121 encode_landing_call_status_list,
122};
123pub use landing_door_status::{decode_landing_door_status, encode_landing_door_status};
124pub use lift_car_call_list::{decode_lift_car_call_list, encode_lift_car_call_list};
125pub use lighting_command::{
126 decode_lighting_command, decode_lighting_command_value, encode_lighting_command,
127};
128pub use log_multiple_record::{decode_log_multiple_record, encode_log_multiple_record};
129pub use log_record::{decode_log_record, encode_log_record};
130pub use name_value::{decode_name_value, encode_name_value};
131pub use network_port::{
132 decode_bdt_entry, decode_bdt_entry_list, decode_fdt_entry, decode_fdt_entry_list,
133 decode_host_address, decode_host_n_port, encode_bdt_entry, encode_bdt_entry_list,
134 encode_fdt_entry, encode_fdt_entry_list, encode_host_address, encode_host_n_port,
135};
136pub use object_property_reference::{
137 decode_object_property_reference, decode_object_property_reference_at,
138 decode_setpoint_reference, decode_setpoint_reference_at, encode_object_property_reference,
139 encode_setpoint_reference,
140};
141pub use port_permission::{decode_port_permission, encode_port_permission};
142pub use property_access_result::{decode_property_access_result, encode_property_access_result};
143pub use property_value::{
144 decode_bacnet_property_value, decode_bacnet_property_value_in_list,
145 decode_bacnet_property_value_in_list_detailed, encode_bacnet_property_value,
146 extract_property_value, PropertyValueBoundary, PropertyValueDecodeError,
147 PropertyValueDecodeFailure, PropertyValueDecodeStage,
148};
149pub use read_access::{
150 decode_property_reference, decode_read_access_specification, encode_property_reference,
151 encode_read_access_specification,
152};
153pub use recipient::{
154 check_decoded_mac_len, check_encoded_mac_len, decode_destination, decode_destination_list,
155 decode_recipient, encode_destination, encode_destination_list, encode_recipient,
156};
157pub use scale::{decode_prescale, decode_scale, encode_prescale, encode_scale};
158pub use schedule::{
159 decode_daily_schedule, decode_exception_schedule, decode_special_event,
160 decode_special_event_period, decode_time_value, decode_weekly_schedule, encode_daily_schedule,
161 encode_exception_schedule, encode_special_event, encode_special_event_period,
162 encode_time_value, encode_weekly_schedule,
163};
164pub use shed_level::{decode_shed_level, encode_shed_level};
165pub use staging::{
166 decode_device_object_reference, decode_stage_limit_value, encode_device_object_reference,
167 encode_stage_limit_value,
168};
169
170pub use value_source::{decode_value_source, encode_value_source};
171
172const MAX_FRAMED_ITEMS: usize = 10_000;
176
177pub fn encode_property_state(
186 buf: &mut BytesMut,
187 state: &BACnetPropertyStates,
188) -> Result<(), Error> {
189 use BACnetPropertyStates as S;
190 if let S::Other(value) = state {
191 if value.is_constructed() {
192 validate_tlv_sequence(value.data(), "proprietary property-state body")?;
193 let mut framed = BytesMut::new();
194 tags::encode_opening_tag(&mut framed, value.tag());
195 let (_, body_start) = tags::decode_tag(&framed, 0)?;
196 framed.extend_from_slice(value.data());
197 tags::encode_closing_tag(&mut framed, value.tag());
198 let (_, end) = tags::extract_context_value(&framed, body_start, value.tag())?;
199 expect_end(&framed, end, end, "proprietary property-state body")?;
200 }
201 }
202 match state {
203 S::BooleanValue(v) => primitives::encode_ctx_boolean(buf, 0, *v),
204 S::BinaryValue(v) => primitives::encode_ctx_enumerated(buf, 1, *v),
205 S::EventType(v) => primitives::encode_ctx_enumerated(buf, 2, *v),
206 S::Polarity(v) => primitives::encode_ctx_enumerated(buf, 3, *v),
207 S::ProgramChange(v) => primitives::encode_ctx_enumerated(buf, 4, *v),
208 S::ProgramState(v) => primitives::encode_ctx_enumerated(buf, 5, *v),
209 S::ReasonForHalt(v) => primitives::encode_ctx_enumerated(buf, 6, *v),
210 S::Reliability(v) => primitives::encode_ctx_enumerated(buf, 7, *v),
211 S::State(v) => primitives::encode_ctx_enumerated(buf, 8, *v),
212 S::SystemStatus(v) => primitives::encode_ctx_enumerated(buf, 9, *v),
213 S::Units(v) => primitives::encode_ctx_enumerated(buf, 10, *v),
214 S::UnsignedValue(v) => primitives::encode_ctx_unsigned(buf, 11, *v as u64),
215 S::LifeSafetyMode(v) => primitives::encode_ctx_enumerated(buf, 12, *v),
216 S::LifeSafetyState(v) => primitives::encode_ctx_enumerated(buf, 13, *v),
217 S::RestartReason(v) => primitives::encode_ctx_enumerated(buf, 14, *v),
218 S::DoorAlarmState(v) => primitives::encode_ctx_enumerated(buf, 15, *v),
219 S::Action(v) => primitives::encode_ctx_enumerated(buf, 16, *v),
220 S::DoorSecuredStatus(v) => primitives::encode_ctx_enumerated(buf, 17, *v),
221 S::DoorStatus(v) => primitives::encode_ctx_enumerated(buf, 18, *v),
222 S::DoorValue(v) => primitives::encode_ctx_enumerated(buf, 19, *v),
223 S::FileAccessMethod(v) => primitives::encode_ctx_enumerated(buf, 20, *v),
224 S::LockStatus(v) => primitives::encode_ctx_enumerated(buf, 21, *v),
225 S::LifeSafetyOperation(v) => primitives::encode_ctx_enumerated(buf, 22, *v),
226 S::Maintenance(v) => primitives::encode_ctx_enumerated(buf, 23, *v),
227 S::NodeType(v) => primitives::encode_ctx_enumerated(buf, 24, *v),
228 S::NotifyType(v) => primitives::encode_ctx_enumerated(buf, 25, *v),
229 S::ShedState(v) => primitives::encode_ctx_enumerated(buf, 27, *v),
230 S::SilencedState(v) => primitives::encode_ctx_enumerated(buf, 28, *v),
231 S::AccessEvent(v) => primitives::encode_ctx_enumerated(buf, 30, *v),
232 S::ZoneOccupancyState(v) => primitives::encode_ctx_enumerated(buf, 31, *v),
233 S::AccessCredentialDisableReason(v) => primitives::encode_ctx_enumerated(buf, 32, *v),
234 S::AccessCredentialDisable(v) => primitives::encode_ctx_enumerated(buf, 33, *v),
235 S::AuthenticationStatus(v) => primitives::encode_ctx_enumerated(buf, 34, *v),
236 S::BackupState(v) => primitives::encode_ctx_enumerated(buf, 36, *v),
237 S::WriteStatus(v) => primitives::encode_ctx_enumerated(buf, 37, *v),
238 S::LightingInProgress(v) => primitives::encode_ctx_enumerated(buf, 38, *v),
239 S::LightingOperation(v) => primitives::encode_ctx_enumerated(buf, 39, *v),
240 S::LightingTransition(v) => primitives::encode_ctx_enumerated(buf, 40, *v),
241 S::IntegerValue(v) => primitives::encode_ctx_signed(buf, 41, *v),
242 S::BinaryLightingValue(v) => primitives::encode_ctx_enumerated(buf, 42, *v),
243 S::TimerState(v) => primitives::encode_ctx_enumerated(buf, 43, *v),
244 S::TimerTransition(v) => primitives::encode_ctx_enumerated(buf, 44, *v),
245 S::BacnetIpMode(v) => primitives::encode_ctx_enumerated(buf, 45, *v),
246 S::NetworkPortCommand(v) => primitives::encode_ctx_enumerated(buf, 46, *v),
247 S::NetworkType(v) => primitives::encode_ctx_enumerated(buf, 47, *v),
248 S::NetworkNumberQuality(v) => primitives::encode_ctx_enumerated(buf, 48, *v),
249 S::EscalatorOperationDirection(v) => primitives::encode_ctx_enumerated(buf, 49, *v),
250 S::EscalatorFault(v) => primitives::encode_ctx_enumerated(buf, 50, *v),
251 S::EscalatorMode(v) => primitives::encode_ctx_enumerated(buf, 51, *v),
252 S::LiftCarDirection(v) => primitives::encode_ctx_enumerated(buf, 52, *v),
253 S::LiftCarDoorCommand(v) => primitives::encode_ctx_enumerated(buf, 53, *v),
254 S::LiftCarDriveStatus(v) => primitives::encode_ctx_enumerated(buf, 54, *v),
255 S::LiftCarMode(v) => primitives::encode_ctx_enumerated(buf, 55, *v),
256 S::LiftGroupMode(v) => primitives::encode_ctx_enumerated(buf, 56, *v),
257 S::LiftFault(v) => primitives::encode_ctx_enumerated(buf, 57, *v),
258 S::ProtocolLevel(v) => primitives::encode_ctx_enumerated(buf, 58, *v),
259 S::AuditLevel(v) => primitives::encode_ctx_enumerated(buf, 59, *v),
260 S::AuditOperation(v) => primitives::encode_ctx_enumerated(buf, 60, *v),
261 S::ExtendedValue(v) => primitives::encode_ctx_unsigned(buf, 63, v.encoded() as u64),
262 S::Other(v) if v.is_constructed() => {
263 tags::encode_opening_tag(buf, v.tag());
264 buf.extend_from_slice(v.data());
265 tags::encode_closing_tag(buf, v.tag());
266 }
267 S::Other(v) => primitives::encode_ctx_octet_string(buf, v.tag(), v.data()),
268 }
269 Ok(())
270}
271
272pub fn decode_property_state(
277 data: &[u8],
278 offset: usize,
279) -> Result<(BACnetPropertyStates, usize), Error> {
280 use BACnetPropertyStates as S;
281 let (tag, pos) = tags::decode_tag(data, offset)?;
282 if tag.class != TagClass::Context || tag.is_closing {
283 return Err(tagged::misplaced_tag(
284 data,
285 &tag,
286 None,
287 offset,
288 "BACnetPropertyStates: expected a context tag",
289 ));
290 }
291 if tag.is_opening {
292 if !(64..=254).contains(&tag.number) {
293 return Err(Error::invalid_tag(
294 offset,
295 "BACnetPropertyStates: constructed form requires a proprietary tag",
296 ));
297 }
298 let (content, end) = tags::extract_context_value(data, pos, tag.number)?;
299 validate_tlv_sequence(content, "proprietary property-state body")?;
300 return Ok((
301 S::Other(BACnetProprietaryPropertyState::constructed(
302 tag.number,
303 content.to_vec(),
304 )?),
305 end,
306 ));
307 }
308 let (content, end) = contents(data, pos, tag.length)?;
309 let unsigned = || -> Result<u32, Error> {
310 u32::try_from(primitives::decode_unsigned(content)?)
311 .map_err(|_| Error::out_of_range(pos, "BACnetPropertyStates: contents exceed u32"))
312 };
313 let state = match tag.number {
314 0 => {
315 if content.len() != 1 {
316 return Err(Error::decoding(
317 offset,
318 format!(
319 "BACnetPropertyStates boolean-value: expected 1 contents octet, got {}",
320 content.len()
321 ),
322 ));
323 }
324 match content[0] {
325 0 => S::BooleanValue(false),
326 1 => S::BooleanValue(true),
327 value => {
328 return Err(Error::decoding(
329 pos,
330 format!("BACnetPropertyStates boolean-value must be 0 or 1, got {value}"),
331 ));
332 }
333 }
334 }
335 1 => S::BinaryValue(unsigned()?),
336 2 => S::EventType(unsigned()?),
337 3 => S::Polarity(unsigned()?),
338 4 => S::ProgramChange(unsigned()?),
339 5 => S::ProgramState(unsigned()?),
340 6 => S::ReasonForHalt(unsigned()?),
341 7 => S::Reliability(unsigned()?),
342 8 => S::State(unsigned()?),
343 9 => S::SystemStatus(unsigned()?),
344 10 => S::Units(unsigned()?),
345 11 => S::UnsignedValue(unsigned()?),
346 12 => S::LifeSafetyMode(unsigned()?),
347 13 => S::LifeSafetyState(unsigned()?),
348 14 => S::RestartReason(unsigned()?),
349 15 => S::DoorAlarmState(unsigned()?),
350 16 => S::Action(unsigned()?),
351 17 => S::DoorSecuredStatus(unsigned()?),
352 18 => S::DoorStatus(unsigned()?),
353 19 => S::DoorValue(unsigned()?),
354 20 => S::FileAccessMethod(unsigned()?),
355 21 => S::LockStatus(unsigned()?),
356 22 => S::LifeSafetyOperation(unsigned()?),
357 23 => S::Maintenance(unsigned()?),
358 24 => S::NodeType(unsigned()?),
359 25 => S::NotifyType(unsigned()?),
360 27 => S::ShedState(unsigned()?),
361 28 => S::SilencedState(unsigned()?),
362 30 => S::AccessEvent(unsigned()?),
363 31 => S::ZoneOccupancyState(unsigned()?),
364 32 => S::AccessCredentialDisableReason(unsigned()?),
365 33 => S::AccessCredentialDisable(unsigned()?),
366 34 => S::AuthenticationStatus(unsigned()?),
367 36 => S::BackupState(unsigned()?),
368 37 => S::WriteStatus(unsigned()?),
369 38 => S::LightingInProgress(unsigned()?),
370 39 => S::LightingOperation(unsigned()?),
371 40 => S::LightingTransition(unsigned()?),
372 41 => S::IntegerValue(primitives::decode_signed_canonical(content)?),
373 42 => S::BinaryLightingValue(unsigned()?),
374 43 => S::TimerState(unsigned()?),
375 44 => S::TimerTransition(unsigned()?),
376 45 => S::BacnetIpMode(unsigned()?),
377 46 => S::NetworkPortCommand(unsigned()?),
378 47 => S::NetworkType(unsigned()?),
379 48 => S::NetworkNumberQuality(unsigned()?),
380 49 => S::EscalatorOperationDirection(unsigned()?),
381 50 => S::EscalatorFault(unsigned()?),
382 51 => S::EscalatorMode(unsigned()?),
383 52 => S::LiftCarDirection(unsigned()?),
384 53 => S::LiftCarDoorCommand(unsigned()?),
385 54 => S::LiftCarDriveStatus(unsigned()?),
386 55 => S::LiftCarMode(unsigned()?),
387 56 => S::LiftGroupMode(unsigned()?),
388 57 => S::LiftFault(unsigned()?),
389 58 => S::ProtocolLevel(unsigned()?),
390 59 => S::AuditLevel(unsigned()?),
391 60 => S::AuditOperation(unsigned()?),
392 63 => S::ExtendedValue(BACnetExtendedPropertyState::from_encoded(unsigned()?)?),
393 other @ 64..=254 => S::Other(BACnetProprietaryPropertyState::primitive(
394 other,
395 content.to_vec(),
396 )?),
397 reserved => {
398 return Err(Error::invalid_tag(
399 offset,
400 format!("BACnetPropertyStates context tag {reserved} is reserved"),
401 ));
402 }
403 };
404 Ok((state, end))
405}
406
407pub(crate) fn encode_dopr_body(buf: &mut BytesMut, r: &BACnetDeviceObjectPropertyReference) {
416 primitives::encode_ctx_object_id(buf, 0, &r.object_identifier);
417 primitives::encode_ctx_unsigned(buf, 1, r.property_identifier as u64);
418 if let Some(index) = r.property_array_index {
419 primitives::encode_ctx_unsigned(buf, 2, index as u64);
420 }
421 if let Some(ref device) = r.device_identifier {
422 primitives::encode_ctx_object_id(buf, 3, device);
423 }
424}
425
426pub(crate) fn decode_dopr_body(
429 data: &[u8],
430 offset: usize,
431 what: &str,
432) -> Result<(BACnetDeviceObjectPropertyReference, usize), Error> {
433 let (object_identifier, offset) = decode_ctx_object_id(data, offset, 0, what)?;
434 let (property_identifier, offset) = decode_ctx_unsigned::<u32>(data, offset, 1, what)?;
435 let (property_array_index, offset) =
436 decode_optional_ctx(data, offset, 2, what, decode_ctx_unsigned::<u32>)?;
437 let (device_identifier, offset) =
438 decode_optional_ctx(data, offset, 3, what, decode_ctx_object_id)?;
439 Ok((
440 BACnetDeviceObjectPropertyReference {
441 object_identifier,
442 property_identifier,
443 property_array_index,
444 device_identifier,
445 },
446 offset,
447 ))
448}
449
450pub fn encode_device_object_property_reference(
454 buf: &mut BytesMut,
455 r: &BACnetDeviceObjectPropertyReference,
456) {
457 encode_dopr_body(buf, r);
458}
459
460pub fn decode_device_object_property_reference(
465 data: &[u8],
466 offset: usize,
467) -> Result<(BACnetDeviceObjectPropertyReference, usize), Error> {
468 decode_dopr_body(data, offset, "BACnetDeviceObjectPropertyReference")
469}
470
471pub fn validate_tlv_sequence(data: &[u8], what: &str) -> Result<(), Error> {
476 let mut offset = 0;
477 let mut count = 0;
478 while offset < data.len() {
479 if count >= MAX_FRAMED_ITEMS {
480 return Err(Error::overflow(
481 offset,
482 format!("{what}: sequence exceeds item limit"),
483 ));
484 }
485 let (tag, content) = tags::decode_tag(data, offset)?;
486 if tag.is_opening {
487 let (inner, next) = tags::extract_context_value(data, content, tag.number)?;
488 validate_tlv_sequence(inner, what)?;
489 offset = next;
490 } else if tag.class == TagClass::Application {
491 offset = primitives::validate_application_value(data, offset)?;
492 } else {
493 let (_, next) = primitives::decode_application_value(data, offset)?;
494 offset = next;
495 }
496 count += 1;
497 }
498 Ok(())
499}
500
501pub(crate) fn validate_extended_parameters(data: &[u8], what: &str) -> Result<(), Error> {
506 let mut offset = 0;
507 let mut count = 0;
508 while offset < data.len() {
509 if count >= MAX_FRAMED_ITEMS {
510 return Err(Error::overflow(
511 offset,
512 format!("{what}: parameters exceed item limit"),
513 ));
514 }
515 let (tag, _) = tags::decode_tag(data, offset)?;
516 if tag.class == TagClass::Context {
517 let content = expect_opening(data, offset, 0, what)?;
518 let (_, after_reference) = decode_dopr_body(data, content, what)?;
519 offset = expect_closing(data, after_reference, 0, what)?;
520 } else {
521 offset = primitives::validate_application_value(data, offset)?;
522 }
523 count += 1;
524 }
525 Ok(())
526}
527
528#[cfg(test)]
529pub(crate) mod tests;