Skip to main content

bacnet_services/
alarm_event.rs

1//! Alarm and event services per ASHRAE 135-2020 Clauses 13.2–13.9.
2//!
3//! - AcknowledgeAlarm (Clause 13.3)
4//! - ConfirmedEventNotification / UnconfirmedEventNotification (Clause 13.5/13.6)
5//! - GetEventInformation (Clause 13.9)
6
7use bacnet_encoding::{primitives, tags};
8use bacnet_types::constructed::{BACnetDeviceObjectPropertyReference, BACnetPropertyStates};
9use bacnet_types::error::Error;
10use bacnet_types::primitives::{BACnetTimeStamp, Date, ObjectIdentifier, Time};
11use bytes::BytesMut;
12
13// ---------------------------------------------------------------------------
14// AcknowledgeAlarm (Clause 13.3)
15// ---------------------------------------------------------------------------
16
17/// AcknowledgeAlarm-Request service parameters.
18#[derive(Debug, Clone, PartialEq)]
19pub struct AcknowledgeAlarmRequest {
20    pub acknowledging_process_identifier: u32,
21    pub event_object_identifier: ObjectIdentifier,
22    pub event_state_acknowledged: u32,
23    pub timestamp: BACnetTimeStamp,
24    pub acknowledgment_source: String,
25}
26
27impl AcknowledgeAlarmRequest {
28    pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
29        // [0] acknowledgingProcessIdentifier
30        primitives::encode_ctx_unsigned(buf, 0, self.acknowledging_process_identifier as u64);
31        // [1] eventObjectIdentifier
32        primitives::encode_ctx_object_id(buf, 1, &self.event_object_identifier);
33        // [2] eventStateAcknowledged
34        primitives::encode_ctx_enumerated(buf, 2, self.event_state_acknowledged);
35        // [3] timestamp
36        primitives::encode_timestamp(buf, 3, &self.timestamp);
37        // [4] acknowledgmentSource
38        primitives::encode_ctx_character_string(buf, 4, &self.acknowledgment_source)?;
39        Ok(())
40    }
41
42    pub fn decode(data: &[u8]) -> Result<Self, Error> {
43        let mut offset = 0;
44
45        // [0]
46        let (_tag, pos) = tags::decode_tag(data, offset)?;
47        let end = pos + _tag.length as usize;
48        if end > data.len() {
49            return Err(Error::decoding(
50                pos,
51                "AcknowledgeAlarm truncated at process-id",
52            ));
53        }
54        let acknowledging_process_identifier = primitives::decode_unsigned(&data[pos..end])? as u32;
55        offset = end;
56
57        // [1]
58        let (_tag, pos) = tags::decode_tag(data, offset)?;
59        let end = pos + _tag.length as usize;
60        if end > data.len() {
61            return Err(Error::decoding(
62                pos,
63                "AcknowledgeAlarm truncated at object-id",
64            ));
65        }
66        let event_object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
67        offset = end;
68
69        // [2]
70        let (_tag, pos) = tags::decode_tag(data, offset)?;
71        let end = pos + _tag.length as usize;
72        if end > data.len() {
73            return Err(Error::decoding(
74                pos,
75                "AcknowledgeAlarm truncated at event-state",
76            ));
77        }
78        let event_state_acknowledged = primitives::decode_unsigned(&data[pos..end])? as u32;
79        offset = end;
80
81        // [3] timestamp
82        let (timestamp, new_offset) = primitives::decode_timestamp(data, offset, 3)?;
83        offset = new_offset;
84
85        // [4] acknowledgmentSource (required per Clause 13.3)
86        let (opt_data, _new_offset) = tags::decode_optional_context(data, offset, 4)?;
87        let acknowledgment_source = match opt_data {
88            Some(content) => primitives::decode_character_string(content)?,
89            None => {
90                return Err(Error::decoding(
91                    offset,
92                    "AcknowledgeAlarm missing required acknowledgment-source [4]",
93                ))
94            }
95        };
96
97        Ok(Self {
98            acknowledging_process_identifier,
99            event_object_identifier,
100            event_state_acknowledged,
101            timestamp,
102            acknowledgment_source,
103        })
104    }
105}
106
107// ---------------------------------------------------------------------------
108// EventNotification (Clause 13.5 / 13.6)
109// ---------------------------------------------------------------------------
110
111/// ConfirmedEventNotification / UnconfirmedEventNotification request parameters.
112///
113/// Encodes all required fields per Clause 13.5/13.6. Event values (tag 12)
114/// are still omitted (simplified).
115#[derive(Debug, Clone)]
116pub struct EventNotificationRequest {
117    /// Process identifier of the notification recipient.
118    pub process_identifier: u32,
119    /// Device that generated the event.
120    pub initiating_device_identifier: ObjectIdentifier,
121    /// Object that triggered the event.
122    pub event_object_identifier: ObjectIdentifier,
123    /// Timestamp of the event transition.
124    pub timestamp: BACnetTimeStamp,
125    /// Notification class for routing.
126    pub notification_class: u32,
127    /// Priority (0-255).
128    pub priority: u8,
129    /// Event type (e.g., OUT_OF_RANGE = 5).
130    pub event_type: u32,
131    /// Notify type: ALARM(0), EVENT(1), ACK_NOTIFICATION(2).
132    pub notify_type: u32,
133    /// Whether the recipient must acknowledge.
134    pub ack_required: bool,
135    /// Event state before this transition.
136    pub from_state: u32,
137    /// Event state after this transition.
138    pub to_state: u32,
139    /// Optional event values (tag [12]).
140    pub event_values: Option<NotificationParameters>,
141}
142
143impl EventNotificationRequest {
144    pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
145        // [0] processIdentifier
146        primitives::encode_ctx_unsigned(buf, 0, self.process_identifier as u64);
147        // [1] initiatingDeviceIdentifier
148        primitives::encode_ctx_object_id(buf, 1, &self.initiating_device_identifier);
149        // [2] eventObjectIdentifier
150        primitives::encode_ctx_object_id(buf, 2, &self.event_object_identifier);
151        // [3] timeStamp
152        primitives::encode_timestamp(buf, 3, &self.timestamp);
153        // [4] notificationClass
154        primitives::encode_ctx_unsigned(buf, 4, self.notification_class as u64);
155        // [5] priority
156        primitives::encode_ctx_unsigned(buf, 5, self.priority as u64);
157        // [6] eventType
158        primitives::encode_ctx_enumerated(buf, 6, self.event_type);
159        // [7] messageText — omitted
160        // [8] notifyType
161        primitives::encode_ctx_enumerated(buf, 8, self.notify_type);
162        // [9] ackRequired (only for ALARM/EVENT)
163        if self.notify_type != 2 {
164            primitives::encode_ctx_boolean(buf, 9, self.ack_required);
165        }
166        // [10] fromState
167        primitives::encode_ctx_enumerated(buf, 10, self.from_state);
168        // [11] toState
169        primitives::encode_ctx_enumerated(buf, 11, self.to_state);
170        // [12] eventValues — optional
171        if let Some(ref params) = self.event_values {
172            tags::encode_opening_tag(buf, 12);
173            params.encode(buf)?;
174            tags::encode_closing_tag(buf, 12);
175        }
176        Ok(())
177    }
178
179    pub fn decode(data: &[u8]) -> Result<Self, Error> {
180        let mut offset = 0;
181
182        // Helper: validate bounds after computing end from tag length
183        macro_rules! check_bounds {
184            ($pos:expr, $end:expr, $field:expr) => {
185                if $end > data.len() {
186                    return Err(Error::decoding(
187                        $pos,
188                        concat!("EventNotification truncated at ", $field),
189                    ));
190                }
191            };
192        }
193
194        // [0] processIdentifier
195        let (_tag, pos) = tags::decode_tag(data, offset)?;
196        let end = pos + _tag.length as usize;
197        check_bounds!(pos, end, "processIdentifier");
198        let process_identifier = primitives::decode_unsigned(&data[pos..end])? as u32;
199        offset = end;
200
201        // [1] initiatingDeviceIdentifier
202        let (_tag, pos) = tags::decode_tag(data, offset)?;
203        let end = pos + _tag.length as usize;
204        check_bounds!(pos, end, "initiatingDeviceIdentifier");
205        let initiating_device_identifier = ObjectIdentifier::decode(&data[pos..end])?;
206        offset = end;
207
208        // [2] eventObjectIdentifier
209        let (_tag, pos) = tags::decode_tag(data, offset)?;
210        let end = pos + _tag.length as usize;
211        check_bounds!(pos, end, "eventObjectIdentifier");
212        let event_object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
213        offset = end;
214
215        // [3] timeStamp
216        let (timestamp, new_offset) = primitives::decode_timestamp(data, offset, 3)?;
217        offset = new_offset;
218
219        // [4] notificationClass
220        let (_tag, pos) = tags::decode_tag(data, offset)?;
221        let end = pos + _tag.length as usize;
222        check_bounds!(pos, end, "notificationClass");
223        let notification_class = primitives::decode_unsigned(&data[pos..end])? as u32;
224        offset = end;
225
226        // [5] priority
227        let (_tag, pos) = tags::decode_tag(data, offset)?;
228        let end = pos + _tag.length as usize;
229        check_bounds!(pos, end, "priority");
230        let priority = primitives::decode_unsigned(&data[pos..end])? as u8;
231        offset = end;
232
233        // [6] eventType
234        let (_tag, pos) = tags::decode_tag(data, offset)?;
235        let end = pos + _tag.length as usize;
236        check_bounds!(pos, end, "eventType");
237        let event_type = primitives::decode_unsigned(&data[pos..end])? as u32;
238        offset = end;
239
240        // Skip [7] messageText if present — scan for [8]
241        while offset < data.len() {
242            let (peek, peek_pos) = tags::decode_tag(data, offset)?;
243            if peek.is_context(8) {
244                break;
245            }
246            if peek.is_opening {
247                // Skip the entire constructed value (opening tag ... closing tag)
248                let (_, new_offset) = tags::extract_context_value(data, peek_pos, peek.number)?;
249                offset = new_offset;
250            } else if peek.is_closing {
251                return Err(Error::decoding(
252                    offset,
253                    "unexpected closing tag skipping to notification-parameters",
254                ));
255            } else {
256                let skip_end = peek_pos + peek.length as usize;
257                if skip_end > data.len() {
258                    return Err(Error::decoding(
259                        peek_pos,
260                        "EventNotification truncated skipping messageText",
261                    ));
262                }
263                offset = skip_end;
264            }
265        }
266
267        // [8] notifyType
268        let (_tag, pos) = tags::decode_tag(data, offset)?;
269        let end = pos + _tag.length as usize;
270        check_bounds!(pos, end, "notifyType");
271        let notify_type = primitives::decode_unsigned(&data[pos..end])? as u32;
272        offset = end;
273
274        // [9] ackRequired (optional — present for ALARM/EVENT)
275        let mut ack_required = false;
276        if offset < data.len() {
277            let (peek, peek_pos) = tags::decode_tag(data, offset)?;
278            if peek.is_context(9) {
279                let end = peek_pos + peek.length as usize;
280                check_bounds!(peek_pos, end, "ackRequired");
281                ack_required = data[peek_pos] != 0;
282                offset = end;
283            }
284        }
285
286        // [10] fromState
287        let (_tag, pos) = tags::decode_tag(data, offset)?;
288        let end = pos + _tag.length as usize;
289        check_bounds!(pos, end, "fromState");
290        let from_state = primitives::decode_unsigned(&data[pos..end])? as u32;
291        offset = end;
292
293        // [11] toState
294        let (_tag, pos) = tags::decode_tag(data, offset)?;
295        let end = pos + _tag.length as usize;
296        check_bounds!(pos, end, "toState");
297        let to_state = primitives::decode_unsigned(&data[pos..end])? as u32;
298        offset = end;
299
300        // [12] eventValues — optional
301        let mut event_values = None;
302        if offset < data.len() {
303            let (peek, _) = tags::decode_tag(data, offset)?;
304            if peek.is_opening && peek.number == 12 {
305                // Skip opening tag [12]
306                let (_, inner_start) = tags::decode_tag(data, offset)?;
307                event_values = Some(NotificationParameters::decode(data, inner_start)?);
308                // Find closing tag [12]
309                let mut scan = inner_start;
310                let mut depth: usize = 1;
311                while depth > 0 && scan < data.len() {
312                    let (t, next) = tags::decode_tag(data, scan)?;
313                    if t.is_opening {
314                        depth += 1;
315                        scan = next;
316                    } else if t.is_closing {
317                        depth -= 1;
318                        if depth == 0 {
319                            offset = next;
320                        } else {
321                            scan = next;
322                        }
323                    } else {
324                        let end = next.saturating_add(t.length as usize);
325                        if end > data.len() {
326                            return Err(Error::decoding(
327                                next,
328                                "EventNotification: truncated tag in eventValues",
329                            ));
330                        }
331                        scan = end;
332                    }
333                }
334            }
335        }
336        let _ = offset;
337
338        Ok(Self {
339            process_identifier,
340            initiating_device_identifier,
341            event_object_identifier,
342            timestamp,
343            notification_class,
344            priority,
345            event_type,
346            notify_type,
347            ack_required,
348            from_state,
349            to_state,
350            event_values,
351        })
352    }
353}
354
355// ---------------------------------------------------------------------------
356// NotificationParameters (Clause 13.5.1 — eventValues [12])
357// ---------------------------------------------------------------------------
358
359/// Notification parameter variants for eventValues (tag [12]).
360#[derive(Debug, Clone, PartialEq)]
361pub enum NotificationParameters {
362    /// [0] Change of bitstring.
363    ChangeOfBitstring {
364        referenced_bitstring: (u8, Vec<u8>),
365        status_flags: u8,
366    },
367    /// [1] Change of state.
368    ChangeOfState {
369        new_state: BACnetPropertyStates,
370        status_flags: u8,
371    },
372    /// [2] Change of value.
373    ChangeOfValue {
374        new_value: ChangeOfValueChoice,
375        status_flags: u8,
376    },
377    /// [3] Command failure.
378    CommandFailure {
379        command_value: Vec<u8>,
380        status_flags: u8,
381        feedback_value: Vec<u8>,
382    },
383    /// [4] Floating limit.
384    FloatingLimit {
385        reference_value: f32,
386        status_flags: u8,
387        setpoint_value: f32,
388        error_limit: f32,
389    },
390    /// [5] Out of range.
391    OutOfRange {
392        exceeding_value: f32,
393        status_flags: u8,
394        deadband: f32,
395        exceeded_limit: f32,
396    },
397    /// [8] Change of life safety.
398    ChangeOfLifeSafety {
399        new_state: u32,
400        new_mode: u32,
401        status_flags: u8,
402        operation_expected: u32,
403    },
404    /// [9] Extended (vendor-defined).
405    Extended {
406        vendor_id: u16,
407        extended_event_type: u32,
408        parameters: Vec<u8>,
409    },
410    /// [10] Buffer ready.
411    BufferReady {
412        buffer_property: BACnetDeviceObjectPropertyReference,
413        previous_notification: u32,
414        current_notification: u32,
415    },
416    /// [11] Unsigned range.
417    UnsignedRange {
418        exceeding_value: u64,
419        status_flags: u8,
420        exceeded_limit: u64,
421    },
422    /// [13] Access event.
423    AccessEvent {
424        access_event: u32,
425        status_flags: u8,
426        access_event_tag: u32,
427        access_event_time: (Date, Time),
428        access_credential: BACnetDeviceObjectPropertyReference,
429        authentication_factor: Vec<u8>,
430    },
431    /// [14] Double out of range.
432    DoubleOutOfRange {
433        exceeding_value: f64,
434        status_flags: u8,
435        deadband: f64,
436        exceeded_limit: f64,
437    },
438    /// [15] Signed out of range.
439    SignedOutOfRange {
440        exceeding_value: i32,
441        status_flags: u8,
442        deadband: u64,
443        exceeded_limit: i32,
444    },
445    /// [16] Unsigned out of range.
446    UnsignedOutOfRange {
447        exceeding_value: u64,
448        status_flags: u8,
449        deadband: u64,
450        exceeded_limit: u64,
451    },
452    /// [17] Change of characterstring.
453    ChangeOfCharacterstring {
454        changed_value: String,
455        status_flags: u8,
456        alarm_value: String,
457    },
458    /// [18] Change of status flags.
459    ChangeOfStatusFlags {
460        present_value: Vec<u8>,
461        referenced_flags: u8,
462    },
463    /// [19] Change of reliability.
464    ChangeOfReliability {
465        reliability: u32,
466        status_flags: u8,
467        property_values: Vec<u8>,
468    },
469    /// [20] None.
470    NoneParams,
471    /// [21] Change of discrete value.
472    ChangeOfDiscreteValue {
473        new_value: Vec<u8>,
474        status_flags: u8,
475    },
476    /// [22] Change of timer.
477    ChangeOfTimer {
478        new_state: u32,
479        status_flags: u8,
480        update_time: (Date, Time),
481        last_state_change: u32,
482        initial_timeout: u32,
483        expiration_time: (Date, Time),
484    },
485}
486
487/// CHOICE within ChangeOfValue notification parameters.
488#[derive(Debug, Clone, PartialEq)]
489pub enum ChangeOfValueChoice {
490    ChangedBits { unused_bits: u8, data: Vec<u8> },
491    ChangedValue(f32),
492}
493
494impl NotificationParameters {
495    /// Encode notification parameters into the buffer.
496    ///
497    /// Each variant is wrapped in its own opening/closing tag pair
498    /// matching the variant's context tag number.
499    pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
500        match self {
501            Self::ChangeOfBitstring {
502                referenced_bitstring,
503                status_flags,
504            } => {
505                tags::encode_opening_tag(buf, 0);
506                primitives::encode_ctx_bit_string(
507                    buf,
508                    0,
509                    referenced_bitstring.0,
510                    &referenced_bitstring.1,
511                );
512                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
513                tags::encode_closing_tag(buf, 0);
514            }
515            Self::ChangeOfState {
516                new_state,
517                status_flags,
518            } => {
519                tags::encode_opening_tag(buf, 1);
520                // [0] new-state: BACnetPropertyStates — wrapped in opening/closing [0]
521                tags::encode_opening_tag(buf, 0);
522                encode_property_states(buf, new_state);
523                tags::encode_closing_tag(buf, 0);
524                // [1] status-flags
525                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
526                tags::encode_closing_tag(buf, 1);
527            }
528            Self::ChangeOfValue {
529                new_value,
530                status_flags,
531            } => {
532                tags::encode_opening_tag(buf, 2);
533                // [0] new-value: CHOICE — wrapped in opening/closing [0]
534                tags::encode_opening_tag(buf, 0);
535                match new_value {
536                    ChangeOfValueChoice::ChangedBits { unused_bits, data } => {
537                        primitives::encode_ctx_bit_string(buf, 0, *unused_bits, data);
538                    }
539                    ChangeOfValueChoice::ChangedValue(v) => {
540                        primitives::encode_ctx_real(buf, 1, *v);
541                    }
542                }
543                tags::encode_closing_tag(buf, 0);
544                // [1] status-flags
545                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
546                tags::encode_closing_tag(buf, 2);
547            }
548            Self::CommandFailure {
549                command_value,
550                status_flags,
551                feedback_value,
552            } => {
553                tags::encode_opening_tag(buf, 3);
554                // [0] command-value — abstract syntax, encoded as raw octet string
555                tags::encode_opening_tag(buf, 0);
556                buf.extend_from_slice(command_value);
557                tags::encode_closing_tag(buf, 0);
558                // [1] status-flags
559                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
560                // [2] feedback-value — abstract syntax, encoded as raw
561                tags::encode_opening_tag(buf, 2);
562                buf.extend_from_slice(feedback_value);
563                tags::encode_closing_tag(buf, 2);
564                tags::encode_closing_tag(buf, 3);
565            }
566            Self::FloatingLimit {
567                reference_value,
568                status_flags,
569                setpoint_value,
570                error_limit,
571            } => {
572                tags::encode_opening_tag(buf, 4);
573                primitives::encode_ctx_real(buf, 0, *reference_value);
574                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
575                primitives::encode_ctx_real(buf, 2, *setpoint_value);
576                primitives::encode_ctx_real(buf, 3, *error_limit);
577                tags::encode_closing_tag(buf, 4);
578            }
579            Self::OutOfRange {
580                exceeding_value,
581                status_flags,
582                deadband,
583                exceeded_limit,
584            } => {
585                tags::encode_opening_tag(buf, 5);
586                primitives::encode_ctx_real(buf, 0, *exceeding_value);
587                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
588                primitives::encode_ctx_real(buf, 2, *deadband);
589                primitives::encode_ctx_real(buf, 3, *exceeded_limit);
590                tags::encode_closing_tag(buf, 5);
591            }
592            Self::ChangeOfLifeSafety {
593                new_state,
594                new_mode,
595                status_flags,
596                operation_expected,
597            } => {
598                tags::encode_opening_tag(buf, 8);
599                primitives::encode_ctx_enumerated(buf, 0, *new_state);
600                primitives::encode_ctx_enumerated(buf, 1, *new_mode);
601                primitives::encode_ctx_bit_string(buf, 2, 4, &[*status_flags << 4]);
602                primitives::encode_ctx_enumerated(buf, 3, *operation_expected);
603                tags::encode_closing_tag(buf, 8);
604            }
605            Self::Extended {
606                vendor_id,
607                extended_event_type,
608                parameters,
609            } => {
610                tags::encode_opening_tag(buf, 9);
611                primitives::encode_ctx_unsigned(buf, 0, *vendor_id as u64);
612                primitives::encode_ctx_unsigned(buf, 1, *extended_event_type as u64);
613                // [2] parameters — raw
614                tags::encode_opening_tag(buf, 2);
615                buf.extend_from_slice(parameters);
616                tags::encode_closing_tag(buf, 2);
617                tags::encode_closing_tag(buf, 9);
618            }
619            Self::BufferReady {
620                buffer_property,
621                previous_notification,
622                current_notification,
623            } => {
624                tags::encode_opening_tag(buf, 10);
625                // [0] buffer-property: BACnetDeviceObjectPropertyReference
626                tags::encode_opening_tag(buf, 0);
627                primitives::encode_ctx_object_id(buf, 0, &buffer_property.object_identifier);
628                primitives::encode_ctx_unsigned(buf, 1, buffer_property.property_identifier as u64);
629                if let Some(idx) = buffer_property.property_array_index {
630                    primitives::encode_ctx_unsigned(buf, 2, idx as u64);
631                }
632                if let Some(ref dev) = buffer_property.device_identifier {
633                    primitives::encode_ctx_object_id(buf, 3, dev);
634                }
635                tags::encode_closing_tag(buf, 0);
636                // [1] previous-notification
637                primitives::encode_ctx_unsigned(buf, 1, *previous_notification as u64);
638                // [2] current-notification
639                primitives::encode_ctx_unsigned(buf, 2, *current_notification as u64);
640                tags::encode_closing_tag(buf, 10);
641            }
642            Self::UnsignedRange {
643                exceeding_value,
644                status_flags,
645                exceeded_limit,
646            } => {
647                tags::encode_opening_tag(buf, 11);
648                primitives::encode_ctx_unsigned(buf, 0, *exceeding_value);
649                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
650                primitives::encode_ctx_unsigned(buf, 2, *exceeded_limit);
651                tags::encode_closing_tag(buf, 11);
652            }
653            Self::AccessEvent {
654                access_event,
655                status_flags,
656                access_event_tag,
657                access_event_time,
658                access_credential,
659                authentication_factor,
660            } => {
661                tags::encode_opening_tag(buf, 13);
662                primitives::encode_ctx_enumerated(buf, 0, *access_event);
663                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
664                primitives::encode_ctx_unsigned(buf, 2, *access_event_tag as u64);
665                // [3] access-event-time: BACnetTimeStamp (DateTime)
666                primitives::encode_timestamp(
667                    buf,
668                    3,
669                    &BACnetTimeStamp::DateTime {
670                        date: access_event_time.0,
671                        time: access_event_time.1,
672                    },
673                );
674                // [4] access-credential: BACnetDeviceObjectPropertyReference
675                tags::encode_opening_tag(buf, 4);
676                primitives::encode_ctx_object_id(buf, 0, &access_credential.object_identifier);
677                primitives::encode_ctx_unsigned(
678                    buf,
679                    1,
680                    access_credential.property_identifier as u64,
681                );
682                if let Some(idx) = access_credential.property_array_index {
683                    primitives::encode_ctx_unsigned(buf, 2, idx as u64);
684                }
685                if let Some(ref dev) = access_credential.device_identifier {
686                    primitives::encode_ctx_object_id(buf, 3, dev);
687                }
688                tags::encode_closing_tag(buf, 4);
689                // [5] authentication-factor — raw
690                tags::encode_opening_tag(buf, 5);
691                buf.extend_from_slice(authentication_factor);
692                tags::encode_closing_tag(buf, 5);
693                tags::encode_closing_tag(buf, 13);
694            }
695            Self::DoubleOutOfRange {
696                exceeding_value,
697                status_flags,
698                deadband,
699                exceeded_limit,
700            } => {
701                tags::encode_opening_tag(buf, 14);
702                primitives::encode_ctx_double(buf, 0, *exceeding_value);
703                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
704                primitives::encode_ctx_double(buf, 2, *deadband);
705                primitives::encode_ctx_double(buf, 3, *exceeded_limit);
706                tags::encode_closing_tag(buf, 14);
707            }
708            Self::SignedOutOfRange {
709                exceeding_value,
710                status_flags,
711                deadband,
712                exceeded_limit,
713            } => {
714                tags::encode_opening_tag(buf, 15);
715                primitives::encode_ctx_signed(buf, 0, *exceeding_value);
716                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
717                primitives::encode_ctx_unsigned(buf, 2, *deadband);
718                primitives::encode_ctx_signed(buf, 3, *exceeded_limit);
719                tags::encode_closing_tag(buf, 15);
720            }
721            Self::UnsignedOutOfRange {
722                exceeding_value,
723                status_flags,
724                deadband,
725                exceeded_limit,
726            } => {
727                tags::encode_opening_tag(buf, 16);
728                primitives::encode_ctx_unsigned(buf, 0, *exceeding_value);
729                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
730                primitives::encode_ctx_unsigned(buf, 2, *deadband);
731                primitives::encode_ctx_unsigned(buf, 3, *exceeded_limit);
732                tags::encode_closing_tag(buf, 16);
733            }
734            Self::ChangeOfCharacterstring {
735                changed_value,
736                status_flags,
737                alarm_value,
738            } => {
739                tags::encode_opening_tag(buf, 17);
740                primitives::encode_ctx_character_string(buf, 0, changed_value)?;
741                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
742                primitives::encode_ctx_character_string(buf, 2, alarm_value)?;
743                tags::encode_closing_tag(buf, 17);
744            }
745            Self::ChangeOfStatusFlags {
746                present_value,
747                referenced_flags,
748            } => {
749                tags::encode_opening_tag(buf, 18);
750                // [0] present-value — abstract syntax, raw
751                tags::encode_opening_tag(buf, 0);
752                buf.extend_from_slice(present_value);
753                tags::encode_closing_tag(buf, 0);
754                // [1] referenced-flags
755                primitives::encode_ctx_bit_string(buf, 1, 4, &[*referenced_flags << 4]);
756                tags::encode_closing_tag(buf, 18);
757            }
758            Self::ChangeOfReliability {
759                reliability,
760                status_flags,
761                property_values,
762            } => {
763                tags::encode_opening_tag(buf, 19);
764                primitives::encode_ctx_enumerated(buf, 0, *reliability);
765                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
766                // [2] property-values — abstract syntax, raw
767                tags::encode_opening_tag(buf, 2);
768                buf.extend_from_slice(property_values);
769                tags::encode_closing_tag(buf, 2);
770                tags::encode_closing_tag(buf, 19);
771            }
772            Self::NoneParams => {
773                tags::encode_opening_tag(buf, 20);
774                tags::encode_closing_tag(buf, 20);
775            }
776            Self::ChangeOfDiscreteValue {
777                new_value,
778                status_flags,
779            } => {
780                tags::encode_opening_tag(buf, 21);
781                // [0] new-value — abstract syntax, raw
782                tags::encode_opening_tag(buf, 0);
783                buf.extend_from_slice(new_value);
784                tags::encode_closing_tag(buf, 0);
785                // [1] status-flags
786                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
787                tags::encode_closing_tag(buf, 21);
788            }
789            Self::ChangeOfTimer {
790                new_state,
791                status_flags,
792                update_time,
793                last_state_change,
794                initial_timeout,
795                expiration_time,
796            } => {
797                tags::encode_opening_tag(buf, 22);
798                primitives::encode_ctx_enumerated(buf, 0, *new_state);
799                primitives::encode_ctx_bit_string(buf, 1, 4, &[*status_flags << 4]);
800                // [2] update-time: BACnetDateTime
801                tags::encode_opening_tag(buf, 2);
802                primitives::encode_app_date(buf, &update_time.0);
803                primitives::encode_app_time(buf, &update_time.1);
804                tags::encode_closing_tag(buf, 2);
805                // [3] last-state-change (optional enumerated — always encode)
806                primitives::encode_ctx_enumerated(buf, 3, *last_state_change);
807                // [4] initial-timeout (optional unsigned — always encode)
808                primitives::encode_ctx_unsigned(buf, 4, *initial_timeout as u64);
809                // [5] expiration-time: BACnetDateTime
810                tags::encode_opening_tag(buf, 5);
811                primitives::encode_app_date(buf, &expiration_time.0);
812                primitives::encode_app_time(buf, &expiration_time.1);
813                tags::encode_closing_tag(buf, 5);
814                tags::encode_closing_tag(buf, 22);
815            }
816        }
817        Ok(())
818    }
819
820    /// Decode notification parameters from a position just past the opening
821    /// tag of the eventValues wrapper.
822    pub fn decode(data: &[u8], offset: usize) -> Result<Self, Error> {
823        // Peek the inner opening tag to determine the variant
824        if offset >= data.len() {
825            return Err(Error::decoding(
826                offset,
827                "NotificationParameters: empty payload",
828            ));
829        }
830        let (inner_tag, inner_start) = tags::decode_tag(data, offset)?;
831        if !inner_tag.is_opening {
832            return Err(Error::decoding(
833                offset,
834                "NotificationParameters: expected opening tag for variant",
835            ));
836        }
837        let variant_tag = inner_tag.number;
838
839        match variant_tag {
840            // [1] Change of state
841            1 => {
842                let mut pos = inner_start;
843                // [0] new-state: BACnetPropertyStates — wrapped in opening/closing [0]
844                let (t, p) = tags::decode_tag(data, pos)?;
845                if !t.is_opening || t.number != 0 {
846                    return Err(Error::decoding(
847                        pos,
848                        "ChangeOfState: expected opening tag [0] for new-state",
849                    ));
850                }
851                pos = p;
852                let new_state = decode_property_states(data, &mut pos)?;
853                // Skip closing tag [0]
854                let (ct, cp) = tags::decode_tag(data, pos)?;
855                if !ct.is_closing || ct.number != 0 {
856                    return Err(Error::decoding(pos, "ChangeOfState: expected closing [0]"));
857                }
858                pos = cp;
859                // [1] status-flags
860                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
861                let sf_end = sf_pos + sf_tag.length as usize;
862                if sf_end > data.len() {
863                    return Err(Error::decoding(sf_pos, "ChangeOfState: truncated flags"));
864                }
865                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
866                Ok(Self::ChangeOfState {
867                    new_state,
868                    status_flags,
869                })
870            }
871            // [2] Change of value
872            2 => {
873                let mut pos = inner_start;
874                // [0] new-value CHOICE — wrapped in opening/closing [0]
875                let (t, p) = tags::decode_tag(data, pos)?;
876                if !t.is_opening || t.number != 0 {
877                    return Err(Error::decoding(
878                        pos,
879                        "ChangeOfValue: expected opening [0] for new-value",
880                    ));
881                }
882                pos = p;
883                // Peek CHOICE tag
884                let (choice_tag, choice_pos) = tags::decode_tag(data, pos)?;
885                let new_value = if choice_tag.number == 0 {
886                    // [0] changed-bits
887                    let end = choice_pos + choice_tag.length as usize;
888                    if end > data.len() {
889                        return Err(Error::decoding(choice_pos, "ChangeOfValue: truncated bits"));
890                    }
891                    let (unused, bits) = primitives::decode_bit_string(&data[choice_pos..end])?;
892                    pos = end;
893                    ChangeOfValueChoice::ChangedBits {
894                        unused_bits: unused,
895                        data: bits,
896                    }
897                } else {
898                    // [1] changed-value (Real)
899                    let end = choice_pos + choice_tag.length as usize;
900                    if end > data.len() {
901                        return Err(Error::decoding(choice_pos, "ChangeOfValue: truncated real"));
902                    }
903                    let v = primitives::decode_real(&data[choice_pos..end])?;
904                    pos = end;
905                    ChangeOfValueChoice::ChangedValue(v)
906                };
907                // Closing tag [0]
908                let (ct, cp) = tags::decode_tag(data, pos)?;
909                if !ct.is_closing || ct.number != 0 {
910                    return Err(Error::decoding(pos, "ChangeOfValue: expected closing [0]"));
911                }
912                pos = cp;
913                // [1] status-flags
914                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
915                let sf_end = sf_pos + sf_tag.length as usize;
916                if sf_end > data.len() {
917                    return Err(Error::decoding(sf_pos, "ChangeOfValue: truncated flags"));
918                }
919                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
920                Ok(Self::ChangeOfValue {
921                    new_value,
922                    status_flags,
923                })
924            }
925            // [5] Out of range
926            5 => {
927                let mut pos = inner_start;
928                // [0] exceeding-value
929                let (t, p) = tags::decode_tag(data, pos)?;
930                let end = p + t.length as usize;
931                if end > data.len() {
932                    return Err(Error::decoding(p, "OutOfRange: truncated exceeding_value"));
933                }
934                let exceeding_value = primitives::decode_real(&data[p..end])?;
935                pos = end;
936                // [1] status-flags
937                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
938                let sf_end = sf_pos + sf_tag.length as usize;
939                if sf_end > data.len() {
940                    return Err(Error::decoding(sf_pos, "OutOfRange: truncated flags"));
941                }
942                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
943                pos = sf_end;
944                // [2] deadband
945                let (t, p) = tags::decode_tag(data, pos)?;
946                let end = p + t.length as usize;
947                if end > data.len() {
948                    return Err(Error::decoding(p, "OutOfRange: truncated deadband"));
949                }
950                let deadband = primitives::decode_real(&data[p..end])?;
951                pos = end;
952                // [3] exceeded-limit
953                let (t, p) = tags::decode_tag(data, pos)?;
954                let end = p + t.length as usize;
955                if end > data.len() {
956                    return Err(Error::decoding(p, "OutOfRange: truncated exceeded_limit"));
957                }
958                let exceeded_limit = primitives::decode_real(&data[p..end])?;
959                let _ = (pos, end);
960                Ok(Self::OutOfRange {
961                    exceeding_value,
962                    status_flags,
963                    deadband,
964                    exceeded_limit,
965                })
966            }
967            // [10] Buffer ready
968            10 => {
969                let mut pos = inner_start;
970                // [0] buffer-property: BACnetDeviceObjectPropertyReference
971                let (t, p) = tags::decode_tag(data, pos)?;
972                if !t.is_opening || t.number != 0 {
973                    return Err(Error::decoding(
974                        pos,
975                        "BufferReady: expected opening [0] for buffer-property",
976                    ));
977                }
978                pos = p;
979                let buffer_property = decode_device_obj_prop_ref(data, &mut pos)?;
980                // Closing tag [0]
981                let (ct, cp) = tags::decode_tag(data, pos)?;
982                if !ct.is_closing || ct.number != 0 {
983                    return Err(Error::decoding(pos, "BufferReady: expected closing [0]"));
984                }
985                pos = cp;
986                // [1] previous-notification
987                let (t, p) = tags::decode_tag(data, pos)?;
988                let end = p + t.length as usize;
989                if end > data.len() {
990                    return Err(Error::decoding(
991                        p,
992                        "BufferReady: truncated previous_notification",
993                    ));
994                }
995                let previous_notification = primitives::decode_unsigned(&data[p..end])? as u32;
996                pos = end;
997                // [2] current-notification
998                let (t, p) = tags::decode_tag(data, pos)?;
999                let end = p + t.length as usize;
1000                if end > data.len() {
1001                    return Err(Error::decoding(
1002                        p,
1003                        "BufferReady: truncated current_notification",
1004                    ));
1005                }
1006                let current_notification = primitives::decode_unsigned(&data[p..end])? as u32;
1007                let _ = (pos, end);
1008                Ok(Self::BufferReady {
1009                    buffer_property,
1010                    previous_notification,
1011                    current_notification,
1012                })
1013            }
1014            // [11] Unsigned range
1015            11 => {
1016                let mut pos = inner_start;
1017                // [0] exceeding-value
1018                let (t, p) = tags::decode_tag(data, pos)?;
1019                let end = p + t.length as usize;
1020                if end > data.len() {
1021                    return Err(Error::decoding(
1022                        p,
1023                        "UnsignedRange: truncated exceeding_value",
1024                    ));
1025                }
1026                let exceeding_value = primitives::decode_unsigned(&data[p..end])?;
1027                pos = end;
1028                // [1] status-flags
1029                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1030                let sf_end = sf_pos + sf_tag.length as usize;
1031                if sf_end > data.len() {
1032                    return Err(Error::decoding(sf_pos, "UnsignedRange: truncated flags"));
1033                }
1034                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1035                pos = sf_end;
1036                // [2] exceeded-limit
1037                let (t, p) = tags::decode_tag(data, pos)?;
1038                let end = p + t.length as usize;
1039                if end > data.len() {
1040                    return Err(Error::decoding(
1041                        p,
1042                        "UnsignedRange: truncated exceeded_limit",
1043                    ));
1044                }
1045                let exceeded_limit = primitives::decode_unsigned(&data[p..end])?;
1046                let _ = (pos, end);
1047                Ok(Self::UnsignedRange {
1048                    exceeding_value,
1049                    status_flags,
1050                    exceeded_limit,
1051                })
1052            }
1053            // [0] Change of bitstring
1054            0 => {
1055                let mut pos = inner_start;
1056                // [0] referenced-bitstring
1057                let (t, p) = tags::decode_tag(data, pos)?;
1058                let end = p + t.length as usize;
1059                if end > data.len() {
1060                    return Err(Error::decoding(p, "ChangeOfBitstring: truncated bitstring"));
1061                }
1062                let (unused, bits) = primitives::decode_bit_string(&data[p..end])?;
1063                pos = end;
1064                // [1] status-flags
1065                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1066                let sf_end = sf_pos + sf_tag.length as usize;
1067                if sf_end > data.len() {
1068                    return Err(Error::decoding(
1069                        sf_pos,
1070                        "ChangeOfBitstring: truncated flags",
1071                    ));
1072                }
1073                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1074                Ok(Self::ChangeOfBitstring {
1075                    referenced_bitstring: (unused, bits),
1076                    status_flags,
1077                })
1078            }
1079            // [3] Command failure
1080            3 => {
1081                let mut pos = inner_start;
1082                // [0] command-value — opening/closing, raw
1083                let (t, p) = tags::decode_tag(data, pos)?;
1084                if !t.is_opening || t.number != 0 {
1085                    return Err(Error::decoding(pos, "CommandFailure: expected opening [0]"));
1086                }
1087                let (command_value, after) = extract_raw_context(data, p, 0)?;
1088                pos = after;
1089                // [1] status-flags
1090                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1091                let sf_end = sf_pos + sf_tag.length as usize;
1092                if sf_end > data.len() {
1093                    return Err(Error::decoding(sf_pos, "CommandFailure: truncated flags"));
1094                }
1095                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1096                pos = sf_end;
1097                // [2] feedback-value — opening/closing, raw
1098                let (t, p) = tags::decode_tag(data, pos)?;
1099                if !t.is_opening || t.number != 2 {
1100                    return Err(Error::decoding(pos, "CommandFailure: expected opening [2]"));
1101                }
1102                let (feedback_value, _after) = extract_raw_context(data, p, 2)?;
1103                Ok(Self::CommandFailure {
1104                    command_value,
1105                    status_flags,
1106                    feedback_value,
1107                })
1108            }
1109            // [4] Floating limit
1110            4 => {
1111                let mut pos = inner_start;
1112                // [0] reference-value
1113                let (t, p) = tags::decode_tag(data, pos)?;
1114                let end = p + t.length as usize;
1115                if end > data.len() {
1116                    return Err(Error::decoding(
1117                        p,
1118                        "FloatingLimit: truncated reference_value",
1119                    ));
1120                }
1121                let reference_value = primitives::decode_real(&data[p..end])?;
1122                pos = end;
1123                // [1] status-flags
1124                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1125                let sf_end = sf_pos + sf_tag.length as usize;
1126                if sf_end > data.len() {
1127                    return Err(Error::decoding(sf_pos, "FloatingLimit: truncated flags"));
1128                }
1129                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1130                pos = sf_end;
1131                // [2] setpoint-value
1132                let (t, p) = tags::decode_tag(data, pos)?;
1133                let end = p + t.length as usize;
1134                if end > data.len() {
1135                    return Err(Error::decoding(
1136                        p,
1137                        "FloatingLimit: truncated setpoint_value",
1138                    ));
1139                }
1140                let setpoint_value = primitives::decode_real(&data[p..end])?;
1141                pos = end;
1142                // [3] error-limit
1143                let (t, p) = tags::decode_tag(data, pos)?;
1144                let end = p + t.length as usize;
1145                if end > data.len() {
1146                    return Err(Error::decoding(p, "FloatingLimit: truncated error_limit"));
1147                }
1148                let error_limit = primitives::decode_real(&data[p..end])?;
1149                let _ = (pos, end);
1150                Ok(Self::FloatingLimit {
1151                    reference_value,
1152                    status_flags,
1153                    setpoint_value,
1154                    error_limit,
1155                })
1156            }
1157            // [8] Change of life safety
1158            8 => {
1159                let mut pos = inner_start;
1160                // [0] new-state
1161                let (t, p) = tags::decode_tag(data, pos)?;
1162                let end = p + t.length as usize;
1163                if end > data.len() {
1164                    return Err(Error::decoding(
1165                        p,
1166                        "ChangeOfLifeSafety: truncated new_state",
1167                    ));
1168                }
1169                let new_state = primitives::decode_unsigned(&data[p..end])? as u32;
1170                pos = end;
1171                // [1] new-mode
1172                let (t, p) = tags::decode_tag(data, pos)?;
1173                let end = p + t.length as usize;
1174                if end > data.len() {
1175                    return Err(Error::decoding(p, "ChangeOfLifeSafety: truncated new_mode"));
1176                }
1177                let new_mode = primitives::decode_unsigned(&data[p..end])? as u32;
1178                pos = end;
1179                // [2] status-flags
1180                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1181                let sf_end = sf_pos + sf_tag.length as usize;
1182                if sf_end > data.len() {
1183                    return Err(Error::decoding(
1184                        sf_pos,
1185                        "ChangeOfLifeSafety: truncated flags",
1186                    ));
1187                }
1188                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1189                pos = sf_end;
1190                // [3] operation-expected
1191                let (t, p) = tags::decode_tag(data, pos)?;
1192                let end = p + t.length as usize;
1193                if end > data.len() {
1194                    return Err(Error::decoding(
1195                        p,
1196                        "ChangeOfLifeSafety: truncated operation_expected",
1197                    ));
1198                }
1199                let operation_expected = primitives::decode_unsigned(&data[p..end])? as u32;
1200                let _ = (pos, end);
1201                Ok(Self::ChangeOfLifeSafety {
1202                    new_state,
1203                    new_mode,
1204                    status_flags,
1205                    operation_expected,
1206                })
1207            }
1208            // [9] Extended
1209            9 => {
1210                let mut pos = inner_start;
1211                // [0] vendor-id
1212                let (t, p) = tags::decode_tag(data, pos)?;
1213                let end = p + t.length as usize;
1214                if end > data.len() {
1215                    return Err(Error::decoding(p, "Extended: truncated vendor_id"));
1216                }
1217                let vendor_id = primitives::decode_unsigned(&data[p..end])? as u16;
1218                pos = end;
1219                // [1] extended-event-type
1220                let (t, p) = tags::decode_tag(data, pos)?;
1221                let end = p + t.length as usize;
1222                if end > data.len() {
1223                    return Err(Error::decoding(
1224                        p,
1225                        "Extended: truncated extended_event_type",
1226                    ));
1227                }
1228                let extended_event_type = primitives::decode_unsigned(&data[p..end])? as u32;
1229                pos = end;
1230                // [2] parameters — opening/closing, raw
1231                let (t, p) = tags::decode_tag(data, pos)?;
1232                if !t.is_opening || t.number != 2 {
1233                    return Err(Error::decoding(pos, "Extended: expected opening [2]"));
1234                }
1235                let (parameters, _after) = extract_raw_context(data, p, 2)?;
1236                Ok(Self::Extended {
1237                    vendor_id,
1238                    extended_event_type,
1239                    parameters,
1240                })
1241            }
1242            // [13] Access event
1243            13 => {
1244                let mut pos = inner_start;
1245                // [0] access-event
1246                let (t, p) = tags::decode_tag(data, pos)?;
1247                let end = p + t.length as usize;
1248                if end > data.len() {
1249                    return Err(Error::decoding(p, "AccessEvent: truncated access_event"));
1250                }
1251                let access_event = primitives::decode_unsigned(&data[p..end])? as u32;
1252                pos = end;
1253                // [1] status-flags
1254                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1255                let sf_end = sf_pos + sf_tag.length as usize;
1256                if sf_end > data.len() {
1257                    return Err(Error::decoding(sf_pos, "AccessEvent: truncated flags"));
1258                }
1259                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1260                pos = sf_end;
1261                // [2] access-event-tag
1262                let (t, p) = tags::decode_tag(data, pos)?;
1263                let end = p + t.length as usize;
1264                if end > data.len() {
1265                    return Err(Error::decoding(
1266                        p,
1267                        "AccessEvent: truncated access_event_tag",
1268                    ));
1269                }
1270                let access_event_tag = primitives::decode_unsigned(&data[p..end])? as u32;
1271                pos = end;
1272                // [3] access-event-time: BACnetTimeStamp (DateTime)
1273                let (ts, new_pos) = primitives::decode_timestamp(data, pos, 3)?;
1274                pos = new_pos;
1275                let access_event_time = match ts {
1276                    BACnetTimeStamp::DateTime { date, time } => (date, time),
1277                    _ => {
1278                        return Err(Error::decoding(
1279                            pos,
1280                            "AccessEvent: expected DateTime timestamp",
1281                        ))
1282                    }
1283                };
1284                // [4] access-credential: BACnetDeviceObjectPropertyReference
1285                let (t, p) = tags::decode_tag(data, pos)?;
1286                if !t.is_opening || t.number != 4 {
1287                    return Err(Error::decoding(
1288                        pos,
1289                        "AccessEvent: expected opening [4] for access-credential",
1290                    ));
1291                }
1292                pos = p;
1293                let access_credential = decode_device_obj_prop_ref(data, &mut pos)?;
1294                let (ct, cp) = tags::decode_tag(data, pos)?;
1295                if !ct.is_closing || ct.number != 4 {
1296                    return Err(Error::decoding(pos, "AccessEvent: expected closing [4]"));
1297                }
1298                pos = cp;
1299                // [5] authentication-factor — opening/closing, raw
1300                let (t, p) = tags::decode_tag(data, pos)?;
1301                if !t.is_opening || t.number != 5 {
1302                    return Err(Error::decoding(
1303                        pos,
1304                        "AccessEvent: expected opening [5] for authentication-factor",
1305                    ));
1306                }
1307                let (authentication_factor, _after) = extract_raw_context(data, p, 5)?;
1308                Ok(Self::AccessEvent {
1309                    access_event,
1310                    status_flags,
1311                    access_event_tag,
1312                    access_event_time,
1313                    access_credential,
1314                    authentication_factor,
1315                })
1316            }
1317            // [14] Double out of range
1318            14 => {
1319                let mut pos = inner_start;
1320                // [0] exceeding-value
1321                let (t, p) = tags::decode_tag(data, pos)?;
1322                let end = p + t.length as usize;
1323                if end > data.len() {
1324                    return Err(Error::decoding(
1325                        p,
1326                        "DoubleOutOfRange: truncated exceeding_value",
1327                    ));
1328                }
1329                let exceeding_value = primitives::decode_double(&data[p..end])?;
1330                pos = end;
1331                // [1] status-flags
1332                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1333                let sf_end = sf_pos + sf_tag.length as usize;
1334                if sf_end > data.len() {
1335                    return Err(Error::decoding(sf_pos, "DoubleOutOfRange: truncated flags"));
1336                }
1337                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1338                pos = sf_end;
1339                // [2] deadband
1340                let (t, p) = tags::decode_tag(data, pos)?;
1341                let end = p + t.length as usize;
1342                if end > data.len() {
1343                    return Err(Error::decoding(p, "DoubleOutOfRange: truncated deadband"));
1344                }
1345                let deadband = primitives::decode_double(&data[p..end])?;
1346                pos = end;
1347                // [3] exceeded-limit
1348                let (t, p) = tags::decode_tag(data, pos)?;
1349                let end = p + t.length as usize;
1350                if end > data.len() {
1351                    return Err(Error::decoding(
1352                        p,
1353                        "DoubleOutOfRange: truncated exceeded_limit",
1354                    ));
1355                }
1356                let exceeded_limit = primitives::decode_double(&data[p..end])?;
1357                let _ = (pos, end);
1358                Ok(Self::DoubleOutOfRange {
1359                    exceeding_value,
1360                    status_flags,
1361                    deadband,
1362                    exceeded_limit,
1363                })
1364            }
1365            // [15] Signed out of range
1366            15 => {
1367                let mut pos = inner_start;
1368                // [0] exceeding-value
1369                let (t, p) = tags::decode_tag(data, pos)?;
1370                let end = p + t.length as usize;
1371                if end > data.len() {
1372                    return Err(Error::decoding(
1373                        p,
1374                        "SignedOutOfRange: truncated exceeding_value",
1375                    ));
1376                }
1377                let exceeding_value = primitives::decode_signed(&data[p..end])?;
1378                pos = end;
1379                // [1] status-flags
1380                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1381                let sf_end = sf_pos + sf_tag.length as usize;
1382                if sf_end > data.len() {
1383                    return Err(Error::decoding(sf_pos, "SignedOutOfRange: truncated flags"));
1384                }
1385                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1386                pos = sf_end;
1387                // [2] deadband
1388                let (t, p) = tags::decode_tag(data, pos)?;
1389                let end = p + t.length as usize;
1390                if end > data.len() {
1391                    return Err(Error::decoding(p, "SignedOutOfRange: truncated deadband"));
1392                }
1393                let deadband = primitives::decode_unsigned(&data[p..end])?;
1394                pos = end;
1395                // [3] exceeded-limit
1396                let (t, p) = tags::decode_tag(data, pos)?;
1397                let end = p + t.length as usize;
1398                if end > data.len() {
1399                    return Err(Error::decoding(
1400                        p,
1401                        "SignedOutOfRange: truncated exceeded_limit",
1402                    ));
1403                }
1404                let exceeded_limit = primitives::decode_signed(&data[p..end])?;
1405                let _ = (pos, end);
1406                Ok(Self::SignedOutOfRange {
1407                    exceeding_value,
1408                    status_flags,
1409                    deadband,
1410                    exceeded_limit,
1411                })
1412            }
1413            // [16] Unsigned out of range
1414            16 => {
1415                let mut pos = inner_start;
1416                // [0] exceeding-value
1417                let (t, p) = tags::decode_tag(data, pos)?;
1418                let end = p + t.length as usize;
1419                if end > data.len() {
1420                    return Err(Error::decoding(
1421                        p,
1422                        "UnsignedOutOfRange: truncated exceeding_value",
1423                    ));
1424                }
1425                let exceeding_value = primitives::decode_unsigned(&data[p..end])?;
1426                pos = end;
1427                // [1] status-flags
1428                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1429                let sf_end = sf_pos + sf_tag.length as usize;
1430                if sf_end > data.len() {
1431                    return Err(Error::decoding(
1432                        sf_pos,
1433                        "UnsignedOutOfRange: truncated flags",
1434                    ));
1435                }
1436                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1437                pos = sf_end;
1438                // [2] deadband
1439                let (t, p) = tags::decode_tag(data, pos)?;
1440                let end = p + t.length as usize;
1441                if end > data.len() {
1442                    return Err(Error::decoding(p, "UnsignedOutOfRange: truncated deadband"));
1443                }
1444                let deadband = primitives::decode_unsigned(&data[p..end])?;
1445                pos = end;
1446                // [3] exceeded-limit
1447                let (t, p) = tags::decode_tag(data, pos)?;
1448                let end = p + t.length as usize;
1449                if end > data.len() {
1450                    return Err(Error::decoding(
1451                        p,
1452                        "UnsignedOutOfRange: truncated exceeded_limit",
1453                    ));
1454                }
1455                let exceeded_limit = primitives::decode_unsigned(&data[p..end])?;
1456                let _ = (pos, end);
1457                Ok(Self::UnsignedOutOfRange {
1458                    exceeding_value,
1459                    status_flags,
1460                    deadband,
1461                    exceeded_limit,
1462                })
1463            }
1464            // [17] Change of characterstring
1465            17 => {
1466                let mut pos = inner_start;
1467                // [0] changed-value
1468                let (opt_data, new_pos) = tags::decode_optional_context(data, pos, 0)?;
1469                let changed_value = match opt_data {
1470                    Some(content) => primitives::decode_character_string(content)?,
1471                    None => {
1472                        return Err(Error::decoding(
1473                            pos,
1474                            "ChangeOfCharacterstring: missing changed_value",
1475                        ))
1476                    }
1477                };
1478                pos = new_pos;
1479                // [1] status-flags
1480                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1481                let sf_end = sf_pos + sf_tag.length as usize;
1482                if sf_end > data.len() {
1483                    return Err(Error::decoding(
1484                        sf_pos,
1485                        "ChangeOfCharacterstring: truncated flags",
1486                    ));
1487                }
1488                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1489                pos = sf_end;
1490                // [2] alarm-value
1491                let (opt_data, _new_pos) = tags::decode_optional_context(data, pos, 2)?;
1492                let alarm_value = match opt_data {
1493                    Some(content) => primitives::decode_character_string(content)?,
1494                    None => {
1495                        return Err(Error::decoding(
1496                            pos,
1497                            "ChangeOfCharacterstring: missing alarm_value",
1498                        ))
1499                    }
1500                };
1501                Ok(Self::ChangeOfCharacterstring {
1502                    changed_value,
1503                    status_flags,
1504                    alarm_value,
1505                })
1506            }
1507            // [18] Change of status flags
1508            18 => {
1509                let mut pos = inner_start;
1510                // [0] present-value — opening/closing, raw
1511                let (t, p) = tags::decode_tag(data, pos)?;
1512                if !t.is_opening || t.number != 0 {
1513                    return Err(Error::decoding(
1514                        pos,
1515                        "ChangeOfStatusFlags: expected opening [0]",
1516                    ));
1517                }
1518                let (present_value, after) = extract_raw_context(data, p, 0)?;
1519                pos = after;
1520                // [1] referenced-flags
1521                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1522                let sf_end = sf_pos + sf_tag.length as usize;
1523                if sf_end > data.len() {
1524                    return Err(Error::decoding(
1525                        sf_pos,
1526                        "ChangeOfStatusFlags: truncated flags",
1527                    ));
1528                }
1529                let referenced_flags = decode_status_flags(&data[sf_pos..sf_end]);
1530                Ok(Self::ChangeOfStatusFlags {
1531                    present_value,
1532                    referenced_flags,
1533                })
1534            }
1535            // [19] Change of reliability
1536            19 => {
1537                let mut pos = inner_start;
1538                // [0] reliability
1539                let (t, p) = tags::decode_tag(data, pos)?;
1540                let end = p + t.length as usize;
1541                if end > data.len() {
1542                    return Err(Error::decoding(
1543                        p,
1544                        "ChangeOfReliability: truncated reliability",
1545                    ));
1546                }
1547                let reliability = primitives::decode_unsigned(&data[p..end])? as u32;
1548                pos = end;
1549                // [1] status-flags
1550                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1551                let sf_end = sf_pos + sf_tag.length as usize;
1552                if sf_end > data.len() {
1553                    return Err(Error::decoding(
1554                        sf_pos,
1555                        "ChangeOfReliability: truncated flags",
1556                    ));
1557                }
1558                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1559                pos = sf_end;
1560                // [2] property-values — opening/closing, raw
1561                let (t, p) = tags::decode_tag(data, pos)?;
1562                if !t.is_opening || t.number != 2 {
1563                    return Err(Error::decoding(
1564                        pos,
1565                        "ChangeOfReliability: expected opening [2]",
1566                    ));
1567                }
1568                let (property_values, _after) = extract_raw_context(data, p, 2)?;
1569                Ok(Self::ChangeOfReliability {
1570                    reliability,
1571                    status_flags,
1572                    property_values,
1573                })
1574            }
1575            // [20] None
1576            20 => Ok(Self::NoneParams),
1577            // [21] Change of discrete value
1578            21 => {
1579                let mut pos = inner_start;
1580                // [0] new-value — opening/closing, raw
1581                let (t, p) = tags::decode_tag(data, pos)?;
1582                if !t.is_opening || t.number != 0 {
1583                    return Err(Error::decoding(
1584                        pos,
1585                        "ChangeOfDiscreteValue: expected opening [0]",
1586                    ));
1587                }
1588                let (new_value, after) = extract_raw_context(data, p, 0)?;
1589                pos = after;
1590                // [1] status-flags
1591                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1592                let sf_end = sf_pos + sf_tag.length as usize;
1593                if sf_end > data.len() {
1594                    return Err(Error::decoding(
1595                        sf_pos,
1596                        "ChangeOfDiscreteValue: truncated flags",
1597                    ));
1598                }
1599                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1600                Ok(Self::ChangeOfDiscreteValue {
1601                    new_value,
1602                    status_flags,
1603                })
1604            }
1605            // [22] Change of timer
1606            22 => {
1607                let mut pos = inner_start;
1608                // [0] new-state
1609                let (t, p) = tags::decode_tag(data, pos)?;
1610                let end = p + t.length as usize;
1611                if end > data.len() {
1612                    return Err(Error::decoding(p, "ChangeOfTimer: truncated new_state"));
1613                }
1614                let new_state = primitives::decode_unsigned(&data[p..end])? as u32;
1615                pos = end;
1616                // [1] status-flags
1617                let (sf_tag, sf_pos) = tags::decode_tag(data, pos)?;
1618                let sf_end = sf_pos + sf_tag.length as usize;
1619                if sf_end > data.len() {
1620                    return Err(Error::decoding(sf_pos, "ChangeOfTimer: truncated flags"));
1621                }
1622                let status_flags = decode_status_flags(&data[sf_pos..sf_end]);
1623                pos = sf_end;
1624                // [2] update-time: BACnetDateTime — opening/closing [2]
1625                let (t, p) = tags::decode_tag(data, pos)?;
1626                if !t.is_opening || t.number != 2 {
1627                    return Err(Error::decoding(
1628                        pos,
1629                        "ChangeOfTimer: expected opening [2] for update-time",
1630                    ));
1631                }
1632                pos = p;
1633                // Application-tagged Date
1634                let (d_tag, d_pos) = tags::decode_tag(data, pos)?;
1635                let d_end = d_pos + d_tag.length as usize;
1636                if d_end > data.len() {
1637                    return Err(Error::decoding(
1638                        d_pos,
1639                        "ChangeOfTimer: truncated update date",
1640                    ));
1641                }
1642                let update_date = Date::decode(&data[d_pos..d_end])?;
1643                pos = d_end;
1644                // Application-tagged Time
1645                let (t_tag, t_pos) = tags::decode_tag(data, pos)?;
1646                let t_end = t_pos + t_tag.length as usize;
1647                if t_end > data.len() {
1648                    return Err(Error::decoding(
1649                        t_pos,
1650                        "ChangeOfTimer: truncated update time",
1651                    ));
1652                }
1653                let update_time_val = Time::decode(&data[t_pos..t_end])?;
1654                pos = t_end;
1655                // Closing tag [2]
1656                let (ct, cp) = tags::decode_tag(data, pos)?;
1657                if !ct.is_closing || ct.number != 2 {
1658                    return Err(Error::decoding(pos, "ChangeOfTimer: expected closing [2]"));
1659                }
1660                pos = cp;
1661                let update_time = (update_date, update_time_val);
1662                // [3] last-state-change
1663                let (t, p) = tags::decode_tag(data, pos)?;
1664                let end = p + t.length as usize;
1665                if end > data.len() {
1666                    return Err(Error::decoding(
1667                        p,
1668                        "ChangeOfTimer: truncated last_state_change",
1669                    ));
1670                }
1671                let last_state_change = primitives::decode_unsigned(&data[p..end])? as u32;
1672                pos = end;
1673                // [4] initial-timeout
1674                let (t, p) = tags::decode_tag(data, pos)?;
1675                let end = p + t.length as usize;
1676                if end > data.len() {
1677                    return Err(Error::decoding(
1678                        p,
1679                        "ChangeOfTimer: truncated initial_timeout",
1680                    ));
1681                }
1682                let initial_timeout = primitives::decode_unsigned(&data[p..end])? as u32;
1683                pos = end;
1684                // [5] expiration-time: BACnetDateTime — opening/closing [5]
1685                let (t, p) = tags::decode_tag(data, pos)?;
1686                if !t.is_opening || t.number != 5 {
1687                    return Err(Error::decoding(
1688                        pos,
1689                        "ChangeOfTimer: expected opening [5] for expiration-time",
1690                    ));
1691                }
1692                pos = p;
1693                let (d_tag, d_pos) = tags::decode_tag(data, pos)?;
1694                let d_end = d_pos + d_tag.length as usize;
1695                if d_end > data.len() {
1696                    return Err(Error::decoding(
1697                        d_pos,
1698                        "ChangeOfTimer: truncated expiration date",
1699                    ));
1700                }
1701                let exp_date = Date::decode(&data[d_pos..d_end])?;
1702                pos = d_end;
1703                let (t_tag, t_pos) = tags::decode_tag(data, pos)?;
1704                let t_end = t_pos + t_tag.length as usize;
1705                if t_end > data.len() {
1706                    return Err(Error::decoding(
1707                        t_pos,
1708                        "ChangeOfTimer: truncated expiration time",
1709                    ));
1710                }
1711                let exp_time = Time::decode(&data[t_pos..t_end])?;
1712                let _ = (pos, t_end);
1713                let expiration_time = (exp_date, exp_time);
1714                Ok(Self::ChangeOfTimer {
1715                    new_state,
1716                    status_flags,
1717                    update_time,
1718                    last_state_change,
1719                    initial_timeout,
1720                    expiration_time,
1721                })
1722            }
1723            other => Err(Error::decoding(
1724                offset,
1725                format!("NotificationParameters variant [{other}] unknown"),
1726            )),
1727        }
1728    }
1729}
1730
1731/// Encode a BACnetPropertyStates value.
1732fn encode_property_states(buf: &mut BytesMut, state: &BACnetPropertyStates) {
1733    match state {
1734        BACnetPropertyStates::BooleanValue(v) => {
1735            primitives::encode_ctx_boolean(buf, 0, *v);
1736        }
1737        BACnetPropertyStates::BinaryValue(v) => {
1738            primitives::encode_ctx_unsigned(buf, 1, *v as u64);
1739        }
1740        BACnetPropertyStates::EventType(v) => {
1741            primitives::encode_ctx_unsigned(buf, 2, *v as u64);
1742        }
1743        BACnetPropertyStates::Polarity(v) => {
1744            primitives::encode_ctx_unsigned(buf, 3, *v as u64);
1745        }
1746        BACnetPropertyStates::ProgramChange(v) => {
1747            primitives::encode_ctx_unsigned(buf, 4, *v as u64);
1748        }
1749        BACnetPropertyStates::ProgramState(v) => {
1750            primitives::encode_ctx_unsigned(buf, 5, *v as u64);
1751        }
1752        BACnetPropertyStates::ReasonForHalt(v) => {
1753            primitives::encode_ctx_unsigned(buf, 6, *v as u64);
1754        }
1755        BACnetPropertyStates::Reliability(v) => {
1756            primitives::encode_ctx_unsigned(buf, 7, *v as u64);
1757        }
1758        BACnetPropertyStates::State(v) => {
1759            primitives::encode_ctx_unsigned(buf, 8, *v as u64);
1760        }
1761        BACnetPropertyStates::SystemStatus(v) => {
1762            primitives::encode_ctx_unsigned(buf, 9, *v as u64);
1763        }
1764        BACnetPropertyStates::Units(v) => {
1765            primitives::encode_ctx_unsigned(buf, 10, *v as u64);
1766        }
1767        BACnetPropertyStates::LifeSafetyMode(v) => {
1768            primitives::encode_ctx_unsigned(buf, 12, *v as u64);
1769        }
1770        BACnetPropertyStates::LifeSafetyState(v) => {
1771            primitives::encode_ctx_unsigned(buf, 13, *v as u64);
1772        }
1773        BACnetPropertyStates::Other { tag, data } => {
1774            primitives::encode_ctx_octet_string(buf, *tag, data);
1775        }
1776    }
1777}
1778
1779/// Decode BACnetPropertyStates from the current position. Advances `pos`.
1780fn decode_property_states(data: &[u8], pos: &mut usize) -> Result<BACnetPropertyStates, Error> {
1781    let (tag, content_start) = tags::decode_tag(data, *pos)?;
1782    let end = content_start + tag.length as usize;
1783    if end > data.len() {
1784        return Err(Error::decoding(
1785            content_start,
1786            "BACnetPropertyStates: truncated",
1787        ));
1788    }
1789    let content = &data[content_start..end];
1790    *pos = end;
1791    match tag.number {
1792        0 => Ok(BACnetPropertyStates::BooleanValue(
1793            !content.is_empty() && content[0] != 0,
1794        )),
1795        1 => Ok(BACnetPropertyStates::BinaryValue(
1796            primitives::decode_unsigned(content)? as u32,
1797        )),
1798        2 => Ok(BACnetPropertyStates::EventType(
1799            primitives::decode_unsigned(content)? as u32,
1800        )),
1801        3 => Ok(BACnetPropertyStates::Polarity(
1802            primitives::decode_unsigned(content)? as u32,
1803        )),
1804        4 => Ok(BACnetPropertyStates::ProgramChange(
1805            primitives::decode_unsigned(content)? as u32,
1806        )),
1807        5 => Ok(BACnetPropertyStates::ProgramState(
1808            primitives::decode_unsigned(content)? as u32,
1809        )),
1810        6 => Ok(BACnetPropertyStates::ReasonForHalt(
1811            primitives::decode_unsigned(content)? as u32,
1812        )),
1813        7 => Ok(BACnetPropertyStates::Reliability(
1814            primitives::decode_unsigned(content)? as u32,
1815        )),
1816        8 => Ok(BACnetPropertyStates::State(
1817            primitives::decode_unsigned(content)? as u32,
1818        )),
1819        9 => Ok(BACnetPropertyStates::SystemStatus(
1820            primitives::decode_unsigned(content)? as u32,
1821        )),
1822        10 => Ok(BACnetPropertyStates::Units(
1823            primitives::decode_unsigned(content)? as u32,
1824        )),
1825        12 => Ok(BACnetPropertyStates::LifeSafetyMode(
1826            primitives::decode_unsigned(content)? as u32,
1827        )),
1828        13 => Ok(BACnetPropertyStates::LifeSafetyState(
1829            primitives::decode_unsigned(content)? as u32,
1830        )),
1831        n => Ok(BACnetPropertyStates::Other {
1832            tag: n,
1833            data: content.to_vec(),
1834        }),
1835    }
1836}
1837
1838/// Decode BACnetDeviceObjectPropertyReference from context-tagged fields.
1839/// Expects to be positioned at the first inner field. Advances `pos` past the last field.
1840fn decode_device_obj_prop_ref(
1841    data: &[u8],
1842    pos: &mut usize,
1843) -> Result<BACnetDeviceObjectPropertyReference, Error> {
1844    // [0] objectIdentifier
1845    let (t, p) = tags::decode_tag(data, *pos)?;
1846    let end = p + t.length as usize;
1847    if end > data.len() {
1848        return Err(Error::decoding(
1849            p,
1850            "DeviceObjectPropertyRef: truncated objectIdentifier",
1851        ));
1852    }
1853    let object_identifier = ObjectIdentifier::decode(&data[p..end])?;
1854    *pos = end;
1855
1856    // [1] propertyIdentifier
1857    let (t, p) = tags::decode_tag(data, *pos)?;
1858    let end = p + t.length as usize;
1859    if end > data.len() {
1860        return Err(Error::decoding(
1861            p,
1862            "DeviceObjectPropertyRef: truncated propertyIdentifier",
1863        ));
1864    }
1865    let property_identifier = primitives::decode_unsigned(&data[p..end])? as u32;
1866    *pos = end;
1867
1868    // [2] propertyArrayIndex — optional
1869    let mut property_array_index = None;
1870    if *pos < data.len() {
1871        let (peek, peek_pos) = tags::decode_tag(data, *pos)?;
1872        if peek.is_context(2) {
1873            let end = peek_pos + peek.length as usize;
1874            if end > data.len() {
1875                return Err(Error::decoding(
1876                    peek_pos,
1877                    "DeviceObjectPropertyRef: truncated propertyArrayIndex",
1878                ));
1879            }
1880            property_array_index = Some(primitives::decode_unsigned(&data[peek_pos..end])? as u32);
1881            *pos = end;
1882        }
1883    }
1884
1885    // [3] deviceIdentifier — optional
1886    let mut device_identifier = None;
1887    if *pos < data.len() {
1888        let (peek, peek_pos) = tags::decode_tag(data, *pos)?;
1889        if peek.is_context(3) {
1890            let end = peek_pos + peek.length as usize;
1891            if end > data.len() {
1892                return Err(Error::decoding(
1893                    peek_pos,
1894                    "DeviceObjectPropertyRef: truncated deviceIdentifier",
1895                ));
1896            }
1897            device_identifier = Some(ObjectIdentifier::decode(&data[peek_pos..end])?);
1898            *pos = end;
1899        }
1900    }
1901
1902    Ok(BACnetDeviceObjectPropertyReference {
1903        object_identifier,
1904        property_identifier,
1905        property_array_index,
1906        device_identifier,
1907    })
1908}
1909
1910/// Extract raw bytes between an opening and its matching closing context tag.
1911///
1912/// `start` is the position just past the opening tag. The closing tag byte for
1913/// context tags 0–14 is `(tag_number << 4) | 0x0F`. This scans byte-by-byte to
1914/// find the matching close without parsing inner content as BACnet tags.
1915fn extract_raw_context(
1916    data: &[u8],
1917    start: usize,
1918    tag_number: u8,
1919) -> Result<(Vec<u8>, usize), Error> {
1920    // For context tags < 15 the opening/closing bytes are single-byte:
1921    //   opening = (tag << 4) | 0x0E, closing = (tag << 4) | 0x0F
1922    let open_byte = (tag_number << 4) | 0x0E;
1923    let close_byte = (tag_number << 4) | 0x0F;
1924    let mut depth: usize = 1;
1925    let mut pos = start;
1926    while pos < data.len() {
1927        let b = data[pos];
1928        if b == open_byte {
1929            depth += 1;
1930        } else if b == close_byte {
1931            depth -= 1;
1932            if depth == 0 {
1933                let raw = data[start..pos].to_vec();
1934                return Ok((raw, pos + 1)); // past closing tag byte
1935            }
1936        }
1937        pos += 1;
1938    }
1939    Err(Error::decoding(
1940        start,
1941        format!("extract_raw_context: missing closing tag [{tag_number}]"),
1942    ))
1943}
1944
1945/// Decode status flags from a bit-string content slice.
1946/// Returns the 4-bit status flags value.
1947fn decode_status_flags(data: &[u8]) -> u8 {
1948    // Bit string format: first byte = unused bits count, rest = data
1949    if data.len() >= 2 {
1950        let unused = data[0];
1951        data[1] >> (unused.min(7))
1952    } else {
1953        0
1954    }
1955}
1956
1957// ---------------------------------------------------------------------------
1958// GetEventInformation (Clause 13.9)
1959// ---------------------------------------------------------------------------
1960
1961/// GetEventInformation-Request — optional last_received_object_identifier.
1962#[derive(Debug, Clone, PartialEq, Eq)]
1963pub struct GetEventInformationRequest {
1964    pub last_received_object_identifier: Option<ObjectIdentifier>,
1965}
1966
1967impl GetEventInformationRequest {
1968    pub fn encode(&self, buf: &mut BytesMut) {
1969        if let Some(ref oid) = self.last_received_object_identifier {
1970            primitives::encode_ctx_object_id(buf, 0, oid);
1971        }
1972    }
1973
1974    pub fn decode(data: &[u8]) -> Result<Self, Error> {
1975        if data.is_empty() {
1976            return Ok(Self {
1977                last_received_object_identifier: None,
1978            });
1979        }
1980        let (opt_data, _) = tags::decode_optional_context(data, 0, 0)?;
1981        let last_received_object_identifier = if let Some(content) = opt_data {
1982            Some(ObjectIdentifier::decode(content)?)
1983        } else {
1984            None
1985        };
1986        Ok(Self {
1987            last_received_object_identifier,
1988        })
1989    }
1990}
1991
1992/// GetEventInformation-ACK service parameters (simplified).
1993#[derive(Debug, Clone)]
1994pub struct GetEventInformationAck {
1995    pub list_of_event_summaries: Vec<EventSummary>,
1996    pub more_events: bool,
1997}
1998
1999/// Event summary for GetEventInformation-ACK per Clause 13.9.1.2.
2000#[derive(Debug, Clone)]
2001pub struct EventSummary {
2002    pub object_identifier: ObjectIdentifier,
2003    pub event_state: u32,
2004    /// 3-bit bitstring: TO_OFFNORMAL, TO_FAULT, TO_NORMAL
2005    pub acknowledged_transitions: u8,
2006    /// Timestamps for TO_OFFNORMAL, TO_FAULT, TO_NORMAL
2007    pub event_timestamps: [BACnetTimeStamp; 3],
2008    /// Notify type: ALARM(0), EVENT(1), ACK_NOTIFICATION(2)
2009    pub notify_type: u32,
2010    /// 3-bit bitstring: TO_OFFNORMAL, TO_FAULT, TO_NORMAL
2011    pub event_enable: u8,
2012    /// Priorities for TO_OFFNORMAL, TO_FAULT, TO_NORMAL
2013    pub event_priorities: [u32; 3],
2014    pub notification_class: u32,
2015}
2016
2017impl GetEventInformationAck {
2018    /// Decode a GetEventInformationAck from wire bytes.
2019    pub fn decode(data: &[u8]) -> Result<Self, Error> {
2020        let mut offset = 0;
2021
2022        // [0] listOfEventSummaries — opening tag
2023        let (tag, pos) = tags::decode_tag(data, offset)?;
2024        if !tag.is_opening_tag(0) {
2025            return Err(Error::decoding(offset, "expected opening tag [0]"));
2026        }
2027        offset = pos;
2028
2029        let mut list_of_event_summaries = Vec::new();
2030
2031        // Parse event summaries until closing tag [0]
2032        loop {
2033            let (tag, _) = tags::decode_tag(data, offset)?;
2034            if tag.is_closing_tag(0) {
2035                // advance past the closing tag byte(s)
2036                let (_, close_pos) = tags::decode_tag(data, offset)?;
2037                offset = close_pos;
2038                break;
2039            }
2040
2041            // [0] objectIdentifier
2042            let (tag, pos) = tags::decode_tag(data, offset)?;
2043            let end = pos + tag.length as usize;
2044            if end > data.len() {
2045                return Err(Error::decoding(pos, "GetEventInfoAck truncated at oid"));
2046            }
2047            let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
2048            offset = end;
2049
2050            // [1] eventState
2051            let (tag, pos) = tags::decode_tag(data, offset)?;
2052            let end = pos + tag.length as usize;
2053            if end > data.len() {
2054                return Err(Error::decoding(
2055                    pos,
2056                    "GetEventInfoAck truncated at eventState",
2057                ));
2058            }
2059            let event_state = primitives::decode_unsigned(&data[pos..end])? as u32;
2060            offset = end;
2061
2062            // [2] acknowledgedTransitions (3-bit bitstring)
2063            let (tag, pos) = tags::decode_tag(data, offset)?;
2064            let end = pos + tag.length as usize;
2065            if end > data.len() {
2066                return Err(Error::decoding(pos, "truncated at ackedTransitions"));
2067            }
2068            // Content: [unused_bits_count, bit_data...]
2069            let acknowledged_transitions = if end > pos + 1 { data[pos + 1] >> 5 } else { 0 };
2070            offset = end;
2071
2072            // [3] eventTimeStamps — opening tag
2073            let (tag, pos) = tags::decode_tag(data, offset)?;
2074            if !tag.is_opening_tag(3) {
2075                return Err(Error::decoding(offset, "expected opening tag [3]"));
2076            }
2077            offset = pos;
2078            let mut event_timestamps = [
2079                BACnetTimeStamp::SequenceNumber(0),
2080                BACnetTimeStamp::SequenceNumber(0),
2081                BACnetTimeStamp::SequenceNumber(0),
2082            ];
2083            for ts in &mut event_timestamps {
2084                let (inner_tag, inner_pos) = tags::decode_tag(data, offset)?;
2085                if inner_tag.is_opening_tag(0) {
2086                    // Time choice [0] { application Time }
2087                    offset = inner_pos;
2088                    let (app_tag, app_pos) = tags::decode_tag(data, offset)?;
2089                    let end = app_pos + app_tag.length as usize;
2090                    if end > data.len() {
2091                        return Err(Error::decoding(app_pos, "truncated timestamp time"));
2092                    }
2093                    *ts = BACnetTimeStamp::Time(Time::decode(&data[app_pos..end])?);
2094                    offset = end;
2095                    // closing tag [0]
2096                    let (_, close_pos) = tags::decode_tag(data, offset)?;
2097                    offset = close_pos;
2098                } else if inner_tag.is_context(1) {
2099                    // SequenceNumber choice [1]
2100                    let end = inner_pos + inner_tag.length as usize;
2101                    if end > data.len() {
2102                        return Err(Error::decoding(inner_pos, "truncated timestamp seqnum"));
2103                    }
2104                    *ts = BACnetTimeStamp::SequenceNumber(primitives::decode_unsigned(
2105                        &data[inner_pos..end],
2106                    )?);
2107                    offset = end;
2108                } else if inner_tag.is_opening_tag(2) {
2109                    // DateTime choice [2] { Date, Time }
2110                    offset = inner_pos;
2111                    let (d_tag, d_pos) = tags::decode_tag(data, offset)?;
2112                    let d_end = d_pos + d_tag.length as usize;
2113                    if d_end > data.len() {
2114                        return Err(Error::decoding(d_pos, "truncated datetime date"));
2115                    }
2116                    let date = Date::decode(&data[d_pos..d_end])?;
2117                    offset = d_end;
2118                    let (t_tag, t_pos) = tags::decode_tag(data, offset)?;
2119                    let t_end = t_pos + t_tag.length as usize;
2120                    if t_end > data.len() {
2121                        return Err(Error::decoding(t_pos, "truncated datetime time"));
2122                    }
2123                    let time = Time::decode(&data[t_pos..t_end])?;
2124                    offset = t_end;
2125                    *ts = BACnetTimeStamp::DateTime { date, time };
2126                    // closing tag [2]
2127                    let (_, close_pos) = tags::decode_tag(data, offset)?;
2128                    offset = close_pos;
2129                } else {
2130                    return Err(Error::decoding(offset, "unexpected timestamp choice"));
2131                }
2132            }
2133            // closing tag [3]
2134            let (tag, _) = tags::decode_tag(data, offset)?;
2135            if !tag.is_closing_tag(3) {
2136                return Err(Error::decoding(offset, "expected closing tag [3]"));
2137            }
2138            let (_, close_pos) = tags::decode_tag(data, offset)?;
2139            offset = close_pos;
2140
2141            // [4] notifyType
2142            let (tag, pos) = tags::decode_tag(data, offset)?;
2143            let end = pos + tag.length as usize;
2144            if end > data.len() {
2145                return Err(Error::decoding(pos, "truncated at notifyType"));
2146            }
2147            let notify_type = primitives::decode_unsigned(&data[pos..end])? as u32;
2148            offset = end;
2149
2150            // [5] eventEnable (3-bit bitstring)
2151            let (tag, pos) = tags::decode_tag(data, offset)?;
2152            let end = pos + tag.length as usize;
2153            if end > data.len() {
2154                return Err(Error::decoding(pos, "truncated at eventEnable"));
2155            }
2156            let event_enable = if end > pos + 1 { data[pos + 1] >> 5 } else { 0 };
2157            offset = end;
2158
2159            // [6] eventPriorities — opening tag
2160            let (tag, pos) = tags::decode_tag(data, offset)?;
2161            if !tag.is_opening_tag(6) {
2162                return Err(Error::decoding(offset, "expected opening tag [6]"));
2163            }
2164            offset = pos;
2165            let mut event_priorities = [0u32; 3];
2166            for pri in &mut event_priorities {
2167                let (tag, pos) = tags::decode_tag(data, offset)?;
2168                let end = pos + tag.length as usize;
2169                if end > data.len() {
2170                    return Err(Error::decoding(pos, "truncated priority"));
2171                }
2172                *pri = primitives::decode_unsigned(&data[pos..end])? as u32;
2173                offset = end;
2174            }
2175            // closing tag [6]
2176            let (tag, _) = tags::decode_tag(data, offset)?;
2177            if !tag.is_closing_tag(6) {
2178                return Err(Error::decoding(offset, "expected closing tag [6]"));
2179            }
2180            let (_, close_pos) = tags::decode_tag(data, offset)?;
2181            offset = close_pos;
2182
2183            list_of_event_summaries.push(EventSummary {
2184                object_identifier,
2185                event_state,
2186                acknowledged_transitions,
2187                event_timestamps,
2188                notify_type,
2189                event_enable,
2190                event_priorities,
2191                notification_class: 0, // not in wire format per Clause 13.9.1.2
2192            });
2193        }
2194
2195        // [1] moreEvents
2196        let (tag, pos) = tags::decode_tag(data, offset)?;
2197        let end = pos + tag.length as usize;
2198        if end > data.len() {
2199            return Err(Error::decoding(pos, "truncated at moreEvents"));
2200        }
2201        let more_events = data[pos] != 0;
2202
2203        Ok(Self {
2204            list_of_event_summaries,
2205            more_events,
2206        })
2207    }
2208
2209    pub fn encode(&self, buf: &mut BytesMut) {
2210        // [0] listOfEventSummaries
2211        tags::encode_opening_tag(buf, 0);
2212        for summary in &self.list_of_event_summaries {
2213            // [0] objectIdentifier
2214            primitives::encode_ctx_object_id(buf, 0, &summary.object_identifier);
2215            // [1] eventState
2216            primitives::encode_ctx_enumerated(buf, 1, summary.event_state);
2217            // [2] acknowledgedTransitions (3-bit bitstring)
2218            primitives::encode_ctx_bit_string(buf, 2, 5, &[summary.acknowledged_transitions << 5]);
2219            // [3] eventTimeStamps (SEQUENCE OF 3 BACnetTimeStamp)
2220            tags::encode_opening_tag(buf, 3);
2221            for ts in &summary.event_timestamps {
2222                // Each timestamp is encoded as a bare CHOICE (no extra wrapping)
2223                // within the SEQUENCE OF
2224                match ts {
2225                    BACnetTimeStamp::Time(t) => {
2226                        tags::encode_opening_tag(buf, 0);
2227                        primitives::encode_app_time(buf, t);
2228                        tags::encode_closing_tag(buf, 0);
2229                    }
2230                    BACnetTimeStamp::SequenceNumber(n) => {
2231                        primitives::encode_ctx_unsigned(buf, 1, *n);
2232                    }
2233                    BACnetTimeStamp::DateTime { date, time } => {
2234                        tags::encode_opening_tag(buf, 2);
2235                        primitives::encode_app_date(buf, date);
2236                        primitives::encode_app_time(buf, time);
2237                        tags::encode_closing_tag(buf, 2);
2238                    }
2239                }
2240            }
2241            tags::encode_closing_tag(buf, 3);
2242            // [4] notifyType
2243            primitives::encode_ctx_enumerated(buf, 4, summary.notify_type);
2244            // [5] eventEnable (3-bit bitstring)
2245            primitives::encode_ctx_bit_string(buf, 5, 5, &[summary.event_enable << 5]);
2246            // [6] eventPriorities (SEQUENCE OF 3 Unsigned)
2247            tags::encode_opening_tag(buf, 6);
2248            for &p in &summary.event_priorities {
2249                primitives::encode_app_unsigned(buf, p as u64);
2250            }
2251            tags::encode_closing_tag(buf, 6);
2252        }
2253        tags::encode_closing_tag(buf, 0);
2254        // [1] moreEvents
2255        primitives::encode_ctx_boolean(buf, 1, self.more_events);
2256    }
2257}
2258
2259#[cfg(test)]
2260mod tests {
2261    use super::*;
2262    use bacnet_types::enums::ObjectType;
2263
2264    #[test]
2265    fn acknowledge_alarm_round_trip() {
2266        let req = AcknowledgeAlarmRequest {
2267            acknowledging_process_identifier: 1,
2268            event_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
2269            event_state_acknowledged: 3, // high-limit
2270            timestamp: BACnetTimeStamp::SequenceNumber(42),
2271            acknowledgment_source: "operator".into(),
2272        };
2273        let mut buf = BytesMut::new();
2274        req.encode(&mut buf).unwrap();
2275        let decoded = AcknowledgeAlarmRequest::decode(&buf).unwrap();
2276        assert_eq!(decoded.acknowledging_process_identifier, 1);
2277        assert_eq!(decoded.event_object_identifier, req.event_object_identifier);
2278        assert_eq!(decoded.event_state_acknowledged, 3);
2279        assert_eq!(decoded.timestamp, BACnetTimeStamp::SequenceNumber(42));
2280        assert_eq!(decoded.acknowledgment_source, "operator");
2281    }
2282
2283    #[test]
2284    fn get_event_info_empty_request() {
2285        let req = GetEventInformationRequest {
2286            last_received_object_identifier: None,
2287        };
2288        let mut buf = BytesMut::new();
2289        req.encode(&mut buf);
2290        let decoded = GetEventInformationRequest::decode(&buf).unwrap();
2291        assert!(decoded.last_received_object_identifier.is_none());
2292    }
2293
2294    #[test]
2295    fn get_event_info_with_last_received() {
2296        let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 5).unwrap();
2297        let req = GetEventInformationRequest {
2298            last_received_object_identifier: Some(oid),
2299        };
2300        let mut buf = BytesMut::new();
2301        req.encode(&mut buf);
2302        let decoded = GetEventInformationRequest::decode(&buf).unwrap();
2303        assert_eq!(decoded.last_received_object_identifier, Some(oid));
2304    }
2305
2306    #[test]
2307    fn event_notification_round_trip() {
2308        let device_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1).unwrap();
2309        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 3).unwrap();
2310
2311        let req = EventNotificationRequest {
2312            process_identifier: 1,
2313            initiating_device_identifier: device_oid,
2314            event_object_identifier: ai_oid,
2315            timestamp: BACnetTimeStamp::SequenceNumber(7),
2316            notification_class: 5,
2317            priority: 100,
2318            event_type: 5,  // OUT_OF_RANGE
2319            notify_type: 0, // ALARM
2320            ack_required: true,
2321            from_state: 0, // NORMAL
2322            to_state: 3,   // HIGH_LIMIT
2323            event_values: None,
2324        };
2325        let mut buf = BytesMut::new();
2326        req.encode(&mut buf).unwrap();
2327
2328        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2329        assert_eq!(decoded.process_identifier, 1);
2330        assert_eq!(decoded.initiating_device_identifier, device_oid);
2331        assert_eq!(decoded.event_object_identifier, ai_oid);
2332        assert_eq!(decoded.timestamp, BACnetTimeStamp::SequenceNumber(7));
2333        assert_eq!(decoded.notification_class, 5);
2334        assert_eq!(decoded.priority, 100);
2335        assert_eq!(decoded.event_type, 5);
2336        assert_eq!(decoded.notify_type, 0);
2337        assert!(decoded.ack_required);
2338        assert_eq!(decoded.from_state, 0);
2339        assert_eq!(decoded.to_state, 3);
2340        assert!(decoded.event_values.is_none());
2341    }
2342
2343    #[test]
2344    fn event_notification_datetime_timestamp_round_trip() {
2345        use bacnet_types::primitives::{Date, Time};
2346
2347        let device_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1).unwrap();
2348        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 3).unwrap();
2349
2350        let ts = BACnetTimeStamp::DateTime {
2351            date: Date {
2352                year: 126,
2353                month: 2,
2354                day: 28,
2355                day_of_week: 6,
2356            },
2357            time: Time {
2358                hour: 14,
2359                minute: 30,
2360                second: 0,
2361                hundredths: 0,
2362            },
2363        };
2364
2365        let req = EventNotificationRequest {
2366            process_identifier: 1,
2367            initiating_device_identifier: device_oid,
2368            event_object_identifier: ai_oid,
2369            timestamp: ts.clone(),
2370            notification_class: 5,
2371            priority: 100,
2372            event_type: 5,
2373            notify_type: 0,
2374            ack_required: true,
2375            from_state: 0,
2376            to_state: 3,
2377            event_values: None,
2378        };
2379        let mut buf = BytesMut::new();
2380        req.encode(&mut buf).unwrap();
2381
2382        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2383        assert_eq!(decoded.timestamp, ts);
2384    }
2385
2386    #[test]
2387    fn event_notification_time_timestamp_round_trip() {
2388        use bacnet_types::primitives::Time;
2389
2390        let device_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1).unwrap();
2391        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 3).unwrap();
2392
2393        let ts = BACnetTimeStamp::Time(Time {
2394            hour: 10,
2395            minute: 15,
2396            second: 30,
2397            hundredths: 50,
2398        });
2399
2400        let req = EventNotificationRequest {
2401            process_identifier: 1,
2402            initiating_device_identifier: device_oid,
2403            event_object_identifier: ai_oid,
2404            timestamp: ts.clone(),
2405            notification_class: 5,
2406            priority: 100,
2407            event_type: 5,
2408            notify_type: 0,
2409            ack_required: true,
2410            from_state: 0,
2411            to_state: 3,
2412            event_values: None,
2413        };
2414        let mut buf = BytesMut::new();
2415        req.encode(&mut buf).unwrap();
2416
2417        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2418        assert_eq!(decoded.timestamp, ts);
2419    }
2420
2421    // -----------------------------------------------------------------------
2422    // Malformed-input decode error tests
2423    // -----------------------------------------------------------------------
2424
2425    #[test]
2426    fn test_decode_acknowledge_alarm_empty_input() {
2427        assert!(AcknowledgeAlarmRequest::decode(&[]).is_err());
2428    }
2429
2430    #[test]
2431    fn test_decode_acknowledge_alarm_truncated_1_byte() {
2432        let req = AcknowledgeAlarmRequest {
2433            acknowledging_process_identifier: 1,
2434            event_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
2435            event_state_acknowledged: 3,
2436            timestamp: BACnetTimeStamp::SequenceNumber(42),
2437            acknowledgment_source: "operator".into(),
2438        };
2439        let mut buf = BytesMut::new();
2440        req.encode(&mut buf).unwrap();
2441        assert!(AcknowledgeAlarmRequest::decode(&buf[..1]).is_err());
2442    }
2443
2444    #[test]
2445    fn test_decode_acknowledge_alarm_truncated_3_bytes() {
2446        let req = AcknowledgeAlarmRequest {
2447            acknowledging_process_identifier: 1,
2448            event_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
2449            event_state_acknowledged: 3,
2450            timestamp: BACnetTimeStamp::SequenceNumber(42),
2451            acknowledgment_source: "operator".into(),
2452        };
2453        let mut buf = BytesMut::new();
2454        req.encode(&mut buf).unwrap();
2455        assert!(AcknowledgeAlarmRequest::decode(&buf[..3]).is_err());
2456    }
2457
2458    #[test]
2459    fn test_decode_acknowledge_alarm_truncated_half() {
2460        let req = AcknowledgeAlarmRequest {
2461            acknowledging_process_identifier: 1,
2462            event_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
2463            event_state_acknowledged: 3,
2464            timestamp: BACnetTimeStamp::SequenceNumber(42),
2465            acknowledgment_source: "operator".into(),
2466        };
2467        let mut buf = BytesMut::new();
2468        req.encode(&mut buf).unwrap();
2469        let half = buf.len() / 2;
2470        assert!(AcknowledgeAlarmRequest::decode(&buf[..half]).is_err());
2471    }
2472
2473    #[test]
2474    fn test_decode_acknowledge_alarm_invalid_tag() {
2475        assert!(AcknowledgeAlarmRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
2476    }
2477
2478    #[test]
2479    fn test_decode_event_notification_empty_input() {
2480        assert!(EventNotificationRequest::decode(&[]).is_err());
2481    }
2482
2483    #[test]
2484    fn test_decode_event_notification_truncated_1_byte() {
2485        let device_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1).unwrap();
2486        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 3).unwrap();
2487        let req = EventNotificationRequest {
2488            process_identifier: 1,
2489            initiating_device_identifier: device_oid,
2490            event_object_identifier: ai_oid,
2491            timestamp: BACnetTimeStamp::SequenceNumber(7),
2492            notification_class: 5,
2493            priority: 100,
2494            event_type: 5,
2495            notify_type: 0,
2496            ack_required: true,
2497            from_state: 0,
2498            to_state: 3,
2499            event_values: None,
2500        };
2501        let mut buf = BytesMut::new();
2502        req.encode(&mut buf).unwrap();
2503        assert!(EventNotificationRequest::decode(&buf[..1]).is_err());
2504    }
2505
2506    #[test]
2507    fn test_decode_event_notification_truncated_3_bytes() {
2508        let device_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1).unwrap();
2509        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 3).unwrap();
2510        let req = EventNotificationRequest {
2511            process_identifier: 1,
2512            initiating_device_identifier: device_oid,
2513            event_object_identifier: ai_oid,
2514            timestamp: BACnetTimeStamp::SequenceNumber(7),
2515            notification_class: 5,
2516            priority: 100,
2517            event_type: 5,
2518            notify_type: 0,
2519            ack_required: true,
2520            from_state: 0,
2521            to_state: 3,
2522            event_values: None,
2523        };
2524        let mut buf = BytesMut::new();
2525        req.encode(&mut buf).unwrap();
2526        assert!(EventNotificationRequest::decode(&buf[..3]).is_err());
2527    }
2528
2529    #[test]
2530    fn test_decode_event_notification_truncated_half() {
2531        let device_oid = ObjectIdentifier::new(ObjectType::DEVICE, 1).unwrap();
2532        let ai_oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 3).unwrap();
2533        let req = EventNotificationRequest {
2534            process_identifier: 1,
2535            initiating_device_identifier: device_oid,
2536            event_object_identifier: ai_oid,
2537            timestamp: BACnetTimeStamp::SequenceNumber(7),
2538            notification_class: 5,
2539            priority: 100,
2540            event_type: 5,
2541            notify_type: 0,
2542            ack_required: true,
2543            from_state: 0,
2544            to_state: 3,
2545            event_values: None,
2546        };
2547        let mut buf = BytesMut::new();
2548        req.encode(&mut buf).unwrap();
2549        let half = buf.len() / 2;
2550        assert!(EventNotificationRequest::decode(&buf[..half]).is_err());
2551    }
2552
2553    #[test]
2554    fn test_decode_event_notification_invalid_tag() {
2555        assert!(EventNotificationRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
2556    }
2557
2558    #[test]
2559    fn test_decode_get_event_info_invalid_tag() {
2560        // Non-matching context tag — decoder treats as no last_received (lenient)
2561        let result = GetEventInformationRequest::decode(&[0xFF, 0xFF]).unwrap();
2562        assert!(result.last_received_object_identifier.is_none());
2563    }
2564
2565    #[test]
2566    fn test_decode_get_event_info_truncated() {
2567        let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 5).unwrap();
2568        let req = GetEventInformationRequest {
2569            last_received_object_identifier: Some(oid),
2570        };
2571        let mut buf = BytesMut::new();
2572        req.encode(&mut buf);
2573        assert!(GetEventInformationRequest::decode(&buf[..1]).is_err());
2574    }
2575
2576    // -----------------------------------------------------------------------
2577    // NotificationParameters round-trip tests
2578    // -----------------------------------------------------------------------
2579
2580    /// Helper: build an EventNotificationRequest with given event_values.
2581    fn make_event_req(event_values: Option<NotificationParameters>) -> EventNotificationRequest {
2582        EventNotificationRequest {
2583            process_identifier: 1,
2584            initiating_device_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 1).unwrap(),
2585            event_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 3).unwrap(),
2586            timestamp: BACnetTimeStamp::SequenceNumber(7),
2587            notification_class: 5,
2588            priority: 100,
2589            event_type: 5,
2590            notify_type: 0,
2591            ack_required: true,
2592            from_state: 0,
2593            to_state: 3,
2594            event_values,
2595        }
2596    }
2597
2598    #[test]
2599    fn notification_params_out_of_range_round_trip() {
2600        let params = NotificationParameters::OutOfRange {
2601            exceeding_value: 85.5,
2602            status_flags: 0b1000, // IN_ALARM
2603            deadband: 1.0,
2604            exceeded_limit: 80.0,
2605        };
2606        let req = make_event_req(Some(params));
2607        let mut buf = BytesMut::new();
2608        req.encode(&mut buf).unwrap();
2609        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2610        let ev = decoded.event_values.unwrap();
2611        match ev {
2612            NotificationParameters::OutOfRange {
2613                exceeding_value,
2614                status_flags,
2615                deadband,
2616                exceeded_limit,
2617            } => {
2618                assert_eq!(exceeding_value, 85.5);
2619                assert_eq!(status_flags, 0b1000);
2620                assert_eq!(deadband, 1.0);
2621                assert_eq!(exceeded_limit, 80.0);
2622            }
2623            other => panic!("expected OutOfRange, got {:?}", other),
2624        }
2625    }
2626
2627    #[test]
2628    fn notification_params_change_of_state_boolean_round_trip() {
2629        let params = NotificationParameters::ChangeOfState {
2630            new_state: BACnetPropertyStates::BooleanValue(true),
2631            status_flags: 0b1100, // IN_ALARM + FAULT
2632        };
2633        let req = make_event_req(Some(params));
2634        let mut buf = BytesMut::new();
2635        req.encode(&mut buf).unwrap();
2636        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2637        let ev = decoded.event_values.unwrap();
2638        match ev {
2639            NotificationParameters::ChangeOfState {
2640                new_state,
2641                status_flags,
2642            } => {
2643                assert_eq!(new_state, BACnetPropertyStates::BooleanValue(true));
2644                assert_eq!(status_flags, 0b1100);
2645            }
2646            other => panic!("expected ChangeOfState, got {:?}", other),
2647        }
2648    }
2649
2650    #[test]
2651    fn notification_params_change_of_state_enumerated_round_trip() {
2652        let params = NotificationParameters::ChangeOfState {
2653            new_state: BACnetPropertyStates::State(3), // HIGH_LIMIT
2654            status_flags: 0b1000,
2655        };
2656        let req = make_event_req(Some(params));
2657        let mut buf = BytesMut::new();
2658        req.encode(&mut buf).unwrap();
2659        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2660        let ev = decoded.event_values.unwrap();
2661        match ev {
2662            NotificationParameters::ChangeOfState {
2663                new_state,
2664                status_flags,
2665            } => {
2666                assert_eq!(new_state, BACnetPropertyStates::State(3));
2667                assert_eq!(status_flags, 0b1000);
2668            }
2669            other => panic!("expected ChangeOfState, got {:?}", other),
2670        }
2671    }
2672
2673    #[test]
2674    fn notification_params_change_of_value_real_round_trip() {
2675        let params = NotificationParameters::ChangeOfValue {
2676            new_value: ChangeOfValueChoice::ChangedValue(72.5),
2677            status_flags: 0b0100,
2678        };
2679        let req = make_event_req(Some(params));
2680        let mut buf = BytesMut::new();
2681        req.encode(&mut buf).unwrap();
2682        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2683        let ev = decoded.event_values.unwrap();
2684        match ev {
2685            NotificationParameters::ChangeOfValue {
2686                new_value,
2687                status_flags,
2688            } => {
2689                assert_eq!(new_value, ChangeOfValueChoice::ChangedValue(72.5));
2690                assert_eq!(status_flags, 0b0100);
2691            }
2692            other => panic!("expected ChangeOfValue, got {:?}", other),
2693        }
2694    }
2695
2696    #[test]
2697    fn notification_params_buffer_ready_round_trip() {
2698        let buffer_prop = BACnetDeviceObjectPropertyReference::new_local(
2699            ObjectIdentifier::new(ObjectType::TREND_LOG, 1).unwrap(),
2700            131, // LOG_BUFFER
2701        );
2702        let params = NotificationParameters::BufferReady {
2703            buffer_property: buffer_prop.clone(),
2704            previous_notification: 10,
2705            current_notification: 20,
2706        };
2707        let req = make_event_req(Some(params));
2708        let mut buf = BytesMut::new();
2709        req.encode(&mut buf).unwrap();
2710        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2711        let ev = decoded.event_values.unwrap();
2712        match ev {
2713            NotificationParameters::BufferReady {
2714                buffer_property,
2715                previous_notification,
2716                current_notification,
2717            } => {
2718                assert_eq!(buffer_property, buffer_prop);
2719                assert_eq!(previous_notification, 10);
2720                assert_eq!(current_notification, 20);
2721            }
2722            other => panic!("expected BufferReady, got {:?}", other),
2723        }
2724    }
2725
2726    #[test]
2727    fn notification_params_none_round_trip() {
2728        let params = NotificationParameters::NoneParams;
2729        let req = make_event_req(Some(params));
2730        let mut buf = BytesMut::new();
2731        req.encode(&mut buf).unwrap();
2732        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2733        assert_eq!(
2734            decoded.event_values,
2735            Some(NotificationParameters::NoneParams)
2736        );
2737    }
2738
2739    #[test]
2740    fn notification_params_unsigned_range_round_trip() {
2741        let params = NotificationParameters::UnsignedRange {
2742            exceeding_value: 500,
2743            status_flags: 0b1000,
2744            exceeded_limit: 400,
2745        };
2746        let req = make_event_req(Some(params));
2747        let mut buf = BytesMut::new();
2748        req.encode(&mut buf).unwrap();
2749        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2750        let ev = decoded.event_values.unwrap();
2751        match ev {
2752            NotificationParameters::UnsignedRange {
2753                exceeding_value,
2754                status_flags,
2755                exceeded_limit,
2756            } => {
2757                assert_eq!(exceeding_value, 500);
2758                assert_eq!(status_flags, 0b1000);
2759                assert_eq!(exceeded_limit, 400);
2760            }
2761            other => panic!("expected UnsignedRange, got {:?}", other),
2762        }
2763    }
2764
2765    #[test]
2766    fn event_notification_no_event_values_backward_compatible() {
2767        // Verify that event_values=None still round-trips correctly
2768        let req = make_event_req(None);
2769        let mut buf = BytesMut::new();
2770        req.encode(&mut buf).unwrap();
2771        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2772        assert!(decoded.event_values.is_none());
2773        assert_eq!(decoded.process_identifier, 1);
2774        assert_eq!(decoded.to_state, 3);
2775    }
2776
2777    #[test]
2778    fn get_event_information_ack_round_trip() {
2779        let ack = GetEventInformationAck {
2780            list_of_event_summaries: vec![EventSummary {
2781                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
2782                event_state: 3,
2783                acknowledged_transitions: 0b101,
2784                event_timestamps: [
2785                    BACnetTimeStamp::SequenceNumber(42),
2786                    BACnetTimeStamp::SequenceNumber(0),
2787                    BACnetTimeStamp::SequenceNumber(100),
2788                ],
2789                notify_type: 0,
2790                event_enable: 0b111,
2791                event_priorities: [3, 3, 3],
2792                notification_class: 0,
2793            }],
2794            more_events: true,
2795        };
2796        let mut buf = BytesMut::new();
2797        ack.encode(&mut buf);
2798        let decoded = GetEventInformationAck::decode(&buf).unwrap();
2799        assert_eq!(decoded.list_of_event_summaries.len(), 1);
2800        assert!(decoded.more_events);
2801        let s = &decoded.list_of_event_summaries[0];
2802        assert_eq!(
2803            s.object_identifier,
2804            ack.list_of_event_summaries[0].object_identifier
2805        );
2806        assert_eq!(s.event_state, 3);
2807        assert_eq!(s.acknowledged_transitions, 0b101);
2808        assert_eq!(s.event_timestamps[0], BACnetTimeStamp::SequenceNumber(42));
2809        assert_eq!(s.notify_type, 0);
2810        assert_eq!(s.event_enable, 0b111);
2811        assert_eq!(s.event_priorities, [3, 3, 3]);
2812    }
2813
2814    #[test]
2815    fn notification_params_change_of_bitstring_round_trip() {
2816        let params = NotificationParameters::ChangeOfBitstring {
2817            referenced_bitstring: (2, vec![0xA0]),
2818            status_flags: 0b1000,
2819        };
2820        let req = make_event_req(Some(params));
2821        let mut buf = BytesMut::new();
2822        req.encode(&mut buf).unwrap();
2823        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2824        let ev = decoded.event_values.unwrap();
2825        match ev {
2826            NotificationParameters::ChangeOfBitstring {
2827                referenced_bitstring,
2828                status_flags,
2829            } => {
2830                assert_eq!(referenced_bitstring, (2, vec![0xA0]));
2831                assert_eq!(status_flags, 0b1000);
2832            }
2833            other => panic!("expected ChangeOfBitstring, got {:?}", other),
2834        }
2835    }
2836
2837    #[test]
2838    fn notification_params_command_failure_round_trip() {
2839        let params = NotificationParameters::CommandFailure {
2840            command_value: vec![0x91, 0x01],
2841            status_flags: 0b1100,
2842            feedback_value: vec![0x91, 0x02],
2843        };
2844        let req = make_event_req(Some(params));
2845        let mut buf = BytesMut::new();
2846        req.encode(&mut buf).unwrap();
2847        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2848        let ev = decoded.event_values.unwrap();
2849        match ev {
2850            NotificationParameters::CommandFailure {
2851                command_value,
2852                status_flags,
2853                feedback_value,
2854            } => {
2855                assert_eq!(command_value, vec![0x91, 0x01]);
2856                assert_eq!(status_flags, 0b1100);
2857                assert_eq!(feedback_value, vec![0x91, 0x02]);
2858            }
2859            other => panic!("expected CommandFailure, got {:?}", other),
2860        }
2861    }
2862
2863    #[test]
2864    fn notification_params_floating_limit_round_trip() {
2865        let params = NotificationParameters::FloatingLimit {
2866            reference_value: 50.0,
2867            status_flags: 0b1000,
2868            setpoint_value: 45.0,
2869            error_limit: 2.0,
2870        };
2871        let req = make_event_req(Some(params));
2872        let mut buf = BytesMut::new();
2873        req.encode(&mut buf).unwrap();
2874        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2875        let ev = decoded.event_values.unwrap();
2876        match ev {
2877            NotificationParameters::FloatingLimit {
2878                reference_value,
2879                status_flags,
2880                setpoint_value,
2881                error_limit,
2882            } => {
2883                assert_eq!(reference_value, 50.0);
2884                assert_eq!(status_flags, 0b1000);
2885                assert_eq!(setpoint_value, 45.0);
2886                assert_eq!(error_limit, 2.0);
2887            }
2888            other => panic!("expected FloatingLimit, got {:?}", other),
2889        }
2890    }
2891
2892    #[test]
2893    fn notification_params_change_of_life_safety_round_trip() {
2894        let params = NotificationParameters::ChangeOfLifeSafety {
2895            new_state: 3,
2896            new_mode: 1,
2897            status_flags: 0b1000,
2898            operation_expected: 2,
2899        };
2900        let req = make_event_req(Some(params));
2901        let mut buf = BytesMut::new();
2902        req.encode(&mut buf).unwrap();
2903        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2904        let ev = decoded.event_values.unwrap();
2905        match ev {
2906            NotificationParameters::ChangeOfLifeSafety {
2907                new_state,
2908                new_mode,
2909                status_flags,
2910                operation_expected,
2911            } => {
2912                assert_eq!(new_state, 3);
2913                assert_eq!(new_mode, 1);
2914                assert_eq!(status_flags, 0b1000);
2915                assert_eq!(operation_expected, 2);
2916            }
2917            other => panic!("expected ChangeOfLifeSafety, got {:?}", other),
2918        }
2919    }
2920
2921    #[test]
2922    fn notification_params_extended_round_trip() {
2923        let params = NotificationParameters::Extended {
2924            vendor_id: 42,
2925            extended_event_type: 7,
2926            parameters: vec![0x01, 0x02, 0x03],
2927        };
2928        let req = make_event_req(Some(params));
2929        let mut buf = BytesMut::new();
2930        req.encode(&mut buf).unwrap();
2931        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2932        let ev = decoded.event_values.unwrap();
2933        match ev {
2934            NotificationParameters::Extended {
2935                vendor_id,
2936                extended_event_type,
2937                parameters,
2938            } => {
2939                assert_eq!(vendor_id, 42);
2940                assert_eq!(extended_event_type, 7);
2941                assert_eq!(parameters, vec![0x01, 0x02, 0x03]);
2942            }
2943            other => panic!("expected Extended, got {:?}", other),
2944        }
2945    }
2946
2947    #[test]
2948    fn notification_params_access_event_round_trip() {
2949        use bacnet_types::primitives::{Date, Time};
2950
2951        let cred = BACnetDeviceObjectPropertyReference::new_local(
2952            ObjectIdentifier::new(ObjectType::ACCESS_CREDENTIAL, 1).unwrap(),
2953            85, // PRESENT_VALUE
2954        );
2955        let params = NotificationParameters::AccessEvent {
2956            access_event: 5,
2957            status_flags: 0b1000,
2958            access_event_tag: 10,
2959            access_event_time: (
2960                Date {
2961                    year: 124,
2962                    month: 6,
2963                    day: 15,
2964                    day_of_week: 3,
2965                },
2966                Time {
2967                    hour: 10,
2968                    minute: 30,
2969                    second: 0,
2970                    hundredths: 0,
2971                },
2972            ),
2973            access_credential: cred.clone(),
2974            authentication_factor: vec![0xAB, 0xCD],
2975        };
2976        let req = make_event_req(Some(params));
2977        let mut buf = BytesMut::new();
2978        req.encode(&mut buf).unwrap();
2979        let decoded = EventNotificationRequest::decode(&buf).unwrap();
2980        let ev = decoded.event_values.unwrap();
2981        match ev {
2982            NotificationParameters::AccessEvent {
2983                access_event,
2984                status_flags,
2985                access_event_tag,
2986                access_event_time,
2987                access_credential,
2988                authentication_factor,
2989            } => {
2990                assert_eq!(access_event, 5);
2991                assert_eq!(status_flags, 0b1000);
2992                assert_eq!(access_event_tag, 10);
2993                assert_eq!(access_event_time.0.year, 124);
2994                assert_eq!(access_event_time.1.hour, 10);
2995                assert_eq!(access_credential, cred);
2996                assert_eq!(authentication_factor, vec![0xAB, 0xCD]);
2997            }
2998            other => panic!("expected AccessEvent, got {:?}", other),
2999        }
3000    }
3001
3002    #[test]
3003    fn notification_params_double_out_of_range_round_trip() {
3004        let params = NotificationParameters::DoubleOutOfRange {
3005            exceeding_value: 100.5,
3006            status_flags: 0b1000,
3007            deadband: 0.5,
3008            exceeded_limit: 100.0,
3009        };
3010        let req = make_event_req(Some(params));
3011        let mut buf = BytesMut::new();
3012        req.encode(&mut buf).unwrap();
3013        let decoded = EventNotificationRequest::decode(&buf).unwrap();
3014        let ev = decoded.event_values.unwrap();
3015        match ev {
3016            NotificationParameters::DoubleOutOfRange {
3017                exceeding_value,
3018                status_flags,
3019                deadband,
3020                exceeded_limit,
3021            } => {
3022                assert_eq!(exceeding_value, 100.5);
3023                assert_eq!(status_flags, 0b1000);
3024                assert_eq!(deadband, 0.5);
3025                assert_eq!(exceeded_limit, 100.0);
3026            }
3027            other => panic!("expected DoubleOutOfRange, got {:?}", other),
3028        }
3029    }
3030
3031    #[test]
3032    fn notification_params_signed_out_of_range_round_trip() {
3033        let params = NotificationParameters::SignedOutOfRange {
3034            exceeding_value: -10,
3035            status_flags: 0b1000,
3036            deadband: 5,
3037            exceeded_limit: -5,
3038        };
3039        let req = make_event_req(Some(params));
3040        let mut buf = BytesMut::new();
3041        req.encode(&mut buf).unwrap();
3042        let decoded = EventNotificationRequest::decode(&buf).unwrap();
3043        let ev = decoded.event_values.unwrap();
3044        match ev {
3045            NotificationParameters::SignedOutOfRange {
3046                exceeding_value,
3047                status_flags,
3048                deadband,
3049                exceeded_limit,
3050            } => {
3051                assert_eq!(exceeding_value, -10);
3052                assert_eq!(status_flags, 0b1000);
3053                assert_eq!(deadband, 5);
3054                assert_eq!(exceeded_limit, -5);
3055            }
3056            other => panic!("expected SignedOutOfRange, got {:?}", other),
3057        }
3058    }
3059
3060    #[test]
3061    fn notification_params_unsigned_out_of_range_round_trip() {
3062        let params = NotificationParameters::UnsignedOutOfRange {
3063            exceeding_value: 200,
3064            status_flags: 0b1000,
3065            deadband: 10,
3066            exceeded_limit: 190,
3067        };
3068        let req = make_event_req(Some(params));
3069        let mut buf = BytesMut::new();
3070        req.encode(&mut buf).unwrap();
3071        let decoded = EventNotificationRequest::decode(&buf).unwrap();
3072        let ev = decoded.event_values.unwrap();
3073        match ev {
3074            NotificationParameters::UnsignedOutOfRange {
3075                exceeding_value,
3076                status_flags,
3077                deadband,
3078                exceeded_limit,
3079            } => {
3080                assert_eq!(exceeding_value, 200);
3081                assert_eq!(status_flags, 0b1000);
3082                assert_eq!(deadband, 10);
3083                assert_eq!(exceeded_limit, 190);
3084            }
3085            other => panic!("expected UnsignedOutOfRange, got {:?}", other),
3086        }
3087    }
3088
3089    #[test]
3090    fn notification_params_change_of_characterstring_round_trip() {
3091        let params = NotificationParameters::ChangeOfCharacterstring {
3092            changed_value: "hello".to_string(),
3093            status_flags: 0b1000,
3094            alarm_value: "alarm".to_string(),
3095        };
3096        let req = make_event_req(Some(params));
3097        let mut buf = BytesMut::new();
3098        req.encode(&mut buf).unwrap();
3099        let decoded = EventNotificationRequest::decode(&buf).unwrap();
3100        let ev = decoded.event_values.unwrap();
3101        match ev {
3102            NotificationParameters::ChangeOfCharacterstring {
3103                changed_value,
3104                status_flags,
3105                alarm_value,
3106            } => {
3107                assert_eq!(changed_value, "hello");
3108                assert_eq!(status_flags, 0b1000);
3109                assert_eq!(alarm_value, "alarm");
3110            }
3111            other => panic!("expected ChangeOfCharacterstring, got {:?}", other),
3112        }
3113    }
3114
3115    #[test]
3116    fn notification_params_change_of_status_flags_round_trip() {
3117        let params = NotificationParameters::ChangeOfStatusFlags {
3118            present_value: vec![0x91, 0x03],
3119            referenced_flags: 0b1010,
3120        };
3121        let req = make_event_req(Some(params));
3122        let mut buf = BytesMut::new();
3123        req.encode(&mut buf).unwrap();
3124        let decoded = EventNotificationRequest::decode(&buf).unwrap();
3125        let ev = decoded.event_values.unwrap();
3126        match ev {
3127            NotificationParameters::ChangeOfStatusFlags {
3128                present_value,
3129                referenced_flags,
3130            } => {
3131                assert_eq!(present_value, vec![0x91, 0x03]);
3132                assert_eq!(referenced_flags, 0b1010);
3133            }
3134            other => panic!("expected ChangeOfStatusFlags, got {:?}", other),
3135        }
3136    }
3137
3138    #[test]
3139    fn notification_params_change_of_reliability_round_trip() {
3140        let params = NotificationParameters::ChangeOfReliability {
3141            reliability: 7,
3142            status_flags: 0b0100,
3143            property_values: vec![0x01, 0x02],
3144        };
3145        let req = make_event_req(Some(params));
3146        let mut buf = BytesMut::new();
3147        req.encode(&mut buf).unwrap();
3148        let decoded = EventNotificationRequest::decode(&buf).unwrap();
3149        let ev = decoded.event_values.unwrap();
3150        match ev {
3151            NotificationParameters::ChangeOfReliability {
3152                reliability,
3153                status_flags,
3154                property_values,
3155            } => {
3156                assert_eq!(reliability, 7);
3157                assert_eq!(status_flags, 0b0100);
3158                assert_eq!(property_values, vec![0x01, 0x02]);
3159            }
3160            other => panic!("expected ChangeOfReliability, got {:?}", other),
3161        }
3162    }
3163
3164    #[test]
3165    fn notification_params_change_of_discrete_value_round_trip() {
3166        let params = NotificationParameters::ChangeOfDiscreteValue {
3167            new_value: vec![0x91, 0x05],
3168            status_flags: 0b1000,
3169        };
3170        let req = make_event_req(Some(params));
3171        let mut buf = BytesMut::new();
3172        req.encode(&mut buf).unwrap();
3173        let decoded = EventNotificationRequest::decode(&buf).unwrap();
3174        let ev = decoded.event_values.unwrap();
3175        match ev {
3176            NotificationParameters::ChangeOfDiscreteValue {
3177                new_value,
3178                status_flags,
3179            } => {
3180                assert_eq!(new_value, vec![0x91, 0x05]);
3181                assert_eq!(status_flags, 0b1000);
3182            }
3183            other => panic!("expected ChangeOfDiscreteValue, got {:?}", other),
3184        }
3185    }
3186
3187    #[test]
3188    fn notification_params_change_of_timer_round_trip() {
3189        use bacnet_types::primitives::{Date, Time};
3190
3191        let params = NotificationParameters::ChangeOfTimer {
3192            new_state: 1,
3193            status_flags: 0b1000,
3194            update_time: (
3195                Date {
3196                    year: 124,
3197                    month: 3,
3198                    day: 10,
3199                    day_of_week: 1,
3200                },
3201                Time {
3202                    hour: 8,
3203                    minute: 0,
3204                    second: 0,
3205                    hundredths: 0,
3206                },
3207            ),
3208            last_state_change: 0,
3209            initial_timeout: 300,
3210            expiration_time: (
3211                Date {
3212                    year: 124,
3213                    month: 3,
3214                    day: 10,
3215                    day_of_week: 1,
3216                },
3217                Time {
3218                    hour: 8,
3219                    minute: 5,
3220                    second: 0,
3221                    hundredths: 0,
3222                },
3223            ),
3224        };
3225        let req = make_event_req(Some(params));
3226        let mut buf = BytesMut::new();
3227        req.encode(&mut buf).unwrap();
3228        let decoded = EventNotificationRequest::decode(&buf).unwrap();
3229        let ev = decoded.event_values.unwrap();
3230        match ev {
3231            NotificationParameters::ChangeOfTimer {
3232                new_state,
3233                status_flags,
3234                update_time,
3235                last_state_change,
3236                initial_timeout,
3237                expiration_time,
3238            } => {
3239                assert_eq!(new_state, 1);
3240                assert_eq!(status_flags, 0b1000);
3241                assert_eq!(update_time.0.year, 124);
3242                assert_eq!(update_time.1.hour, 8);
3243                assert_eq!(last_state_change, 0);
3244                assert_eq!(initial_timeout, 300);
3245                assert_eq!(expiration_time.0.year, 124);
3246                assert_eq!(expiration_time.1.minute, 5);
3247            }
3248            other => panic!("expected ChangeOfTimer, got {:?}", other),
3249        }
3250    }
3251
3252    #[test]
3253    fn get_event_information_ack_empty_list() {
3254        let ack = GetEventInformationAck {
3255            list_of_event_summaries: vec![],
3256            more_events: false,
3257        };
3258        let mut buf = BytesMut::new();
3259        ack.encode(&mut buf);
3260        let decoded = GetEventInformationAck::decode(&buf).unwrap();
3261        assert!(decoded.list_of_event_summaries.is_empty());
3262        assert!(!decoded.more_events);
3263    }
3264}