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