1use bacnet_types::constructed::BACnetPropertyValue;
5use bacnet_types::enums::{PropertyIdentifier, RejectReason};
6use bacnet_types::error::Error;
7
8use super::{extract_property_value, matches_property_boundary, PropertyValueBoundary};
9use crate::constructed::tagged::misplaced_kind;
10use crate::{primitives, tags};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum PropertyValueDecodeStage {
15 PropertyIdentifier,
17 ArrayIndex,
19 Value,
21 Priority,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum PropertyValueDecodeFailure {
28 Syntax(RejectReason),
30 PriorityOutOfRange,
32}
33
34#[derive(Debug)]
36pub struct PropertyValueDecodeError {
37 pub error: Error,
39 pub offset: usize,
41 pub stage: PropertyValueDecodeStage,
43 pub kind: PropertyValueDecodeFailure,
45 pub property_identifier: Option<PropertyIdentifier>,
47 pub property_array_index: Option<u32>,
49 pub reference_complete: bool,
52}
53
54impl PropertyValueDecodeError {
55 fn syntax(
56 error: Error,
57 offset: usize,
58 stage: PropertyValueDecodeStage,
59 reject_reason: RejectReason,
60 property_identifier: Option<PropertyIdentifier>,
61 property_array_index: Option<u32>,
62 reference_complete: bool,
63 ) -> Self {
64 Self {
65 error,
66 offset,
67 stage,
68 kind: PropertyValueDecodeFailure::Syntax(reject_reason),
69 property_identifier,
70 property_array_index,
71 reference_complete,
72 }
73 }
74}
75
76fn reason_of(error: &Error) -> RejectReason {
80 error
81 .reject_reason()
82 .unwrap_or(RejectReason::INVALID_DATA_ENCODING)
83}
84
85fn error_offset(error: &Error, fallback: usize) -> usize {
86 match error {
87 Error::Decoding { offset, .. } => *offset,
88 _ => fallback,
89 }
90}
91
92pub fn decode_bacnet_property_value(
97 data: &[u8],
98 offset: usize,
99) -> Result<(BACnetPropertyValue, usize), Error> {
100 decode_with_boundaries(
101 data,
102 offset,
103 &[
104 PropertyValueBoundary::End,
105 PropertyValueBoundary::Context(0),
106 PropertyValueBoundary::ContextToEnd(3),
107 ],
108 )
109}
110
111pub fn decode_bacnet_property_value_in_list(
114 data: &[u8],
115 offset: usize,
116 closing_tag: u8,
117) -> Result<(BACnetPropertyValue, usize), Error> {
118 decode_bacnet_property_value_in_list_detailed(data, offset, closing_tag)
119 .map_err(|error| error.error)
120}
121
122pub fn decode_bacnet_property_value_in_list_detailed(
125 data: &[u8],
126 offset: usize,
127 closing_tag: u8,
128) -> Result<(BACnetPropertyValue, usize), PropertyValueDecodeError> {
129 decode_with_boundaries_detailed(
130 data,
131 offset,
132 &[
133 PropertyValueBoundary::Context(3),
134 PropertyValueBoundary::Context(0),
135 PropertyValueBoundary::Closing(closing_tag),
136 ],
137 )
138}
139
140fn decode_with_boundaries(
141 data: &[u8],
142 offset: usize,
143 boundaries: &[PropertyValueBoundary],
144) -> Result<(BACnetPropertyValue, usize), Error> {
145 decode_with_boundaries_detailed(data, offset, boundaries).map_err(|error| error.error)
146}
147
148fn decode_with_boundaries_detailed(
149 data: &[u8],
150 offset: usize,
151 boundaries: &[PropertyValueBoundary],
152) -> Result<(BACnetPropertyValue, usize), PropertyValueDecodeError> {
153 let start = offset;
154 let (tag, content_start) = tags::decode_tag(data, offset).map_err(|error| {
155 let reason = reason_of(&error);
156 PropertyValueDecodeError::syntax(
157 error,
158 offset,
159 PropertyValueDecodeStage::PropertyIdentifier,
160 reason,
161 None,
162 None,
163 false,
164 )
165 })?;
166 if !tag.is_context(0) {
167 let kind = misplaced_kind(data, offset, &tag, Some(0));
168 return Err(PropertyValueDecodeError::syntax(
169 Error::decoding_kind(
170 kind,
171 offset,
172 "BACnetPropertyValue property-id expected context tag 0",
173 ),
174 offset,
175 PropertyValueDecodeStage::PropertyIdentifier,
176 kind.reject_reason(),
177 None,
178 None,
179 false,
180 ));
181 }
182 let property_end = content_start
183 .checked_add(tag.length as usize)
184 .ok_or_else(|| {
185 PropertyValueDecodeError::syntax(
186 Error::decoding(
187 content_start,
188 "BACnetPropertyValue property-id length overflow",
189 ),
190 content_start,
191 PropertyValueDecodeStage::PropertyIdentifier,
192 RejectReason::INVALID_DATA_ENCODING,
193 None,
194 None,
195 false,
196 )
197 })?;
198 if property_end > data.len() {
199 return Err(PropertyValueDecodeError::syntax(
200 Error::buffer_too_short(property_end, data.len()),
201 content_start,
202 PropertyValueDecodeStage::PropertyIdentifier,
203 RejectReason::MISSING_REQUIRED_PARAMETER,
204 None,
205 None,
206 false,
207 ));
208 }
209 let prop_id =
210 primitives::decode_unsigned(&data[content_start..property_end]).map_err(|error| {
211 PropertyValueDecodeError::syntax(
212 error,
213 content_start,
214 PropertyValueDecodeStage::PropertyIdentifier,
215 RejectReason::INVALID_DATA_ENCODING,
216 None,
217 None,
218 false,
219 )
220 })?;
221 let prop_id = u32::try_from(prop_id).map_err(|_| {
222 PropertyValueDecodeError::syntax(
223 Error::out_of_range(start, "BACnetPropertyValue property-id exceeds u32"),
224 start,
225 PropertyValueDecodeStage::PropertyIdentifier,
226 RejectReason::PARAMETER_OUT_OF_RANGE,
227 None,
228 None,
229 false,
230 )
231 })?;
232 let property_identifier = PropertyIdentifier::from_raw(prop_id);
233 let mut offset = property_end;
234
235 let mut array_index = None;
236 if offset < data.len() {
237 let (tag, content_start) = tags::decode_tag(data, offset).map_err(|error| {
238 let reason = reason_of(&error);
239 PropertyValueDecodeError::syntax(
240 error,
241 offset,
242 PropertyValueDecodeStage::ArrayIndex,
243 reason,
244 Some(property_identifier),
245 None,
246 false,
247 )
248 })?;
249 if tag.is_context(1) {
250 let end = content_start
251 .checked_add(tag.length as usize)
252 .ok_or_else(|| {
253 PropertyValueDecodeError::syntax(
254 Error::decoding(
255 content_start,
256 "BACnetPropertyValue array-index length overflow",
257 ),
258 content_start,
259 PropertyValueDecodeStage::ArrayIndex,
260 RejectReason::INVALID_DATA_ENCODING,
261 Some(property_identifier),
262 None,
263 false,
264 )
265 })?;
266 if end > data.len() {
267 return Err(PropertyValueDecodeError::syntax(
268 Error::buffer_too_short(end, data.len()),
269 content_start,
270 PropertyValueDecodeStage::ArrayIndex,
271 RejectReason::MISSING_REQUIRED_PARAMETER,
272 Some(property_identifier),
273 None,
274 false,
275 ));
276 }
277 let value =
278 primitives::decode_unsigned(&data[content_start..end]).map_err(|error| {
279 PropertyValueDecodeError::syntax(
280 error,
281 content_start,
282 PropertyValueDecodeStage::ArrayIndex,
283 RejectReason::INVALID_DATA_ENCODING,
284 Some(property_identifier),
285 None,
286 false,
287 )
288 })?;
289 let value = u32::try_from(value).map_err(|_| {
290 PropertyValueDecodeError::syntax(
291 Error::out_of_range(offset, "BACnetPropertyValue array-index exceeds u32"),
292 offset,
293 PropertyValueDecodeStage::ArrayIndex,
294 RejectReason::PARAMETER_OUT_OF_RANGE,
295 Some(property_identifier),
296 None,
297 false,
298 )
299 })?;
300 array_index = Some(value);
301 offset = end;
302 }
303 }
304
305 let (tag, tag_end) = tags::decode_tag(data, offset).map_err(|error| {
306 let reason = reason_of(&error);
307 PropertyValueDecodeError::syntax(
308 error,
309 offset,
310 PropertyValueDecodeStage::Value,
311 reason,
312 Some(property_identifier),
313 array_index,
314 true,
315 )
316 })?;
317 if !tag.is_opening_tag(2) {
318 let kind = misplaced_kind(data, offset, &tag, Some(2));
319 return Err(PropertyValueDecodeError::syntax(
320 Error::decoding_kind(kind, offset, "BACnetPropertyValue expected opening tag 2"),
321 offset,
322 PropertyValueDecodeStage::Value,
323 kind.reject_reason(),
324 Some(property_identifier),
325 array_index,
326 true,
327 ));
328 }
329 let (value_bytes, offset) =
330 extract_property_value(data, tag_end, 2, property_identifier, boundaries).map_err(
331 |error| {
332 let reject_reason = reason_of(&error);
333 let offset = error_offset(&error, tag_end);
334 PropertyValueDecodeError::syntax(
335 error,
336 offset,
337 PropertyValueDecodeStage::Value,
338 reject_reason,
339 Some(property_identifier),
340 array_index,
341 true,
342 )
343 },
344 )?;
345 let value = value_bytes.to_vec();
346
347 let mut priority = None;
348 if offset < data.len() {
349 let (tag, new_pos) = tags::decode_tag(data, offset).map_err(|error| {
350 let reason = reason_of(&error);
351 PropertyValueDecodeError::syntax(
352 error,
353 offset,
354 PropertyValueDecodeStage::Priority,
355 reason,
356 Some(property_identifier),
357 array_index,
358 true,
359 )
360 })?;
361 if tag.is_context(3) {
362 let end = new_pos.checked_add(tag.length as usize).ok_or_else(|| {
363 PropertyValueDecodeError::syntax(
364 Error::decoding(new_pos, "BACnetPropertyValue priority length overflow"),
365 new_pos,
366 PropertyValueDecodeStage::Priority,
367 RejectReason::INVALID_DATA_ENCODING,
368 Some(property_identifier),
369 array_index,
370 true,
371 )
372 })?;
373 if end > data.len() {
374 return Err(PropertyValueDecodeError::syntax(
375 Error::buffer_too_short(end, data.len()),
376 new_pos,
377 PropertyValueDecodeStage::Priority,
378 RejectReason::MISSING_REQUIRED_PARAMETER,
379 Some(property_identifier),
380 array_index,
381 true,
382 ));
383 }
384 let prio = primitives::decode_unsigned(&data[new_pos..end]).map_err(|error| {
385 PropertyValueDecodeError::syntax(
386 error,
387 new_pos,
388 PropertyValueDecodeStage::Priority,
389 RejectReason::INVALID_DATA_ENCODING,
390 Some(property_identifier),
391 array_index,
392 true,
393 )
394 })?;
395 if !(1..=16).contains(&prio) {
396 return Err(PropertyValueDecodeError {
397 error: Error::out_of_range(
398 new_pos,
399 format!("BACnetPropertyValue priority {prio} out of range 1-16"),
400 ),
401 offset: new_pos,
402 stage: PropertyValueDecodeStage::Priority,
403 kind: PropertyValueDecodeFailure::PriorityOutOfRange,
404 property_identifier: Some(property_identifier),
405 property_array_index: array_index,
406 reference_complete: true,
407 });
408 }
409 priority = Some(prio as u8);
410 if boundaries
411 .iter()
412 .any(|boundary| matches_property_boundary(data, end, *boundary))
413 {
414 return Ok((
415 BACnetPropertyValue {
416 property_identifier,
417 property_array_index: array_index,
418 value,
419 priority,
420 },
421 end,
422 ));
423 }
424 return Err(boundary_error(data, end, property_identifier, array_index));
425 }
426 }
427
428 if !boundaries
429 .iter()
430 .any(|boundary| matches_property_boundary(data, offset, *boundary))
431 {
432 return Err(boundary_error(
433 data,
434 offset,
435 property_identifier,
436 array_index,
437 ));
438 }
439
440 Ok((
441 BACnetPropertyValue {
442 property_identifier,
443 property_array_index: array_index,
444 value,
445 priority,
446 },
447 offset,
448 ))
449}
450
451fn boundary_error(
452 data: &[u8],
453 offset: usize,
454 property_identifier: PropertyIdentifier,
455 property_array_index: Option<u32>,
456) -> PropertyValueDecodeError {
457 let error = if offset >= data.len() {
458 Error::missing(offset, "BACnetPropertyValue is missing its list boundary")
459 } else {
460 match tags::decode_tag(data, offset) {
461 Ok(_) => {
462 Error::invalid_tag(offset, "BACnetPropertyValue has an unexpected trailing tag")
463 }
464 Err(error) => error,
465 }
466 };
467 let reject_reason = reason_of(&error);
468 PropertyValueDecodeError::syntax(
469 error,
470 offset,
471 PropertyValueDecodeStage::Priority,
472 reject_reason,
473 Some(property_identifier),
474 property_array_index,
475 true,
476 )
477}