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