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