1use bacnet_types::constructed::BACnetPropertyValue;
5use bacnet_types::enums::{PropertyIdentifier, RejectReason};
6use bacnet_types::error::{DecodeError, DecodingKind, Error};
7
8use super::{PropertyValueBoundary, extract_property_value, matches_property_boundary};
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 error.decode_fault().map_or(fallback, |(offset, _)| offset)
87}
88
89pub fn decode_bacnet_property_value(
94 data: &[u8],
95 offset: usize,
96) -> Result<(BACnetPropertyValue, usize), Error> {
97 decode_with_boundaries(
98 data,
99 offset,
100 &[
101 PropertyValueBoundary::End,
102 PropertyValueBoundary::Context(0),
103 PropertyValueBoundary::ContextToEnd(3),
104 ],
105 )
106}
107
108pub fn decode_bacnet_property_value_in_list(
111 data: &[u8],
112 offset: usize,
113 closing_tag: u8,
114) -> Result<(BACnetPropertyValue, usize), Error> {
115 decode_bacnet_property_value_in_list_detailed(data, offset, closing_tag)
116 .map_err(|error| error.error)
117}
118
119pub fn decode_bacnet_property_value_in_list_detailed(
122 data: &[u8],
123 offset: usize,
124 closing_tag: u8,
125) -> Result<(BACnetPropertyValue, usize), PropertyValueDecodeError> {
126 decode_with_boundaries_detailed(
127 data,
128 offset,
129 &[
130 PropertyValueBoundary::Context(3),
131 PropertyValueBoundary::Context(0),
132 PropertyValueBoundary::Closing(closing_tag),
133 ],
134 )
135}
136
137fn decode_with_boundaries(
138 data: &[u8],
139 offset: usize,
140 boundaries: &[PropertyValueBoundary],
141) -> Result<(BACnetPropertyValue, usize), Error> {
142 decode_with_boundaries_detailed(data, offset, boundaries).map_err(|error| error.error)
143}
144
145fn decode_with_boundaries_detailed(
146 data: &[u8],
147 offset: usize,
148 boundaries: &[PropertyValueBoundary],
149) -> Result<(BACnetPropertyValue, usize), PropertyValueDecodeError> {
150 let start = offset;
151 let (tag, content_start) = tags::decode_tag(data, offset).map_err(|error| {
152 let reason = reason_of(&error);
153 PropertyValueDecodeError::syntax(
154 error,
155 offset,
156 PropertyValueDecodeStage::PropertyIdentifier,
157 reason,
158 None,
159 None,
160 false,
161 )
162 })?;
163 if !tag.is_context(0) {
164 let kind = misplaced_kind(data, offset, &tag, Some(0));
165 return Err(PropertyValueDecodeError::syntax(
166 Error::decoding_kind(
167 kind,
168 offset,
169 "BACnetPropertyValue property-id expected context tag 0",
170 ),
171 offset,
172 PropertyValueDecodeStage::PropertyIdentifier,
173 kind.reject_reason(),
174 None,
175 None,
176 false,
177 ));
178 }
179 let property_end = content_start
180 .checked_add(tag.length as usize)
181 .ok_or_else(|| {
182 PropertyValueDecodeError::syntax(
183 Error::decoding(
184 content_start,
185 "BACnetPropertyValue property-id length overflow",
186 ),
187 content_start,
188 PropertyValueDecodeStage::PropertyIdentifier,
189 RejectReason::INVALID_DATA_ENCODING,
190 None,
191 None,
192 false,
193 )
194 })?;
195 if property_end > data.len() {
196 return Err(PropertyValueDecodeError::syntax(
197 Error::buffer_too_short(property_end, data.len()),
198 content_start,
199 PropertyValueDecodeStage::PropertyIdentifier,
200 RejectReason::MISSING_REQUIRED_PARAMETER,
201 None,
202 None,
203 false,
204 ));
205 }
206 let prop_id =
207 primitives::decode_unsigned(&data[content_start..property_end]).map_err(|error| {
208 PropertyValueDecodeError::syntax(
209 error,
210 content_start,
211 PropertyValueDecodeStage::PropertyIdentifier,
212 RejectReason::INVALID_DATA_ENCODING,
213 None,
214 None,
215 false,
216 )
217 })?;
218 let prop_id = u32::try_from(prop_id).map_err(|_| {
219 PropertyValueDecodeError::syntax(
220 Error::out_of_range(start, "BACnetPropertyValue property-id exceeds u32"),
221 start,
222 PropertyValueDecodeStage::PropertyIdentifier,
223 RejectReason::PARAMETER_OUT_OF_RANGE,
224 None,
225 None,
226 false,
227 )
228 })?;
229 let property_identifier = PropertyIdentifier::from_raw(prop_id);
230 let mut offset = property_end;
231
232 let mut array_index = None;
233 if offset < data.len() {
234 let (tag, content_start) = tags::decode_tag(data, offset).map_err(|error| {
235 let reason = reason_of(&error);
236 PropertyValueDecodeError::syntax(
237 error,
238 offset,
239 PropertyValueDecodeStage::ArrayIndex,
240 reason,
241 Some(property_identifier),
242 None,
243 false,
244 )
245 })?;
246 if tag.is_context(1) {
247 let end = content_start
248 .checked_add(tag.length as usize)
249 .ok_or_else(|| {
250 PropertyValueDecodeError::syntax(
251 Error::decoding(
252 content_start,
253 "BACnetPropertyValue array-index length overflow",
254 ),
255 content_start,
256 PropertyValueDecodeStage::ArrayIndex,
257 RejectReason::INVALID_DATA_ENCODING,
258 Some(property_identifier),
259 None,
260 false,
261 )
262 })?;
263 if end > data.len() {
264 return Err(PropertyValueDecodeError::syntax(
265 Error::buffer_too_short(end, data.len()),
266 content_start,
267 PropertyValueDecodeStage::ArrayIndex,
268 RejectReason::MISSING_REQUIRED_PARAMETER,
269 Some(property_identifier),
270 None,
271 false,
272 ));
273 }
274 let value =
275 primitives::decode_unsigned(&data[content_start..end]).map_err(|error| {
276 PropertyValueDecodeError::syntax(
277 error,
278 content_start,
279 PropertyValueDecodeStage::ArrayIndex,
280 RejectReason::INVALID_DATA_ENCODING,
281 Some(property_identifier),
282 None,
283 false,
284 )
285 })?;
286 let value = u32::try_from(value).map_err(|_| {
287 PropertyValueDecodeError::syntax(
288 Error::out_of_range(offset, "BACnetPropertyValue array-index exceeds u32"),
289 offset,
290 PropertyValueDecodeStage::ArrayIndex,
291 RejectReason::PARAMETER_OUT_OF_RANGE,
292 Some(property_identifier),
293 None,
294 false,
295 )
296 })?;
297 array_index = Some(value);
298 offset = end;
299 }
300 }
301
302 let (tag, tag_end) = tags::decode_tag(data, offset).map_err(|error| {
303 let reason = reason_of(&error);
304 PropertyValueDecodeError::syntax(
305 error,
306 offset,
307 PropertyValueDecodeStage::Value,
308 reason,
309 Some(property_identifier),
310 array_index,
311 true,
312 )
313 })?;
314 if !tag.is_opening_tag(2) {
315 let kind = misplaced_kind(data, offset, &tag, Some(2));
316 return Err(PropertyValueDecodeError::syntax(
317 Error::decoding_kind(kind, offset, "BACnetPropertyValue expected opening tag 2"),
318 offset,
319 PropertyValueDecodeStage::Value,
320 kind.reject_reason(),
321 Some(property_identifier),
322 array_index,
323 true,
324 ));
325 }
326 let (value_bytes, offset) =
327 extract_property_value(data, tag_end, 2, property_identifier, boundaries).map_err(
328 |error| {
329 let reject_reason = reason_of(&error);
330 let offset = error_offset(&error, tag_end);
331 PropertyValueDecodeError::syntax(
332 error,
333 offset,
334 PropertyValueDecodeStage::Value,
335 reject_reason,
336 Some(property_identifier),
337 array_index,
338 true,
339 )
340 },
341 )?;
342 let value = value_bytes.to_vec();
343
344 let mut priority = None;
345 if offset < data.len() {
346 let (tag, new_pos) = tags::decode_tag(data, offset).map_err(|error| {
347 let reason = reason_of(&error);
348 PropertyValueDecodeError::syntax(
349 error,
350 offset,
351 PropertyValueDecodeStage::Priority,
352 reason,
353 Some(property_identifier),
354 array_index,
355 true,
356 )
357 })?;
358 if tag.is_context(3) {
359 let end = new_pos.checked_add(tag.length as usize).ok_or_else(|| {
360 PropertyValueDecodeError::syntax(
361 Error::decoding(new_pos, "BACnetPropertyValue priority length overflow"),
362 new_pos,
363 PropertyValueDecodeStage::Priority,
364 RejectReason::INVALID_DATA_ENCODING,
365 Some(property_identifier),
366 array_index,
367 true,
368 )
369 })?;
370 if end > data.len() {
371 return Err(PropertyValueDecodeError::syntax(
372 Error::buffer_too_short(end, data.len()),
373 new_pos,
374 PropertyValueDecodeStage::Priority,
375 RejectReason::MISSING_REQUIRED_PARAMETER,
376 Some(property_identifier),
377 array_index,
378 true,
379 ));
380 }
381 let prio = primitives::decode_unsigned(&data[new_pos..end]).map_err(|error| {
382 PropertyValueDecodeError::syntax(
383 error,
384 new_pos,
385 PropertyValueDecodeStage::Priority,
386 RejectReason::INVALID_DATA_ENCODING,
387 Some(property_identifier),
388 array_index,
389 true,
390 )
391 })?;
392 if !(1..=16).contains(&prio) {
393 return Err(PropertyValueDecodeError {
394 error: DecodeError::new(
395 DecodingKind::OutOfRange,
396 new_pos,
397 "BACnetPropertyValue priority {} out of range 1-16",
398 )
399 .with_value(prio)
400 .into(),
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}