Skip to main content

bacnet_encoding/constructed/property_value/
decode.rs

1//! The `BACnetPropertyValue` decoder, which reports how far it got when a
2//! value is malformed so a service can choose its reject reason.
3
4use 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/// The member a property value decode was reading when it failed.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum PropertyValueDecodeStage {
15    /// The property identifier, context `[0]`.
16    PropertyIdentifier,
17    /// The optional array index, context `[1]`.
18    ArrayIndex,
19    /// The value inside the `[2]` pair.
20    Value,
21    /// The optional priority, context `[3]`, or what follows the value.
22    Priority,
23}
24
25/// Why a property value failed to decode.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum PropertyValueDecodeFailure {
28    /// The encoding is malformed; the reason a service would reject it with.
29    Syntax(RejectReason),
30    /// The priority is a well-formed Unsigned outside 1-16.
31    PriorityOutOfRange,
32}
33
34/// A failed property value decode, with what was read before the failure.
35#[derive(Debug)]
36pub struct PropertyValueDecodeError {
37    /// The decode error.
38    pub error: Error,
39    /// Offset of the failure in the input.
40    pub offset: usize,
41    /// The member being read.
42    pub stage: PropertyValueDecodeStage,
43    /// Why the decode failed.
44    pub kind: PropertyValueDecodeFailure,
45    /// The property identifier, once it was read.
46    pub property_identifier: Option<PropertyIdentifier>,
47    /// The array index, once it was read.
48    pub property_array_index: Option<u32>,
49    /// Whether the property reference (identifier and optional index) was
50    /// read in full before the failure.
51    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
76/// The Reject reason a decoder's refusal draws, the one the server gives
77/// every confirmed request (#1446; the table on
78/// [`DecodingKind::reject_reason`](bacnet_types::error::DecodingKind::reject_reason)).
79fn 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
92/// Decode a property value at `offset` in `data`; returns it and the offset just past it.
93///
94/// The value may be followed by the end of the input, the next value's
95/// context `[0]`, or a context `[3]` that runs to the end.
96pub 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
111/// Decode one property value of a list that ends with the closing tag
112/// `closing_tag`; returns it and the offset just past it.
113pub 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
122/// [`decode_bacnet_property_value_in_list`], reporting the stage, the reject
123/// reason and the reference read so far when the value is malformed.
124pub 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}