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// CreateObjectAck
137// ---------------------------------------------------------------------------
138
139/// CreateObject-ACK service parameters, the Result(+) of Clause 15.3.1.2 as
140/// Clause 21 encodes it: the identifier of the object the server created,
141/// application-tagged.
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub struct CreateObjectAck {
144    /// Identifier of the newly created object.
145    pub object_identifier: ObjectIdentifier,
146}
147
148impl CreateObjectAck {
149    /// Encode the acknowledgment 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 acknowledgment from service-ack octets; fails on malformed or truncated
155    /// input, on an 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, "CreateObject-ACK object-identifier")?;
159        expect_end(data, end, end, "CreateObject-ACK")?;
160        Ok(Self { object_identifier })
161    }
162}
163
164// ---------------------------------------------------------------------------
165// DeleteObjectRequest
166// ---------------------------------------------------------------------------
167
168/// DeleteObject-Request service parameters (APPLICATION-tagged).
169///
170/// Uses SimpleACK (no ACK struct needed).
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct DeleteObjectRequest {
173    /// Object to delete.
174    pub object_identifier: ObjectIdentifier,
175}
176
177impl DeleteObjectRequest {
178    /// Encode the request parameter into `buf`.
179    pub fn encode(&self, buf: &mut BytesMut) {
180        primitives::encode_app_object_id(buf, &self.object_identifier);
181    }
182
183    /// Decode the request from service-request octets; fails on malformed or truncated input,
184    /// on an object identifier under any tag but its application tag, and on octets after it.
185    pub fn decode(data: &[u8]) -> Result<Self, Error> {
186        let (object_identifier, end) =
187            decode_app_object_id(data, 0, "DeleteObject object-identifier")?;
188        expect_end(data, end, end, "DeleteObject")?;
189        Ok(Self { object_identifier })
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use bacnet_types::enums::{ObjectType, PropertyIdentifier};
197
198    fn create_object_type_bytes(object_type: &[u8]) -> BytesMut {
199        let mut buf = BytesMut::new();
200        tags::encode_opening_tag(&mut buf, 0);
201        primitives::encode_ctx_octet_string(&mut buf, 0, object_type);
202        tags::encode_closing_tag(&mut buf, 0);
203        buf
204    }
205
206    #[test]
207    fn create_object_by_type_round_trip() {
208        let req = CreateObjectRequest {
209            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
210            list_of_initial_values: vec![],
211        };
212        let mut buf = BytesMut::new();
213        req.encode(&mut buf);
214        let decoded = CreateObjectRequest::decode(&buf).unwrap();
215        assert_eq!(req, decoded);
216    }
217
218    #[test]
219    fn create_object_by_id_with_values() {
220        let req = CreateObjectRequest {
221            object_specifier: ObjectSpecifier::Identifier(
222                ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
223            ),
224            list_of_initial_values: vec![BACnetPropertyValue {
225                property_identifier: PropertyIdentifier::OBJECT_NAME,
226                property_array_index: None,
227                value: vec![0x75, 0x06, 0x00, 0x5A, 0x6F, 0x6E, 0x65, 0x31],
228                priority: None,
229            }],
230        };
231        let mut buf = BytesMut::new();
232        req.encode(&mut buf);
233        let decoded = CreateObjectRequest::decode(&buf).unwrap();
234        assert_eq!(req, decoded);
235    }
236
237    #[test]
238    fn create_object_type_must_fit_u32() {
239        let max_with_leading_zero = [0, 0xFF, 0xFF, 0xFF, 0xFF];
240        let decoded =
241            CreateObjectRequest::decode(&create_object_type_bytes(&max_with_leading_zero)).unwrap();
242        assert_eq!(
243            decoded.object_specifier,
244            ObjectSpecifier::Type(ObjectType::from_raw(u32::MAX))
245        );
246
247        for overflow in [u32::MAX as u64 + 1, u64::MAX] {
248            assert!(CreateObjectRequest::decode(&create_object_type_bytes(
249                &overflow.to_be_bytes(),
250            ))
251            .is_err());
252        }
253    }
254
255    #[test]
256    fn create_object_rejects_unexpected_or_trailing_fields() {
257        let encoded = create_object_type_bytes(&[0]);
258
259        let mut empty_values = encoded.clone();
260        tags::encode_opening_tag(&mut empty_values, 1);
261        tags::encode_closing_tag(&mut empty_values, 1);
262        assert!(CreateObjectRequest::decode(&empty_values).is_ok());
263
264        let mut unexpected = encoded.clone();
265        primitives::encode_ctx_unsigned(&mut unexpected, 1, 1);
266        assert!(CreateObjectRequest::decode(&unexpected).is_err());
267
268        let mut trailing = empty_values;
269        primitives::encode_app_null(&mut trailing);
270        assert!(CreateObjectRequest::decode(&trailing).is_err());
271    }
272
273    #[test]
274    fn create_object_accepts_exact_initial_value_limit() {
275        let value = BACnetPropertyValue {
276            property_identifier: PropertyIdentifier::PRESENT_VALUE,
277            property_array_index: None,
278            value: vec![0],
279            priority: None,
280        };
281        let request = CreateObjectRequest {
282            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
283            list_of_initial_values: vec![value.clone(); MAX_DECODED_ITEMS],
284        };
285        let mut encoded = BytesMut::new();
286        request.encode(&mut encoded);
287        assert_eq!(
288            CreateObjectRequest::decode(&encoded)
289                .unwrap()
290                .list_of_initial_values
291                .len(),
292            MAX_DECODED_ITEMS
293        );
294
295        let request = CreateObjectRequest {
296            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
297            list_of_initial_values: vec![value; MAX_DECODED_ITEMS + 1],
298        };
299        encoded.clear();
300        request.encode(&mut encoded);
301        assert!(CreateObjectRequest::decode(&encoded).is_err());
302    }
303
304    #[test]
305    fn create_object_ack_round_trip_is_one_application_object_id() {
306        let ack = CreateObjectAck {
307            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 7).unwrap(),
308        };
309        let mut buf = BytesMut::new();
310        ack.encode(&mut buf);
311        // Application tag 12 (object identifier), length 4, then the packed id.
312        assert_eq!(&buf[..], &[0xC4, 0x00, 0x80, 0x00, 0x07]);
313        assert_eq!(CreateObjectAck::decode(&buf).unwrap(), ack);
314    }
315
316    #[test]
317    fn create_object_ack_rejects_malformed_input() {
318        let ack = CreateObjectAck {
319            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 7).unwrap(),
320        };
321        let mut buf = BytesMut::new();
322        ack.encode(&mut buf);
323        assert!(CreateObjectAck::decode(&[]).is_err());
324        for cut in 1..buf.len() {
325            assert!(CreateObjectAck::decode(&buf[..cut]).is_err(), "cut {cut}");
326        }
327        // Trailing octet after the identifier.
328        let mut trailing = buf.to_vec();
329        trailing.push(0x00);
330        assert!(CreateObjectAck::decode(&trailing).is_err());
331        // Context tag 0 instead of the application tag.
332        let mut context = BytesMut::new();
333        primitives::encode_ctx_object_id(&mut context, 0, &ack.object_identifier);
334        assert!(CreateObjectAck::decode(&context).is_err());
335        // An application-tagged unsigned is not an object identifier.
336        assert!(CreateObjectAck::decode(&[0x21, 0x07]).is_err());
337    }
338
339    #[test]
340    fn delete_object_round_trip() {
341        let req = DeleteObjectRequest {
342            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
343        };
344        let mut buf = BytesMut::new();
345        req.encode(&mut buf);
346        let decoded = DeleteObjectRequest::decode(&buf).unwrap();
347        assert_eq!(req, decoded);
348    }
349
350    // -----------------------------------------------------------------------
351    // Malformed-input decode error tests
352    // -----------------------------------------------------------------------
353
354    #[test]
355    fn test_decode_create_object_empty_input() {
356        assert!(CreateObjectRequest::decode(&[]).is_err());
357    }
358
359    #[test]
360    fn test_decode_create_object_truncated_1_byte() {
361        let req = CreateObjectRequest {
362            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
363            list_of_initial_values: vec![],
364        };
365        let mut buf = BytesMut::new();
366        req.encode(&mut buf);
367        assert!(CreateObjectRequest::decode(&buf[..1]).is_err());
368    }
369
370    #[test]
371    fn test_decode_create_object_truncated_2_bytes() {
372        let req = CreateObjectRequest {
373            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
374            list_of_initial_values: vec![],
375        };
376        let mut buf = BytesMut::new();
377        req.encode(&mut buf);
378        assert!(CreateObjectRequest::decode(&buf[..2]).is_err());
379    }
380
381    #[test]
382    fn test_decode_create_object_truncated_3_bytes() {
383        let req = CreateObjectRequest {
384            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
385            list_of_initial_values: vec![],
386        };
387        let mut buf = BytesMut::new();
388        req.encode(&mut buf);
389        if buf.len() > 3 {
390            assert!(CreateObjectRequest::decode(&buf[..3]).is_err());
391        }
392    }
393
394    #[test]
395    fn test_decode_create_object_invalid_tag() {
396        // First byte should be opening tag 0, not 0xFF
397        assert!(CreateObjectRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
398    }
399
400    #[test]
401    fn test_decode_create_object_missing_closing_tag() {
402        // Opening tag 0 (0x0E) but no closing tag
403        assert!(CreateObjectRequest::decode(&[0x0E, 0x09, 0x00]).is_err());
404    }
405
406    #[test]
407    fn test_decode_delete_object_empty_input() {
408        assert!(DeleteObjectRequest::decode(&[]).is_err());
409    }
410
411    #[test]
412    fn test_decode_delete_object_truncated_1_byte() {
413        let req = DeleteObjectRequest {
414            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
415        };
416        let mut buf = BytesMut::new();
417        req.encode(&mut buf);
418        assert!(DeleteObjectRequest::decode(&buf[..1]).is_err());
419    }
420
421    #[test]
422    fn test_decode_delete_object_truncated_2_bytes() {
423        let req = DeleteObjectRequest {
424            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
425        };
426        let mut buf = BytesMut::new();
427        req.encode(&mut buf);
428        assert!(DeleteObjectRequest::decode(&buf[..2]).is_err());
429    }
430
431    #[test]
432    fn test_decode_delete_object_invalid_tag() {
433        assert!(DeleteObjectRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
434    }
435}