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 (Clause 15.3.1.1)
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        // CHOICE inside [0]: context [0] object-type OR context [1] object-identifier
73        let (tag, pos) = tags::decode_tag(data, offset)?;
74        let end = pos + tag.length as usize;
75        if end > data.len() {
76            return Err(Error::decoding(
77                pos,
78                "CreateObject truncated at object-specifier",
79            ));
80        }
81
82        let object_specifier = if tag.is_context(0) {
83            let raw = primitives::decode_unsigned(&data[pos..end])? as u32;
84            ObjectSpecifier::Type(ObjectType::from_raw(raw))
85        } else if tag.is_context(1) {
86            ObjectSpecifier::Identifier(ObjectIdentifier::decode(&data[pos..end])?)
87        } else {
88            return Err(Error::decoding(
89                offset,
90                "CreateObject expected context tag 0 or 1 inside object-specifier",
91            ));
92        };
93        offset = end;
94
95        // closing tag 0
96        let (tag, tag_end) = tags::decode_tag(data, offset)?;
97        if !tag.is_closing_tag(0) {
98            return Err(Error::decoding(
99                offset,
100                "CreateObject expected closing tag 0",
101            ));
102        }
103        offset = tag_end;
104
105        // [1] list-of-initial-values (optional, opening tag 1)
106        let mut values = Vec::new();
107        if offset < data.len() {
108            let (tag, tag_end) = tags::decode_tag(data, offset)?;
109            if tag.is_opening_tag(1) {
110                offset = tag_end;
111                loop {
112                    if offset >= data.len() {
113                        return Err(Error::decoding(
114                            offset,
115                            "CreateObject missing closing tag 1",
116                        ));
117                    }
118                    if values.len() >= MAX_DECODED_ITEMS {
119                        return Err(Error::decoding(offset, "CreateObject values exceeds max"));
120                    }
121                    let (tag, _tag_end) = tags::decode_tag(data, offset)?;
122                    if tag.is_closing_tag(1) {
123                        break;
124                    }
125                    let (pv, new_offset) = BACnetPropertyValue::decode(data, offset)?;
126                    values.push(pv);
127                    offset = new_offset;
128                }
129            }
130        }
131
132        Ok(Self {
133            object_specifier,
134            list_of_initial_values: values,
135        })
136    }
137}
138
139// ---------------------------------------------------------------------------
140// DeleteObjectRequest (Clause 15.4.1.1)
141// ---------------------------------------------------------------------------
142
143/// DeleteObject-Request service parameters (APPLICATION-tagged).
144///
145/// Uses SimpleACK (no ACK struct needed).
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct DeleteObjectRequest {
148    pub object_identifier: ObjectIdentifier,
149}
150
151impl DeleteObjectRequest {
152    pub fn encode(&self, buf: &mut BytesMut) {
153        primitives::encode_app_object_id(buf, &self.object_identifier);
154    }
155
156    pub fn decode(data: &[u8]) -> Result<Self, Error> {
157        let (tag, pos) = tags::decode_tag(data, 0)?;
158        let end = pos + tag.length as usize;
159        if end > data.len() {
160            return Err(Error::decoding(pos, "DeleteObject truncated at object-id"));
161        }
162        let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
163        Ok(Self { object_identifier })
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use bacnet_types::enums::{ObjectType, PropertyIdentifier};
171
172    #[test]
173    fn create_object_by_type_round_trip() {
174        let req = CreateObjectRequest {
175            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
176            list_of_initial_values: vec![],
177        };
178        let mut buf = BytesMut::new();
179        req.encode(&mut buf);
180        let decoded = CreateObjectRequest::decode(&buf).unwrap();
181        assert_eq!(req, decoded);
182    }
183
184    #[test]
185    fn create_object_by_id_with_values() {
186        let req = CreateObjectRequest {
187            object_specifier: ObjectSpecifier::Identifier(
188                ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
189            ),
190            list_of_initial_values: vec![BACnetPropertyValue {
191                property_identifier: PropertyIdentifier::OBJECT_NAME,
192                property_array_index: None,
193                value: vec![0x75, 0x06, 0x00, 0x5A, 0x6F, 0x6E, 0x65, 0x31],
194                priority: None,
195            }],
196        };
197        let mut buf = BytesMut::new();
198        req.encode(&mut buf);
199        let decoded = CreateObjectRequest::decode(&buf).unwrap();
200        assert_eq!(req, decoded);
201    }
202
203    #[test]
204    fn delete_object_round_trip() {
205        let req = DeleteObjectRequest {
206            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
207        };
208        let mut buf = BytesMut::new();
209        req.encode(&mut buf);
210        let decoded = DeleteObjectRequest::decode(&buf).unwrap();
211        assert_eq!(req, decoded);
212    }
213
214    // -----------------------------------------------------------------------
215    // Malformed-input decode error tests
216    // -----------------------------------------------------------------------
217
218    #[test]
219    fn test_decode_create_object_empty_input() {
220        assert!(CreateObjectRequest::decode(&[]).is_err());
221    }
222
223    #[test]
224    fn test_decode_create_object_truncated_1_byte() {
225        let req = CreateObjectRequest {
226            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
227            list_of_initial_values: vec![],
228        };
229        let mut buf = BytesMut::new();
230        req.encode(&mut buf);
231        assert!(CreateObjectRequest::decode(&buf[..1]).is_err());
232    }
233
234    #[test]
235    fn test_decode_create_object_truncated_2_bytes() {
236        let req = CreateObjectRequest {
237            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
238            list_of_initial_values: vec![],
239        };
240        let mut buf = BytesMut::new();
241        req.encode(&mut buf);
242        assert!(CreateObjectRequest::decode(&buf[..2]).is_err());
243    }
244
245    #[test]
246    fn test_decode_create_object_truncated_3_bytes() {
247        let req = CreateObjectRequest {
248            object_specifier: ObjectSpecifier::Type(ObjectType::ANALOG_INPUT),
249            list_of_initial_values: vec![],
250        };
251        let mut buf = BytesMut::new();
252        req.encode(&mut buf);
253        if buf.len() > 3 {
254            assert!(CreateObjectRequest::decode(&buf[..3]).is_err());
255        }
256    }
257
258    #[test]
259    fn test_decode_create_object_invalid_tag() {
260        // First byte should be opening tag 0, not 0xFF
261        assert!(CreateObjectRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
262    }
263
264    #[test]
265    fn test_decode_create_object_missing_closing_tag() {
266        // Opening tag 0 (0x0E) but no closing tag
267        assert!(CreateObjectRequest::decode(&[0x0E, 0x09, 0x00]).is_err());
268    }
269
270    #[test]
271    fn test_decode_delete_object_empty_input() {
272        assert!(DeleteObjectRequest::decode(&[]).is_err());
273    }
274
275    #[test]
276    fn test_decode_delete_object_truncated_1_byte() {
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        assert!(DeleteObjectRequest::decode(&buf[..1]).is_err());
283    }
284
285    #[test]
286    fn test_decode_delete_object_truncated_2_bytes() {
287        let req = DeleteObjectRequest {
288            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
289        };
290        let mut buf = BytesMut::new();
291        req.encode(&mut buf);
292        assert!(DeleteObjectRequest::decode(&buf[..2]).is_err());
293    }
294
295    #[test]
296    fn test_decode_delete_object_invalid_tag() {
297        assert!(DeleteObjectRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
298    }
299}