1use super::*;
2
3fn validate_read_range_ack(
4 request: &bacnet_services::read_range::ReadRangeRequest,
5 ack: &bacnet_services::read_range::ReadRangeAck,
6) -> Result<(), Error> {
7 if ack.object_identifier != request.object_identifier {
8 return Err(Error::decoding(
9 0,
10 "ReadRange ACK object identifier does not match the request",
11 ));
12 }
13 if ack.property_identifier != request.property_identifier {
14 return Err(Error::decoding(
15 0,
16 "ReadRange ACK property identifier does not match the request",
17 ));
18 }
19 if ack.property_array_index != request.property_array_index {
20 return Err(Error::decoding(
21 0,
22 "ReadRange ACK array index does not match the request",
23 ));
24 }
25
26 let sequence_range = matches!(
27 request.range.as_ref(),
28 Some(
29 bacnet_services::read_range::RangeSpec::BySequenceNumber { .. }
30 | bacnet_services::read_range::RangeSpec::ByTime { .. }
31 )
32 );
33 match (sequence_range, ack.item_count, ack.first_sequence_number) {
34 (true, 1.., Some(1..)) | (true, 0, None) | (false, _, None) => {}
35 (true, 1.., _) => {
36 return Err(Error::decoding(
37 0,
38 "nonempty ReadRange By Sequence/Time ACK requires a nonzero first sequence number",
39 ));
40 }
41 _ => {
42 return Err(Error::decoding(
43 0,
44 "ReadRange ACK first sequence number is invalid for the request range",
45 ));
46 }
47 }
48
49 Ok(())
50}
51
52fn validate_atomic_read_file_ack(
53 request: &bacnet_services::file::FileAccessMethod,
54 ack: &bacnet_services::file::AtomicReadFileAck,
55) -> Result<(), Error> {
56 use bacnet_services::file::{FileAccessMethod, FileReadAckMethod};
57
58 match (request, &ack.access) {
59 (
60 FileAccessMethod::Stream {
61 requested_octet_count,
62 ..
63 },
64 FileReadAckMethod::Stream { file_data, .. },
65 ) if file_data.len() <= *requested_octet_count as usize => Ok(()),
66 (FileAccessMethod::Stream { .. }, FileReadAckMethod::Stream { .. }) => Err(
67 Error::decoding(0, "AtomicReadFile ACK exceeds the requested octet window"),
68 ),
69 (
70 FileAccessMethod::Record {
71 requested_record_count,
72 ..
73 },
74 FileReadAckMethod::Record {
75 returned_record_count,
76 file_record_data,
77 ..
78 },
79 ) if returned_record_count <= requested_record_count
80 && *returned_record_count as usize == file_record_data.len() =>
81 {
82 Ok(())
83 }
84 (FileAccessMethod::Record { .. }, FileReadAckMethod::Record { .. }) => Err(
85 Error::decoding(0, "AtomicReadFile ACK exceeds the requested record window"),
86 ),
87 _ => Err(Error::decoding(
88 0,
89 "AtomicReadFile ACK access method does not match the request",
90 )),
91 }
92}
93
94impl<T: TransportPort + 'static> BACnetClient<T> {
95 pub async fn get_event_information(
97 &self,
98 destination_mac: &[u8],
99 last_received_object_identifier: Option<bacnet_types::primitives::ObjectIdentifier>,
100 ) -> Result<Bytes, Error> {
101 use bacnet_services::alarm_event::GetEventInformationRequest;
102
103 let request = GetEventInformationRequest {
104 last_received_object_identifier,
105 };
106 let mut buf = BytesMut::new();
107 request.encode(&mut buf);
108
109 self.confirmed_request(
110 destination_mac,
111 ConfirmedServiceChoice::GET_EVENT_INFORMATION,
112 &buf,
113 )
114 .await
115 }
116
117 pub async fn acknowledge_alarm_request(
119 &self,
120 destination_mac: &[u8],
121 request: &bacnet_services::alarm_event::AcknowledgeAlarmRequest,
122 ) -> Result<(), Error> {
123 let mut buf = BytesMut::new();
124 request.encode(&mut buf)?;
125
126 let _ = self
127 .confirmed_request(
128 destination_mac,
129 ConfirmedServiceChoice::ACKNOWLEDGE_ALARM,
130 &buf,
131 )
132 .await?;
133
134 Ok(())
135 }
136
137 #[deprecated(
144 note = "use acknowledge_alarm_request with caller-supplied correlation timestamps"
145 )]
146 pub async fn acknowledge_alarm(
147 &self,
148 destination_mac: &[u8],
149 acknowledging_process_identifier: u32,
150 event_object_identifier: bacnet_types::primitives::ObjectIdentifier,
151 event_state_acknowledged: u32,
152 acknowledgment_source: &str,
153 ) -> Result<(), Error> {
154 use bacnet_services::alarm_event::AcknowledgeAlarmRequest;
155
156 let request = AcknowledgeAlarmRequest {
157 acknowledging_process_identifier,
158 event_object_identifier,
159 event_state_acknowledged,
160 timestamp: bacnet_types::primitives::BACnetTimeStamp::SequenceNumber(0),
161 acknowledgment_source: acknowledgment_source.to_string(),
162 time_of_acknowledgment: bacnet_types::primitives::BACnetTimeStamp::SequenceNumber(0),
163 };
164 self.acknowledge_alarm_request(destination_mac, &request)
165 .await
166 }
167
168 pub async fn read_range(
170 &self,
171 destination_mac: &[u8],
172 object_identifier: bacnet_types::primitives::ObjectIdentifier,
173 property_identifier: bacnet_types::enums::PropertyIdentifier,
174 property_array_index: Option<u32>,
175 range: Option<bacnet_services::read_range::RangeSpec>,
176 ) -> Result<bacnet_services::read_range::ReadRangeAck, Error> {
177 use bacnet_services::read_range::{ReadRangeAck, ReadRangeRequest};
178
179 let request = ReadRangeRequest {
180 object_identifier,
181 property_identifier,
182 property_array_index,
183 range,
184 };
185 let mut buf = BytesMut::new();
186 request.encode(&mut buf);
187
188 let response_data = self
189 .confirmed_request(destination_mac, ConfirmedServiceChoice::READ_RANGE, &buf)
190 .await?;
191
192 let ack = ReadRangeAck::decode(&response_data)?;
193 validate_read_range_ack(&request, &ack)?;
194 Ok(ack)
195 }
196
197 pub async fn atomic_read_file(
199 &self,
200 destination_mac: &[u8],
201 file_identifier: bacnet_types::primitives::ObjectIdentifier,
202 access: bacnet_services::file::FileAccessMethod,
203 ) -> Result<Bytes, Error> {
204 use bacnet_services::file::AtomicReadFileRequest;
205
206 let request = AtomicReadFileRequest {
207 file_identifier,
208 access,
209 };
210 let mut buf = BytesMut::new();
211 request.encode(&mut buf);
212
213 self.confirmed_request(
214 destination_mac,
215 ConfirmedServiceChoice::ATOMIC_READ_FILE,
216 &buf,
217 )
218 .await
219 }
220
221 pub async fn atomic_read_file_decoded(
228 &self,
229 destination_mac: &[u8],
230 file_identifier: bacnet_types::primitives::ObjectIdentifier,
231 access: bacnet_services::file::FileAccessMethod,
232 ) -> Result<bacnet_services::file::AtomicReadFileAck, Error> {
233 let requested_access = access.clone();
234 let response = self
235 .atomic_read_file(destination_mac, file_identifier, access)
236 .await?;
237 let ack = bacnet_services::file::AtomicReadFileAck::decode(&response)?;
238 validate_atomic_read_file_ack(&requested_access, &ack)?;
239 Ok(ack)
240 }
241
242 pub async fn atomic_write_file(
244 &self,
245 destination_mac: &[u8],
246 file_identifier: bacnet_types::primitives::ObjectIdentifier,
247 access: bacnet_services::file::FileWriteAccessMethod,
248 ) -> Result<Bytes, Error> {
249 use bacnet_services::file::AtomicWriteFileRequest;
250
251 let request = AtomicWriteFileRequest {
252 file_identifier,
253 access,
254 };
255 let mut buf = BytesMut::new();
256 request.encode(&mut buf);
257
258 self.confirmed_request(
259 destination_mac,
260 ConfirmedServiceChoice::ATOMIC_WRITE_FILE,
261 &buf,
262 )
263 .await
264 }
265
266 pub async fn add_list_element(
268 &self,
269 destination_mac: &[u8],
270 object_identifier: bacnet_types::primitives::ObjectIdentifier,
271 property_identifier: bacnet_types::enums::PropertyIdentifier,
272 property_array_index: Option<u32>,
273 list_of_elements: Vec<u8>,
274 ) -> Result<(), Error> {
275 use bacnet_services::list_manipulation::ListElementRequest;
276
277 let request = ListElementRequest {
278 object_identifier,
279 property_identifier,
280 property_array_index,
281 list_of_elements,
282 };
283 let mut buf = BytesMut::new();
284 request.encode(&mut buf);
285
286 let _ = self
287 .confirmed_request(
288 destination_mac,
289 ConfirmedServiceChoice::ADD_LIST_ELEMENT,
290 &buf,
291 )
292 .await?;
293
294 Ok(())
295 }
296
297 pub async fn remove_list_element(
299 &self,
300 destination_mac: &[u8],
301 object_identifier: bacnet_types::primitives::ObjectIdentifier,
302 property_identifier: bacnet_types::enums::PropertyIdentifier,
303 property_array_index: Option<u32>,
304 list_of_elements: Vec<u8>,
305 ) -> Result<(), Error> {
306 use bacnet_services::list_manipulation::ListElementRequest;
307
308 let request = ListElementRequest {
309 object_identifier,
310 property_identifier,
311 property_array_index,
312 list_of_elements,
313 };
314 let mut buf = BytesMut::new();
315 request.encode(&mut buf);
316
317 let _ = self
318 .confirmed_request(
319 destination_mac,
320 ConfirmedServiceChoice::REMOVE_LIST_ELEMENT,
321 &buf,
322 )
323 .await?;
324
325 Ok(())
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use bacnet_services::read_range::{RangeSpec, ReadRangeAck, ReadRangeRequest};
333 use bacnet_types::enums::{ObjectType, PropertyIdentifier};
334 use bacnet_types::primitives::{Date, ObjectIdentifier, Time};
335
336 fn request(range: Option<RangeSpec>) -> ReadRangeRequest {
337 ReadRangeRequest {
338 object_identifier: ObjectIdentifier::new(ObjectType::TREND_LOG, 1).unwrap(),
339 property_identifier: PropertyIdentifier::LOG_BUFFER,
340 property_array_index: Some(1),
341 range,
342 }
343 }
344
345 fn ack(request: &ReadRangeRequest, item_count: u32, first: Option<u32>) -> ReadRangeAck {
346 ReadRangeAck {
347 object_identifier: request.object_identifier,
348 property_identifier: request.property_identifier,
349 property_array_index: request.property_array_index,
350 result_flags: (true, true, false),
351 item_count,
352 item_data: Vec::new(),
353 first_sequence_number: first,
354 }
355 }
356
357 #[test]
358 fn read_range_ack_must_echo_the_request() {
359 let request = request(Some(RangeSpec::ByPosition {
360 reference_index: 1,
361 count: 1,
362 }));
363 let valid = ack(&request, 1, None);
364 assert!(validate_read_range_ack(&request, &valid).is_ok());
365
366 let mut wrong_object = valid.clone();
367 wrong_object.object_identifier = ObjectIdentifier::new(ObjectType::TREND_LOG, 2).unwrap();
368 assert!(validate_read_range_ack(&request, &wrong_object).is_err());
369
370 let mut wrong_property = valid.clone();
371 wrong_property.property_identifier = PropertyIdentifier::PRESENT_VALUE;
372 assert!(validate_read_range_ack(&request, &wrong_property).is_err());
373
374 let mut wrong_index = valid;
375 wrong_index.property_array_index = Some(2);
376 assert!(validate_read_range_ack(&request, &wrong_index).is_err());
377 }
378
379 #[test]
380 fn first_sequence_number_must_match_the_requested_range() {
381 let by_sequence = request(Some(RangeSpec::BySequenceNumber {
382 reference_seq: 1,
383 count: 1,
384 }));
385 assert!(validate_read_range_ack(&by_sequence, &ack(&by_sequence, 1, Some(1))).is_ok());
386 assert!(validate_read_range_ack(&by_sequence, &ack(&by_sequence, 1, None)).is_err());
387 assert!(validate_read_range_ack(&by_sequence, &ack(&by_sequence, 1, Some(0))).is_err());
388 assert!(validate_read_range_ack(&by_sequence, &ack(&by_sequence, 0, None)).is_ok());
389 assert!(validate_read_range_ack(&by_sequence, &ack(&by_sequence, 0, Some(1))).is_err());
390
391 let by_time = request(Some(RangeSpec::ByTime {
392 reference_time: (
393 Date {
394 year: 126,
395 month: 3,
396 day: 1,
397 day_of_week: 7,
398 },
399 Time {
400 hour: 14,
401 minute: 30,
402 second: 0,
403 hundredths: 0,
404 },
405 ),
406 count: 1,
407 }));
408 assert!(validate_read_range_ack(&by_time, &ack(&by_time, 1, Some(1))).is_ok());
409 assert!(validate_read_range_ack(&by_time, &ack(&by_time, 1, None)).is_err());
410 assert!(validate_read_range_ack(&by_time, &ack(&by_time, 1, Some(0))).is_err());
411
412 let by_position = request(Some(RangeSpec::ByPosition {
413 reference_index: 1,
414 count: 1,
415 }));
416 assert!(validate_read_range_ack(&by_position, &ack(&by_position, 1, Some(1))).is_err());
417
418 let no_range = request(None);
419 assert!(validate_read_range_ack(&no_range, &ack(&no_range, 1, Some(1))).is_err());
420 }
421}
422
423#[cfg(test)]
424#[path = "file_list_decoded_tests.rs"]
425mod decoded_tests;