Skip to main content

bacnet_encoding/constructed/event_notification/
parameters_decode.rs

1use super::decode_helpers::{
2    closing_tag_start, decode_context_status_flags, decode_context_u16, decode_context_value,
3    finish_variant as check_variant_end,
4};
5use super::decode_timer::{decode_change_of_discrete_value, decode_change_of_timer};
6use super::structured::{decode_access_event, decode_complex_event_type};
7use super::*;
8use crate::constructed::tagged::decode_ctx_character_string;
9
10/// Decode one notification-parameter choice, with an optional enclosing `[12]` close.
11pub fn decode_notification_parameters(
12    data: &[u8],
13    offset: usize,
14) -> Result<NotificationParameters, Error> {
15    let end = closing_tag_start(data, data.len(), 12, "NotificationParameters event-values")
16        .unwrap_or(data.len());
17    decode_bounded(data, offset, end)
18}
19
20/// Decode one notification-parameter choice that ends exactly at `end`.
21pub(super) fn decode_bounded(
22    data: &[u8],
23    offset: usize,
24    end: usize,
25) -> Result<NotificationParameters, Error> {
26    let data = data
27        .get(..end)
28        .ok_or_else(|| Error::decoding(end, "NotificationParameters boundary exceeds input"))?;
29    let framed = data
30        .get(offset..)
31        .ok_or_else(|| Error::decoding(offset, "NotificationParameters offset exceeds input"))?;
32    // Peek the inner opening tag to determine the variant
33    if offset >= data.len() {
34        return Err(Error::missing(
35            offset,
36            "NotificationParameters: empty payload",
37        ));
38    }
39    let (inner_tag, inner_start) = tags::decode_tag(data, offset)?;
40    if !inner_tag.is_opening {
41        return Err(misplaced_tag(
42            data,
43            &inner_tag,
44            None,
45            offset,
46            "NotificationParameters: expected opening tag for variant",
47        ));
48    }
49    let variant_tag = inner_tag.number;
50    if variant_tag == 6 {
51        let (_, next) = tags::extract_context_value(data, inner_start, 6)?;
52        if next != data.len() {
53            return Err(Error::trailing(
54                next,
55                "ComplexEventType has trailing encoded fields",
56            ));
57        }
58    } else {
59        validate_tlv_sequence(framed, "NotificationParameters")?;
60    }
61    let variant_body_end = closing_tag_start(
62        data,
63        data.len(),
64        variant_tag,
65        "NotificationParameters variant",
66    )?;
67    let finish_variant =
68        |value, consumed| check_variant_end(value, consumed, variant_body_end, variant_tag);
69
70    match variant_tag {
71        // [1] Change of state
72        1 => {
73            let mut pos = inner_start;
74            // [0] new-state: BACnetPropertyStates — wrapped in opening/closing [0]
75            let (t, p) = tags::decode_tag(data, pos)?;
76            if !t.is_opening || t.number != 0 {
77                return Err(misplaced_tag(
78                    data,
79                    &t,
80                    Some(0),
81                    pos,
82                    "ChangeOfState: expected opening tag [0] for new-state",
83                ));
84            }
85            pos = p;
86            let new_state = decode_property_states(data, &mut pos)?;
87            // Skip closing tag [0]
88            let (ct, cp) = tags::decode_tag(data, pos)?;
89            if !ct.is_closing || ct.number != 0 {
90                return Err(Error::decoding_kind(
91                    unclosed_kind(&ct),
92                    pos,
93                    "ChangeOfState: expected closing [0]",
94                ));
95            }
96            pos = cp;
97            // [1] status-flags
98            let (status_flags, pos) =
99                decode_context_status_flags(data, pos, 1, "ChangeOfState status-flags")?;
100            finish_variant(
101                NotificationParameters::ChangeOfState {
102                    new_state,
103                    status_flags,
104                },
105                pos,
106            )
107        }
108        // [2] Change of value
109        2 => {
110            let mut pos = inner_start;
111            // [0] new-value CHOICE — wrapped in opening/closing [0]
112            let (t, p) = tags::decode_tag(data, pos)?;
113            if !t.is_opening || t.number != 0 {
114                return Err(misplaced_tag(
115                    data,
116                    &t,
117                    Some(0),
118                    pos,
119                    "ChangeOfValue: expected opening [0] for new-value",
120                ));
121            }
122            pos = p;
123            // Peek CHOICE tag
124            let (choice_tag, _) = tags::decode_tag(data, pos)?;
125            let new_value = match choice_tag.number {
126                0 if choice_tag.is_context(0) => {
127                    let ((unused_bits, data), end) = decode_context_value(
128                        data,
129                        pos,
130                        0,
131                        "ChangeOfValue changed-bits",
132                        primitives::decode_bit_string,
133                    )?;
134                    pos = end;
135                    ChangeOfValueChoice::ChangedBits { unused_bits, data }
136                }
137                1 if choice_tag.is_context(1) => {
138                    let (value, end) = decode_context_value(
139                        data,
140                        pos,
141                        1,
142                        "ChangeOfValue changed-value",
143                        primitives::decode_real,
144                    )?;
145                    pos = end;
146                    ChangeOfValueChoice::ChangedValue(value)
147                }
148                _ => {
149                    return Err(misplaced_tag(
150                        data,
151                        &choice_tag,
152                        None,
153                        pos,
154                        "ChangeOfValue: expected context choice [0] or [1]",
155                    ));
156                }
157            };
158            // Closing tag [0]
159            let (ct, cp) = tags::decode_tag(data, pos)?;
160            if !ct.is_closing || ct.number != 0 {
161                return Err(Error::decoding_kind(
162                    unclosed_kind(&ct),
163                    pos,
164                    "ChangeOfValue: expected closing [0]",
165                ));
166            }
167            pos = cp;
168            // [1] status-flags
169            let (status_flags, pos) =
170                decode_context_status_flags(data, pos, 1, "ChangeOfValue status-flags")?;
171            finish_variant(
172                NotificationParameters::ChangeOfValue {
173                    new_value,
174                    status_flags,
175                },
176                pos,
177            )
178        }
179        // [5] Out of range
180        5 => {
181            // [0] exceeding-value
182            let (exceeding_value, pos) = decode_context_value(
183                data,
184                inner_start,
185                0,
186                "OutOfRange exceeding-value",
187                primitives::decode_real,
188            )?;
189            // [1] status-flags
190            let (status_flags, pos) =
191                decode_context_status_flags(data, pos, 1, "OutOfRange status-flags")?;
192            // [2] deadband
193            let (deadband, pos) =
194                decode_context_value(data, pos, 2, "OutOfRange deadband", primitives::decode_real)?;
195            // [3] exceeded-limit
196            let (exceeded_limit, pos) = decode_context_value(
197                data,
198                pos,
199                3,
200                "OutOfRange exceeded-limit",
201                primitives::decode_real,
202            )?;
203            finish_variant(
204                NotificationParameters::OutOfRange {
205                    exceeding_value,
206                    status_flags,
207                    deadband,
208                    exceeded_limit,
209                },
210                pos,
211            )
212        }
213        // [6] Complex event type
214        6 => decode_complex_event_type(data, inner_start, variant_body_end),
215        // [10] Buffer ready
216        10 => {
217            let mut pos = inner_start;
218            // [0] buffer-property: BACnetDeviceObjectPropertyReference
219            let (t, p) = tags::decode_tag(data, pos)?;
220            if !t.is_opening || t.number != 0 {
221                return Err(misplaced_tag(
222                    data,
223                    &t,
224                    Some(0),
225                    pos,
226                    "BufferReady: expected opening [0] for buffer-property",
227                ));
228            }
229            pos = p;
230            let buffer_property = decode_device_obj_prop_ref(data, &mut pos)?;
231            // Closing tag [0]
232            let (ct, cp) = tags::decode_tag(data, pos)?;
233            if !ct.is_closing || ct.number != 0 {
234                return Err(Error::decoding_kind(
235                    unclosed_kind(&ct),
236                    pos,
237                    "BufferReady: expected closing [0]",
238                ));
239            }
240            pos = cp;
241            // [1] previous-notification
242            let (previous_notification, pos) =
243                decode_context_u32(data, pos, 1, "BufferReady previous-notification")?;
244            // [2] current-notification
245            let (current_notification, end) =
246                decode_context_u32(data, pos, 2, "BufferReady current-notification")?;
247            finish_variant(
248                NotificationParameters::BufferReady {
249                    buffer_property,
250                    previous_notification,
251                    current_notification,
252                },
253                end,
254            )
255        }
256        // [11] Unsigned range
257        11 => {
258            // [0] exceeding-value
259            let (exceeding_value, pos) = decode_context_value(
260                data,
261                inner_start,
262                0,
263                "UnsignedRange exceeding-value",
264                primitives::decode_unsigned,
265            )?;
266            // [1] status-flags
267            let (status_flags, pos) =
268                decode_context_status_flags(data, pos, 1, "UnsignedRange status-flags")?;
269            // [2] exceeded-limit
270            let (exceeded_limit, pos) = decode_context_value(
271                data,
272                pos,
273                2,
274                "UnsignedRange exceeded-limit",
275                primitives::decode_unsigned,
276            )?;
277            finish_variant(
278                NotificationParameters::UnsignedRange {
279                    exceeding_value,
280                    status_flags,
281                    exceeded_limit,
282                },
283                pos,
284            )
285        }
286        // [0] Change of bitstring
287        0 => {
288            // [0] referenced-bitstring
289            let ((unused, bits), pos) = decode_context_value(
290                data,
291                inner_start,
292                0,
293                "ChangeOfBitstring referenced-bitstring",
294                primitives::decode_bit_string,
295            )?;
296            // [1] status-flags
297            let (status_flags, pos) =
298                decode_context_status_flags(data, pos, 1, "ChangeOfBitstring status-flags")?;
299            finish_variant(
300                NotificationParameters::ChangeOfBitstring {
301                    referenced_bitstring: (unused, bits),
302                    status_flags,
303                },
304                pos,
305            )
306        }
307        // [3] Command failure
308        3 => {
309            let mut pos = inner_start;
310            // [0] command-value — opening/closing, raw
311            let (t, p) = tags::decode_tag(data, pos)?;
312            if !t.is_opening || t.number != 0 {
313                return Err(misplaced_tag(
314                    data,
315                    &t,
316                    Some(0),
317                    pos,
318                    "CommandFailure: expected opening [0]",
319                ));
320            }
321            let (command_value, after) = extract_raw_context(data, p, 0)?;
322            pos = after;
323            // [1] status-flags
324            let (status_flags, pos) =
325                decode_context_status_flags(data, pos, 1, "CommandFailure status-flags")?;
326            // [2] feedback-value — opening/closing, raw
327            let (t, p) = tags::decode_tag(data, pos)?;
328            if !t.is_opening || t.number != 2 {
329                return Err(misplaced_tag(
330                    data,
331                    &t,
332                    Some(2),
333                    pos,
334                    "CommandFailure: expected opening [2]",
335                ));
336            }
337            let (feedback_value, after) = extract_raw_context(data, p, 2)?;
338            finish_variant(
339                NotificationParameters::CommandFailure {
340                    command_value,
341                    status_flags,
342                    feedback_value,
343                },
344                after,
345            )
346        }
347        // [4] Floating limit
348        4 => {
349            // [0] reference-value
350            let (reference_value, pos) = decode_context_value(
351                data,
352                inner_start,
353                0,
354                "FloatingLimit reference-value",
355                primitives::decode_real,
356            )?;
357            // [1] status-flags
358            let (status_flags, pos) =
359                decode_context_status_flags(data, pos, 1, "FloatingLimit status-flags")?;
360            // [2] setpoint-value
361            let (setpoint_value, pos) = decode_context_value(
362                data,
363                pos,
364                2,
365                "FloatingLimit setpoint-value",
366                primitives::decode_real,
367            )?;
368            // [3] error-limit
369            let (error_limit, pos) = decode_context_value(
370                data,
371                pos,
372                3,
373                "FloatingLimit error-limit",
374                primitives::decode_real,
375            )?;
376            finish_variant(
377                NotificationParameters::FloatingLimit {
378                    reference_value,
379                    status_flags,
380                    setpoint_value,
381                    error_limit,
382                },
383                pos,
384            )
385        }
386        // [8] Change of life safety
387        8 => {
388            let (new_state, pos) = decode_context_enum(
389                data,
390                inner_start,
391                0,
392                "ChangeOfLifeSafety new-state",
393                LifeSafetyState::from_raw,
394            )?;
395            let (new_mode, pos) = decode_context_enum(
396                data,
397                pos,
398                1,
399                "ChangeOfLifeSafety new-mode",
400                LifeSafetyMode::from_raw,
401            )?;
402            let (status_flags, pos) =
403                decode_context_status_flags(data, pos, 2, "ChangeOfLifeSafety status-flags")?;
404            let (operation_expected, pos) = decode_context_enum(
405                data,
406                pos,
407                3,
408                "ChangeOfLifeSafety operation-expected",
409                LifeSafetyOperation::from_raw,
410            )?;
411            finish_variant(
412                NotificationParameters::ChangeOfLifeSafety {
413                    new_state,
414                    new_mode,
415                    status_flags,
416                    operation_expected,
417                },
418                pos,
419            )
420        }
421        // [9] Extended
422        9 => {
423            // [0] vendor-id
424            let (vendor_id, pos) = decode_context_u16(data, inner_start, 0, "Extended vendor-id")?;
425            // [1] extended-event-type
426            let (extended_event_type, pos) =
427                decode_context_u32(data, pos, 1, "Extended extended-event-type")?;
428            // [2] parameters — opening/closing, raw
429            let (t, p) = tags::decode_tag(data, pos)?;
430            if !t.is_opening_tag(2) {
431                return Err(misplaced_tag(
432                    data,
433                    &t,
434                    Some(2),
435                    pos,
436                    "Extended: expected opening [2]",
437                ));
438            }
439            let (parameters, after) = extract_raw_context(data, p, 2)?;
440            finish_variant(
441                NotificationParameters::Extended {
442                    vendor_id,
443                    extended_event_type,
444                    parameters,
445                },
446                after,
447            )
448        }
449        // [13] Access event
450        13 => decode_access_event(data, inner_start, variant_body_end),
451        // [14] Double out of range
452        14 => {
453            // [0] exceeding-value
454            let (exceeding_value, pos) = decode_context_value(
455                data,
456                inner_start,
457                0,
458                "DoubleOutOfRange exceeding-value",
459                primitives::decode_double,
460            )?;
461            // [1] status-flags
462            let (status_flags, pos) =
463                decode_context_status_flags(data, pos, 1, "DoubleOutOfRange status-flags")?;
464            // [2] deadband
465            let (deadband, pos) = decode_context_value(
466                data,
467                pos,
468                2,
469                "DoubleOutOfRange deadband",
470                primitives::decode_double,
471            )?;
472            // [3] exceeded-limit
473            let (exceeded_limit, pos) = decode_context_value(
474                data,
475                pos,
476                3,
477                "DoubleOutOfRange exceeded-limit",
478                primitives::decode_double,
479            )?;
480            finish_variant(
481                NotificationParameters::DoubleOutOfRange {
482                    exceeding_value,
483                    status_flags,
484                    deadband,
485                    exceeded_limit,
486                },
487                pos,
488            )
489        }
490        // [15] Signed out of range
491        15 => {
492            // [0] exceeding-value
493            let (exceeding_value, pos) = decode_context_value(
494                data,
495                inner_start,
496                0,
497                "SignedOutOfRange exceeding-value",
498                primitives::decode_signed,
499            )?;
500            // [1] status-flags
501            let (status_flags, pos) =
502                decode_context_status_flags(data, pos, 1, "SignedOutOfRange status-flags")?;
503            // [2] deadband
504            let (deadband, pos) = decode_context_value(
505                data,
506                pos,
507                2,
508                "SignedOutOfRange deadband",
509                primitives::decode_unsigned,
510            )?;
511            // [3] exceeded-limit
512            let (exceeded_limit, pos) = decode_context_value(
513                data,
514                pos,
515                3,
516                "SignedOutOfRange exceeded-limit",
517                primitives::decode_signed,
518            )?;
519            finish_variant(
520                NotificationParameters::SignedOutOfRange {
521                    exceeding_value,
522                    status_flags,
523                    deadband,
524                    exceeded_limit,
525                },
526                pos,
527            )
528        }
529        // [16] Unsigned out of range
530        16 => {
531            // [0] exceeding-value
532            let (exceeding_value, pos) = decode_context_value(
533                data,
534                inner_start,
535                0,
536                "UnsignedOutOfRange exceeding-value",
537                primitives::decode_unsigned,
538            )?;
539            // [1] status-flags
540            let (status_flags, pos) =
541                decode_context_status_flags(data, pos, 1, "UnsignedOutOfRange status-flags")?;
542            // [2] deadband
543            let (deadband, pos) = decode_context_value(
544                data,
545                pos,
546                2,
547                "UnsignedOutOfRange deadband",
548                primitives::decode_unsigned,
549            )?;
550            // [3] exceeded-limit
551            let (exceeded_limit, pos) = decode_context_value(
552                data,
553                pos,
554                3,
555                "UnsignedOutOfRange exceeded-limit",
556                primitives::decode_unsigned,
557            )?;
558            finish_variant(
559                NotificationParameters::UnsignedOutOfRange {
560                    exceeding_value,
561                    status_flags,
562                    deadband,
563                    exceeded_limit,
564                },
565                pos,
566            )
567        }
568        // [17] Change of characterstring
569        17 => {
570            // [0] changed-value
571            let (changed_value, pos) = decode_ctx_character_string(
572                data,
573                inner_start,
574                0,
575                "ChangeOfCharacterstring changed-value",
576            )?;
577            // [1] status-flags
578            let (status_flags, pos) =
579                decode_context_status_flags(data, pos, 1, "ChangeOfCharacterstring status-flags")?;
580            // [2] alarm-value
581            let (alarm_value, new_pos) =
582                decode_ctx_character_string(data, pos, 2, "ChangeOfCharacterstring alarm-value")?;
583            finish_variant(
584                NotificationParameters::ChangeOfCharacterstring {
585                    changed_value,
586                    status_flags,
587                    alarm_value,
588                },
589                new_pos,
590            )
591        }
592        // [18] Change of status flags
593        18 => {
594            let mut pos = inner_start;
595            // [0] present-value — opening/closing, raw, optional
596            let (t, p) = tags::decode_tag(data, pos)?;
597            let present_value = if t.is_opening_tag(0) {
598                let (value, after) = extract_raw_context(data, p, 0)?;
599                pos = after;
600                Some(value)
601            } else {
602                None
603            };
604            // [1] referenced-flags
605            let (referenced_flags, pos) =
606                decode_context_status_flags(data, pos, 1, "ChangeOfStatusFlags referenced-flags")?;
607            finish_variant(
608                NotificationParameters::ChangeOfStatusFlags {
609                    present_value,
610                    referenced_flags,
611                },
612                pos,
613            )
614        }
615        // [19] Change of reliability
616        19 => {
617            // [0] reliability
618            let (reliability, pos) = decode_context_enum(
619                data,
620                inner_start,
621                0,
622                "ChangeOfReliability reliability",
623                Reliability::from_raw,
624            )?;
625            // [1] status-flags
626            let (status_flags, pos) =
627                decode_context_status_flags(data, pos, 1, "ChangeOfReliability status-flags")?;
628            // [2] property-values — opening/closing, raw
629            let (t, p) = tags::decode_tag(data, pos)?;
630            if !t.is_opening_tag(2) {
631                return Err(misplaced_tag(
632                    data,
633                    &t,
634                    Some(2),
635                    pos,
636                    "ChangeOfReliability: expected opening [2]",
637                ));
638            }
639            let (property_values, after) = extract_raw_context(data, p, 2)?;
640            finish_variant(
641                NotificationParameters::ChangeOfReliability {
642                    reliability,
643                    status_flags,
644                    property_values,
645                },
646                after,
647            )
648        }
649        // [21] Change of discrete value
650        21 => decode_change_of_discrete_value(data, inner_start, variant_body_end),
651        // [22] Change of timer
652        22 => decode_change_of_timer(data, inner_start, variant_body_end),
653        other => Err(Error::invalid_tag(
654            offset,
655            format!("NotificationParameters variant [{other}] unknown"),
656        )),
657    }
658}