Skip to main content

bacnet_services/
read_range.rs

1//! ReadRange service per ASHRAE 135-2020 Clause 15.8.
2//!
3//! Reads a range of items from a list or log-buffer property.
4
5use bacnet_encoding::{primitives, tags};
6use bacnet_types::enums::PropertyIdentifier;
7use bacnet_types::error::Error;
8use bacnet_types::primitives::{Date, ObjectIdentifier, Time};
9use bytes::BytesMut;
10
11/// Decode a tag from content and validate the slice bounds.
12fn checked_slice<'a>(
13    content: &'a [u8],
14    offset: usize,
15    context: &str,
16) -> Result<(&'a [u8], usize), Error> {
17    let (t, p) = tags::decode_tag(content, offset)?;
18    let end = p + t.length as usize;
19    if end > content.len() {
20        return Err(Error::decoding(p, format!("{context} truncated")));
21    }
22    Ok((&content[p..end], end))
23}
24
25/// ReadRange-Request service parameters.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct ReadRangeRequest {
28    pub object_identifier: ObjectIdentifier,
29    pub property_identifier: PropertyIdentifier,
30    pub property_array_index: Option<u32>,
31    /// Range specification: by-position, by-sequence-number, or by-time.
32    pub range: Option<RangeSpec>,
33}
34
35/// Range specification for ReadRange.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum RangeSpec {
38    /// By position: reference_index, count.
39    ByPosition { reference_index: u32, count: i32 },
40    /// By sequence number: reference_seq, count.
41    BySequenceNumber { reference_seq: u32, count: i32 },
42    /// By time: reference_time (Date, Time), count.
43    ByTime {
44        reference_time: (Date, Time),
45        count: i32,
46    },
47}
48
49impl ReadRangeRequest {
50    pub fn encode(&self, buf: &mut BytesMut) {
51        // [0] objectIdentifier
52        primitives::encode_ctx_object_id(buf, 0, &self.object_identifier);
53        // [1] propertyIdentifier
54        primitives::encode_ctx_enumerated(buf, 1, self.property_identifier.to_raw());
55        // [2] propertyArrayIndex (optional)
56        if let Some(idx) = self.property_array_index {
57            primitives::encode_ctx_unsigned(buf, 2, idx as u64);
58        }
59        // Range specification
60        if let Some(ref range) = self.range {
61            match range {
62                RangeSpec::ByPosition {
63                    reference_index,
64                    count,
65                } => {
66                    tags::encode_opening_tag(buf, 3);
67                    primitives::encode_app_unsigned(buf, *reference_index as u64);
68                    primitives::encode_app_signed(buf, *count);
69                    tags::encode_closing_tag(buf, 3);
70                }
71                RangeSpec::BySequenceNumber {
72                    reference_seq,
73                    count,
74                } => {
75                    tags::encode_opening_tag(buf, 6);
76                    primitives::encode_app_unsigned(buf, *reference_seq as u64);
77                    primitives::encode_app_signed(buf, *count);
78                    tags::encode_closing_tag(buf, 6);
79                }
80                RangeSpec::ByTime {
81                    reference_time,
82                    count,
83                } => {
84                    tags::encode_opening_tag(buf, 7);
85                    primitives::encode_app_date(buf, &reference_time.0);
86                    primitives::encode_app_time(buf, &reference_time.1);
87                    primitives::encode_app_signed(buf, *count);
88                    tags::encode_closing_tag(buf, 7);
89                }
90            }
91        }
92    }
93
94    pub fn decode(data: &[u8]) -> Result<Self, Error> {
95        let mut offset = 0;
96
97        // [0] objectIdentifier
98        let (tag, pos) = tags::decode_tag(data, offset)?;
99        let end = pos + tag.length as usize;
100        if end > data.len() {
101            return Err(Error::decoding(
102                pos,
103                "ReadRange request truncated at object-id",
104            ));
105        }
106        let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
107        offset = end;
108
109        // [1] propertyIdentifier
110        let (tag, pos) = tags::decode_tag(data, offset)?;
111        let end = pos + tag.length as usize;
112        if end > data.len() {
113            return Err(Error::decoding(
114                pos,
115                "ReadRange request truncated at property-id",
116            ));
117        }
118        let property_identifier =
119            PropertyIdentifier::from_raw(primitives::decode_unsigned(&data[pos..end])? as u32);
120        offset = end;
121
122        // [2] propertyArrayIndex (optional)
123        let mut property_array_index = None;
124        if offset < data.len() {
125            let (opt_data, new_offset) = tags::decode_optional_context(data, offset, 2)?;
126            if let Some(content) = opt_data {
127                property_array_index = Some(primitives::decode_unsigned(content)? as u32);
128                offset = new_offset;
129            }
130        }
131
132        // Range specification (optional)
133        let mut range = None;
134        if offset < data.len() {
135            let (tag, tag_end) = tags::decode_tag(data, offset)?;
136            if tag.is_opening_tag(3) {
137                // byPosition
138                let (content, new_offset) = tags::extract_context_value(data, tag_end, 3)?;
139                let (slice, inner_offset) =
140                    checked_slice(content, 0, "ReadRange byPosition reference-index")?;
141                let reference_index = primitives::decode_unsigned(slice)? as u32;
142                let (slice, _) =
143                    checked_slice(content, inner_offset, "ReadRange byPosition count")?;
144                let count = primitives::decode_signed(slice)?;
145                range = Some(RangeSpec::ByPosition {
146                    reference_index,
147                    count,
148                });
149                offset = new_offset;
150            } else if tag.is_opening_tag(6) {
151                // bySequenceNumber
152                let (content, new_offset) = tags::extract_context_value(data, tag_end, 6)?;
153                let (slice, inner_offset) =
154                    checked_slice(content, 0, "ReadRange bySequenceNumber reference-seq")?;
155                let reference_seq = primitives::decode_unsigned(slice)? as u32;
156                let (slice, _) =
157                    checked_slice(content, inner_offset, "ReadRange bySequenceNumber count")?;
158                let count = primitives::decode_signed(slice)?;
159                range = Some(RangeSpec::BySequenceNumber {
160                    reference_seq,
161                    count,
162                });
163                offset = new_offset;
164            } else if tag.is_opening_tag(7) {
165                // byTime
166                let (content, new_offset) = tags::extract_context_value(data, tag_end, 7)?;
167                let (slice, inner_offset) = checked_slice(content, 0, "ReadRange byTime date")?;
168                let date = Date::decode(slice)?;
169                let (slice, inner_offset) =
170                    checked_slice(content, inner_offset, "ReadRange byTime time")?;
171                let time = Time::decode(slice)?;
172                let (slice, _) = checked_slice(content, inner_offset, "ReadRange byTime count")?;
173                let count = primitives::decode_signed(slice)?;
174                range = Some(RangeSpec::ByTime {
175                    reference_time: (date, time),
176                    count,
177                });
178                offset = new_offset;
179            }
180        }
181        let _ = offset;
182
183        Ok(Self {
184            object_identifier,
185            property_identifier,
186            property_array_index,
187            range,
188        })
189    }
190}
191
192/// ReadRange-ACK service parameters.
193#[derive(Debug, Clone)]
194pub struct ReadRangeAck {
195    pub object_identifier: ObjectIdentifier,
196    pub property_identifier: PropertyIdentifier,
197    pub property_array_index: Option<u32>,
198    /// Result flags: first_item, last_item, more_items.
199    pub result_flags: (bool, bool, bool),
200    pub item_count: u32,
201    /// Raw item data (application-layer interprets content).
202    pub item_data: Vec<u8>,
203    /// Optional first sequence number (context tag [6]).
204    pub first_sequence_number: Option<u32>,
205}
206
207impl ReadRangeAck {
208    pub fn encode(&self, buf: &mut BytesMut) {
209        // [0] objectIdentifier
210        primitives::encode_ctx_object_id(buf, 0, &self.object_identifier);
211        // [1] propertyIdentifier
212        primitives::encode_ctx_enumerated(buf, 1, self.property_identifier.to_raw());
213        // [2] propertyArrayIndex (optional)
214        if let Some(idx) = self.property_array_index {
215            primitives::encode_ctx_unsigned(buf, 2, idx as u64);
216        }
217        // [3] resultFlags — 3-bit bitstring
218        let mut flags: u8 = 0;
219        if self.result_flags.0 {
220            flags |= 0x80;
221        }
222        if self.result_flags.1 {
223            flags |= 0x40;
224        }
225        if self.result_flags.2 {
226            flags |= 0x20;
227        }
228        primitives::encode_ctx_bit_string(buf, 3, 5, &[flags]);
229        // [4] itemCount
230        primitives::encode_ctx_unsigned(buf, 4, self.item_count as u64);
231        // [5] itemData
232        tags::encode_opening_tag(buf, 5);
233        buf.extend_from_slice(&self.item_data);
234        tags::encode_closing_tag(buf, 5);
235        // [6] firstSequenceNumber (optional)
236        if let Some(seq) = self.first_sequence_number {
237            primitives::encode_ctx_unsigned(buf, 6, seq as u64);
238        }
239    }
240
241    pub fn decode(data: &[u8]) -> Result<Self, Error> {
242        let mut offset = 0;
243
244        // [0] objectIdentifier
245        let (tag, pos) = tags::decode_tag(data, offset)?;
246        let end = pos + tag.length as usize;
247        if end > data.len() {
248            return Err(Error::decoding(pos, "ReadRange ACK truncated at object-id"));
249        }
250        let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
251        offset = end;
252
253        // [1] propertyIdentifier
254        let (tag, pos) = tags::decode_tag(data, offset)?;
255        let end = pos + tag.length as usize;
256        if end > data.len() {
257            return Err(Error::decoding(
258                pos,
259                "ReadRange ACK truncated at property-id",
260            ));
261        }
262        let property_identifier =
263            PropertyIdentifier::from_raw(primitives::decode_unsigned(&data[pos..end])? as u32);
264        offset = end;
265
266        // [2] propertyArrayIndex (optional)
267        let mut property_array_index = None;
268        let (opt_data, new_offset) = tags::decode_optional_context(data, offset, 2)?;
269        if let Some(content) = opt_data {
270            property_array_index = Some(primitives::decode_unsigned(content)? as u32);
271            offset = new_offset;
272        }
273
274        // [3] resultFlags
275        let (tag, pos) = tags::decode_tag(data, offset)?;
276        let end = pos + tag.length as usize;
277        if end > data.len() {
278            return Err(Error::decoding(
279                pos,
280                "ReadRange ACK truncated at result-flags",
281            ));
282        }
283        let (_, bits) = primitives::decode_bit_string(&data[pos..end])?;
284        let b = bits.first().copied().unwrap_or(0);
285        let result_flags = (b & 0x80 != 0, b & 0x40 != 0, b & 0x20 != 0);
286        offset = end;
287
288        // [4] itemCount
289        let (tag, pos) = tags::decode_tag(data, offset)?;
290        let end = pos + tag.length as usize;
291        if end > data.len() {
292            return Err(Error::decoding(
293                pos,
294                "ReadRange ACK truncated at item-count",
295            ));
296        }
297        let item_count = primitives::decode_unsigned(&data[pos..end])? as u32;
298        offset = end;
299
300        // [5] itemData
301        let (_tag, tag_end) = tags::decode_tag(data, offset)?;
302        let (content, _new_offset) = tags::extract_context_value(data, tag_end, 5)?;
303        let item_data = content.to_vec();
304        let mut new_offset = _new_offset;
305
306        // [6] firstSequenceNumber (optional)
307        let mut first_sequence_number = None;
308        if new_offset < data.len() {
309            let (opt_data, after) = tags::decode_optional_context(data, new_offset, 6)?;
310            if let Some(content) = opt_data {
311                first_sequence_number = Some(primitives::decode_unsigned(content)? as u32);
312                new_offset = after;
313            }
314        }
315        let _ = new_offset;
316
317        Ok(Self {
318            object_identifier,
319            property_identifier,
320            property_array_index,
321            result_flags,
322            item_count,
323            item_data,
324            first_sequence_number,
325        })
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332    use bacnet_types::enums::ObjectType;
333    use bacnet_types::primitives::{Date, Time};
334
335    fn make_oid() -> ObjectIdentifier {
336        ObjectIdentifier::new(ObjectType::TREND_LOG, 1).unwrap()
337    }
338
339    #[test]
340    fn request_round_trip() {
341        let req = ReadRangeRequest {
342            object_identifier: make_oid(),
343            property_identifier: PropertyIdentifier::LOG_BUFFER,
344            property_array_index: None,
345            range: Some(RangeSpec::ByPosition {
346                reference_index: 1,
347                count: 10,
348            }),
349        };
350        let mut buf = BytesMut::new();
351        req.encode(&mut buf);
352        let decoded = ReadRangeRequest::decode(&buf).unwrap();
353        assert_eq!(decoded.object_identifier, req.object_identifier);
354        assert_eq!(decoded.property_identifier, req.property_identifier);
355        assert_eq!(decoded.range, req.range);
356    }
357
358    #[test]
359    fn request_no_range() {
360        let req = ReadRangeRequest {
361            object_identifier: make_oid(),
362            property_identifier: PropertyIdentifier::LOG_BUFFER,
363            property_array_index: None,
364            range: None,
365        };
366        let mut buf = BytesMut::new();
367        req.encode(&mut buf);
368        let decoded = ReadRangeRequest::decode(&buf).unwrap();
369        assert!(decoded.range.is_none());
370    }
371
372    #[test]
373    fn request_by_sequence_number() {
374        let req = ReadRangeRequest {
375            object_identifier: make_oid(),
376            property_identifier: PropertyIdentifier::LOG_BUFFER,
377            property_array_index: None,
378            range: Some(RangeSpec::BySequenceNumber {
379                reference_seq: 100,
380                count: -5,
381            }),
382        };
383        let mut buf = BytesMut::new();
384        req.encode(&mut buf);
385        let decoded = ReadRangeRequest::decode(&buf).unwrap();
386        assert_eq!(decoded.range, req.range);
387    }
388
389    #[test]
390    fn ack_round_trip() {
391        let ack = ReadRangeAck {
392            object_identifier: make_oid(),
393            property_identifier: PropertyIdentifier::LOG_BUFFER,
394            property_array_index: None,
395            result_flags: (true, false, true),
396            item_count: 2,
397            item_data: vec![0xAA, 0xBB, 0xCC],
398            first_sequence_number: None,
399        };
400        let mut buf = BytesMut::new();
401        ack.encode(&mut buf);
402        let decoded = ReadRangeAck::decode(&buf).unwrap();
403        assert_eq!(decoded.object_identifier, ack.object_identifier);
404        assert_eq!(decoded.result_flags, (true, false, true));
405        assert_eq!(decoded.item_count, 2);
406        assert_eq!(decoded.item_data, vec![0xAA, 0xBB, 0xCC]);
407        assert_eq!(decoded.first_sequence_number, None);
408    }
409
410    #[test]
411    fn ack_round_trip_with_first_sequence_number() {
412        let ack = ReadRangeAck {
413            object_identifier: make_oid(),
414            property_identifier: PropertyIdentifier::LOG_BUFFER,
415            property_array_index: None,
416            result_flags: (true, true, false),
417            item_count: 5,
418            item_data: vec![0x01, 0x02],
419            first_sequence_number: Some(42),
420        };
421        let mut buf = BytesMut::new();
422        ack.encode(&mut buf);
423        let decoded = ReadRangeAck::decode(&buf).unwrap();
424        assert_eq!(decoded.object_identifier, ack.object_identifier);
425        assert_eq!(decoded.result_flags, (true, true, false));
426        assert_eq!(decoded.item_count, 5);
427        assert_eq!(decoded.item_data, vec![0x01, 0x02]);
428        assert_eq!(decoded.first_sequence_number, Some(42));
429    }
430
431    #[test]
432    fn request_by_time() {
433        let req = ReadRangeRequest {
434            object_identifier: make_oid(),
435            property_identifier: PropertyIdentifier::LOG_BUFFER,
436            property_array_index: None,
437            range: Some(RangeSpec::ByTime {
438                reference_time: (
439                    Date {
440                        year: 126, // 2026
441                        month: 3,
442                        day: 1,
443                        day_of_week: 7, // Sunday
444                    },
445                    Time {
446                        hour: 14,
447                        minute: 30,
448                        second: 0,
449                        hundredths: 0,
450                    },
451                ),
452                count: -10,
453            }),
454        };
455        let mut buf = BytesMut::new();
456        req.encode(&mut buf);
457        let decoded = ReadRangeRequest::decode(&buf).unwrap();
458        assert_eq!(decoded.range, req.range);
459    }
460
461    // -----------------------------------------------------------------------
462    // Malformed-input decode error tests
463    // -----------------------------------------------------------------------
464
465    #[test]
466    fn test_decode_read_range_request_empty_input() {
467        assert!(ReadRangeRequest::decode(&[]).is_err());
468    }
469
470    #[test]
471    fn test_decode_read_range_request_truncated_1_byte() {
472        let req = ReadRangeRequest {
473            object_identifier: make_oid(),
474            property_identifier: PropertyIdentifier::LOG_BUFFER,
475            property_array_index: None,
476            range: Some(RangeSpec::ByPosition {
477                reference_index: 1,
478                count: 10,
479            }),
480        };
481        let mut buf = BytesMut::new();
482        req.encode(&mut buf);
483        assert!(ReadRangeRequest::decode(&buf[..1]).is_err());
484    }
485
486    #[test]
487    fn test_decode_read_range_request_truncated_3_bytes() {
488        let req = ReadRangeRequest {
489            object_identifier: make_oid(),
490            property_identifier: PropertyIdentifier::LOG_BUFFER,
491            property_array_index: None,
492            range: Some(RangeSpec::ByPosition {
493                reference_index: 1,
494                count: 10,
495            }),
496        };
497        let mut buf = BytesMut::new();
498        req.encode(&mut buf);
499        assert!(ReadRangeRequest::decode(&buf[..3]).is_err());
500    }
501
502    #[test]
503    fn test_decode_read_range_request_invalid_tag() {
504        assert!(ReadRangeRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
505    }
506
507    #[test]
508    fn test_decode_read_range_ack_empty_input() {
509        assert!(ReadRangeAck::decode(&[]).is_err());
510    }
511
512    #[test]
513    fn test_decode_read_range_ack_truncated_1_byte() {
514        let ack = ReadRangeAck {
515            object_identifier: make_oid(),
516            property_identifier: PropertyIdentifier::LOG_BUFFER,
517            property_array_index: None,
518            result_flags: (true, false, true),
519            item_count: 2,
520            item_data: vec![0xAA, 0xBB, 0xCC],
521            first_sequence_number: None,
522        };
523        let mut buf = BytesMut::new();
524        ack.encode(&mut buf);
525        assert!(ReadRangeAck::decode(&buf[..1]).is_err());
526    }
527
528    #[test]
529    fn test_decode_read_range_ack_truncated_3_bytes() {
530        let ack = ReadRangeAck {
531            object_identifier: make_oid(),
532            property_identifier: PropertyIdentifier::LOG_BUFFER,
533            property_array_index: None,
534            result_flags: (true, false, true),
535            item_count: 2,
536            item_data: vec![0xAA, 0xBB, 0xCC],
537            first_sequence_number: None,
538        };
539        let mut buf = BytesMut::new();
540        ack.encode(&mut buf);
541        assert!(ReadRangeAck::decode(&buf[..3]).is_err());
542    }
543
544    #[test]
545    fn test_decode_read_range_ack_truncated_half() {
546        let ack = ReadRangeAck {
547            object_identifier: make_oid(),
548            property_identifier: PropertyIdentifier::LOG_BUFFER,
549            property_array_index: None,
550            result_flags: (true, false, true),
551            item_count: 2,
552            item_data: vec![0xAA, 0xBB, 0xCC],
553            first_sequence_number: None,
554        };
555        let mut buf = BytesMut::new();
556        ack.encode(&mut buf);
557        let half = buf.len() / 2;
558        assert!(ReadRangeAck::decode(&buf[..half]).is_err());
559    }
560
561    #[test]
562    fn test_decode_read_range_ack_invalid_tag() {
563        assert!(ReadRangeAck::decode(&[0xFF, 0xFF, 0xFF]).is_err());
564    }
565
566    #[test]
567    fn read_range_request_truncated_inner_tag() {
568        // Craft a ReadRangeRequest with truncated inner content in byPosition
569        let data = [
570            0x0C, 0x05, 0x00, 0x00, 0x01, // [0] object id (TrendLog:1)
571            0x19, 0x83, // [1] property id (LOG_BUFFER=131)
572            // Opening tag [3] byPosition
573            0x3E, // Inner tag claiming 50 bytes but only 1 byte present
574            0x21, 50, 0x01, // Closing tag [3]
575            0x3F,
576        ];
577        assert!(ReadRangeRequest::decode(&data).is_err());
578    }
579}