1#[cfg(not(feature = "std"))]
6use alloc::vec::Vec;
7
8use bacnet_encoding::{primitives, tags};
9use bacnet_types::enums::PropertyIdentifier;
10use bacnet_types::error::{DecodeError, DecodingKind, Error};
11use bacnet_types::primitives::{Date, ObjectIdentifier, Time};
12use bytes::BytesMut;
13
14use bacnet_encoding::constructed::tagged::{
15 decode_app_primitive, decode_app_unsigned, decode_ctx_object_id, decode_ctx_primitive,
16 decode_ctx_unsigned, expect_end, misplaced_tag, next_is_context,
17};
18
19fn decode_count(data: &[u8], offset: usize, field: &'static str) -> Result<(i32, usize), Error> {
20 let (content, end) = decode_app_primitive(data, offset, tags::app_tag::SIGNED, field)?;
21 let value = primitives::decode_signed(content)?;
22 let value = i16::try_from(value).map_err(|_| {
23 DecodeError::new(DecodingKind::OutOfRange, offset, "exceeds INTEGER16").within(field)
24 })?;
25 if value == 0 {
26 return Err(
27 DecodeError::new(DecodingKind::OutOfRange, offset, "may not be zero")
28 .within(field)
29 .into(),
30 );
31 }
32 Ok((i32::from(value), end))
33}
34
35fn specific_datetime(date: &Date, time: &Time) -> bool {
36 date.year != Date::UNSPECIFIED
37 && (1..=12).contains(&date.month)
38 && (1..=31).contains(&date.day)
39 && (1..=7).contains(&date.day_of_week)
40 && time.hour <= 23
41 && time.minute <= 59
42 && time.second <= 59
43 && time.hundredths <= 99
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct ReadRangeRequest {
49 pub object_identifier: ObjectIdentifier,
51 pub property_identifier: PropertyIdentifier,
53 pub property_array_index: Option<u32>,
56 pub range: Option<RangeSpec>,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum RangeSpec {
63 ByPosition {
65 reference_index: u64,
69 count: i32,
72 },
73 BySequenceNumber {
75 reference_seq: u64,
78 count: i32,
81 },
82 ByTime {
84 reference_time: (Date, Time),
86 count: i32,
90 },
91}
92
93impl ReadRangeRequest {
94 pub fn validate(&self) -> Result<(), Error> {
96 if matches!(
97 self.property_identifier,
98 PropertyIdentifier::ALL | PropertyIdentifier::REQUIRED | PropertyIdentifier::OPTIONAL
99 ) {
100 return Err(Error::encode("ReadRange property may not be ALL, REQUIRED, or OPTIONAL"));
101 }
102 if self.property_array_index == Some(0) {
103 return Err(Error::encode("ReadRange array index may not be zero"));
104 }
105 if let Some(range) = &self.range {
106 let count = match range {
107 RangeSpec::ByPosition { count, .. } | RangeSpec::BySequenceNumber { count, .. } => {
108 *count
109 }
110 RangeSpec::ByTime {
111 reference_time: (date, time),
112 count,
113 } => {
114 if !specific_datetime(date, time) {
115 return Err(Error::encode("ReadRange byTime requires a specific datetime"));
116 }
117 *count
118 }
119 };
120 if count == 0 || i16::try_from(count).is_err() {
121 return Err(Error::encode("ReadRange count must be a nonzero INTEGER16"));
122 }
123 }
124 Ok(())
125 }
126
127 pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
129 self.validate()?;
130 primitives::encode_ctx_object_id(buf, 0, &self.object_identifier);
132 primitives::encode_ctx_enumerated(buf, 1, self.property_identifier.to_raw());
134 if let Some(idx) = self.property_array_index {
136 primitives::encode_ctx_unsigned(buf, 2, idx as u64);
137 }
138 if let Some(ref range) = self.range {
140 match range {
141 RangeSpec::ByPosition {
142 reference_index,
143 count,
144 } => {
145 tags::encode_opening_tag(buf, 3);
146 primitives::encode_app_unsigned(buf, *reference_index);
147 primitives::encode_app_signed(buf, *count);
148 tags::encode_closing_tag(buf, 3);
149 }
150 RangeSpec::BySequenceNumber {
151 reference_seq,
152 count,
153 } => {
154 tags::encode_opening_tag(buf, 6);
155 primitives::encode_app_unsigned(buf, *reference_seq);
156 primitives::encode_app_signed(buf, *count);
157 tags::encode_closing_tag(buf, 6);
158 }
159 RangeSpec::ByTime {
160 reference_time,
161 count,
162 } => {
163 tags::encode_opening_tag(buf, 7);
164 primitives::encode_app_date(buf, &reference_time.0);
165 primitives::encode_app_time(buf, &reference_time.1);
166 primitives::encode_app_signed(buf, *count);
167 tags::encode_closing_tag(buf, 7);
168 }
169 }
170 }
171 Ok(())
172 }
173
174 pub fn decode(data: &[u8]) -> Result<Self, Error> {
176 let mut offset = 0;
177
178 let (object_identifier, end) =
180 decode_ctx_object_id(data, offset, 0, "ReadRange request object-id")?;
181 offset = end;
182
183 let (property_identifier, end) =
185 decode_ctx_unsigned::<u32>(data, offset, 1, "ReadRange request property-id")?;
186 let property_identifier = PropertyIdentifier::from_raw(property_identifier);
187 if matches!(
188 property_identifier,
189 PropertyIdentifier::ALL | PropertyIdentifier::REQUIRED | PropertyIdentifier::OPTIONAL
190 ) {
191 return Err(Error::decoding(
192 offset,
193 "ReadRange request property-id may not be ALL, REQUIRED, or OPTIONAL",
194 ));
195 }
196 offset = end;
197
198 let mut property_array_index = None;
200 if next_is_context(data, offset, 2)? {
201 let (index, end) =
202 decode_ctx_unsigned::<u32>(data, offset, 2, "ReadRange request array-index")?;
203 if index == 0 {
204 return Err(Error::out_of_range(
205 offset,
206 "ReadRange request array-index may not be zero",
207 ));
208 }
209 property_array_index = Some(index);
210 offset = end;
211 }
212
213 let mut range = None;
215 if offset < data.len() {
216 let (tag, tag_end) = tags::decode_tag(data, offset)?;
217 if tag.is_opening_tag(3) {
218 let (content, new_offset) = tags::extract_context_value(data, tag_end, 3)?;
220 let (reference_index, inner_offset) =
221 decode_app_unsigned::<u64>(content, 0, "ReadRange byPosition reference-index")?;
222 let (count, inner_offset) =
223 decode_count(content, inner_offset, "ReadRange byPosition count")?;
224 expect_end(content, inner_offset, inner_offset, "ReadRange byPosition")?;
225 range = Some(RangeSpec::ByPosition {
226 reference_index,
227 count,
228 });
229 offset = new_offset;
230 } else if tag.is_opening_tag(6) {
231 let (content, new_offset) = tags::extract_context_value(data, tag_end, 6)?;
233 let (reference_seq, inner_offset) = decode_app_unsigned::<u64>(
234 content,
235 0,
236 "ReadRange bySequenceNumber reference-seq",
237 )?;
238 let (count, inner_offset) =
239 decode_count(content, inner_offset, "ReadRange bySequenceNumber count")?;
240 expect_end(
241 content,
242 inner_offset,
243 inner_offset,
244 "ReadRange bySequenceNumber",
245 )?;
246 range = Some(RangeSpec::BySequenceNumber {
247 reference_seq,
248 count,
249 });
250 offset = new_offset;
251 } else if tag.is_opening_tag(7) {
252 let (content, new_offset) = tags::extract_context_value(data, tag_end, 7)?;
254 let (date, inner_offset) =
255 decode_app_primitive(content, 0, tags::app_tag::DATE, "ReadRange byTime date")?;
256 let date = Date::decode(date)?;
257 let (time, inner_offset) = decode_app_primitive(
258 content,
259 inner_offset,
260 tags::app_tag::TIME,
261 "ReadRange byTime time",
262 )?;
263 let time = Time::decode(time)?;
264 if !specific_datetime(&date, &time) {
265 return Err(Error::decoding(
266 inner_offset,
267 "ReadRange byTime requires a specific datetime",
268 ));
269 }
270 let (count, inner_offset) =
271 decode_count(content, inner_offset, "ReadRange byTime count")?;
272 expect_end(content, inner_offset, inner_offset, "ReadRange byTime")?;
273 range = Some(RangeSpec::ByTime {
274 reference_time: (date, time),
275 count,
276 });
277 offset = new_offset;
278 } else {
279 return Err(misplaced_tag(
280 data,
281 &tag,
282 None,
283 offset,
284 "ReadRange request has invalid range choice",
285 ));
286 }
287 }
288 expect_end(data, offset, offset, "ReadRange request")?;
289
290 Ok(Self {
291 object_identifier,
292 property_identifier,
293 property_array_index,
294 range,
295 })
296 }
297}
298
299#[derive(Debug, Clone)]
301pub struct ReadRangeAck {
302 pub object_identifier: ObjectIdentifier,
304 pub property_identifier: PropertyIdentifier,
306 pub property_array_index: Option<u32>,
308 pub result_flags: (bool, bool, bool),
310 pub item_count: u32,
312 pub item_data: Vec<u8>,
314 pub first_sequence_number: Option<u64>,
317}
318
319impl ReadRangeAck {
320 pub fn encode(&self, buf: &mut BytesMut) {
322 primitives::encode_ctx_object_id(buf, 0, &self.object_identifier);
324 primitives::encode_ctx_enumerated(buf, 1, self.property_identifier.to_raw());
326 if let Some(idx) = self.property_array_index {
328 primitives::encode_ctx_unsigned(buf, 2, idx as u64);
329 }
330 let mut flags: u8 = 0;
332 if self.result_flags.0 {
333 flags |= 0x80;
334 }
335 if self.result_flags.1 {
336 flags |= 0x40;
337 }
338 if self.result_flags.2 {
339 flags |= 0x20;
340 }
341 primitives::encode_ctx_bit_string(buf, 3, 5, &[flags]);
342 primitives::encode_ctx_unsigned(buf, 4, self.item_count as u64);
344 tags::encode_opening_tag(buf, 5);
346 buf.extend_from_slice(&self.item_data);
347 tags::encode_closing_tag(buf, 5);
348 if let Some(seq) = self.first_sequence_number {
350 primitives::encode_ctx_unsigned(buf, 6, seq);
351 }
352 }
353
354 pub fn decode(data: &[u8]) -> Result<Self, Error> {
357 let mut offset = 0;
358
359 let (object_identifier, end) =
361 decode_ctx_object_id(data, offset, 0, "ReadRange ACK object-id")?;
362 offset = end;
363
364 let (property_identifier, end) =
366 decode_ctx_unsigned::<u32>(data, offset, 1, "ReadRange ACK property-id")?;
367 let property_identifier = PropertyIdentifier::from_raw(property_identifier);
368 offset = end;
369
370 let mut property_array_index = None;
372 if next_is_context(data, offset, 2)? {
373 let (index, end) =
374 decode_ctx_unsigned::<u32>(data, offset, 2, "ReadRange ACK array-index")?;
375 property_array_index = Some(index);
376 offset = end;
377 }
378
379 let (content, end) = decode_ctx_primitive(data, offset, 3, "ReadRange ACK result-flags")?;
381 let (unused_bits, bits) = primitives::decode_bit_string(content)?;
382 if unused_bits != 5 || bits.len() != 1 || bits[0] & 0x1f != 0 {
383 return Err(Error::decoding(
384 offset,
385 "ReadRange ACK result-flags must contain three bits with zero padding",
386 ));
387 }
388 let b = bits[0];
389 let result_flags = (b & 0x80 != 0, b & 0x40 != 0, b & 0x20 != 0);
390 offset = end;
391
392 let (item_count, end) =
394 decode_ctx_unsigned::<u32>(data, offset, 4, "ReadRange ACK item-count")?;
395 offset = end;
396
397 let (tag, tag_end) = tags::decode_tag(data, offset)?;
399 if !tag.is_opening_tag(5) {
400 return Err(misplaced_tag(
401 data,
402 &tag,
403 Some(5),
404 offset,
405 "ReadRange ACK item-data expected opening tag 5",
406 ));
407 }
408 let (content, new_offset) = tags::extract_context_value(data, tag_end, 5)?;
409 if (item_count == 0) != content.is_empty() {
410 return Err(Error::decoding(
411 offset,
412 "ReadRange ACK item-count contradicts empty item-data",
413 ));
414 }
415 let item_data = content.to_vec();
416 offset = new_offset;
417
418 let mut first_sequence_number = None;
420 if offset < data.len() {
421 let (tag, _) = tags::decode_tag(data, offset)?;
422 if !tag.is_context(6) {
423 return Err(misplaced_tag(
424 data,
425 &tag,
426 Some(6),
427 offset,
428 "ReadRange ACK expected context tag 6 for first-sequence-number",
429 ));
430 }
431 if item_count == 0 {
432 return Err(Error::decoding(
433 offset,
434 "ReadRange ACK first-sequence-number requires a nonzero item-count",
435 ));
436 }
437 let (sequence_number, end) =
438 decode_ctx_unsigned::<u64>(data, offset, 6, "ReadRange ACK first-sequence-number")?;
439 first_sequence_number = Some(sequence_number);
440 offset = end;
441 }
442 expect_end(data, offset, offset, "ReadRange ACK")?;
443
444 Ok(Self {
445 object_identifier,
446 property_identifier,
447 property_array_index,
448 result_flags,
449 item_count,
450 item_data,
451 first_sequence_number,
452 })
453 }
454}
455
456#[path = "read_range_records.rs"]
457mod records;
458pub use records::{LogBufferRecord, LogRecords, LogRecordsError};
459#[path = "read_range_checks.rs"]
460mod checks;
461pub use bacnet_types::error::ReadRangeViolation;
462pub use checks::{ReadRangeReply, ReadRangeValidation};
463
464#[cfg(test)]
465#[path = "read_range_width_tests.rs"]
466mod width_tests;
467
468#[cfg(test)]
469#[path = "read_range_tests.rs"]
470mod tests;
471
472#[cfg(test)]
473#[path = "read_range_validation_tests.rs"]
474mod validation_tests;