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::primitives;
7use bacnet_encoding::tags;
8use bacnet_types::enums::ObjectType;
9use bacnet_types::error::Error;
10use bacnet_types::primitives::ObjectIdentifier;
11use bytes::BytesMut;
12
13use crate::common::{BACnetPropertyValue, MAX_DECODED_ITEMS};
14
15// ---------------------------------------------------------------------------
16// CreateObjectRequest
17// ---------------------------------------------------------------------------
18
19/// The object specifier: by type (server picks instance) or by identifier.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum ObjectSpecifier {
22    /// Create by type — server assigns instance number ([0] context tag inside [0] constructed).
23    Type(ObjectType),
24    /// Create with a specific identifier ([1] context tag inside [0] constructed).
25    Identifier(ObjectIdentifier),
26}
27
28/// CreateObject-Request service parameters.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct CreateObjectRequest {
31    pub object_specifier: ObjectSpecifier,
32    pub list_of_initial_values: Vec<BACnetPropertyValue>,
33}
34
35impl CreateObjectRequest {
36    pub fn encode(&self, buf: &mut BytesMut) {
37        // [0] object-specifier (constructed)
38        tags::encode_opening_tag(buf, 0);
39        match &self.object_specifier {
40            ObjectSpecifier::Type(obj_type) => {
41                primitives::encode_ctx_enumerated(buf, 0, obj_type.to_raw());
42            }
43            ObjectSpecifier::Identifier(oid) => {
44                primitives::encode_ctx_object_id(buf, 1, oid);
45            }
46        }
47        tags::encode_closing_tag(buf, 0);
48
49        // [1] list-of-initial-values (optional, constructed)
50        if !self.list_of_initial_values.is_empty() {
51            tags::encode_opening_tag(buf, 1);
52            for pv in &self.list_of_initial_values {
53                pv.encode(buf);
54            }
55            tags::encode_closing_tag(buf, 1);
56        }
57    }
58
59    pub fn decode(data: &[u8]) -> Result<Self, Error> {
60        let mut offset = 0;
61
62        // [0] object-specifier (opening tag 0)
63        let (tag, tag_end) = tags::decode_tag(data, offset)?;
64        if !tag.is_opening_tag(0) {
65            return Err(Error::decoding(
66                offset,
67                "CreateObject expected opening tag 0",
68            ));
69        }
70        offset = tag_end;
71
72        let (tag, pos) = tags::decode_tag(data, offset)?;
73        let end = pos + tag.length as usize;
74        if end > data.len() {
75            return Err(Error::decoding(
76                pos,
77                "CreateObject truncated at object-specifier",
78            ));
79        }
80
81        let object_specifier = if tag.is_context(0) {
82            let raw = primitives::decode_unsigned(&data[pos..end])? as u32;
83            ObjectSpecifier::Type(ObjectType::from_raw(raw))
84        } else if tag.is_context(1) {
85            ObjectSpecifier::Identifier(ObjectIdentifier::decode(&data[pos..end])?)
86        } else {
87            return Err(Error::decoding(
88                offset,
89                "CreateObject expected context tag 0 or 1 inside object-specifier",
90            ));
91        };
92        offset = end;
93
94        let (tag, tag_end) = tags::decode_tag(data, offset)?;
95        if !tag.is_closing_tag(0) {
96            return Err(Error::decoding(
97                offset,
98                "CreateObject expected closing tag 0",
99            ));
100        }
101        offset = tag_end;
102
103        // [1] list-of-initial-values (optional, opening tag 1)
104        let mut values = Vec::new();
105        if offset < data.len() {
106            let (tag, tag_end) = tags::decode_tag(data, offset)?;
107            if tag.is_opening_tag(1) {
108                offset = tag_end;
109                loop {
110                    if offset >= data.len() {
111                        return Err(Error::decoding(
112                            offset,
113                            "CreateObject missing closing tag 1",
114                        ));
115                    }
116                    if values.len() >= MAX_DECODED_ITEMS {
117                        return Err(Error::decoding(offset, "CreateObject values exceeds max"));
118                    }
119                    let (tag, _tag_end) = tags::decode_tag(data, offset)?;
120                    if tag.is_closing_tag(1) {
121                        break;
122                    }
123                    let (pv, new_offset) = BACnetPropertyValue::decode(data, offset)?;
124                    values.push(pv);
125                    offset = new_offset;
126                }
127            }
128        }
129
130        Ok(Self {
131            object_specifier,
132            list_of_initial_values: values,
133        })
134    }
135}
136
137// ---------------------------------------------------------------------------
138// DeleteObjectRequest
139// ---------------------------------------------------------------------------
140
141/// DeleteObject-Request service parameters (APPLICATION-tagged).
142///
143/// Uses SimpleACK (no ACK struct needed).
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct DeleteObjectRequest {
146    pub object_identifier: ObjectIdentifier,
147}
148
149impl DeleteObjectRequest {
150    pub fn encode(&self, buf: &mut BytesMut) {
151        primitives::encode_app_object_id(buf, &self.object_identifier);
152    }
153
154    pub fn decode(data: &[u8]) -> Result<Self, Error> {
155        let (tag, pos) = tags::decode_tag(data, 0)?;
156        let end = pos + tag.length as usize;
157        if end > data.len() {
158            return Err(Error::decoding(pos, "DeleteObject truncated at object-id"));
159        }
160        let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
161        Ok(Self { object_identifier })
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use bacnet_types::enums::{ObjectType, PropertyIdentifier};
169
170    #[test]
171    fn create_object_by_type_round_trip() {
172        let req = CreateObjectRequest {
173            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
174            list_of_initial_values: vec![],
175        };
176        let mut buf = BytesMut::new();
177        req.encode(&mut buf);
178        let decoded = CreateObjectRequest::decode(&buf).unwrap();
179        assert_eq!(req, decoded);
180    }
181
182    #[test]
183    fn create_object_by_id_with_values() {
184        let req = CreateObjectRequest {
185            object_specifier: ObjectSpecifier::Identifier(
186                ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
187            ),
188            list_of_initial_values: vec![BACnetPropertyValue {
189                property_identifier: PropertyIdentifier::OBJECT_NAME,
190                property_array_index: None,
191                value: vec![0x75, 0x06, 0x00, 0x5A, 0x6F, 0x6E, 0x65, 0x31],
192                priority: None,
193            }],
194        };
195        let mut buf = BytesMut::new();
196        req.encode(&mut buf);
197        let decoded = CreateObjectRequest::decode(&buf).unwrap();
198        assert_eq!(req, decoded);
199    }
200
201    #[test]
202    fn delete_object_round_trip() {
203        let req = DeleteObjectRequest {
204            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
205        };
206        let mut buf = BytesMut::new();
207        req.encode(&mut buf);
208        let decoded = DeleteObjectRequest::decode(&buf).unwrap();
209        assert_eq!(req, decoded);
210    }
211
212    // -----------------------------------------------------------------------
213    // Malformed-input decode error tests
214    // -----------------------------------------------------------------------
215
216    #[test]
217    fn test_decode_create_object_empty_input() {
218        assert!(CreateObjectRequest::decode(&[]).is_err());
219    }
220
221    #[test]
222    fn test_decode_create_object_truncated_1_byte() {
223        let req = CreateObjectRequest {
224            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
225            list_of_initial_values: vec![],
226        };
227        let mut buf = BytesMut::new();
228        req.encode(&mut buf);
229        assert!(CreateObjectRequest::decode(&buf[..1]).is_err());
230    }
231
232    #[test]
233    fn test_decode_create_object_truncated_2_bytes() {
234        let req = CreateObjectRequest {
235            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
236            list_of_initial_values: vec![],
237        };
238        let mut buf = BytesMut::new();
239        req.encode(&mut buf);
240        assert!(CreateObjectRequest::decode(&buf[..2]).is_err());
241    }
242
243    #[test]
244    fn test_decode_create_object_truncated_3_bytes() {
245        let req = CreateObjectRequest {
246            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
247            list_of_initial_values: vec![],
248        };
249        let mut buf = BytesMut::new();
250        req.encode(&mut buf);
251        if buf.len() > 3 {
252            assert!(CreateObjectRequest::decode(&buf[..3]).is_err());
253        }
254    }
255
256    #[test]
257    fn test_decode_create_object_invalid_tag() {
258        // First byte should be opening tag 0, not 0xFF
259        assert!(CreateObjectRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
260    }
261
262    #[test]
263    fn test_decode_create_object_missing_closing_tag() {
264        // Opening tag 0 (0x0E) but no closing tag
265        assert!(CreateObjectRequest::decode(&[0x0E, 0x09, 0x00]).is_err());
266    }
267
268    #[test]
269    fn test_decode_delete_object_empty_input() {
270        assert!(DeleteObjectRequest::decode(&[]).is_err());
271    }
272
273    #[test]
274    fn test_decode_delete_object_truncated_1_byte() {
275        let req = DeleteObjectRequest {
276            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
277        };
278        let mut buf = BytesMut::new();
279        req.encode(&mut buf);
280        assert!(DeleteObjectRequest::decode(&buf[..1]).is_err());
281    }
282
283    #[test]
284    fn test_decode_delete_object_truncated_2_bytes() {
285        let req = DeleteObjectRequest {
286            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
287        };
288        let mut buf = BytesMut::new();
289        req.encode(&mut buf);
290        assert!(DeleteObjectRequest::decode(&buf[..2]).is_err());
291    }
292
293    #[test]
294    fn test_decode_delete_object_invalid_tag() {
295        assert!(DeleteObjectRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
296    }
297}