Skip to main content

bacnet_rs/service/
mod.rs

1//! BACnet Application Layer Services
2//!
3//! This module implements BACnet application layer services as defined in ASHRAE Standard 135.
4//! Services are the fundamental communication primitives that enable devices to interact in a
5//! BACnet network, providing standardized operations for reading data, writing values, receiving
6//! notifications, and managing devices.
7//!
8//! # Overview
9//!
10//! BACnet services define the application-level protocols for device communication. They abstract
11//! the underlying network complexity and provide a consistent interface for building automation
12//! operations. Each service defines:
13//!
14//! - **Request Structure**: Parameters needed to invoke the service
15//! - **Response Structure**: Data returned by the service
16//! - **Error Handling**: Standardized error codes and descriptions
17//! - **Encoding Rules**: How requests and responses are serialized
18//!
19//! # Service Categories
20//!
21//! BACnet services are organized into functional groups:
22//!
23//! ## Alarm and Event Services
24//! Handle alarm conditions and event notifications:
25//! - **AcknowledgeAlarm**: Acknowledge alarm conditions
26//! - **ConfirmedEventNotification**: Reliable event notifications
27//! - **UnconfirmedEventNotification**: Best-effort event notifications
28//! - **GetAlarmSummary**: Retrieve active alarms
29//! - **GetEventInformation**: Get detailed event information
30//!
31//! ## File Access Services
32//! Provide file system operations for devices that support file access:
33//! - **AtomicReadFile**: Read file contents atomically
34//! - **AtomicWriteFile**: Write file contents atomically
35//! - **CreateObject**: Create new objects (including file objects)
36//! - **DeleteObject**: Remove objects from the device
37//!
38//! ## Object Access Services
39//! Core services for reading and writing object properties:
40//! - **ReadProperty**: Read a single property value
41//! - **ReadPropertyMultiple**: Read multiple properties efficiently
42//! - **WriteProperty**: Write a single property value
43//! - **WritePropertyMultiple**: Write multiple properties efficiently
44//!
45//! ## Remote Device Management Services
46//! Enable device configuration and management:
47//! - **DeviceCommunicationControl**: Enable/disable device communication
48//! - **ConfirmedPrivateTransfer**: Vendor-specific communications
49//! - **UnconfirmedPrivateTransfer**: Vendor-specific notifications
50//! - **ReinitializeDevice**: Restart or reconfigure devices
51//!
52//! ## Virtual Terminal Services
53//! Support text-based terminal access:
54//! - **VTOpen**: Open virtual terminal session
55//! - **VTClose**: Close virtual terminal session
56//! - **VTData**: Exchange terminal data
57//!
58//! ## Remote Device Discovery
59//! Services for network discovery and device identification:
60//! - **WhoIs**: Discover devices on the network
61//! - **IHave**: Announce object availability
62//! - **WhoHas**: Search for specific objects
63//! - **IAm**: Device identification response
64//!
65//! # Service Types
66//!
67//! BACnet services are classified by their reliability requirements:
68//!
69//! ## Confirmed Services
70//! These services require acknowledgment from the recipient and provide reliable delivery:
71//! - Use sequence numbers for duplicate detection
72//! - Support segmentation for large messages
73//! - Provide error responses for failed operations
74//! - Include timeout and retry mechanisms
75//!
76//! ## Unconfirmed Services  
77//! These services are "fire-and-forget" with no acknowledgment:
78//! - Lower overhead and faster transmission
79//! - No delivery guarantee
80//! - Suitable for periodic updates and notifications
81//! - No error reporting mechanism
82//!
83//! # Examples
84//!
85//! ## Reading a Property
86//!
87//! ```rust
88//! use bacnet_rs::service::{ConfirmedServiceChoice, ReadPropertyRequest};
89//! use bacnet_rs::object::{ObjectIdentifier, ObjectType, PropertyIdentifier};
90//!
91//! // Create a read property request
92//! let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
93//! let request = ReadPropertyRequest::new(object_id, PropertyIdentifier::PresentValue);
94//!
95//! // This would be sent as a confirmed service
96//! let service_choice = ConfirmedServiceChoice::ReadProperty;
97//! ```
98//!
99//! ## Device Discovery
100//!
101//! ```rust
102//! use bacnet_rs::service::{UnconfirmedServiceChoice, WhoIsRequest};
103//!
104//! // Create a Who-Is request to discover all devices
105//! let who_is = WhoIsRequest::new();
106//!
107//! // This would be sent as an unconfirmed service
108//! let service_choice = UnconfirmedServiceChoice::WhoIs;
109//! ```
110//!
111//! ## Reading Multiple Properties
112//!
113//! ```rust
114//! use bacnet_rs::service::{ConfirmedServiceChoice, ReadPropertyMultipleRequest, ReadAccessSpecification, PropertyReference};
115//! use bacnet_rs::object::{ObjectIdentifier, ObjectType, PropertyIdentifier};
116//!
117//! // Create a read property multiple request
118//! let object_id = ObjectIdentifier::new(ObjectType::Device, 12345);
119//! let property_refs = vec![
120//!     PropertyReference::new(PropertyIdentifier::ObjectName),
121//!     PropertyReference::new(PropertyIdentifier::ModelName),
122//!     PropertyReference::new(PropertyIdentifier::VendorName),
123//! ];
124//! let spec = ReadAccessSpecification::new(object_id, property_refs);
125//!
126//! let request = ReadPropertyMultipleRequest::new(vec![spec]);
127//!
128//! let service_choice = ConfirmedServiceChoice::ReadPropertyMultiple;
129//! ```
130//!
131//! # Error Handling
132//!
133//! Services can fail for various reasons, and BACnet defines standardized error responses:
134//!
135//! ```rust
136//! use bacnet_rs::service::ServiceError;
137//!
138//! // Example error handling
139//! let error = ServiceError::InvalidParameters("Missing required property".to_string());
140//!
141//! match error {
142//!     ServiceError::UnsupportedService => println!("Service not supported"),
143//!     ServiceError::InvalidParameters(msg) => println!("Invalid parameters: {}", msg),
144//!     ServiceError::Timeout => println!("Request timed out"),
145//!     ServiceError::EncodingError(msg) => println!("Encoding error: {}", msg),
146//!     _ => println!("Other error: {:?}", error),
147//! }
148//! ```
149//!
150//! # Protocol Integration
151//!
152//! Services integrate with the lower protocol layers:
153//!
154//! 1. **Application Layer**: Services define the high-level operations
155//! 2. **Transport Layer**: Handles reliability, segmentation, and flow control
156//! 3. **Network Layer**: Provides routing and addressing
157//! 4. **Data Link Layer**: Manages frame transmission and media access
158//!
159//! This layered approach allows services to work across different network types
160//! and provides a consistent programming interface regardless of the underlying
161//! communication technology.
162
163#[cfg(feature = "std")]
164use std::error::Error;
165
166#[cfg(feature = "std")]
167use std::fmt;
168
169#[cfg(not(feature = "std"))]
170use core::fmt;
171
172#[cfg(not(feature = "std"))]
173use alloc::{format, string::String, vec::Vec};
174
175/// Result type for service operations
176#[cfg(feature = "std")]
177pub type Result<T> = std::result::Result<T, ServiceError>;
178
179#[cfg(not(feature = "std"))]
180pub type Result<T> = core::result::Result<T, ServiceError>;
181
182/// Errors that can occur during service operations
183#[derive(Debug)]
184pub enum ServiceError {
185    /// Service is not supported
186    UnsupportedService,
187    /// Invalid service parameters
188    InvalidParameters(String),
189    /// Service timeout
190    Timeout,
191    /// Service rejected by remote device
192    Rejected(RejectReason),
193    /// Service aborted by remote device
194    Aborted(AbortReason),
195    /// Encoding/decoding error
196    EncodingError(String),
197    /// Unsupported service choice
198    UnsupportedServiceChoice(u8),
199}
200
201impl fmt::Display for ServiceError {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match self {
204            ServiceError::UnsupportedService => write!(f, "Service not supported"),
205            ServiceError::InvalidParameters(msg) => write!(f, "Invalid parameters: {}", msg),
206            ServiceError::Timeout => write!(f, "Service timeout"),
207            ServiceError::Rejected(reason) => write!(f, "Service rejected: {:?}", reason),
208            ServiceError::Aborted(reason) => write!(f, "Service aborted: {:?}", reason),
209            ServiceError::EncodingError(msg) => write!(f, "Encoding error: {}", msg),
210            ServiceError::UnsupportedServiceChoice(choice) => {
211                write!(f, "Unsupported service choice: {}", choice)
212            }
213        }
214    }
215}
216
217#[cfg(feature = "std")]
218impl Error for ServiceError {}
219
220/// Confirmed service choices
221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222#[repr(u8)]
223pub enum ConfirmedServiceChoice {
224    // Alarm and Event Services
225    AcknowledgeAlarm = 0,
226    ConfirmedEventNotification = 2,
227    GetAlarmSummary = 3,
228    GetEnrollmentSummary = 4,
229    GetEventInformation = 29,
230
231    // File Access Services
232    AtomicReadFile = 6,
233    AtomicWriteFile = 7,
234
235    // Object Access Services
236    AddListElement = 8,
237    RemoveListElement = 9,
238    CreateObject = 10,
239    DeleteObject = 11,
240    ReadProperty = 12,
241    ReadPropertyMultiple = 14,
242    WriteProperty = 15,
243    WritePropertyMultiple = 16,
244
245    // Remote Device Management Services
246    DeviceCommunicationControl = 17,
247    ReinitializeDevice = 20,
248
249    // Virtual Terminal Services
250    VtOpen = 21,
251    VtClose = 22,
252    VtData = 23,
253
254    // Security Services
255    Authenticate = 24,
256    RequestKey = 25,
257
258    // Other Services
259    ReadRange = 26,
260    SubscribeCOV = 5,
261    SubscribeCOVProperty = 28,
262
263    // Protocol Revision 30 - Security Services
264    AuthRequest = 34,
265}
266
267impl TryFrom<u8> for ConfirmedServiceChoice {
268    type Error = ServiceError;
269
270    fn try_from(value: u8) -> Result<Self> {
271        match value {
272            0 => Ok(Self::AcknowledgeAlarm),
273            2 => Ok(Self::ConfirmedEventNotification),
274            3 => Ok(Self::GetAlarmSummary),
275            4 => Ok(Self::GetEnrollmentSummary),
276            29 => Ok(Self::GetEventInformation),
277            6 => Ok(Self::AtomicReadFile),
278            7 => Ok(Self::AtomicWriteFile),
279            8 => Ok(Self::AddListElement),
280            9 => Ok(Self::RemoveListElement),
281            10 => Ok(Self::CreateObject),
282            11 => Ok(Self::DeleteObject),
283            12 => Ok(Self::ReadProperty),
284            14 => Ok(Self::ReadPropertyMultiple),
285            15 => Ok(Self::WriteProperty),
286            16 => Ok(Self::WritePropertyMultiple),
287            17 => Ok(Self::DeviceCommunicationControl),
288            20 => Ok(Self::ReinitializeDevice),
289            21 => Ok(Self::VtOpen),
290            22 => Ok(Self::VtClose),
291            23 => Ok(Self::VtData),
292            24 => Ok(Self::Authenticate),
293            25 => Ok(Self::RequestKey),
294            26 => Ok(Self::ReadRange),
295            5 => Ok(Self::SubscribeCOV),
296            28 => Ok(Self::SubscribeCOVProperty),
297            34 => Ok(Self::AuthRequest),
298            _ => Err(ServiceError::UnsupportedServiceChoice(value)),
299        }
300    }
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304#[repr(u8)]
305pub enum UnconfirmedServiceChoice {
306    IAm = 0,
307    IHave = 1,
308    UnconfirmedCOVNotification = 2,
309    UnconfirmedEventNotification = 3,
310    UnconfirmedPrivateTransfer = 4,
311    UnconfirmedTextMessage = 5,
312    TimeSynchronization = 6,
313    WhoHas = 7,
314    WhoIs = 8,
315    UtcTimeSynchronization = 9,
316    WriteGroup = 10,
317    UnconfirmedCOVNotificationMultiple = 11,
318    UnconfirmedAuditNotification = 12,
319    WhoAmI = 13,
320    YouAre = 14,
321}
322
323impl TryFrom<u8> for UnconfirmedServiceChoice {
324    type Error = ServiceError;
325
326    fn try_from(value: u8) -> Result<Self> {
327        match value {
328            0 => Ok(Self::IAm),
329            1 => Ok(Self::IHave),
330            2 => Ok(Self::UnconfirmedCOVNotification),
331            3 => Ok(Self::UnconfirmedEventNotification),
332            4 => Ok(Self::UnconfirmedPrivateTransfer),
333            5 => Ok(Self::UnconfirmedTextMessage),
334            6 => Ok(Self::TimeSynchronization),
335            7 => Ok(Self::WhoHas),
336            8 => Ok(Self::WhoIs),
337            9 => Ok(Self::UtcTimeSynchronization),
338            10 => Ok(Self::WriteGroup),
339            11 => Ok(Self::UnconfirmedCOVNotificationMultiple),
340            12 => Ok(Self::UnconfirmedAuditNotification),
341            13 => Ok(Self::WhoAmI),
342            14 => Ok(Self::YouAre),
343            _ => Err(ServiceError::UnsupportedServiceChoice(value)),
344        }
345    }
346}
347
348generate_custom_enum!(
349    /// Reject reason codes
350    RejectReason{
351    Other = 0,
352    BufferOverflow = 1,
353    InconsistentParameters = 2,
354    InvalidParameterDataType = 3,
355    InvalidTag = 4,
356    MissingRequiredParameter = 5,
357    ParameterOutOfRange = 6,
358    TooManyArguments = 7,
359    UndefinedEnumeration = 8,
360    UnrecognizedService = 9,
361    InvalidDataEncoding = 10,
362}, u8, 64..=255);
363
364generate_custom_enum!(
365    /// Abort reason codes (ASHRAE 135 `BACnetAbortReason`).
366    ///
367    /// Unrecognized values surface as `Reserved` (codes 12-63, reserved by
368    /// ASHRAE) or `Custom` (codes 64-255, vendor-proprietary) so any abort
369    /// reason round-trips losslessly.
370    AbortReason{
371    Other = 0,
372    BufferOverflow = 1,
373    InvalidApduInThisState = 2,
374    PreemptedByHigherPriorityTask = 3,
375    SegmentationNotSupported = 4,
376    SecurityError = 5,
377    InsufficientSecurity = 6,
378    WindowSizeOutOfRange = 7,
379    ApplicationExceededReplyTime = 8,
380    OutOfResources = 9,
381    TsmTimeout = 10,
382    ApduTooLong = 11,
383}, u8, 64..=255);
384
385use crate::encoding::{
386    decode_context_enumerated, decode_context_object_id, decode_context_tag,
387    decode_context_unsigned, decode_enumerated, decode_object_identifier, decode_tag,
388    decode_unsigned, encode_context_enumerated, encode_context_object_id, encode_context_unsigned,
389    encode_enumerated, encode_object_identifier, encode_unsigned, BACnetTag,
390    Result as EncodingResult,
391};
392use crate::object::{
393    ObjectError, ObjectIdentifier, PropertyIdentifier, PropertyValue, Segmentation,
394};
395use crate::property::{self, decode_property_value, encode_property_value};
396use crate::{generate_custom_enum, EncodingError};
397
398/// Special array index value indicating all elements
399pub const BACNET_ARRAY_ALL: u32 = 0xFFFFFFFF;
400
401/// Who-Is request (unconfirmed service)
402#[derive(Debug, Clone, PartialEq, Eq, Default)]
403pub struct WhoIsRequest {
404    /// Low limit of device instance range (optional)
405    pub device_instance_range_low_limit: Option<u32>,
406    /// High limit of device instance range (optional)
407    pub device_instance_range_high_limit: Option<u32>,
408}
409
410impl WhoIsRequest {
411    /// Create a new Who-Is request for all devices
412    pub fn new() -> Self {
413        Self {
414            device_instance_range_low_limit: None,
415            device_instance_range_high_limit: None,
416        }
417    }
418
419    /// Create a new Who-Is request for a specific device
420    pub fn for_device(device_instance: u32) -> Self {
421        Self {
422            device_instance_range_low_limit: Some(device_instance),
423            device_instance_range_high_limit: Some(device_instance),
424        }
425    }
426
427    /// Create a new Who-Is request for a range of devices
428    pub fn for_range(low: u32, high: u32) -> Self {
429        Self {
430            device_instance_range_low_limit: Some(low),
431            device_instance_range_high_limit: Some(high),
432        }
433    }
434
435    /// Encode the Who-Is request
436    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
437        // Both low and high limits must be present together, or both absent
438        // This matches bacnet-stack behavior
439        if let (Some(low), Some(high)) = (
440            self.device_instance_range_low_limit,
441            self.device_instance_range_high_limit,
442        ) {
443            // Context tag 0 - low limit
444            let low_bytes = encode_context_unsigned(low, 0)?;
445            buffer.extend_from_slice(&low_bytes);
446
447            // Context tag 1 - high limit
448            let high_bytes = encode_context_unsigned(high, 1)?;
449            buffer.extend_from_slice(&high_bytes);
450        }
451        // If only one limit is present, encode nothing (broadcast to all)
452
453        Ok(())
454    }
455
456    /// Decode a Who-Is request
457    pub fn decode(data: &[u8]) -> EncodingResult<Self> {
458        let mut request = WhoIsRequest::new();
459        let mut pos = 0;
460
461        // Try to decode context tag 0 (low limit)
462        if pos < data.len() {
463            match decode_context_unsigned(&data[pos..], 0) {
464                Ok((low, consumed)) => {
465                    request.device_instance_range_low_limit = Some(low);
466                    pos += consumed;
467
468                    // If we have low limit, we must have high limit
469                    if pos < data.len() {
470                        match decode_context_unsigned(&data[pos..], 1) {
471                            Ok((high, _consumed)) => {
472                                request.device_instance_range_high_limit = Some(high);
473                            }
474                            Err(_) => {
475                                // Invalid format - low without high
476                                return Err(crate::encoding::EncodingError::InvalidFormat(
477                                    "Who-Is request has low limit without high limit".to_string(),
478                                ));
479                            }
480                        }
481                    }
482                }
483                Err(_) => {
484                    // No device range specified - broadcast to all
485                }
486            }
487        }
488
489        Ok(request)
490    }
491
492    /// Check if this request matches a device instance
493    pub fn matches(&self, device_instance: u32) -> bool {
494        match (
495            self.device_instance_range_low_limit,
496            self.device_instance_range_high_limit,
497        ) {
498            (None, None) => true, // Matches all devices
499            (Some(low), Some(high)) => device_instance >= low && device_instance <= high,
500            (Some(low), None) => device_instance >= low,
501            (None, Some(high)) => device_instance <= high,
502        }
503    }
504}
505
506/// I-Am response (unconfirmed service)
507#[derive(Debug, Clone, PartialEq, Eq)]
508pub struct IAmRequest {
509    /// Device object identifier
510    pub device_identifier: ObjectIdentifier,
511    /// Maximum APDU length accepted
512    pub max_apdu_length_accepted: u32,
513    /// Segmentation supported
514    pub segmentation_supported: Segmentation,
515    /// Vendor identifier
516    pub vendor_identifier: u16,
517}
518
519impl IAmRequest {
520    /// Create a new I-Am request
521    pub fn new(
522        device_identifier: ObjectIdentifier,
523        max_apdu_length_accepted: u32,
524        segmentation_supported: Segmentation,
525        vendor_identifier: u16,
526    ) -> Self {
527        Self {
528            device_identifier,
529            max_apdu_length_accepted,
530            segmentation_supported,
531            vendor_identifier,
532        }
533    }
534
535    /// Encode the I-Am request
536    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
537        // Device identifier (object identifier) - application tag
538        encode_object_identifier(buffer, self.device_identifier)?;
539
540        // Maximum APDU length accepted - application tag
541        encode_unsigned(buffer, self.max_apdu_length_accepted)?;
542
543        // Segmentation supported - application tag (enumerated)
544        encode_enumerated(buffer, self.segmentation_supported as u32);
545
546        // Vendor identifier - application tag
547        encode_unsigned(buffer, self.vendor_identifier as u32)?;
548
549        Ok(())
550    }
551
552    /// Decode an I-Am request
553    pub fn decode(data: &[u8]) -> EncodingResult<Self> {
554        let mut pos = 0;
555
556        // Decode device identifier - application tag
557        let (device_identifier, consumed) = decode_object_identifier(&data[pos..])?;
558        pos += consumed;
559
560        // Decode max APDU length accepted - application tag
561        let (max_apdu_length_accepted, consumed) = decode_unsigned(&data[pos..])?;
562        pos += consumed;
563
564        // Decode segmentation supported - application tag (enumerated)
565        let (segmentation_supported, consumed) = decode_enumerated(&data[pos..])?;
566        pos += consumed;
567
568        // Decode vendor identifier - application tag
569        let (vendor_identifier, _consumed) = decode_unsigned(&data[pos..])?;
570
571        Ok(IAmRequest::new(
572            device_identifier,
573            max_apdu_length_accepted,
574            segmentation_supported
575                .try_into()
576                .map_err(|e: ObjectError| EncodingError::InvalidFormat(e.to_string()))?,
577            vendor_identifier as u16,
578        ))
579    }
580}
581
582/// Read Property request (confirmed service)
583#[derive(Debug, Clone, PartialEq, Eq)]
584pub struct ReadPropertyRequest {
585    /// Object identifier to read from
586    pub object_identifier: ObjectIdentifier,
587    /// Property identifier to read
588    pub property_identifier: PropertyIdentifier,
589    /// Property array index (optional)
590    pub property_array_index: Option<u32>,
591}
592
593impl ReadPropertyRequest {
594    /// Create a new Read Property request
595    pub fn new(
596        object_identifier: ObjectIdentifier,
597        property_identifier: PropertyIdentifier,
598    ) -> Self {
599        Self {
600            object_identifier,
601            property_identifier,
602            property_array_index: None,
603        }
604    }
605
606    /// Create a new Read Property request with array index
607    pub fn with_array_index(
608        object_identifier: ObjectIdentifier,
609        property_identifier: PropertyIdentifier,
610        array_index: u32,
611    ) -> Self {
612        Self {
613            object_identifier,
614            property_identifier,
615            property_array_index: Some(array_index),
616        }
617    }
618
619    /// Encode the Read Property request
620    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
621        // Object identifier - context tag 0
622        let obj_id_bytes = encode_context_object_id(self.object_identifier, 0)?;
623        buffer.extend_from_slice(&obj_id_bytes);
624
625        // Property identifier - context tag 1 (as enumerated)
626        let prop_id_bytes = encode_context_enumerated(self.property_identifier.into(), 1)?;
627        buffer.extend_from_slice(&prop_id_bytes);
628
629        // Property array index - context tag 2 (optional)
630        if let Some(array_index) = self.property_array_index {
631            let array_bytes = encode_context_unsigned(array_index, 2)?;
632            buffer.extend_from_slice(&array_bytes);
633        }
634
635        Ok(())
636    }
637
638    pub fn decode(buffer: &[u8]) -> EncodingResult<Self> {
639        let mut offset = 0;
640        let (object_identifier, consumed) = decode_context_object_id(&buffer[offset..], 0)?;
641        offset += consumed;
642        let (property_identifier, consumed) = decode_context_enumerated(&buffer[offset..], 1)?;
643        offset += consumed;
644
645        let property_array_index = if offset < buffer.len() {
646            let (array_index, _) = decode_context_unsigned(&buffer[offset..], 2)?;
647            Some(array_index)
648        } else {
649            None
650        };
651
652        Ok(Self {
653            object_identifier,
654            property_identifier: property_identifier.into(),
655            property_array_index,
656        })
657    }
658}
659
660/// Read Property response (confirmed service)
661#[derive(Debug, Clone)]
662pub struct ReadPropertyResponse {
663    /// Object identifier that was read
664    pub object_identifier: ObjectIdentifier,
665    /// Property identifier that was read
666    pub property_identifier: PropertyIdentifier,
667    /// Property array index (optional)
668    pub property_array_index: Option<u32>,
669    /// Property value
670    pub property_values: Vec<property::PropertyValue>, // Raw encoded property value
671}
672
673impl ReadPropertyResponse {
674    /// Create a new Read Property response
675    pub fn new(
676        object_identifier: ObjectIdentifier,
677        property_identifier: PropertyIdentifier,
678        property_values: Vec<property::PropertyValue>,
679    ) -> Self {
680        Self {
681            object_identifier,
682            property_identifier,
683            property_array_index: None,
684            property_values,
685        }
686    }
687
688    /// Decode a Read Property response
689    pub fn decode(data: &[u8]) -> EncodingResult<Self> {
690        let mut pos = 0;
691
692        // Decode object identifier - context tag 0
693        let (object_identifier, consumed) = decode_context_object_id(&data[pos..], 0)?;
694        pos += consumed;
695
696        // Decode property identifier - context tag 1
697        let (property_identifier, consumed) = decode_context_enumerated(&data[pos..], 1)?;
698        pos += consumed;
699
700        // Property array index - context tag 2 (optional)
701        let property_array_index = match decode_context_unsigned(&data[pos..], 2) {
702            Ok((array_index, consumed)) => {
703                pos += consumed;
704                if array_index == BACNET_ARRAY_ALL {
705                    None
706                } else {
707                    Some(array_index)
708                }
709            }
710            Err(_) => None,
711        };
712
713        let (tag, _, consumed) = decode_tag(&data[pos..])?;
714        pos += consumed;
715
716        let property_values = if let BACnetTag::Context(3) = tag {
717            let (tag, _, _) = decode_tag(&data[pos..])?;
718            let mut current_tag = tag;
719            let mut values = Vec::new();
720
721            while let BACnetTag::Application(_) = current_tag {
722                let (value, consumed) = decode_property_value(&data[pos..])?;
723                values.push(value);
724                pos += consumed;
725                let (tag, _, _) = decode_tag(&data[pos..])?;
726                current_tag = tag;
727            }
728            values
729        } else {
730            return Err(EncodingError::InvalidTag);
731        };
732
733        let (tag, _, _) = decode_tag(&data[pos..])?;
734
735        if let BACnetTag::Context(tag) = tag {
736            if tag != 3 {
737                return Err(EncodingError::InvalidTag);
738            }
739        }
740
741        Ok(ReadPropertyResponse {
742            object_identifier,
743            property_identifier: property_identifier.into(),
744            property_array_index,
745            property_values,
746        })
747    }
748
749    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
750        let object_id = encode_context_object_id(self.object_identifier, 0)?;
751        buffer.extend_from_slice(&object_id);
752
753        let prop_id = encode_context_enumerated(self.property_identifier.into(), 1)?;
754        buffer.extend_from_slice(&prop_id);
755
756        // Property array index - context tag 2 (optional)
757        if let Some(array_index) = self.property_array_index {
758            let array_bytes = encode_context_unsigned(array_index, 2)?;
759            buffer.extend_from_slice(&array_bytes);
760        }
761
762        // Property value - context tag 3 (opening tag)
763        buffer.push(0x3E); // Context tag 3, opening tag
764        for property_value in &self.property_values {
765            encode_property_value(property_value, buffer)?;
766        }
767        buffer.push(0x3F); // Context tag 3, closing tag
768
769        Ok(())
770    }
771}
772
773/// Write Property request (confirmed service)
774#[derive(Debug, Clone)]
775pub struct WritePropertyRequest {
776    /// Object identifier to write to
777    pub object_identifier: ObjectIdentifier,
778    /// Property identifier to write
779    pub property_identifier: u32,
780    /// Property array index (optional)
781    pub property_array_index: Option<u32>,
782    /// Property value to write
783    pub property_value: Vec<u8>, // Raw encoded property value
784    /// Priority (optional, 1-16)
785    pub priority: Option<u8>,
786}
787
788impl WritePropertyRequest {
789    /// Create a new Write Property request
790    pub fn new(
791        object_identifier: ObjectIdentifier,
792        property_identifier: u32,
793        property_value: Vec<u8>,
794    ) -> Self {
795        Self {
796            object_identifier,
797            property_identifier,
798            property_array_index: None,
799            property_value,
800            priority: None,
801        }
802    }
803
804    /// Create a new Write Property request with priority
805    pub fn with_priority(
806        object_identifier: ObjectIdentifier,
807        property_identifier: u32,
808        property_value: Vec<u8>,
809        priority: u8,
810    ) -> Self {
811        Self {
812            object_identifier,
813            property_identifier,
814            property_array_index: None,
815            property_value,
816            priority: Some(priority),
817        }
818    }
819
820    /// Create a new Write Property request with array index
821    pub fn with_array_index(
822        object_identifier: ObjectIdentifier,
823        property_identifier: u32,
824        array_index: u32,
825        property_value: Vec<u8>,
826    ) -> Self {
827        Self {
828            object_identifier,
829            property_identifier,
830            property_array_index: Some(array_index),
831            property_value,
832            priority: None,
833        }
834    }
835
836    /// Encode the Write Property request
837    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
838        // Object identifier - context tag 0
839        let object_id: u32 = self.object_identifier.try_into()?;
840        buffer.push(0x0C); // Context tag 0, length 4
841        buffer.extend_from_slice(&object_id.to_be_bytes());
842
843        // Property identifier - context tag 1
844        buffer.push(0x19); // Context tag 1, length 1
845        buffer.push(self.property_identifier as u8);
846
847        // Property array index - context tag 2 (optional)
848        if let Some(array_index) = self.property_array_index {
849            buffer.push(0x29); // Context tag 2, length 1
850            buffer.push(array_index as u8);
851        }
852
853        // Property value - context tag 3 (opening tag)
854        buffer.push(0x3E); // Context tag 3, opening tag
855        buffer.extend_from_slice(&self.property_value);
856        buffer.push(0x3F); // Context tag 3, closing tag
857
858        // Priority - context tag 4 (optional)
859        if let Some(priority) = self.priority {
860            buffer.push(0x49); // Context tag 4, length 1
861            buffer.push(priority);
862        }
863
864        Ok(())
865    }
866
867    /// Decode a Write Property request
868    pub fn decode(data: &[u8]) -> EncodingResult<Self> {
869        let mut pos = 0;
870
871        // Decode object identifier - context tag 0
872        if pos + 5 > data.len() || data[pos] != 0x0C {
873            return Err(crate::encoding::EncodingError::InvalidTag);
874        }
875        pos += 1;
876
877        let object_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]];
878        let object_id = u32::from_be_bytes(object_id_bytes);
879        let object_identifier = object_id.into();
880        pos += 4;
881
882        // Decode property identifier - context tag 1
883        if pos + 2 > data.len() || data[pos] != 0x19 {
884            return Err(crate::encoding::EncodingError::InvalidTag);
885        }
886        pos += 1;
887        let property_identifier = data[pos] as u32;
888        pos += 1;
889
890        // Property array index - context tag 2 (optional)
891        let property_array_index = if pos < data.len() && data[pos] == 0x29 {
892            pos += 1;
893            let array_index = data[pos] as u32;
894            pos += 1;
895            Some(array_index)
896        } else {
897            None
898        };
899
900        // Property value - context tag 3 (opening tag)
901        if pos >= data.len() || data[pos] != 0x3E {
902            return Err(crate::encoding::EncodingError::InvalidTag);
903        }
904        pos += 1;
905
906        // Find closing tag
907        let value_start = pos;
908        let mut value_end = pos;
909        while value_end < data.len() {
910            if data[value_end] == 0x3F {
911                break;
912            }
913            value_end += 1;
914        }
915
916        if value_end >= data.len() {
917            return Err(crate::encoding::EncodingError::InvalidTag);
918        }
919
920        let property_value = data[value_start..value_end].to_vec();
921        pos = value_end + 1;
922
923        // Priority - context tag 4 (optional)
924        let priority = if pos < data.len() && data[pos] == 0x49 {
925            pos += 1;
926            if pos < data.len() {
927                Some(data[pos])
928            } else {
929                None
930            }
931        } else {
932            None
933        };
934
935        Ok(WritePropertyRequest {
936            object_identifier,
937            property_identifier,
938            property_array_index,
939            property_value,
940            priority,
941        })
942    }
943}
944
945/// Read Property Multiple request (confirmed service)
946#[derive(Debug, Clone)]
947pub struct ReadPropertyMultipleRequest {
948    /// List of objects and properties to read
949    pub read_access_specifications: Vec<ReadAccessSpecification>,
950}
951
952#[derive(Debug, Clone)]
953pub struct ReadAccessSpecification {
954    /// Object identifier
955    pub object_identifier: ObjectIdentifier,
956    /// List of properties to read
957    pub property_references: Vec<PropertyReference>,
958}
959
960#[derive(Debug, Clone)]
961pub struct PropertyReference {
962    /// Property identifier
963    pub property_identifier: PropertyIdentifier,
964    /// Property array index (optional)
965    pub property_array_index: Option<u32>,
966}
967
968impl ReadPropertyMultipleRequest {
969    /// Create a new Read Property Multiple request
970    pub fn new(read_access_specifications: Vec<ReadAccessSpecification>) -> Self {
971        Self {
972            read_access_specifications,
973        }
974    }
975
976    /// Add a read access specification
977    pub fn add_specification(&mut self, spec: ReadAccessSpecification) {
978        self.read_access_specifications.push(spec);
979    }
980
981    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
982        for spec in &self.read_access_specifications {
983            spec.encode(buffer)?;
984        }
985
986        Ok(())
987    }
988}
989
990impl ReadAccessSpecification {
991    /// Create a new read access specification
992    pub fn new(
993        object_identifier: ObjectIdentifier,
994        property_references: Vec<PropertyReference>,
995    ) -> Self {
996        Self {
997            object_identifier,
998            property_references,
999        }
1000    }
1001
1002    /// Add a property reference
1003    pub fn add_property(&mut self, property_reference: PropertyReference) {
1004        self.property_references.push(property_reference);
1005    }
1006
1007    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
1008        // Object identifier - context tag 0
1009        let object_id_bytes = encode_context_object_id(self.object_identifier, 0)?;
1010        buffer.extend_from_slice(&object_id_bytes);
1011
1012        // Property references - context tag 1 (opening tag)
1013        buffer.push(0x1E); // Context tag 1, opening tag
1014
1015        for property_ref in &self.property_references {
1016            property_ref.encode(buffer)?;
1017        }
1018
1019        buffer.push(0x1F); // Context tag 1, closing tag
1020
1021        Ok(())
1022    }
1023}
1024
1025impl PropertyReference {
1026    /// Create a new property reference
1027    pub fn new(property_identifier: PropertyIdentifier) -> Self {
1028        Self {
1029            property_identifier,
1030            property_array_index: None,
1031        }
1032    }
1033
1034    /// Create a new property reference with array index
1035    pub fn with_array_index(property_identifier: PropertyIdentifier, array_index: u32) -> Self {
1036        Self {
1037            property_identifier,
1038            property_array_index: Some(array_index),
1039        }
1040    }
1041
1042    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
1043        let prop_id_bytes = encode_context_enumerated(self.property_identifier.into(), 0)?;
1044        buffer.extend_from_slice(&prop_id_bytes);
1045
1046        if let Some(array_index) = self.property_array_index {
1047            let array_bytes = encode_context_unsigned(array_index, 1)?;
1048            buffer.extend_from_slice(&array_bytes);
1049        }
1050
1051        Ok(())
1052    }
1053}
1054
1055#[derive(Debug, Clone)]
1056pub struct ReadPropertyMultipleResponse {
1057    pub read_access_results: Vec<ReadAccessResult>,
1058}
1059
1060impl ReadPropertyMultipleResponse {
1061    pub fn decode(data: &[u8]) -> EncodingResult<Self> {
1062        let mut read_access_results = Vec::new();
1063
1064        let mut total_consumed = 0;
1065
1066        while total_consumed < data.len() {
1067            let (result, consumed) = ReadAccessResult::decode(&data[total_consumed..])?;
1068            total_consumed += consumed;
1069            read_access_results.push(result);
1070        }
1071
1072        Ok(Self {
1073            read_access_results,
1074        })
1075    }
1076}
1077
1078#[derive(Debug, Clone)]
1079pub struct ReadAccessResult {
1080    pub object_identifier: ObjectIdentifier,
1081    pub results: Vec<PropertyResult>,
1082}
1083
1084impl ReadAccessResult {
1085    pub fn decode(data: &[u8]) -> EncodingResult<(Self, usize)> {
1086        let mut total_consumed = 0;
1087        let mut results = Vec::new();
1088        let (object_id, consumed) = decode_context_object_id(data, 0)?;
1089        total_consumed += consumed;
1090
1091        let (context_id, context_size, consumed) = decode_context_tag(&data[total_consumed..])?;
1092        total_consumed += consumed;
1093
1094        if context_id == 1 && context_size == 6 {
1095            let (mut context_id, _, _) = decode_context_tag(&data[total_consumed..])?;
1096
1097            while context_id != 1 {
1098                let (result, consumed) = PropertyResult::decode(&data[total_consumed..])?;
1099                total_consumed += consumed;
1100                results.push(result);
1101
1102                let (id, _, _) = decode_context_tag(&data[total_consumed..])?;
1103                context_id = id;
1104            }
1105
1106            let (_, _, consumed) = decode_context_tag(&data[total_consumed..])?;
1107            total_consumed += consumed;
1108
1109            if context_id != 1 {
1110                return Err(EncodingError::InvalidTag);
1111            }
1112        } else {
1113            return Err(EncodingError::InvalidTag);
1114        }
1115
1116        Ok((
1117            Self {
1118                object_identifier: object_id,
1119                results,
1120            },
1121            total_consumed,
1122        ))
1123    }
1124}
1125
1126#[derive(Debug, Clone)]
1127pub struct PropertyResult {
1128    pub property_identifier: PropertyIdentifier,
1129    pub array_index: Option<u32>,
1130    pub value: PropertyResultValue,
1131}
1132
1133impl PropertyResult {
1134    pub fn decode(bytes: &[u8]) -> EncodingResult<(Self, usize)> {
1135        let (property_identifier, consumed) = decode_context_enumerated(bytes, 2)?;
1136        let mut total_consumed = consumed;
1137
1138        let (tag, _, _) = decode_tag(&bytes[total_consumed..])?;
1139
1140        let array_index = if let BACnetTag::Context(3) = tag {
1141            let (index, consumed) = decode_context_unsigned(&bytes[total_consumed..], 3)?;
1142            total_consumed += consumed;
1143            Some(index)
1144        } else {
1145            None
1146        };
1147
1148        let (tag, _, consumed) = decode_tag(&bytes[total_consumed..])?;
1149        total_consumed += consumed;
1150
1151        let value = if let BACnetTag::Context(4) = tag {
1152            let (tag, _, _) = decode_tag(&bytes[total_consumed..])?;
1153            let mut current_tag = tag;
1154            let mut values = Vec::new();
1155
1156            while let BACnetTag::Application(_) = current_tag {
1157                let (value, consumed) = decode_property_value(&bytes[total_consumed..])?;
1158                values.push(value);
1159                total_consumed += consumed;
1160                let (tag, _, _) = decode_tag(&bytes[total_consumed..])?;
1161                current_tag = tag;
1162            }
1163            PropertyResultValue::Value(values)
1164        } else if let BACnetTag::Context(5) = tag {
1165            let (error_class, consumed) = decode_enumerated(&bytes[total_consumed..])?;
1166            total_consumed += consumed;
1167            let (error_code, consumed) = decode_enumerated(&bytes[total_consumed..])?;
1168            total_consumed += consumed;
1169            PropertyResultValue::Error(error_class, error_code)
1170        } else {
1171            return Err(EncodingError::InvalidTag);
1172        };
1173
1174        let (tag, _, consumed) = decode_tag(&bytes[total_consumed..])?;
1175        total_consumed += consumed;
1176
1177        if let BACnetTag::Context(tag) = tag {
1178            if tag != 4 && tag != 5 {
1179                return Err(EncodingError::InvalidTag);
1180            }
1181        } else {
1182            return Err(EncodingError::InvalidTag);
1183        }
1184
1185        Ok((
1186            Self {
1187                property_identifier: property_identifier.into(),
1188                array_index,
1189                value,
1190            },
1191            total_consumed,
1192        ))
1193    }
1194}
1195
1196#[derive(Debug, Clone, PartialEq)]
1197pub enum PropertyResultValue {
1198    Value(Vec<property::PropertyValue>),
1199    Error(u32, u32),
1200}
1201
1202/// Subscribe COV request (confirmed service)
1203#[derive(Debug, Clone)]
1204pub struct SubscribeCovRequest {
1205    /// Subscriber process identifier
1206    pub subscriber_process_identifier: u32,
1207    /// Monitored object identifier
1208    pub monitored_object_identifier: ObjectIdentifier,
1209    /// Issue confirmed notifications
1210    pub issue_confirmed_notifications: Option<bool>,
1211    /// Lifetime (seconds, 0 = permanent)
1212    pub lifetime: Option<u32>,
1213}
1214
1215impl SubscribeCovRequest {
1216    /// Create a new Subscribe COV request
1217    pub fn new(
1218        subscriber_process_identifier: u32,
1219        monitored_object_identifier: ObjectIdentifier,
1220    ) -> Self {
1221        Self {
1222            subscriber_process_identifier,
1223            monitored_object_identifier,
1224            issue_confirmed_notifications: None,
1225            lifetime: None,
1226        }
1227    }
1228
1229    /// Create a new Subscribe COV request with confirmation preference
1230    pub fn with_confirmation(
1231        subscriber_process_identifier: u32,
1232        monitored_object_identifier: ObjectIdentifier,
1233        issue_confirmed_notifications: bool,
1234    ) -> Self {
1235        Self {
1236            subscriber_process_identifier,
1237            monitored_object_identifier,
1238            issue_confirmed_notifications: Some(issue_confirmed_notifications),
1239            lifetime: None,
1240        }
1241    }
1242
1243    /// Create a new Subscribe COV request with lifetime
1244    pub fn with_lifetime(
1245        subscriber_process_identifier: u32,
1246        monitored_object_identifier: ObjectIdentifier,
1247        lifetime: u32,
1248    ) -> Self {
1249        Self {
1250            subscriber_process_identifier,
1251            monitored_object_identifier,
1252            issue_confirmed_notifications: None,
1253            lifetime: Some(lifetime),
1254        }
1255    }
1256
1257    /// Encode the Subscribe COV request
1258    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
1259        // Subscriber process identifier - context tag 0
1260        buffer.push(0x09); // Context tag 0, length 1
1261        buffer.push(self.subscriber_process_identifier as u8);
1262
1263        // Monitored object identifier - context tag 1
1264        let object_id: u32 = self.monitored_object_identifier.try_into()?;
1265        buffer.push(0x1C); // Context tag 1, length 4
1266        buffer.extend_from_slice(&object_id.to_be_bytes());
1267
1268        // Issue confirmed notifications - context tag 2 (optional)
1269        if let Some(confirmed) = self.issue_confirmed_notifications {
1270            buffer.push(0x22); // Context tag 2, length 2
1271            buffer.push(if confirmed { 1 } else { 0 });
1272        }
1273
1274        // Lifetime - context tag 3 (optional)
1275        if let Some(lifetime) = self.lifetime {
1276            buffer.push(0x39); // Context tag 3, length 1
1277            buffer.push(lifetime as u8);
1278        }
1279
1280        Ok(())
1281    }
1282}
1283
1284/// Subscribe COV Property request (confirmed service)
1285#[derive(Debug, Clone)]
1286pub struct SubscribeCovPropertyRequest {
1287    /// Subscriber process identifier
1288    pub subscriber_process_identifier: u32,
1289    /// Monitored object identifier
1290    pub monitored_object_identifier: ObjectIdentifier,
1291    /// Issue confirmed notifications
1292    pub issue_confirmed_notifications: Option<bool>,
1293    /// Lifetime (seconds, 0 = permanent)
1294    pub lifetime: Option<u32>,
1295    /// Monitored property reference
1296    pub monitored_property: PropertyReference,
1297    /// COV increment (optional)
1298    pub cov_increment: Option<f32>,
1299}
1300
1301impl SubscribeCovPropertyRequest {
1302    /// Create a new Subscribe COV Property request
1303    pub fn new(
1304        subscriber_process_identifier: u32,
1305        monitored_object_identifier: ObjectIdentifier,
1306        monitored_property: PropertyReference,
1307    ) -> Self {
1308        Self {
1309            subscriber_process_identifier,
1310            monitored_object_identifier,
1311            issue_confirmed_notifications: None,
1312            lifetime: None,
1313            monitored_property,
1314            cov_increment: None,
1315        }
1316    }
1317
1318    /// Set COV increment
1319    pub fn with_cov_increment(mut self, increment: f32) -> Self {
1320        self.cov_increment = Some(increment);
1321        self
1322    }
1323}
1324
1325/// COV Notification request (unconfirmed service)
1326#[derive(Debug, Clone)]
1327pub struct CovNotificationRequest {
1328    /// Subscriber process identifier
1329    pub subscriber_process_identifier: u32,
1330    /// Initiating device identifier
1331    pub initiating_device_identifier: ObjectIdentifier,
1332    /// Monitored object identifier
1333    pub monitored_object_identifier: ObjectIdentifier,
1334    /// Time remaining (seconds)
1335    pub time_remaining: u32,
1336    /// List of values (property-value pairs)
1337    pub list_of_values: Vec<PropertyValue>,
1338}
1339
1340impl CovNotificationRequest {
1341    /// Create a new COV Notification request
1342    pub fn new(
1343        subscriber_process_identifier: u32,
1344        initiating_device_identifier: ObjectIdentifier,
1345        monitored_object_identifier: ObjectIdentifier,
1346        time_remaining: u32,
1347        list_of_values: Vec<PropertyValue>,
1348    ) -> Self {
1349        Self {
1350            subscriber_process_identifier,
1351            initiating_device_identifier,
1352            monitored_object_identifier,
1353            time_remaining,
1354            list_of_values,
1355        }
1356    }
1357
1358    /// Encode the COV Notification request
1359    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
1360        // Subscriber process identifier - context tag 0
1361        buffer.push(0x09); // Context tag 0, length 1
1362        buffer.push(self.subscriber_process_identifier as u8);
1363
1364        // Initiating device identifier - context tag 1
1365        let device_id: u32 = self.initiating_device_identifier.try_into()?;
1366        buffer.push(0x1C); // Context tag 1, length 4
1367        buffer.extend_from_slice(&device_id.to_be_bytes());
1368
1369        // Monitored object identifier - context tag 2
1370        let object_id: u32 = self.monitored_object_identifier.try_into()?;
1371        buffer.push(0x2C); // Context tag 2, length 4
1372        buffer.extend_from_slice(&object_id.to_be_bytes());
1373
1374        // Time remaining - context tag 3
1375        buffer.push(0x39); // Context tag 3, length 1
1376        buffer.push(self.time_remaining as u8);
1377
1378        // List of values would be encoded here in a real implementation
1379        // This is complex as it involves encoding property-value pairs
1380
1381        Ok(())
1382    }
1383}
1384
1385/// COV Subscription information
1386#[derive(Debug, Clone)]
1387pub struct CovSubscription {
1388    /// Subscriber process identifier
1389    pub subscriber_process_identifier: u32,
1390    /// Subscriber device identifier
1391    pub subscriber_device_identifier: ObjectIdentifier,
1392    /// Monitored object identifier
1393    pub monitored_object_identifier: ObjectIdentifier,
1394    /// Monitored property (for COV Property subscriptions)
1395    pub monitored_property: Option<PropertyReference>,
1396    /// Issue confirmed notifications
1397    pub issue_confirmed_notifications: bool,
1398    /// Lifetime (seconds, 0 = permanent)
1399    pub lifetime: u32,
1400    /// Remaining time (seconds)
1401    pub time_remaining: u32,
1402    /// COV increment (for analog properties)
1403    pub cov_increment: Option<f32>,
1404}
1405
1406impl CovSubscription {
1407    /// Create a new COV subscription
1408    pub fn new(
1409        subscriber_process_identifier: u32,
1410        subscriber_device_identifier: ObjectIdentifier,
1411        monitored_object_identifier: ObjectIdentifier,
1412        lifetime: u32,
1413    ) -> Self {
1414        Self {
1415            subscriber_process_identifier,
1416            subscriber_device_identifier,
1417            monitored_object_identifier,
1418            monitored_property: None,
1419            issue_confirmed_notifications: false,
1420            lifetime,
1421            time_remaining: lifetime,
1422            cov_increment: None,
1423        }
1424    }
1425
1426    /// Check if subscription has expired
1427    pub fn is_expired(&self) -> bool {
1428        self.lifetime > 0 && self.time_remaining == 0
1429    }
1430
1431    /// Update time remaining (should be called periodically)
1432    pub fn update_time(&mut self, elapsed_seconds: u32) {
1433        if self.lifetime > 0 {
1434            self.time_remaining = self.time_remaining.saturating_sub(elapsed_seconds);
1435        }
1436    }
1437}
1438
1439/// COV Subscription manager
1440#[derive(Debug, Default)]
1441pub struct CovSubscriptionManager {
1442    /// List of active subscriptions
1443    subscriptions: Vec<CovSubscription>,
1444}
1445
1446impl CovSubscriptionManager {
1447    /// Create a new COV subscription manager
1448    pub fn new() -> Self {
1449        Self {
1450            subscriptions: Vec::new(),
1451        }
1452    }
1453
1454    /// Add a new subscription
1455    pub fn add_subscription(&mut self, subscription: CovSubscription) {
1456        // Remove any existing subscription for the same object and subscriber
1457        self.subscriptions.retain(|s| {
1458            !(s.subscriber_device_identifier == subscription.subscriber_device_identifier
1459                && s.subscriber_process_identifier == subscription.subscriber_process_identifier
1460                && s.monitored_object_identifier == subscription.monitored_object_identifier)
1461        });
1462
1463        self.subscriptions.push(subscription);
1464    }
1465
1466    /// Remove a subscription
1467    pub fn remove_subscription(
1468        &mut self,
1469        subscriber_device: ObjectIdentifier,
1470        subscriber_process: u32,
1471        monitored_object: ObjectIdentifier,
1472    ) {
1473        self.subscriptions.retain(|s| {
1474            !(s.subscriber_device_identifier == subscriber_device
1475                && s.subscriber_process_identifier == subscriber_process
1476                && s.monitored_object_identifier == monitored_object)
1477        });
1478    }
1479
1480    /// Get all subscriptions for a monitored object
1481    pub fn get_subscriptions_for_object(
1482        &self,
1483        object_id: ObjectIdentifier,
1484    ) -> Vec<&CovSubscription> {
1485        self.subscriptions
1486            .iter()
1487            .filter(|s| s.monitored_object_identifier == object_id && !s.is_expired())
1488            .collect()
1489    }
1490
1491    /// Remove expired subscriptions
1492    pub fn cleanup_expired(&mut self) {
1493        self.subscriptions.retain(|s| !s.is_expired());
1494    }
1495
1496    /// Update all subscription timers
1497    pub fn update_timers(&mut self, elapsed_seconds: u32) {
1498        for subscription in &mut self.subscriptions {
1499            subscription.update_time(elapsed_seconds);
1500        }
1501    }
1502
1503    /// Get total number of active subscriptions
1504    pub fn active_count(&self) -> usize {
1505        self.subscriptions
1506            .iter()
1507            .filter(|s| !s.is_expired())
1508            .count()
1509    }
1510}
1511
1512/// Atomic Read File request (confirmed service)
1513#[derive(Debug, Clone)]
1514pub struct AtomicReadFileRequest {
1515    /// File object identifier
1516    pub file_identifier: ObjectIdentifier,
1517    /// Access method specification
1518    pub access_method: FileAccessMethod,
1519}
1520
1521/// File access method for atomic read/write
1522#[derive(Debug, Clone)]
1523pub enum FileAccessMethod {
1524    /// Stream access - read/write bytes at position
1525    StreamAccess {
1526        /// File position to start reading/writing
1527        file_start_position: i32,
1528        /// Number of octets to read (for read operations)
1529        requested_octet_count: u32,
1530    },
1531    /// Record access - read/write records
1532    RecordAccess {
1533        /// Starting record number
1534        file_start_record: i32,
1535        /// Number of records to read (for read operations)
1536        requested_record_count: u32,
1537    },
1538}
1539
1540impl AtomicReadFileRequest {
1541    /// Create a new Atomic Read File request with stream access
1542    pub fn new_stream_access(
1543        file_identifier: ObjectIdentifier,
1544        start_position: i32,
1545        octet_count: u32,
1546    ) -> Self {
1547        Self {
1548            file_identifier,
1549            access_method: FileAccessMethod::StreamAccess {
1550                file_start_position: start_position,
1551                requested_octet_count: octet_count,
1552            },
1553        }
1554    }
1555
1556    /// Create a new Atomic Read File request with record access
1557    pub fn new_record_access(
1558        file_identifier: ObjectIdentifier,
1559        start_record: i32,
1560        record_count: u32,
1561    ) -> Self {
1562        Self {
1563            file_identifier,
1564            access_method: FileAccessMethod::RecordAccess {
1565                file_start_record: start_record,
1566                requested_record_count: record_count,
1567            },
1568        }
1569    }
1570
1571    /// Encode the Atomic Read File request
1572    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
1573        // File identifier - context tag 0
1574        let file_id: u32 = self.file_identifier.try_into()?;
1575        buffer.push(0x0C); // Context tag 0, length 4
1576        buffer.extend_from_slice(&file_id.to_be_bytes());
1577
1578        // Access method - context tag 1 (opening tag)
1579        buffer.push(0x1E); // Context tag 1, opening tag
1580
1581        match &self.access_method {
1582            FileAccessMethod::StreamAccess {
1583                file_start_position,
1584                requested_octet_count,
1585            } => {
1586                // Stream access - context tag 0 (opening tag)
1587                buffer.push(0x0E); // Context tag 0, opening tag
1588
1589                // File start position - context tag 0
1590                buffer.push(0x09); // Context tag 0, length 1
1591                buffer.extend_from_slice(&file_start_position.to_be_bytes());
1592
1593                // Requested octet count - context tag 1
1594                buffer.push(0x19); // Context tag 1, length 1
1595                buffer.extend_from_slice(&requested_octet_count.to_be_bytes());
1596
1597                buffer.push(0x0F); // Context tag 0, closing tag
1598            }
1599            FileAccessMethod::RecordAccess {
1600                file_start_record,
1601                requested_record_count,
1602            } => {
1603                // Record access - context tag 1 (opening tag)
1604                buffer.push(0x1E); // Context tag 1, opening tag
1605
1606                // File start record - context tag 0
1607                buffer.push(0x09); // Context tag 0, length 1
1608                buffer.extend_from_slice(&file_start_record.to_be_bytes());
1609
1610                // Requested record count - context tag 1
1611                buffer.push(0x19); // Context tag 1, length 1
1612                buffer.extend_from_slice(&requested_record_count.to_be_bytes());
1613
1614                buffer.push(0x1F); // Context tag 1, closing tag
1615            }
1616        }
1617
1618        buffer.push(0x1F); // Context tag 1, closing tag
1619
1620        Ok(())
1621    }
1622}
1623
1624/// Atomic Read File response (confirmed service)
1625#[derive(Debug, Clone)]
1626pub struct AtomicReadFileResponse {
1627    /// End of file flag
1628    pub end_of_file: bool,
1629    /// Access method and data
1630    pub access_method_result: FileAccessMethodResult,
1631}
1632
1633/// File access method result for atomic read response
1634#[derive(Debug, Clone)]
1635pub enum FileAccessMethodResult {
1636    /// Stream access result
1637    StreamAccess {
1638        /// File position after read
1639        file_start_position: i32,
1640        /// File data read
1641        file_data: Vec<u8>,
1642    },
1643    /// Record access result
1644    RecordAccess {
1645        /// Starting record number
1646        file_start_record: i32,
1647        /// Number of records returned
1648        record_count: u32,
1649        /// Record data
1650        file_record_data: Vec<Vec<u8>>,
1651    },
1652}
1653
1654impl AtomicReadFileResponse {
1655    /// Create a new stream access response
1656    pub fn new_stream_access(end_of_file: bool, start_position: i32, data: Vec<u8>) -> Self {
1657        Self {
1658            end_of_file,
1659            access_method_result: FileAccessMethodResult::StreamAccess {
1660                file_start_position: start_position,
1661                file_data: data,
1662            },
1663        }
1664    }
1665
1666    /// Create a new record access response
1667    pub fn new_record_access(end_of_file: bool, start_record: i32, records: Vec<Vec<u8>>) -> Self {
1668        let record_count = records.len() as u32;
1669        Self {
1670            end_of_file,
1671            access_method_result: FileAccessMethodResult::RecordAccess {
1672                file_start_record: start_record,
1673                record_count,
1674                file_record_data: records,
1675            },
1676        }
1677    }
1678}
1679
1680/// Atomic Write File request (confirmed service)
1681#[derive(Debug, Clone)]
1682pub struct AtomicWriteFileRequest {
1683    /// File object identifier
1684    pub file_identifier: ObjectIdentifier,
1685    /// Access method and data
1686    pub access_method: FileWriteAccessMethod,
1687}
1688
1689/// File write access method for atomic write
1690#[derive(Debug, Clone)]
1691pub enum FileWriteAccessMethod {
1692    /// Stream access - write bytes at position
1693    StreamAccess {
1694        /// File position to start writing
1695        file_start_position: i32,
1696        /// Data to write
1697        file_data: Vec<u8>,
1698    },
1699    /// Record access - write records
1700    RecordAccess {
1701        /// Starting record number
1702        file_start_record: i32,
1703        /// Number of records to write
1704        record_count: u32,
1705        /// Record data to write
1706        file_record_data: Vec<Vec<u8>>,
1707    },
1708}
1709
1710impl AtomicWriteFileRequest {
1711    /// Create a new Atomic Write File request with stream access
1712    pub fn new_stream_access(
1713        file_identifier: ObjectIdentifier,
1714        start_position: i32,
1715        data: Vec<u8>,
1716    ) -> Self {
1717        Self {
1718            file_identifier,
1719            access_method: FileWriteAccessMethod::StreamAccess {
1720                file_start_position: start_position,
1721                file_data: data,
1722            },
1723        }
1724    }
1725
1726    /// Create a new Atomic Write File request with record access
1727    pub fn new_record_access(
1728        file_identifier: ObjectIdentifier,
1729        start_record: i32,
1730        records: Vec<Vec<u8>>,
1731    ) -> Self {
1732        let record_count = records.len() as u32;
1733        Self {
1734            file_identifier,
1735            access_method: FileWriteAccessMethod::RecordAccess {
1736                file_start_record: start_record,
1737                record_count,
1738                file_record_data: records,
1739            },
1740        }
1741    }
1742
1743    /// Encode the Atomic Write File request
1744    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
1745        // File identifier - context tag 0
1746        let file_id: u32 = self.file_identifier.try_into()?;
1747        buffer.push(0x0C); // Context tag 0, length 4
1748        buffer.extend_from_slice(&file_id.to_be_bytes());
1749
1750        // Access method - context tag 1 (opening tag)
1751        buffer.push(0x1E); // Context tag 1, opening tag
1752
1753        match &self.access_method {
1754            FileWriteAccessMethod::StreamAccess {
1755                file_start_position,
1756                file_data,
1757            } => {
1758                // Stream access - context tag 0 (opening tag)
1759                buffer.push(0x0E); // Context tag 0, opening tag
1760
1761                // File start position - context tag 0
1762                buffer.push(0x09); // Context tag 0, length 1
1763                buffer.extend_from_slice(&file_start_position.to_be_bytes());
1764
1765                // File data - context tag 1 (opening tag)
1766                buffer.push(0x1E); // Context tag 1, opening tag
1767                buffer.extend_from_slice(file_data);
1768                buffer.push(0x1F); // Context tag 1, closing tag
1769
1770                buffer.push(0x0F); // Context tag 0, closing tag
1771            }
1772            FileWriteAccessMethod::RecordAccess {
1773                file_start_record,
1774                record_count: _,
1775                file_record_data,
1776            } => {
1777                // Record access - context tag 1 (opening tag)
1778                buffer.push(0x1E); // Context tag 1, opening tag
1779
1780                // File start record - context tag 0
1781                buffer.push(0x09); // Context tag 0, length 1
1782                buffer.extend_from_slice(&file_start_record.to_be_bytes());
1783
1784                // Record count - context tag 1
1785                let record_count = file_record_data.len() as u32;
1786                buffer.push(0x19); // Context tag 1, length 1
1787                buffer.extend_from_slice(&record_count.to_be_bytes());
1788
1789                // File record data - context tag 2 (opening tag)
1790                buffer.push(0x2E); // Context tag 2, opening tag
1791                for record in file_record_data {
1792                    // Each record as octet string
1793                    buffer.push(0x65); // Application tag 6 (OctetString), length depends on record size
1794                    buffer.push(record.len() as u8);
1795                    buffer.extend_from_slice(record);
1796                }
1797                buffer.push(0x2F); // Context tag 2, closing tag
1798
1799                buffer.push(0x1F); // Context tag 1, closing tag
1800            }
1801        }
1802
1803        buffer.push(0x1F); // Context tag 1, closing tag
1804
1805        Ok(())
1806    }
1807}
1808
1809/// Atomic Write File response (confirmed service)
1810#[derive(Debug, Clone)]
1811pub struct AtomicWriteFileResponse {
1812    /// File start position (for stream access) or start record (for record access)
1813    pub file_start_position: i32,
1814}
1815
1816/// Time Synchronization request (unconfirmed service)
1817#[derive(Debug, Clone)]
1818pub struct TimeSynchronizationRequest {
1819    /// Date and time to synchronize to
1820    pub date_time: BacnetDateTime,
1821}
1822
1823/// UTC Time Synchronization request (unconfirmed service)
1824#[derive(Debug, Clone)]
1825pub struct UtcTimeSynchronizationRequest {
1826    /// UTC date and time to synchronize to
1827    pub utc_date_time: BacnetDateTime,
1828}
1829
1830/// BACnet Date and Time structure
1831#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1832pub struct BacnetDateTime {
1833    /// Date component
1834    pub date: crate::object::Date,
1835    /// Time component
1836    pub time: crate::object::Time,
1837}
1838
1839impl BacnetDateTime {
1840    /// Create a new BACnet DateTime from Date and Time components
1841    pub fn new(date: crate::object::Date, time: crate::object::Time) -> Self {
1842        Self { date, time }
1843    }
1844
1845    /// Create from current system time (requires std feature)
1846    #[cfg(feature = "std")]
1847    pub fn now() -> Self {
1848        use chrono::{Datelike, Local, Timelike};
1849
1850        let now = Local::now();
1851        let year = now.year() as u16;
1852        let month = now.month() as u8;
1853        let day = now.day() as u8;
1854        let weekday = now.weekday().number_from_monday() as u8; // BACnet uses 1=Monday
1855        let hour = now.hour() as u8;
1856        let minute = now.minute() as u8;
1857        let second = now.second() as u8;
1858        let hundredths = (now.nanosecond() / 10_000_000) as u8;
1859
1860        let date = crate::object::Date {
1861            year,
1862            month,
1863            day,
1864            weekday,
1865        };
1866        let time = crate::object::Time {
1867            hour,
1868            minute,
1869            second,
1870            hundredths,
1871        };
1872        Self::new(date, time)
1873    }
1874
1875    /// Create unspecified time (all 255 values)
1876    pub fn unspecified() -> Self {
1877        Self {
1878            date: crate::object::Date {
1879                year: 255,
1880                month: 255,
1881                day: 255,
1882                weekday: 255,
1883            },
1884            time: crate::object::Time {
1885                hour: 255,
1886                minute: 255,
1887                second: 255,
1888                hundredths: 255,
1889            },
1890        }
1891    }
1892
1893    /// Check if this datetime is unspecified
1894    pub fn is_unspecified(&self) -> bool {
1895        self.date.year == 255
1896            && self.date.month == 255
1897            && self.date.day == 255
1898            && self.date.weekday == 255
1899            && self.time.hour == 255
1900            && self.time.minute == 255
1901            && self.time.second == 255
1902            && self.time.hundredths == 255
1903    }
1904
1905    /// Encode BACnet DateTime
1906    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
1907        use crate::encoding::{encode_date, encode_time};
1908
1909        // Encode date
1910        encode_date(
1911            buffer,
1912            self.date.year,
1913            self.date.month,
1914            self.date.day,
1915            self.date.weekday,
1916        )?;
1917
1918        // Encode time
1919        encode_time(
1920            buffer,
1921            self.time.hour,
1922            self.time.minute,
1923            self.time.second,
1924            self.time.hundredths,
1925        )?;
1926
1927        Ok(())
1928    }
1929
1930    /// Decode BACnet DateTime
1931    pub fn decode(data: &[u8]) -> EncodingResult<(Self, usize)> {
1932        use crate::encoding::{decode_date, decode_time};
1933
1934        // Decode date (4 bytes)
1935        let ((year, month, day, weekday), consumed_date) = decode_date(data)?;
1936
1937        // Decode time (4 bytes)
1938        let ((hour, minute, second, hundredths), consumed_time) =
1939            decode_time(&data[consumed_date..])?;
1940
1941        let datetime = BacnetDateTime {
1942            date: crate::object::Date {
1943                year,
1944                month,
1945                day,
1946                weekday,
1947            },
1948            time: crate::object::Time {
1949                hour,
1950                minute,
1951                second,
1952                hundredths,
1953            },
1954        };
1955
1956        Ok((datetime, consumed_date + consumed_time))
1957    }
1958}
1959
1960impl TimeSynchronizationRequest {
1961    /// Create a new Time Synchronization request
1962    pub fn new(date_time: BacnetDateTime) -> Self {
1963        Self { date_time }
1964    }
1965
1966    /// Create Time Synchronization request with current time
1967    #[cfg(feature = "std")]
1968    pub fn now() -> Self {
1969        Self::new(BacnetDateTime::now())
1970    }
1971
1972    /// Encode the Time Synchronization request
1973    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
1974        self.date_time.encode(buffer)
1975    }
1976
1977    /// Decode a Time Synchronization request
1978    pub fn decode(data: &[u8]) -> EncodingResult<Self> {
1979        let (date_time, _consumed) = BacnetDateTime::decode(data)?;
1980        Ok(Self::new(date_time))
1981    }
1982}
1983
1984impl UtcTimeSynchronizationRequest {
1985    /// Create a new UTC Time Synchronization request
1986    pub fn new(utc_date_time: BacnetDateTime) -> Self {
1987        Self { utc_date_time }
1988    }
1989
1990    /// Create UTC Time Synchronization request with current UTC time
1991    #[cfg(feature = "std")]
1992    pub fn now() -> Self {
1993        use chrono::{Datelike, Timelike, Utc};
1994
1995        let now = Utc::now();
1996        let year = now.year() as u16;
1997        let month = now.month() as u8;
1998        let day = now.day() as u8;
1999        let weekday = now.weekday().number_from_monday() as u8;
2000        let hour = now.hour() as u8;
2001        let minute = now.minute() as u8;
2002        let second = now.second() as u8;
2003        let hundredths = (now.nanosecond() / 10_000_000) as u8;
2004
2005        let date = crate::object::Date {
2006            year,
2007            month,
2008            day,
2009            weekday,
2010        };
2011        let time = crate::object::Time {
2012            hour,
2013            minute,
2014            second,
2015            hundredths,
2016        };
2017        let utc_date_time = BacnetDateTime::new(date, time);
2018        Self::new(utc_date_time)
2019    }
2020
2021    /// Encode the UTC Time Synchronization request
2022    pub fn encode(&self, buffer: &mut Vec<u8>) -> EncodingResult<()> {
2023        self.utc_date_time.encode(buffer)
2024    }
2025
2026    /// Decode a UTC Time Synchronization request
2027    pub fn decode(data: &[u8]) -> EncodingResult<Self> {
2028        let (utc_date_time, _consumed) = BacnetDateTime::decode(data)?;
2029        Ok(Self::new(utc_date_time))
2030    }
2031}
2032
2033#[cfg(test)]
2034mod tests {
2035    use super::*;
2036    use crate::object::{ObjectIdentifier, ObjectType};
2037
2038    #[test]
2039    fn test_whois_request() {
2040        // Test Who-Is for all devices
2041        let whois_all = WhoIsRequest::new();
2042        assert!(whois_all.matches(123));
2043        assert!(whois_all.matches(456));
2044
2045        // Test Who-Is for specific device
2046        let whois_specific = WhoIsRequest::for_device(123);
2047        assert!(whois_specific.matches(123));
2048        assert!(!whois_specific.matches(124));
2049
2050        // Test Who-Is for range
2051        let whois_range = WhoIsRequest::for_range(100, 200);
2052        assert!(whois_range.matches(150));
2053        assert!(!whois_range.matches(50));
2054        assert!(!whois_range.matches(250));
2055    }
2056
2057    #[test]
2058    fn test_whois_encoding() {
2059        let mut buffer = Vec::new();
2060
2061        // Test encoding Who-Is for all devices
2062        let whois_all = WhoIsRequest::new();
2063        whois_all.encode(&mut buffer).unwrap();
2064        assert_eq!(buffer.len(), 0); // No parameters for all devices
2065
2066        // Test encoding Who-Is for specific device
2067        buffer.clear();
2068        let whois_specific = WhoIsRequest::for_device(123);
2069        whois_specific.encode(&mut buffer).unwrap();
2070        assert!(!buffer.is_empty());
2071
2072        // Test decoding
2073        let decoded = WhoIsRequest::decode(&buffer).unwrap();
2074        assert_eq!(decoded, whois_specific);
2075    }
2076
2077    #[test]
2078    fn test_iam_request() {
2079        let device_id = ObjectIdentifier::new(ObjectType::Device, 123);
2080        let iam = IAmRequest::new(device_id, 1476, Segmentation::Both, 999);
2081
2082        assert_eq!(iam.device_identifier.instance, 123);
2083        assert_eq!(iam.max_apdu_length_accepted, 1476);
2084        assert_eq!(iam.vendor_identifier, 999);
2085    }
2086
2087    #[test]
2088    fn test_read_property_request() {
2089        let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
2090        let read_prop = ReadPropertyRequest::new(object_id, PropertyIdentifier::PresentValue); // Present Value
2091
2092        assert_eq!(read_prop.object_identifier.instance, 1);
2093        assert_eq!(
2094            read_prop.property_identifier,
2095            PropertyIdentifier::PresentValue
2096        );
2097        assert_eq!(read_prop.property_array_index, None);
2098
2099        let read_prop_array =
2100            ReadPropertyRequest::with_array_index(object_id, PropertyIdentifier::PresentValue, 0);
2101        assert_eq!(read_prop_array.property_array_index, Some(0));
2102    }
2103
2104    #[test]
2105    fn test_write_property_request() {
2106        let object_id = ObjectIdentifier::new(ObjectType::AnalogOutput, 1);
2107        let property_value = vec![0x44, 0x42, 0x20, 0x00, 0x00]; // Real 40.0
2108        let write_prop = WritePropertyRequest::new(object_id, 85, property_value.clone());
2109
2110        assert_eq!(write_prop.object_identifier.instance, 1);
2111        assert_eq!(write_prop.property_identifier, 85);
2112        assert_eq!(write_prop.property_value, property_value);
2113        assert_eq!(write_prop.priority, None);
2114
2115        // Test with priority
2116        let write_prop_priority =
2117            WritePropertyRequest::with_priority(object_id, 85, property_value.clone(), 8);
2118        assert_eq!(write_prop_priority.priority, Some(8));
2119
2120        // Test encoding/decoding
2121        let mut buffer = Vec::new();
2122        write_prop.encode(&mut buffer).unwrap();
2123        assert!(!buffer.is_empty());
2124
2125        let decoded = WritePropertyRequest::decode(&buffer).unwrap();
2126        assert_eq!(decoded.object_identifier.instance, 1);
2127        assert_eq!(decoded.property_identifier, 85);
2128        assert_eq!(decoded.property_value, property_value);
2129    }
2130
2131    #[test]
2132    fn test_read_property_multiple_request() {
2133        let object_id1 = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
2134        let object_id2 = ObjectIdentifier::new(ObjectType::BinaryInput, 2);
2135
2136        let prop_ref1 = PropertyReference::new(PropertyIdentifier::PresentValue); // Present Value
2137        let prop_ref2 = PropertyReference::new(PropertyIdentifier::ObjectName); // Object Name
2138        let prop_ref3 = PropertyReference::with_array_index(PropertyIdentifier::PriorityArray, 8); // Priority Array[8]
2139
2140        let spec1 = ReadAccessSpecification::new(object_id1, vec![prop_ref1, prop_ref2]);
2141        let spec2 = ReadAccessSpecification::new(object_id2, vec![prop_ref3]);
2142
2143        let rpm_request = ReadPropertyMultipleRequest::new(vec![spec1, spec2]);
2144
2145        assert_eq!(rpm_request.read_access_specifications.len(), 2);
2146        assert_eq!(
2147            rpm_request.read_access_specifications[0]
2148                .property_references
2149                .len(),
2150            2
2151        );
2152        assert_eq!(
2153            rpm_request.read_access_specifications[1]
2154                .property_references
2155                .len(),
2156            1
2157        );
2158        assert_eq!(
2159            rpm_request.read_access_specifications[1].property_references[0].property_array_index,
2160            Some(8)
2161        );
2162    }
2163
2164    #[test]
2165    fn test_subscribe_cov_request() {
2166        let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
2167        let cov_req = SubscribeCovRequest::new(123, object_id);
2168
2169        assert_eq!(cov_req.subscriber_process_identifier, 123);
2170        assert_eq!(cov_req.monitored_object_identifier.instance, 1);
2171        assert_eq!(cov_req.issue_confirmed_notifications, None);
2172        assert_eq!(cov_req.lifetime, None);
2173
2174        // Test with confirmation
2175        let cov_confirmed = SubscribeCovRequest::with_confirmation(123, object_id, true);
2176        assert_eq!(cov_confirmed.issue_confirmed_notifications, Some(true));
2177
2178        // Test with lifetime
2179        let cov_lifetime = SubscribeCovRequest::with_lifetime(123, object_id, 3600);
2180        assert_eq!(cov_lifetime.lifetime, Some(3600));
2181
2182        // Test encoding
2183        let mut buffer = Vec::new();
2184        cov_req.encode(&mut buffer).unwrap();
2185        assert!(!buffer.is_empty());
2186    }
2187
2188    #[test]
2189    fn test_cov_subscription_manager() {
2190        let mut manager = CovSubscriptionManager::new();
2191
2192        let device_id = ObjectIdentifier::new(ObjectType::Device, 1);
2193        let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
2194
2195        let subscription = CovSubscription::new(123, device_id, object_id, 3600);
2196        manager.add_subscription(subscription);
2197
2198        assert_eq!(manager.active_count(), 1);
2199
2200        let subscriptions = manager.get_subscriptions_for_object(object_id);
2201        assert_eq!(subscriptions.len(), 1);
2202        assert_eq!(subscriptions[0].subscriber_process_identifier, 123);
2203
2204        // Test time updates
2205        manager.update_timers(1800); // 30 minutes
2206        let subscriptions = manager.get_subscriptions_for_object(object_id);
2207        assert_eq!(subscriptions[0].time_remaining, 1800);
2208
2209        // Test expiration
2210        manager.update_timers(1800); // Another 30 minutes
2211        assert_eq!(manager.active_count(), 0);
2212
2213        manager.cleanup_expired();
2214        assert_eq!(manager.subscriptions.len(), 0);
2215    }
2216
2217    #[test]
2218    fn test_cov_notification_request() {
2219        let device_id = ObjectIdentifier::new(ObjectType::Device, 1);
2220        let object_id = ObjectIdentifier::new(ObjectType::AnalogInput, 1);
2221        let values = vec![
2222            crate::object::PropertyValue::Real(25.5), // Present Value
2223            crate::object::PropertyValue::Boolean(false), // Status Flags
2224        ];
2225
2226        let notification = CovNotificationRequest::new(123, device_id, object_id, 3600, values);
2227
2228        assert_eq!(notification.subscriber_process_identifier, 123);
2229        assert_eq!(notification.initiating_device_identifier, device_id);
2230        assert_eq!(notification.monitored_object_identifier, object_id);
2231        assert_eq!(notification.time_remaining, 3600);
2232        assert_eq!(notification.list_of_values.len(), 2);
2233
2234        // Test encoding
2235        let mut buffer = Vec::new();
2236        notification.encode(&mut buffer).unwrap();
2237        assert!(!buffer.is_empty());
2238    }
2239
2240    #[test]
2241    fn test_atomic_read_file_request() {
2242        let file_id = ObjectIdentifier::new(ObjectType::File, 1);
2243
2244        // Test stream access
2245        let read_stream = AtomicReadFileRequest::new_stream_access(file_id, 0, 1024);
2246        match &read_stream.access_method {
2247            FileAccessMethod::StreamAccess {
2248                file_start_position,
2249                requested_octet_count,
2250            } => {
2251                assert_eq!(*file_start_position, 0);
2252                assert_eq!(*requested_octet_count, 1024);
2253            }
2254            _ => panic!("Expected StreamAccess"),
2255        }
2256
2257        // Test record access
2258        let read_record = AtomicReadFileRequest::new_record_access(file_id, 5, 10);
2259        match &read_record.access_method {
2260            FileAccessMethod::RecordAccess {
2261                file_start_record,
2262                requested_record_count,
2263            } => {
2264                assert_eq!(*file_start_record, 5);
2265                assert_eq!(*requested_record_count, 10);
2266            }
2267            _ => panic!("Expected RecordAccess"),
2268        }
2269
2270        // Test encoding
2271        let mut buffer = Vec::new();
2272        read_stream.encode(&mut buffer).unwrap();
2273        assert!(!buffer.is_empty());
2274    }
2275
2276    #[test]
2277    fn test_atomic_read_file_response() {
2278        // Test stream access response
2279        let data = vec![1, 2, 3, 4, 5];
2280        let response_stream = AtomicReadFileResponse::new_stream_access(false, 0, data.clone());
2281        assert!(!response_stream.end_of_file);
2282
2283        match &response_stream.access_method_result {
2284            FileAccessMethodResult::StreamAccess {
2285                file_start_position,
2286                file_data,
2287            } => {
2288                assert_eq!(*file_start_position, 0);
2289                assert_eq!(*file_data, data);
2290            }
2291            _ => panic!("Expected StreamAccess result"),
2292        }
2293
2294        // Test record access response
2295        let records = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
2296        let response_record = AtomicReadFileResponse::new_record_access(true, 10, records.clone());
2297        assert!(response_record.end_of_file);
2298
2299        match &response_record.access_method_result {
2300            FileAccessMethodResult::RecordAccess {
2301                file_start_record,
2302                record_count,
2303                file_record_data,
2304            } => {
2305                assert_eq!(*file_start_record, 10);
2306                assert_eq!(*record_count, 3);
2307                assert_eq!(*file_record_data, records);
2308            }
2309            _ => panic!("Expected RecordAccess result"),
2310        }
2311    }
2312
2313    #[test]
2314    fn test_atomic_write_file_request() {
2315        let file_id = ObjectIdentifier::new(ObjectType::File, 1);
2316
2317        // Test stream access
2318        let data = vec![65, 66, 67, 68]; // "ABCD"
2319        let write_stream = AtomicWriteFileRequest::new_stream_access(file_id, 100, data.clone());
2320        match &write_stream.access_method {
2321            FileWriteAccessMethod::StreamAccess {
2322                file_start_position,
2323                file_data,
2324            } => {
2325                assert_eq!(*file_start_position, 100);
2326                assert_eq!(*file_data, data);
2327            }
2328            _ => panic!("Expected StreamAccess"),
2329        }
2330
2331        // Test record access
2332        let records = vec![
2333            b"Record 1".to_vec(),
2334            b"Record 2".to_vec(),
2335            b"Record 3".to_vec(),
2336        ];
2337        let write_record = AtomicWriteFileRequest::new_record_access(file_id, 5, records.clone());
2338        match &write_record.access_method {
2339            FileWriteAccessMethod::RecordAccess {
2340                file_start_record,
2341                record_count,
2342                file_record_data,
2343            } => {
2344                assert_eq!(*file_start_record, 5);
2345                assert_eq!(*record_count, 3);
2346                assert_eq!(*file_record_data, records);
2347            }
2348            _ => panic!("Expected RecordAccess"),
2349        }
2350
2351        // Test encoding
2352        let mut buffer = Vec::new();
2353        write_stream.encode(&mut buffer).unwrap();
2354        assert!(!buffer.is_empty());
2355    }
2356
2357    #[test]
2358    fn test_atomic_write_file_response() {
2359        let response = AtomicWriteFileResponse {
2360            file_start_position: 150,
2361        };
2362        assert_eq!(response.file_start_position, 150);
2363    }
2364
2365    #[test]
2366    fn test_bacnet_datetime() {
2367        // Test creating specific datetime
2368        let date = crate::object::Date {
2369            year: 2024,
2370            month: 3,
2371            day: 15,
2372            weekday: 5,
2373        };
2374        let time = crate::object::Time {
2375            hour: 14,
2376            minute: 30,
2377            second: 45,
2378            hundredths: 50,
2379        };
2380        let datetime = BacnetDateTime::new(date, time);
2381        assert_eq!(datetime.date.year, 2024);
2382        assert_eq!(datetime.date.month, 3);
2383        assert_eq!(datetime.date.day, 15);
2384        assert_eq!(datetime.date.weekday, 5); // Friday
2385        assert_eq!(datetime.time.hour, 14);
2386        assert_eq!(datetime.time.minute, 30);
2387        assert_eq!(datetime.time.second, 45);
2388        assert_eq!(datetime.time.hundredths, 50);
2389
2390        // Test unspecified datetime
2391        let unspecified = BacnetDateTime::unspecified();
2392        assert!(unspecified.is_unspecified());
2393        assert_eq!(unspecified.date.year, 255);
2394        assert_eq!(unspecified.time.hour, 255);
2395
2396        // Test encoding/decoding
2397        let mut buffer = Vec::new();
2398        datetime.encode(&mut buffer).unwrap();
2399        assert_eq!(buffer.len(), 10); // 1 byte tag + 4 bytes date + 1 byte tag + 4 bytes time
2400
2401        let (decoded, consumed) = BacnetDateTime::decode(&buffer).unwrap();
2402        assert_eq!(consumed, 10);
2403        assert_eq!(decoded, datetime);
2404    }
2405
2406    #[test]
2407    fn test_time_synchronization_request() {
2408        let date = crate::object::Date {
2409            year: 2024,
2410            month: 6,
2411            day: 20,
2412            weekday: 4,
2413        };
2414        let time = crate::object::Time {
2415            hour: 10,
2416            minute: 15,
2417            second: 30,
2418            hundredths: 25,
2419        };
2420        let datetime = BacnetDateTime::new(date, time);
2421        let time_sync = TimeSynchronizationRequest::new(datetime);
2422
2423        assert_eq!(time_sync.date_time, datetime);
2424
2425        // Test encoding/decoding
2426        let mut buffer = Vec::new();
2427        time_sync.encode(&mut buffer).unwrap();
2428        assert_eq!(buffer.len(), 10);
2429
2430        let decoded = TimeSynchronizationRequest::decode(&buffer).unwrap();
2431        assert_eq!(decoded.date_time, datetime);
2432    }
2433
2434    #[test]
2435    fn test_utc_time_synchronization_request() {
2436        let date = crate::object::Date {
2437            year: 2024,
2438            month: 6,
2439            day: 20,
2440            weekday: 4,
2441        };
2442        let time = crate::object::Time {
2443            hour: 18,
2444            minute: 45,
2445            second: 15,
2446            hundredths: 75,
2447        };
2448        let utc_datetime = BacnetDateTime::new(date, time);
2449        let utc_sync = UtcTimeSynchronizationRequest::new(utc_datetime);
2450
2451        assert_eq!(utc_sync.utc_date_time, utc_datetime);
2452
2453        // Test encoding/decoding
2454        let mut buffer = Vec::new();
2455        utc_sync.encode(&mut buffer).unwrap();
2456        assert_eq!(buffer.len(), 10);
2457
2458        let decoded = UtcTimeSynchronizationRequest::decode(&buffer).unwrap();
2459        assert_eq!(decoded.utc_date_time, utc_datetime);
2460    }
2461
2462    #[cfg(feature = "std")]
2463    #[test]
2464    fn test_time_synchronization_now() {
2465        // Test creating time sync with current time
2466        let now_sync = TimeSynchronizationRequest::now();
2467        assert!(!now_sync.date_time.is_unspecified());
2468
2469        let utc_now_sync = UtcTimeSynchronizationRequest::now();
2470        assert!(!utc_now_sync.utc_date_time.is_unspecified());
2471
2472        // The current time should be reasonable
2473        assert!(now_sync.date_time.date.year >= 2024);
2474        assert!(now_sync.date_time.date.month >= 1 && now_sync.date_time.date.month <= 12);
2475        assert!(now_sync.date_time.date.day >= 1 && now_sync.date_time.date.day <= 31);
2476        assert!(now_sync.date_time.time.hour <= 23);
2477        assert!(now_sync.date_time.time.minute <= 59);
2478        assert!(now_sync.date_time.time.second <= 59);
2479        assert!(now_sync.date_time.time.hundredths <= 99);
2480    }
2481
2482    #[test]
2483    fn test_read_property_multiple_response() {
2484        let data = [
2485            0xc, 0x0, 0x80, 0x75, 0x3b, 0x1e, 0x29, 0x4b, 0x4e, 0xc4, 0x0, 0x80, 0x75, 0x3b, 0x4f,
2486            0x29, 0x4d, 0x4e, 0x75, 0xc, 0x0, 0x48, 0x53, 0x31, 0x43, 0x75, 0x72, 0x76, 0x65, 0x5f,
2487            0x59, 0x33, 0x4f, 0x29, 0x4f, 0x4e, 0x91, 0x2, 0x4f, 0x29, 0x55, 0x4e, 0x44, 0x42,
2488            0x6c, 0x0, 0x0, 0x4f, 0x29, 0x6f, 0x4e, 0x82, 0x4, 0x0, 0x4f, 0x29, 0x24, 0x4e, 0x91,
2489            0x0, 0x4f, 0x29, 0x51, 0x4e, 0x10, 0x4f, 0x29, 0x75, 0x4e, 0x91, 0x3e, 0x4f, 0x29,
2490            0x1c, 0x4e, 0x75, 0x43, 0x0, 0x53, 0x65, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20,
2491            0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x69, 0x72, 0x64, 0x20, 0x63, 0x75, 0x72, 0x76,
2492            0x65, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6f, 0x75, 0x74,
2493            0x64, 0x6f, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x65, 0x6e, 0x73, 0x61, 0x74,
2494            0x65, 0x64, 0x20, 0x73, 0x65, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x48, 0x53,
2495            0x31, 0x4f, 0x29, 0x67, 0x4e, 0x91, 0x0, 0x4f, 0x1f,
2496        ];
2497
2498        let response = ReadPropertyMultipleResponse::decode(&data).unwrap();
2499        assert_eq!(response.read_access_results.len(), 1);
2500
2501        let result = &response.read_access_results[0];
2502        assert_eq!(
2503            result.object_identifier,
2504            ObjectIdentifier::new(ObjectType::AnalogValue, 30011)
2505        );
2506        assert_eq!(result.results.len(), 10);
2507        assert_eq!(
2508            result.results[0].property_identifier,
2509            PropertyIdentifier::ObjectIdentifier
2510        );
2511        assert_eq!(
2512            result.results[0].value,
2513            PropertyResultValue::Value(vec![property::PropertyValue::ObjectIdentifier(
2514                ObjectIdentifier::new(ObjectType::AnalogValue, 30011)
2515            )])
2516        );
2517        assert_eq!(
2518            result.results[1].property_identifier,
2519            PropertyIdentifier::ObjectName
2520        );
2521        assert_eq!(
2522            result.results[1].value,
2523            PropertyResultValue::Value(vec![property::PropertyValue::CharacterString(
2524                "HS1Curve_Y3".to_string()
2525            )])
2526        );
2527        assert_eq!(
2528            result.results[2].property_identifier,
2529            PropertyIdentifier::ObjectType
2530        );
2531        assert_eq!(
2532            result.results[2].value,
2533            PropertyResultValue::Value(vec![property::PropertyValue::Enumerated(
2534                ObjectType::AnalogValue.into()
2535            )])
2536        );
2537        assert_eq!(
2538            result.results[3].property_identifier,
2539            PropertyIdentifier::PresentValue
2540        );
2541        assert_eq!(
2542            result.results[3].value,
2543            PropertyResultValue::Value(vec![property::PropertyValue::Real(59.0)])
2544        );
2545        assert_eq!(
2546            result.results[4].property_identifier,
2547            PropertyIdentifier::StatusFlags
2548        );
2549        assert_eq!(
2550            result.results[4].value,
2551            PropertyResultValue::Value(vec![property::PropertyValue::BitString(vec![
2552                false, false, false, false
2553            ])])
2554        );
2555        assert_eq!(
2556            result.results[5].property_identifier,
2557            PropertyIdentifier::EventState
2558        );
2559        assert_eq!(
2560            result.results[5].value,
2561            PropertyResultValue::Value(vec![property::PropertyValue::Enumerated(0)])
2562        );
2563        assert_eq!(
2564            result.results[6].property_identifier,
2565            PropertyIdentifier::OutOfService
2566        );
2567        assert_eq!(
2568            result.results[6].value,
2569            PropertyResultValue::Value(vec![property::PropertyValue::Boolean(false)])
2570        );
2571        assert_eq!(
2572            result.results[7].property_identifier,
2573            PropertyIdentifier::Units
2574        );
2575        assert_eq!(
2576            result.results[7].value,
2577            PropertyResultValue::Value(vec![property::PropertyValue::Enumerated(62)])
2578        );
2579        assert_eq!(
2580            result.results[8].property_identifier,
2581            PropertyIdentifier::Description
2582        );
2583        assert_eq!(
2584            result.results[8].value,
2585            PropertyResultValue::Value(vec![property::PropertyValue::CharacterString(
2586                "Setpoint for third curvepoint for outdoor compensated setpoint HS1".to_string()
2587            )])
2588        );
2589        assert_eq!(
2590            result.results[9].property_identifier,
2591            PropertyIdentifier::Reliability
2592        );
2593        assert_eq!(
2594            result.results[9].value,
2595            PropertyResultValue::Value(vec![property::PropertyValue::Enumerated(0)])
2596        );
2597    }
2598
2599    #[test]
2600    fn test_read_property_response() {
2601        let data = [
2602            0xc, 0x2, 0x0, 0xa, 0x50, 0x19, 0x4d, 0x3e, 0x75, 0xf, 0x0, 0x43, 0x6f, 0x72, 0x72,
2603            0x69, 0x67, 0x6f, 0x48, 0x65, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x3f,
2604        ];
2605
2606        let response = ReadPropertyResponse::decode(&data).unwrap();
2607
2608        assert_eq!(
2609            response.object_identifier,
2610            ObjectIdentifier::new(ObjectType::Device, 2640)
2611        );
2612        assert_eq!(response.property_identifier, PropertyIdentifier::ObjectName);
2613        assert_eq!(response.property_array_index, None);
2614        assert_eq!(response.property_values.len(), 1);
2615        assert_eq!(
2616            response.property_values[0],
2617            property::PropertyValue::CharacterString("CorrigoHeating".to_string())
2618        );
2619
2620        let mut encoded = Vec::new();
2621        response.encode(&mut encoded).unwrap();
2622        assert_eq!(encoded.len(), data.len());
2623        assert_eq!(encoded, data);
2624    }
2625}