Skip to main content

bacnet_services/
rpm.rs

1//! ReadPropertyMultiple service per ASHRAE 135-2020 Clause 15.7.
2
3use bacnet_encoding::primitives;
4use bacnet_encoding::tags;
5use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier};
6use bacnet_types::error::Error;
7use bacnet_types::primitives::ObjectIdentifier;
8use bytes::BytesMut;
9
10use crate::common::{PropertyReference, MAX_DECODED_ITEMS};
11
12// ---------------------------------------------------------------------------
13// ReadPropertyMultipleRequest
14// ---------------------------------------------------------------------------
15
16/// A single object + list of property references.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct ReadAccessSpecification {
19    pub object_identifier: ObjectIdentifier,
20    pub list_of_property_references: Vec<PropertyReference>,
21}
22
23/// ReadPropertyMultiple-Request service parameters.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct ReadPropertyMultipleRequest {
26    pub list_of_read_access_specs: Vec<ReadAccessSpecification>,
27}
28
29impl ReadPropertyMultipleRequest {
30    pub fn encode(&self, buf: &mut BytesMut) {
31        for spec in &self.list_of_read_access_specs {
32            // [0] object-identifier
33            primitives::encode_ctx_object_id(buf, 0, &spec.object_identifier);
34            // [1] list-of-property-references (opening/closing)
35            tags::encode_opening_tag(buf, 1);
36            for prop_ref in &spec.list_of_property_references {
37                prop_ref.encode(buf);
38            }
39            tags::encode_closing_tag(buf, 1);
40        }
41    }
42
43    pub fn decode(data: &[u8]) -> Result<Self, Error> {
44        let mut offset = 0;
45        let mut specs = Vec::new();
46
47        while offset < data.len() {
48            if specs.len() >= MAX_DECODED_ITEMS {
49                return Err(Error::decoding(
50                    offset,
51                    "RPM request exceeds max decoded items",
52                ));
53            }
54
55            // [0] object-identifier
56            let (tag, pos) = tags::decode_tag(data, offset)?;
57            let end = pos + tag.length as usize;
58            if end > data.len() {
59                return Err(Error::decoding(pos, "RPM request truncated at object-id"));
60            }
61            let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
62            offset = end;
63
64            // [1] list-of-property-references (opening tag 1)
65            let (tag, tag_end) = tags::decode_tag(data, offset)?;
66            if !tag.is_opening_tag(1) {
67                return Err(Error::decoding(
68                    offset,
69                    "RPM request expected opening tag 1",
70                ));
71            }
72            offset = tag_end;
73
74            let mut prop_refs = Vec::new();
75            loop {
76                if offset >= data.len() {
77                    return Err(Error::decoding(offset, "RPM request missing closing tag 1"));
78                }
79                if prop_refs.len() >= MAX_DECODED_ITEMS {
80                    return Err(Error::decoding(offset, "RPM property refs exceeds max"));
81                }
82                // Check for closing tag 1
83                let (tag, tag_end) = tags::decode_tag(data, offset)?;
84                if tag.is_closing_tag(1) {
85                    offset = tag_end;
86                    break;
87                }
88                // Decode property reference starting from current offset (not tag_end)
89                let (pr, new_offset) = PropertyReference::decode(data, offset)?;
90                prop_refs.push(pr);
91                offset = new_offset;
92            }
93
94            specs.push(ReadAccessSpecification {
95                object_identifier,
96                list_of_property_references: prop_refs,
97            });
98        }
99
100        Ok(Self {
101            list_of_read_access_specs: specs,
102        })
103    }
104}
105
106// ---------------------------------------------------------------------------
107// ReadPropertyMultipleACK
108// ---------------------------------------------------------------------------
109
110/// A single result element: success (value) or failure (error).
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct ReadResultElement {
113    pub property_identifier: PropertyIdentifier,
114    pub property_array_index: Option<u32>,
115    /// Success: raw application-tagged value bytes. Mutually exclusive with `error`.
116    pub property_value: Option<Vec<u8>>,
117    /// Failure: (ErrorClass, ErrorCode). Mutually exclusive with `property_value`.
118    pub error: Option<(ErrorClass, ErrorCode)>,
119}
120
121/// Results for a single object.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct ReadAccessResult {
124    pub object_identifier: ObjectIdentifier,
125    pub list_of_results: Vec<ReadResultElement>,
126}
127
128/// ReadPropertyMultiple-ACK service parameters.
129#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct ReadPropertyMultipleACK {
131    pub list_of_read_access_results: Vec<ReadAccessResult>,
132}
133
134impl ReadPropertyMultipleACK {
135    pub fn encode(&self, buf: &mut BytesMut) {
136        for result in &self.list_of_read_access_results {
137            // [0] object-identifier
138            primitives::encode_ctx_object_id(buf, 0, &result.object_identifier);
139            // [1] list-of-results (opening/closing)
140            tags::encode_opening_tag(buf, 1);
141            for elem in &result.list_of_results {
142                // [2] property-identifier
143                primitives::encode_ctx_unsigned(buf, 2, elem.property_identifier.to_raw() as u64);
144                // [3] property-array-index (optional)
145                if let Some(idx) = elem.property_array_index {
146                    primitives::encode_ctx_unsigned(buf, 3, idx as u64);
147                }
148                if let Some(ref value) = elem.property_value {
149                    // [4] property-value (opening/closing)
150                    tags::encode_opening_tag(buf, 4);
151                    buf.extend_from_slice(value);
152                    tags::encode_closing_tag(buf, 4);
153                } else if let Some((class, code)) = elem.error {
154                    // [5] property-access-error (opening/closing)
155                    tags::encode_opening_tag(buf, 5);
156                    primitives::encode_app_enumerated(buf, class.to_raw() as u32);
157                    primitives::encode_app_enumerated(buf, code.to_raw() as u32);
158                    tags::encode_closing_tag(buf, 5);
159                }
160            }
161            tags::encode_closing_tag(buf, 1);
162        }
163    }
164
165    pub fn decode(data: &[u8]) -> Result<Self, Error> {
166        let mut offset = 0;
167        let mut results = Vec::new();
168
169        while offset < data.len() {
170            if results.len() >= MAX_DECODED_ITEMS {
171                return Err(Error::decoding(offset, "RPM ACK exceeds max decoded items"));
172            }
173
174            // [0] object-identifier
175            let (tag, pos) = tags::decode_tag(data, offset)?;
176            let end = pos + tag.length as usize;
177            if end > data.len() {
178                return Err(Error::decoding(pos, "RPM ACK truncated at object-id"));
179            }
180            let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
181            offset = end;
182
183            // [1] list-of-results (opening tag 1)
184            let (tag, tag_end) = tags::decode_tag(data, offset)?;
185            if !tag.is_opening_tag(1) {
186                return Err(Error::decoding(offset, "RPM ACK expected opening tag 1"));
187            }
188            offset = tag_end;
189
190            let mut elements = Vec::new();
191            loop {
192                if offset >= data.len() {
193                    return Err(Error::decoding(offset, "RPM ACK missing closing tag 1"));
194                }
195                if elements.len() >= MAX_DECODED_ITEMS {
196                    return Err(Error::decoding(offset, "RPM ACK results exceeds max"));
197                }
198                let (tag, tag_end) = tags::decode_tag(data, offset)?;
199                if tag.is_closing_tag(1) {
200                    offset = tag_end;
201                    break;
202                }
203
204                // [2] property-identifier
205                if !tag.is_context(2) {
206                    return Err(Error::decoding(offset, "RPM ACK expected context tag 2"));
207                }
208                let end = tag_end + tag.length as usize;
209                if end > data.len() {
210                    return Err(Error::decoding(tag_end, "RPM ACK truncated at property-id"));
211                }
212                let prop_raw = primitives::decode_unsigned(&data[tag_end..end])? as u32;
213                let property_identifier = PropertyIdentifier::from_raw(prop_raw);
214                offset = end;
215
216                // [3] property-array-index (optional)
217                let mut array_index = None;
218                let (tag, tag_end) = tags::decode_tag(data, offset)?;
219                if tag.is_context(3) {
220                    let end = tag_end + tag.length as usize;
221                    if end > data.len() {
222                        return Err(Error::decoding(tag_end, "RPM ACK truncated at array-index"));
223                    }
224                    array_index = Some(primitives::decode_unsigned(&data[tag_end..end])? as u32);
225                    offset = end;
226                    let (tag, tag_end) = tags::decode_tag(data, offset)?;
227                    if tag.is_opening_tag(4) {
228                        let (value_bytes, new_offset) =
229                            tags::extract_context_value(data, tag_end, 4)?;
230                        elements.push(ReadResultElement {
231                            property_identifier,
232                            property_array_index: array_index,
233                            property_value: Some(value_bytes.to_vec()),
234                            error: None,
235                        });
236                        offset = new_offset;
237                    } else if tag.is_opening_tag(5) {
238                        let (error_class, error_code, new_offset) =
239                            decode_error_pair(data, tag_end)?;
240                        elements.push(ReadResultElement {
241                            property_identifier,
242                            property_array_index: array_index,
243                            property_value: None,
244                            error: Some((error_class, error_code)),
245                        });
246                        offset = new_offset;
247                    } else {
248                        return Err(Error::decoding(offset, "RPM ACK expected tag 4 or 5"));
249                    }
250                } else if tag.is_opening_tag(4) {
251                    // [4] property-value
252                    let (value_bytes, new_offset) = tags::extract_context_value(data, tag_end, 4)?;
253                    elements.push(ReadResultElement {
254                        property_identifier,
255                        property_array_index: array_index,
256                        property_value: Some(value_bytes.to_vec()),
257                        error: None,
258                    });
259                    offset = new_offset;
260                } else if tag.is_opening_tag(5) {
261                    // [5] property-access-error
262                    let (error_class, error_code, new_offset) = decode_error_pair(data, tag_end)?;
263                    elements.push(ReadResultElement {
264                        property_identifier,
265                        property_array_index: array_index,
266                        property_value: None,
267                        error: Some((error_class, error_code)),
268                    });
269                    offset = new_offset;
270                } else {
271                    return Err(Error::decoding(offset, "RPM ACK expected tag 3, 4, or 5"));
272                }
273            }
274
275            results.push(ReadAccessResult {
276                object_identifier,
277                list_of_results: elements,
278            });
279        }
280
281        Ok(Self {
282            list_of_read_access_results: results,
283        })
284    }
285}
286
287/// Decode an error-class + error-code pair from inside opening/closing tag 5,
288/// followed by consuming the closing tag.
289fn decode_error_pair(data: &[u8], offset: usize) -> Result<(ErrorClass, ErrorCode, usize), Error> {
290    // error-class: app-tagged enumerated
291    let (tag, pos) = tags::decode_tag(data, offset)?;
292    let end = pos + tag.length as usize;
293    if end > data.len() {
294        return Err(Error::decoding(pos, "RPM error truncated at error-class"));
295    }
296    let error_class = ErrorClass::from_raw(primitives::decode_unsigned(&data[pos..end])? as u16);
297    let mut offset = end;
298
299    // error-code: app-tagged enumerated
300    let (tag, pos) = tags::decode_tag(data, offset)?;
301    let end = pos + tag.length as usize;
302    if end > data.len() {
303        return Err(Error::decoding(pos, "RPM error truncated at error-code"));
304    }
305    let error_code = ErrorCode::from_raw(primitives::decode_unsigned(&data[pos..end])? as u16);
306    offset = end;
307
308    // closing tag 5
309    let (tag, tag_end) = tags::decode_tag(data, offset)?;
310    if !tag.is_closing_tag(5) {
311        return Err(Error::decoding(offset, "RPM error expected closing tag 5"));
312    }
313
314    Ok((error_class, error_code, tag_end))
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    use bacnet_types::enums::ObjectType;
321
322    #[test]
323    fn request_single_object_round_trip() {
324        let req = ReadPropertyMultipleRequest {
325            list_of_read_access_specs: vec![ReadAccessSpecification {
326                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
327                list_of_property_references: vec![
328                    PropertyReference {
329                        property_identifier: PropertyIdentifier::PRESENT_VALUE,
330                        property_array_index: None,
331                    },
332                    PropertyReference {
333                        property_identifier: PropertyIdentifier::OBJECT_NAME,
334                        property_array_index: None,
335                    },
336                ],
337            }],
338        };
339        let mut buf = BytesMut::new();
340        req.encode(&mut buf);
341        let decoded = ReadPropertyMultipleRequest::decode(&buf).unwrap();
342        assert_eq!(req, decoded);
343    }
344
345    #[test]
346    fn request_multi_object_round_trip() {
347        let req = ReadPropertyMultipleRequest {
348            list_of_read_access_specs: vec![
349                ReadAccessSpecification {
350                    object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
351                    list_of_property_references: vec![PropertyReference {
352                        property_identifier: PropertyIdentifier::PRESENT_VALUE,
353                        property_array_index: None,
354                    }],
355                },
356                ReadAccessSpecification {
357                    object_identifier: ObjectIdentifier::new(ObjectType::BINARY_OUTPUT, 3).unwrap(),
358                    list_of_property_references: vec![PropertyReference {
359                        property_identifier: PropertyIdentifier::PRESENT_VALUE,
360                        property_array_index: None,
361                    }],
362                },
363            ],
364        };
365        let mut buf = BytesMut::new();
366        req.encode(&mut buf);
367        let decoded = ReadPropertyMultipleRequest::decode(&buf).unwrap();
368        assert_eq!(req, decoded);
369    }
370
371    #[test]
372    fn ack_success_round_trip() {
373        let ack = ReadPropertyMultipleACK {
374            list_of_read_access_results: vec![ReadAccessResult {
375                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
376                list_of_results: vec![ReadResultElement {
377                    property_identifier: PropertyIdentifier::PRESENT_VALUE,
378                    property_array_index: None,
379                    property_value: Some(vec![0x44, 0x42, 0x90, 0x00, 0x00]),
380                    error: None,
381                }],
382            }],
383        };
384        let mut buf = BytesMut::new();
385        ack.encode(&mut buf);
386        let decoded = ReadPropertyMultipleACK::decode(&buf).unwrap();
387        assert_eq!(ack, decoded);
388    }
389
390    #[test]
391    fn ack_mixed_success_error_round_trip() {
392        let ack = ReadPropertyMultipleACK {
393            list_of_read_access_results: vec![ReadAccessResult {
394                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
395                list_of_results: vec![
396                    ReadResultElement {
397                        property_identifier: PropertyIdentifier::PRESENT_VALUE,
398                        property_array_index: None,
399                        property_value: Some(vec![0x44, 0x42, 0x90, 0x00, 0x00]),
400                        error: None,
401                    },
402                    ReadResultElement {
403                        property_identifier: PropertyIdentifier::from_raw(9999),
404                        property_array_index: None,
405                        property_value: None,
406                        error: Some((ErrorClass::PROPERTY, ErrorCode::UNKNOWN_PROPERTY)),
407                    },
408                ],
409            }],
410        };
411        let mut buf = BytesMut::new();
412        ack.encode(&mut buf);
413        let decoded = ReadPropertyMultipleACK::decode(&buf).unwrap();
414        assert_eq!(ack, decoded);
415    }
416
417    // -----------------------------------------------------------------------
418    // Malformed-input decode error tests
419    // -----------------------------------------------------------------------
420
421    #[test]
422    fn test_decode_rpm_request_truncated_1_byte() {
423        let req = ReadPropertyMultipleRequest {
424            list_of_read_access_specs: vec![ReadAccessSpecification {
425                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
426                list_of_property_references: vec![PropertyReference {
427                    property_identifier: PropertyIdentifier::PRESENT_VALUE,
428                    property_array_index: None,
429                }],
430            }],
431        };
432        let mut buf = BytesMut::new();
433        req.encode(&mut buf);
434        assert!(ReadPropertyMultipleRequest::decode(&buf[..1]).is_err());
435    }
436
437    #[test]
438    fn test_decode_rpm_request_truncated_3_bytes() {
439        let req = ReadPropertyMultipleRequest {
440            list_of_read_access_specs: vec![ReadAccessSpecification {
441                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
442                list_of_property_references: vec![PropertyReference {
443                    property_identifier: PropertyIdentifier::PRESENT_VALUE,
444                    property_array_index: None,
445                }],
446            }],
447        };
448        let mut buf = BytesMut::new();
449        req.encode(&mut buf);
450        assert!(ReadPropertyMultipleRequest::decode(&buf[..3]).is_err());
451    }
452
453    #[test]
454    fn test_decode_rpm_request_truncated_half() {
455        let req = ReadPropertyMultipleRequest {
456            list_of_read_access_specs: vec![ReadAccessSpecification {
457                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
458                list_of_property_references: vec![PropertyReference {
459                    property_identifier: PropertyIdentifier::PRESENT_VALUE,
460                    property_array_index: None,
461                }],
462            }],
463        };
464        let mut buf = BytesMut::new();
465        req.encode(&mut buf);
466        let half = buf.len() / 2;
467        assert!(ReadPropertyMultipleRequest::decode(&buf[..half]).is_err());
468    }
469
470    #[test]
471    fn test_decode_rpm_request_invalid_tag() {
472        assert!(ReadPropertyMultipleRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
473    }
474
475    #[test]
476    fn test_decode_rpm_ack_truncated_1_byte() {
477        let ack = ReadPropertyMultipleACK {
478            list_of_read_access_results: vec![ReadAccessResult {
479                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
480                list_of_results: vec![ReadResultElement {
481                    property_identifier: PropertyIdentifier::PRESENT_VALUE,
482                    property_array_index: None,
483                    property_value: Some(vec![0x44, 0x42, 0x90, 0x00, 0x00]),
484                    error: None,
485                }],
486            }],
487        };
488        let mut buf = BytesMut::new();
489        ack.encode(&mut buf);
490        assert!(ReadPropertyMultipleACK::decode(&buf[..1]).is_err());
491    }
492
493    #[test]
494    fn test_decode_rpm_ack_truncated_3_bytes() {
495        let ack = ReadPropertyMultipleACK {
496            list_of_read_access_results: vec![ReadAccessResult {
497                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
498                list_of_results: vec![ReadResultElement {
499                    property_identifier: PropertyIdentifier::PRESENT_VALUE,
500                    property_array_index: None,
501                    property_value: Some(vec![0x44, 0x42, 0x90, 0x00, 0x00]),
502                    error: None,
503                }],
504            }],
505        };
506        let mut buf = BytesMut::new();
507        ack.encode(&mut buf);
508        assert!(ReadPropertyMultipleACK::decode(&buf[..3]).is_err());
509    }
510
511    #[test]
512    fn test_decode_rpm_ack_truncated_half() {
513        let ack = ReadPropertyMultipleACK {
514            list_of_read_access_results: vec![ReadAccessResult {
515                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
516                list_of_results: vec![ReadResultElement {
517                    property_identifier: PropertyIdentifier::PRESENT_VALUE,
518                    property_array_index: None,
519                    property_value: Some(vec![0x44, 0x42, 0x90, 0x00, 0x00]),
520                    error: None,
521                }],
522            }],
523        };
524        let mut buf = BytesMut::new();
525        ack.encode(&mut buf);
526        let half = buf.len() / 2;
527        assert!(ReadPropertyMultipleACK::decode(&buf[..half]).is_err());
528    }
529
530    #[test]
531    fn test_decode_rpm_ack_invalid_tag() {
532        assert!(ReadPropertyMultipleACK::decode(&[0xFF, 0xFF, 0xFF]).is_err());
533    }
534}