Skip to main content

bacnet_encoding/constructed/
fault_parameter.rs

1//! `BACnetFaultParameter` full ASN.1 framing.
2//!
3//! `BACnetFaultParameter` (ASHRAE 135-2020 Clause 21) selects one fault
4//! algorithm by context tag and carries its settings; [`FaultParameters`]
5//! models every alternative. `none` is a primitive NULL. Every other
6//! alternative is a constructed value holding context-tagged members, all
7//! required:
8//!
9//! | Tag | Alternative | Member tag | Member | Type |
10//! |---|---|---|---|---|
11//! | `[0]` | `none` | | | NULL, no members |
12//! | `[1]` | `fault-characterstring` | `[0]` | `list-of-fault-values` | list of CharacterString |
13//! | `[2]` | `fault-extended` | `[0]` | `vendor-id` | Unsigned16 |
14//! | | | `[1]` | `extended-fault-type` | Unsigned |
15//! | | | `[2]` | `parameters` | list of extended values, see below |
16//! | `[3]` | `fault-life-safety` | `[0]` | `list-of-fault-values` | list of life-safety states |
17//! | | | `[1]` | `mode-property-reference` | `BACnetDeviceObjectPropertyReference` |
18//! | `[4]` | `fault-state` | `[0]` | `list-of-fault-values` | list of `BACnetPropertyStates` |
19//! | `[5]` | `fault-status-flags` | `[0]` | `status-flags-reference` | device property reference |
20//! | `[6]` | `fault-out-of-range` | `[0]` | `min-normal-value` | numeric choice, see below |
21//! | | | `[1]` | `max-normal-value` | the same four-way choice |
22//! | `[7]` | `fault-listed` | `[0]` | `fault-list-reference` | device property reference |
23//!
24//! Each "device property reference" is a `BACnetDeviceObjectPropertyReference`,
25//! and the life-safety list holds `BACnetLifeSafetyState` values. Each extended
26//! `parameters` entry is an application-tagged primitive or a `[0]`-framed
27//! `BACnetDeviceObjectPropertyReference`.
28//!
29//! The min/max inner CHOICE alternatives are untagged — discovered by their
30//! APPLICATION tag (REAL=4, Unsigned=2, Double=5, INTEGER=3). The Rust type
31//! stores `f64`, so encode always selects the `Double` alternative; decode
32//! accepts all four.
33
34#[cfg(not(feature = "std"))]
35use alloc::vec::Vec;
36
37use bacnet_types::constructed::FaultParameters;
38use bacnet_types::enums::LifeSafetyState;
39use bacnet_types::error::{DecodeError, DecodingKind, Error};
40use bytes::BytesMut;
41
42use crate::primitives;
43use crate::tags::{self, TagClass};
44
45use super::tagged::{
46    contents, decode_app_character_string, decode_app_enumerated, decode_ctx_constructed,
47    decode_ctx_unsigned, expect_closing, expect_opening, next_is_closing,
48};
49use super::{
50    decode_dopr_body, decode_property_state, encode_dopr_body, encode_property_state,
51    validate_extended_parameters, MAX_FRAMED_ITEMS,
52};
53
54/// Encode a [`FaultParameters`] as its full CHOICE framing.
55pub fn encode_fault_parameters(buf: &mut BytesMut, value: &FaultParameters) -> Result<(), Error> {
56    let mut encoded = BytesMut::new();
57    encode_fault_parameters_into(&mut encoded, value)?;
58    super::validate_tlv_sequence(&encoded, "FaultParameters")
59        .map_err(Error::into_encode_refusal)?;
60    buf.extend_from_slice(&encoded);
61    Ok(())
62}
63
64fn encode_fault_parameters_into(buf: &mut BytesMut, value: &FaultParameters) -> Result<(), Error> {
65    use FaultParameters as F;
66    match value {
67        F::FaultNone => {
68            // none [0] NULL — primitive context tag, no contents.
69            tags::encode_tag(buf, 0, TagClass::Context, 0);
70        }
71        F::FaultCharacterString { fault_values } => {
72            tags::encode_opening_tag(buf, 1);
73            tags::encode_opening_tag(buf, 0);
74            for s in fault_values {
75                primitives::encode_app_character_string(buf, s)?;
76            }
77            tags::encode_closing_tag(buf, 0);
78            tags::encode_closing_tag(buf, 1);
79        }
80        F::FaultExtended {
81            vendor_id,
82            extended_fault_type,
83            parameters,
84        } => {
85            validate_extended_parameters(parameters, "fault-extended")
86                .map_err(Error::into_encode_refusal)?;
87            tags::encode_opening_tag(buf, 2);
88            primitives::encode_ctx_unsigned(buf, 0, *vendor_id as u64);
89            primitives::encode_ctx_unsigned(buf, 1, *extended_fault_type as u64);
90            tags::encode_opening_tag(buf, 2);
91            buf.extend_from_slice(parameters);
92            tags::encode_closing_tag(buf, 2);
93            tags::encode_closing_tag(buf, 2);
94        }
95        F::FaultLifeSafety {
96            fault_values,
97            mode_property_reference,
98        } => {
99            tags::encode_opening_tag(buf, 3);
100            tags::encode_opening_tag(buf, 0);
101            for v in fault_values {
102                primitives::encode_app_enumerated(buf, v.to_raw());
103            }
104            tags::encode_closing_tag(buf, 0);
105            // mode-property-reference [1] BACnetDeviceObjectPropertyReference
106            tags::encode_opening_tag(buf, 1);
107            encode_dopr_body(buf, mode_property_reference);
108            tags::encode_closing_tag(buf, 1);
109            tags::encode_closing_tag(buf, 3);
110        }
111        F::FaultState { fault_values } => {
112            let mut encoded_states = BytesMut::new();
113            for state in fault_values {
114                encode_property_state(&mut encoded_states, state)?;
115            }
116            tags::encode_opening_tag(buf, 4);
117            tags::encode_opening_tag(buf, 0);
118            buf.extend_from_slice(&encoded_states);
119            tags::encode_closing_tag(buf, 0);
120            tags::encode_closing_tag(buf, 4);
121        }
122        F::FaultStatusFlags { reference } => {
123            tags::encode_opening_tag(buf, 5);
124            tags::encode_opening_tag(buf, 0);
125            encode_dopr_body(buf, reference);
126            tags::encode_closing_tag(buf, 0);
127            tags::encode_closing_tag(buf, 5);
128        }
129        F::FaultOutOfRange {
130            min_normal,
131            max_normal,
132        } => {
133            tags::encode_opening_tag(buf, 6);
134            // min/max-normal-value [n] CHOICE — explicitly tagged around the
135            // application-tagged alternative; the model stores f64, so the
136            // `double` alternative encodes it losslessly.
137            tags::encode_opening_tag(buf, 0);
138            primitives::encode_app_double(buf, *min_normal);
139            tags::encode_closing_tag(buf, 0);
140            tags::encode_opening_tag(buf, 1);
141            primitives::encode_app_double(buf, *max_normal);
142            tags::encode_closing_tag(buf, 1);
143            tags::encode_closing_tag(buf, 6);
144        }
145        F::FaultListed { reference } => {
146            tags::encode_opening_tag(buf, 7);
147            tags::encode_opening_tag(buf, 0);
148            encode_dopr_body(buf, reference);
149            tags::encode_closing_tag(buf, 0);
150            tags::encode_closing_tag(buf, 7);
151        }
152    }
153    Ok(())
154}
155
156/// Decode one application-tagged REAL/Unsigned/Double/INTEGER as f64 — the
157/// `fault-out-of-range` min/max inner CHOICE alternatives.
158fn decode_fault_normal_value(data: &[u8], offset: usize) -> Result<(f64, usize), Error> {
159    let (t, pos) = tags::decode_tag(data, offset)?;
160    if t.class != TagClass::Application || t.is_opening || t.is_closing {
161        return Err(Error::decoding(
162            offset,
163            "fault-out-of-range: min/max-normal-value must be application-tagged",
164        ));
165    }
166    let what = "fault-out-of-range min/max-normal-value";
167    if t.number == tags::app_tag::BOOLEAN {
168        return Err(DecodeError::new(
169            DecodingKind::InvalidEncoding,
170            offset,
171            "BOOLEAN is not a valid alternative",
172        )
173        .within(what)
174        .into());
175    }
176    let (content, end) = contents(data, pos, t.length)?;
177    let value = match t.number {
178        tags::app_tag::REAL => primitives::decode_real(content)? as f64,
179        tags::app_tag::UNSIGNED => primitives::decode_unsigned(content)? as f64,
180        tags::app_tag::DOUBLE => primitives::decode_double(content)?,
181        tags::app_tag::SIGNED => primitives::decode_signed(content)? as f64,
182        other => {
183            return Err(DecodeError::new(
184                DecodingKind::InvalidEncoding,
185                offset,
186                "unexpected application tag {}",
187            )
188            .within(what)
189            .with_value(other.into())
190            .into())
191        }
192    };
193    Ok((value, end))
194}
195
196/// Decode one framed [`FaultParameters`] CHOICE alternative at `offset`.
197///
198/// Returns the value and the offset past its encoding.
199pub fn decode_fault_parameters(
200    data: &[u8],
201    offset: usize,
202) -> Result<(FaultParameters, usize), Error> {
203    use FaultParameters as F;
204    let (tag, pos) = tags::decode_tag(data, offset)?;
205    let what = "BACnetFaultParameter";
206
207    // none [0] NULL — primitive context tag, no contents.
208    if tag.is_context(0) {
209        if tag.length != 0 {
210            return Err(Error::decoding(
211                offset,
212                "BACnetFaultParameter: none [0] NULL must have no contents",
213            ));
214        }
215        return Ok((F::FaultNone, pos));
216    }
217    if !tag.is_opening {
218        return Err(Error::decoding(
219            offset,
220            "BACnetFaultParameter: expected an opening context tag or [0] NULL",
221        ));
222    }
223
224    let value = match tag.number {
225        1 => {
226            let mut pos = expect_opening(data, pos, 0, what)?;
227            let mut fault_values = Vec::new();
228            while !next_is_closing(data, pos, 0)? {
229                if fault_values.len() >= MAX_FRAMED_ITEMS {
230                    return Err(Error::overflow(
231                        pos,
232                        "fault-characterstring: list-of-fault-values exceeds limit",
233                    ));
234                }
235                let (s, p) = decode_app_character_string(data, pos, what)?;
236                fault_values.push(s);
237                pos = p;
238            }
239            pos = expect_closing(data, pos, 0, what)?;
240            pos = expect_closing(data, pos, 1, what)?;
241            (F::FaultCharacterString { fault_values }, pos)
242        }
243        2 => {
244            let what = "BACnetFaultParameter fault-extended";
245            let (vendor_id, pos) = decode_ctx_unsigned::<u16>(data, pos, 0, what)?;
246            let (extended_fault_type, pos) = decode_ctx_unsigned::<u32>(data, pos, 1, what)?;
247            let (raw_params, pos) = decode_ctx_constructed(data, pos, 2, what)?;
248            validate_extended_parameters(raw_params, "fault-extended")?;
249            let pos = expect_closing(data, pos, 2, what)?;
250            (
251                F::FaultExtended {
252                    vendor_id,
253                    extended_fault_type,
254                    parameters: raw_params.to_vec(),
255                },
256                pos,
257            )
258        }
259        3 => {
260            let mut pos = expect_opening(data, pos, 0, what)?;
261            let mut fault_values = Vec::new();
262            while !next_is_closing(data, pos, 0)? {
263                if fault_values.len() >= MAX_FRAMED_ITEMS {
264                    return Err(Error::overflow(
265                        pos,
266                        "fault-life-safety: list-of-fault-values exceeds limit",
267                    ));
268                }
269                let (v, p) = decode_app_enumerated::<u32>(data, pos, what)?;
270                fault_values.push(LifeSafetyState::from_raw(v));
271                pos = p;
272            }
273            pos = expect_closing(data, pos, 0, what)?;
274            pos = expect_opening(data, pos, 1, what)?;
275            let (mode_property_reference, p) = decode_dopr_body(data, pos, what)?;
276            pos = p;
277            pos = expect_closing(data, pos, 1, what)?;
278            pos = expect_closing(data, pos, 3, what)?;
279            (
280                F::FaultLifeSafety {
281                    fault_values,
282                    mode_property_reference,
283                },
284                pos,
285            )
286        }
287        4 => {
288            let mut pos = expect_opening(data, pos, 0, what)?;
289            let mut fault_values = Vec::new();
290            while !next_is_closing(data, pos, 0)? {
291                if fault_values.len() >= MAX_FRAMED_ITEMS {
292                    return Err(Error::overflow(
293                        pos,
294                        "fault-state: list-of-fault-values exceeds limit",
295                    ));
296                }
297                let (state, p) = decode_property_state(data, pos)?;
298                fault_values.push(state);
299                pos = p;
300            }
301            pos = expect_closing(data, pos, 0, what)?;
302            pos = expect_closing(data, pos, 4, what)?;
303            (F::FaultState { fault_values }, pos)
304        }
305        5 => {
306            let mut pos = expect_opening(data, pos, 0, what)?;
307            let (reference, p) = decode_dopr_body(data, pos, what)?;
308            pos = p;
309            pos = expect_closing(data, pos, 0, what)?;
310            pos = expect_closing(data, pos, 5, what)?;
311            (F::FaultStatusFlags { reference }, pos)
312        }
313        6 => {
314            let mut pos = expect_opening(data, pos, 0, what)?;
315            let (min_normal, p) = decode_fault_normal_value(data, pos)?;
316            pos = p;
317            pos = expect_closing(data, pos, 0, what)?;
318            pos = expect_opening(data, pos, 1, what)?;
319            let (max_normal, p) = decode_fault_normal_value(data, pos)?;
320            pos = p;
321            pos = expect_closing(data, pos, 1, what)?;
322            pos = expect_closing(data, pos, 6, what)?;
323            (
324                F::FaultOutOfRange {
325                    min_normal,
326                    max_normal,
327                },
328                pos,
329            )
330        }
331        7 => {
332            let mut pos = expect_opening(data, pos, 0, what)?;
333            let (reference, p) = decode_dopr_body(data, pos, what)?;
334            pos = p;
335            pos = expect_closing(data, pos, 0, what)?;
336            pos = expect_closing(data, pos, 7, what)?;
337            (F::FaultListed { reference }, pos)
338        }
339        n => {
340            return Err(DecodeError::new(
341                DecodingKind::InvalidEncoding,
342                offset,
343                "BACnetFaultParameter: unknown CHOICE tag [{}]",
344            )
345            .with_value(n.into())
346            .into());
347        }
348    };
349    let (value, end) = value;
350    super::validate_tlv_sequence(&data[offset..end], what)?;
351    Ok((value, end))
352}