Skip to main content

bacnet_services/
object_mgmt.rs

1//! Object management services per ASHRAE 135-2020 Clause 15.3-15.4.
2//!
3//! - CreateObject (Clause 15.3)
4//! - DeleteObject (Clause 15.4)
5
6use bacnet_encoding::constructed::tagged::{
7    decode_app_object_id, decode_ctx_object_id, decode_ctx_unsigned, expect_closing, expect_end,
8    expect_opening, misplaced_tag, next_is_context, next_is_opening,
9};
10use bacnet_encoding::constructed::{
11    decode_bacnet_property_value_in_list, encode_bacnet_property_value,
12};
13use bacnet_encoding::primitives;
14use bacnet_encoding::tags;
15use bacnet_types::enums::ObjectType;
16use bacnet_types::error::Error;
17use bacnet_types::primitives::ObjectIdentifier;
18use bytes::BytesMut;
19
20use crate::common::{BACnetPropertyValue, MAX_DECODED_ITEMS};
21
22mod error;
23pub use error::CreateObjectError;
24
25// ---------------------------------------------------------------------------
26// CreateObjectRequest
27// ---------------------------------------------------------------------------
28
29/// The object specifier: by type (server picks instance) or by identifier.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum ObjectSpecifier {
32    /// Create by type — server assigns instance number (\[0\] context tag inside \[0\] constructed).
33    Type(ObjectType),
34    /// Create with a specific identifier (\[1\] context tag inside \[0\] constructed).
35    Identifier(ObjectIdentifier),
36}
37
38/// CreateObject-Request service parameters.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct CreateObjectRequest {
41    /// Object to create: either a type, letting the server choose the instance, or a full
42    /// identifier.
43    pub object_specifier: ObjectSpecifier,
44    /// Initial property values to apply to the new object; empty means none were supplied.
45    pub list_of_initial_values: Vec<BACnetPropertyValue>,
46}
47
48impl CreateObjectRequest {
49    /// Encode the request parameters into `buf`.
50    pub fn encode(&self, buf: &mut BytesMut) {
51        // [0] object-specifier (constructed)
52        tags::encode_opening_tag(buf, 0);
53        match &self.object_specifier {
54            ObjectSpecifier::Type(obj_type) => {
55                primitives::encode_ctx_enumerated(buf, 0, obj_type.to_raw());
56            }
57            ObjectSpecifier::Identifier(oid) => {
58                primitives::encode_ctx_object_id(buf, 1, oid);
59            }
60        }
61        tags::encode_closing_tag(buf, 0);
62
63        // [1] list-of-initial-values (optional, constructed)
64        if !self.list_of_initial_values.is_empty() {
65            tags::encode_opening_tag(buf, 1);
66            for pv in &self.list_of_initial_values {
67                encode_bacnet_property_value(pv, buf);
68            }
69            tags::encode_closing_tag(buf, 1);
70        }
71    }
72
73    /// Decode the request from service-request octets; fails on malformed or truncated input.
74    pub fn decode(data: &[u8]) -> Result<Self, Error> {
75        // [0] object-specifier, a CHOICE read in place so a member cut short
76        // is reported as such
77        let what = "CreateObject object-specifier";
78        let offset = expect_opening(data, 0, 0, what)?;
79        let (object_specifier, offset) = if next_is_context(data, offset, 0)? {
80            let (raw, end) =
81                decode_ctx_unsigned::<u32>(data, offset, 0, "CreateObject object-type")?;
82            (ObjectSpecifier::Type(ObjectType::from_raw(raw)), end)
83        } else if next_is_context(data, offset, 1)? {
84            let (oid, end) =
85                decode_ctx_object_id(data, offset, 1, "CreateObject object-identifier")?;
86            (ObjectSpecifier::Identifier(oid), end)
87        } else {
88            // Neither alternative: the frame closes empty, or another tag
89            // stands there.
90            let (found, _) = tags::decode_tag(data, offset)?;
91            return Err(misplaced_tag(
92                data,
93                &found,
94                None,
95                offset,
96                "CreateObject expected context tag 0 or 1 inside object-specifier",
97            ));
98        };
99        let mut offset = expect_closing(data, offset, 0, what)?;
100
101        // [1] list-of-initial-values (optional, opening tag 1)
102        let mut values = Vec::new();
103        if next_is_opening(data, offset, 1)? {
104            offset = expect_opening(data, offset, 1, "CreateObject list-of-initial-values")?;
105            loop {
106                if offset >= data.len() {
107                    return Err(Error::missing(offset, "CreateObject missing closing tag 1"));
108                }
109                let (tag, closing_end) = tags::decode_tag(data, offset)?;
110                if tag.is_closing_tag(1) {
111                    offset = closing_end;
112                    break;
113                }
114                if values.len() >= MAX_DECODED_ITEMS {
115                    return Err(Error::overflow(offset, "CreateObject values exceeds max"));
116                }
117                let (pv, new_offset) = decode_bacnet_property_value_in_list(data, offset, 1)?;
118                values.push(pv);
119                offset = new_offset;
120            }
121        }
122
123        expect_end(data, offset, offset, "CreateObject")?;
124
125        Ok(Self {
126            object_specifier,
127            list_of_initial_values: values,
128        })
129    }
130}
131
132// ---------------------------------------------------------------------------
133// DeleteObjectRequest
134// ---------------------------------------------------------------------------
135
136/// DeleteObject-Request service parameters (APPLICATION-tagged).
137///
138/// Uses SimpleACK (no ACK struct needed).
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct DeleteObjectRequest {
141    /// Object to delete.
142    pub object_identifier: ObjectIdentifier,
143}
144
145impl DeleteObjectRequest {
146    /// Encode the request parameter into `buf`.
147    pub fn encode(&self, buf: &mut BytesMut) {
148        primitives::encode_app_object_id(buf, &self.object_identifier);
149    }
150
151    /// Decode the request from service-request octets; fails on malformed or truncated input,
152    /// on an object identifier under any tag but its application tag, and on octets after it.
153    pub fn decode(data: &[u8]) -> Result<Self, Error> {
154        let (object_identifier, end) =
155            decode_app_object_id(data, 0, "DeleteObject object-identifier")?;
156        expect_end(data, end, end, "DeleteObject")?;
157        Ok(Self { object_identifier })
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use bacnet_types::enums::{ObjectType, PropertyIdentifier};
165
166    fn create_object_type_bytes(object_type: &[u8]) -> BytesMut {
167        let mut buf = BytesMut::new();
168        tags::encode_opening_tag(&mut buf, 0);
169        primitives::encode_ctx_octet_string(&mut buf, 0, object_type);
170        tags::encode_closing_tag(&mut buf, 0);
171        buf
172    }
173
174    #[test]
175    fn create_object_by_type_round_trip() {
176        let req = CreateObjectRequest {
177            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
178            list_of_initial_values: vec![],
179        };
180        let mut buf = BytesMut::new();
181        req.encode(&mut buf);
182        let decoded = CreateObjectRequest::decode(&buf).unwrap();
183        assert_eq!(req, decoded);
184    }
185
186    #[test]
187    fn create_object_by_id_with_values() {
188        let req = CreateObjectRequest {
189            object_specifier: ObjectSpecifier::Identifier(
190                ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
191            ),
192            list_of_initial_values: vec![BACnetPropertyValue {
193                property_identifier: PropertyIdentifier::OBJECT_NAME,
194                property_array_index: None,
195                value: vec![0x75, 0x06, 0x00, 0x5A, 0x6F, 0x6E, 0x65, 0x31],
196                priority: None,
197            }],
198        };
199        let mut buf = BytesMut::new();
200        req.encode(&mut buf);
201        let decoded = CreateObjectRequest::decode(&buf).unwrap();
202        assert_eq!(req, decoded);
203    }
204
205    #[test]
206    fn create_object_type_must_fit_u32() {
207        let max_with_leading_zero = [0, 0xFF, 0xFF, 0xFF, 0xFF];
208        let decoded =
209            CreateObjectRequest::decode(&create_object_type_bytes(&max_with_leading_zero)).unwrap();
210        assert_eq!(
211            decoded.object_specifier,
212            ObjectSpecifier::Type(ObjectType::from_raw(u32::MAX))
213        );
214
215        for overflow in [u32::MAX as u64 + 1, u64::MAX] {
216            assert!(CreateObjectRequest::decode(&create_object_type_bytes(
217                &overflow.to_be_bytes(),
218            ))
219            .is_err());
220        }
221    }
222
223    #[test]
224    fn create_object_rejects_unexpected_or_trailing_fields() {
225        let encoded = create_object_type_bytes(&[0]);
226
227        let mut empty_values = encoded.clone();
228        tags::encode_opening_tag(&mut empty_values, 1);
229        tags::encode_closing_tag(&mut empty_values, 1);
230        assert!(CreateObjectRequest::decode(&empty_values).is_ok());
231
232        let mut unexpected = encoded.clone();
233        primitives::encode_ctx_unsigned(&mut unexpected, 1, 1);
234        assert!(CreateObjectRequest::decode(&unexpected).is_err());
235
236        let mut trailing = empty_values;
237        primitives::encode_app_null(&mut trailing);
238        assert!(CreateObjectRequest::decode(&trailing).is_err());
239    }
240
241    #[test]
242    fn create_object_accepts_exact_initial_value_limit() {
243        let value = BACnetPropertyValue {
244            property_identifier: PropertyIdentifier::PRESENT_VALUE,
245            property_array_index: None,
246            value: vec![0],
247            priority: None,
248        };
249        let request = CreateObjectRequest {
250            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
251            list_of_initial_values: vec![value.clone(); MAX_DECODED_ITEMS],
252        };
253        let mut encoded = BytesMut::new();
254        request.encode(&mut encoded);
255        assert_eq!(
256            CreateObjectRequest::decode(&encoded)
257                .unwrap()
258                .list_of_initial_values
259                .len(),
260            MAX_DECODED_ITEMS
261        );
262
263        let request = CreateObjectRequest {
264            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
265            list_of_initial_values: vec![value; MAX_DECODED_ITEMS + 1],
266        };
267        encoded.clear();
268        request.encode(&mut encoded);
269        assert!(CreateObjectRequest::decode(&encoded).is_err());
270    }
271
272    #[test]
273    fn delete_object_round_trip() {
274        let req = DeleteObjectRequest {
275            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
276        };
277        let mut buf = BytesMut::new();
278        req.encode(&mut buf);
279        let decoded = DeleteObjectRequest::decode(&buf).unwrap();
280        assert_eq!(req, decoded);
281    }
282
283    // -----------------------------------------------------------------------
284    // Malformed-input decode error tests
285    // -----------------------------------------------------------------------
286
287    #[test]
288    fn test_decode_create_object_empty_input() {
289        assert!(CreateObjectRequest::decode(&[]).is_err());
290    }
291
292    #[test]
293    fn test_decode_create_object_truncated_1_byte() {
294        let req = CreateObjectRequest {
295            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
296            list_of_initial_values: vec![],
297        };
298        let mut buf = BytesMut::new();
299        req.encode(&mut buf);
300        assert!(CreateObjectRequest::decode(&buf[..1]).is_err());
301    }
302
303    #[test]
304    fn test_decode_create_object_truncated_2_bytes() {
305        let req = CreateObjectRequest {
306            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
307            list_of_initial_values: vec![],
308        };
309        let mut buf = BytesMut::new();
310        req.encode(&mut buf);
311        assert!(CreateObjectRequest::decode(&buf[..2]).is_err());
312    }
313
314    #[test]
315    fn test_decode_create_object_truncated_3_bytes() {
316        let req = CreateObjectRequest {
317            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
318            list_of_initial_values: vec![],
319        };
320        let mut buf = BytesMut::new();
321        req.encode(&mut buf);
322        if buf.len() > 3 {
323            assert!(CreateObjectRequest::decode(&buf[..3]).is_err());
324        }
325    }
326
327    #[test]
328    fn test_decode_create_object_invalid_tag() {
329        // First byte should be opening tag 0, not 0xFF
330        assert!(CreateObjectRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
331    }
332
333    #[test]
334    fn test_decode_create_object_missing_closing_tag() {
335        // Opening tag 0 (0x0E) but no closing tag
336        assert!(CreateObjectRequest::decode(&[0x0E, 0x09, 0x00]).is_err());
337    }
338
339    #[test]
340    fn test_decode_delete_object_empty_input() {
341        assert!(DeleteObjectRequest::decode(&[]).is_err());
342    }
343
344    #[test]
345    fn test_decode_delete_object_truncated_1_byte() {
346        let req = DeleteObjectRequest {
347            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
348        };
349        let mut buf = BytesMut::new();
350        req.encode(&mut buf);
351        assert!(DeleteObjectRequest::decode(&buf[..1]).is_err());
352    }
353
354    #[test]
355    fn test_decode_delete_object_truncated_2_bytes() {
356        let req = DeleteObjectRequest {
357            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
358        };
359        let mut buf = BytesMut::new();
360        req.encode(&mut buf);
361        assert!(DeleteObjectRequest::decode(&buf[..2]).is_err());
362    }
363
364    #[test]
365    fn test_decode_delete_object_invalid_tag() {
366        assert!(DeleteObjectRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
367    }
368}