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