Skip to main content

bacnet_client/client/
file_list.rs

1use super::*;
2
3fn validate_atomic_read_file_ack(
4    request: &bacnet_services::file::FileAccessMethod,
5    ack: &bacnet_services::file::AtomicReadFileAck,
6) -> Result<(), Error> {
7    use bacnet_services::file::{FileAccessMethod, FileReadAckMethod};
8
9    match (request, &ack.access) {
10        (
11            FileAccessMethod::Stream {
12                requested_octet_count,
13                ..
14            },
15            FileReadAckMethod::Stream { file_data, .. },
16        ) if file_data.len() <= *requested_octet_count as usize => Ok(()),
17        (FileAccessMethod::Stream { .. }, FileReadAckMethod::Stream { .. }) => Err(
18            Error::decoding(0, "AtomicReadFile ACK exceeds the requested octet window"),
19        ),
20        (
21            FileAccessMethod::Record {
22                requested_record_count,
23                ..
24            },
25            FileReadAckMethod::Record {
26                returned_record_count,
27                file_record_data,
28                ..
29            },
30        ) if returned_record_count <= requested_record_count
31            && *returned_record_count as usize == file_record_data.len() =>
32        {
33            Ok(())
34        }
35        (FileAccessMethod::Record { .. }, FileReadAckMethod::Record { .. }) => Err(
36            Error::decoding(0, "AtomicReadFile ACK exceeds the requested record window"),
37        ),
38        _ => Err(Error::decoding(
39            0,
40            "AtomicReadFile ACK access method does not match the request",
41        )),
42    }
43}
44
45impl<T: TransportPort + 'static> BACnetClient<T> {
46    /// Get event information from a remote device.
47    pub async fn get_event_information(
48        &self,
49        destination_mac: &[u8],
50        last_received_object_identifier: Option<bacnet_types::primitives::ObjectIdentifier>,
51    ) -> Result<Bytes, Error> {
52        use bacnet_services::alarm_event::GetEventInformationRequest;
53
54        let request = GetEventInformationRequest {
55            last_received_object_identifier,
56        };
57        let mut buf = BytesMut::new();
58        request.encode(&mut buf);
59
60        self.confirmed_request(
61            destination_mac,
62            ConfirmedServiceChoice::GET_EVENT_INFORMATION,
63            &buf,
64        )
65        .await
66    }
67
68    /// Send a caller-supplied AcknowledgeAlarm request without fabricating fields.
69    pub async fn acknowledge_alarm_request(
70        &self,
71        destination_mac: &[u8],
72        request: &bacnet_services::alarm_event::AcknowledgeAlarmRequest,
73    ) -> Result<(), Error> {
74        let mut buf = BytesMut::new();
75        request.encode(&mut buf)?;
76
77        let _ = self
78            .confirmed_request(
79                destination_mac,
80                ConfirmedServiceChoice::ACKNOWLEDGE_ALARM,
81                &buf,
82            )
83            .await?;
84
85        Ok(())
86    }
87
88    /// Acknowledge an alarm on a remote device.
89    ///
90    /// This compatibility helper fabricates sequence-zero timestamps, which
91    /// are not a valid general correlation mechanism. Use
92    /// [`Self::acknowledge_alarm_request`] with the original notification's
93    /// exact timestamp and a caller-selected acknowledgment time.
94    #[deprecated(
95        note = "use acknowledge_alarm_request with caller-supplied correlation timestamps"
96    )]
97    pub async fn acknowledge_alarm(
98        &self,
99        destination_mac: &[u8],
100        acknowledging_process_identifier: u32,
101        event_object_identifier: bacnet_types::primitives::ObjectIdentifier,
102        event_state_acknowledged: bacnet_types::enums::EventState,
103        acknowledgment_source: &str,
104    ) -> Result<(), Error> {
105        use bacnet_services::alarm_event::AcknowledgeAlarmRequest;
106
107        let request = AcknowledgeAlarmRequest {
108            acknowledging_process_identifier,
109            event_object_identifier,
110            event_state_acknowledged,
111            timestamp: bacnet_types::primitives::BACnetTimeStamp::SequenceNumber(0),
112            acknowledgment_source: acknowledgment_source.to_string(),
113            time_of_acknowledgment: bacnet_types::primitives::BACnetTimeStamp::SequenceNumber(0),
114        };
115        self.acknowledge_alarm_request(destination_mac, &request)
116            .await
117    }
118
119    /// Read file data from a remote device (stream or record access).
120    pub async fn atomic_read_file(
121        &self,
122        destination_mac: &[u8],
123        file_identifier: bacnet_types::primitives::ObjectIdentifier,
124        access: bacnet_services::file::FileAccessMethod,
125    ) -> Result<Bytes, Error> {
126        use bacnet_services::file::AtomicReadFileRequest;
127
128        let request = AtomicReadFileRequest {
129            file_identifier,
130            access,
131        };
132        let mut buf = BytesMut::new();
133        request.encode(&mut buf);
134
135        self.confirmed_request(
136            destination_mac,
137            ConfirmedServiceChoice::ATOMIC_READ_FILE,
138            &buf,
139        )
140        .await
141    }
142
143    /// Read and strictly decode one AtomicReadFile ACK window.
144    ///
145    /// This additive typed boundary validates that the ACK uses the requested
146    /// stream/record access arm and does not return more octets or records than
147    /// requested. Use [`Self::atomic_read_file`] when the raw encoded service
148    /// payload is required for compatibility.
149    pub async fn atomic_read_file_decoded(
150        &self,
151        destination_mac: &[u8],
152        file_identifier: bacnet_types::primitives::ObjectIdentifier,
153        access: bacnet_services::file::FileAccessMethod,
154    ) -> Result<bacnet_services::file::AtomicReadFileAck, Error> {
155        let requested_access = access.clone();
156        let response = self
157            .atomic_read_file(destination_mac, file_identifier, access)
158            .await?;
159        let ack = bacnet_services::file::AtomicReadFileAck::decode(&response)?;
160        validate_atomic_read_file_ack(&requested_access, &ack)?;
161        Ok(ack)
162    }
163
164    /// Write file data to a remote device (stream or record access).
165    pub async fn atomic_write_file(
166        &self,
167        destination_mac: &[u8],
168        file_identifier: bacnet_types::primitives::ObjectIdentifier,
169        access: bacnet_services::file::FileWriteAccessMethod,
170    ) -> Result<Bytes, Error> {
171        use bacnet_services::file::AtomicWriteFileRequest;
172
173        let request = AtomicWriteFileRequest {
174            file_identifier,
175            access,
176        };
177        let mut buf = BytesMut::new();
178        request.encode(&mut buf);
179
180        self.confirmed_request(
181            destination_mac,
182            ConfirmedServiceChoice::ATOMIC_WRITE_FILE,
183            &buf,
184        )
185        .await
186    }
187
188    /// Add elements to a list property on a remote device.
189    ///
190    /// Rejects index zero, empty elements and malformed tag framing before
191    /// transaction admission or traffic. Element datatype remains target-owned.
192    pub async fn add_list_element(
193        &self,
194        destination_mac: &[u8],
195        object_identifier: bacnet_types::primitives::ObjectIdentifier,
196        property_identifier: bacnet_types::enums::PropertyIdentifier,
197        property_array_index: Option<u32>,
198        list_of_elements: Vec<u8>,
199    ) -> Result<(), Error> {
200        use bacnet_services::list_manipulation::ListElementRequest;
201
202        let request = ListElementRequest {
203            object_identifier,
204            property_identifier,
205            property_array_index,
206            list_of_elements,
207        };
208        let mut buf = BytesMut::new();
209        request.encode(&mut buf)?;
210
211        let _ = self
212            .confirmed_request(
213                destination_mac,
214                ConfirmedServiceChoice::ADD_LIST_ELEMENT,
215                &buf,
216            )
217            .await?;
218
219        Ok(())
220    }
221
222    /// Remove elements from a list property on a remote device.
223    ///
224    /// Rejects index zero, empty elements and malformed tag framing before
225    /// transaction admission or traffic. Element datatype remains target-owned.
226    pub async fn remove_list_element(
227        &self,
228        destination_mac: &[u8],
229        object_identifier: bacnet_types::primitives::ObjectIdentifier,
230        property_identifier: bacnet_types::enums::PropertyIdentifier,
231        property_array_index: Option<u32>,
232        list_of_elements: Vec<u8>,
233    ) -> Result<(), Error> {
234        use bacnet_services::list_manipulation::ListElementRequest;
235
236        let request = ListElementRequest {
237            object_identifier,
238            property_identifier,
239            property_array_index,
240            list_of_elements,
241        };
242        let mut buf = BytesMut::new();
243        request.encode(&mut buf)?;
244
245        let _ = self
246            .confirmed_request(
247                destination_mac,
248                ConfirmedServiceChoice::REMOVE_LIST_ELEMENT,
249                &buf,
250            )
251            .await?;
252
253        Ok(())
254    }
255}
256
257#[cfg(test)]
258#[path = "file_list_decoded_tests.rs"]
259mod decoded_tests;