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::{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/// 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    error.decode_fault().map_or(fallback, |(offset, _)| offset)
87}
88
89/// Decode a property value at `offset` in `data`; returns it and the offset just past it.
90///
91/// The value may be followed by the end of the input, the next value's
92/// context `[0]`, or a context `[3]` that runs to the end.
93pub 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
108/// Decode one property value of a list that ends with the closing tag
109/// `closing_tag`; returns it and the offset just past it.
110pub 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
119/// [`decode_bacnet_property_value_in_list`], reporting the stage, the reject
120/// reason and the reference read so far when the value is malformed.
121pub 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}