1use bacnet_encoding::primitives;
5use bacnet_encoding::tags;
6use bacnet_types::enums::PropertyIdentifier;
7use bacnet_types::error::Error;
8use bacnet_types::primitives::{BACnetTimeStamp, ObjectIdentifier};
9use bytes::BytesMut;
10
11use crate::common::{PropertyReference, MAX_DECODED_ITEMS};
12
13#[derive(Debug, Clone, PartialEq)]
19pub struct COVReference {
20 pub monitored_property: PropertyReference,
21 pub cov_increment: Option<f32>,
22 pub timestamped: bool,
23}
24
25#[derive(Debug, Clone, PartialEq)]
27pub struct COVSubscriptionSpecification {
28 pub monitored_object_identifier: ObjectIdentifier,
29 pub list_of_cov_references: Vec<COVReference>,
30}
31
32#[derive(Debug, Clone, PartialEq)]
34pub struct SubscribeCOVPropertyMultipleRequest {
35 pub subscriber_process_identifier: u32,
36 pub max_notification_delay: Option<u32>,
37 pub issue_confirmed_notifications: Option<bool>,
38 pub list_of_cov_subscription_specifications: Vec<COVSubscriptionSpecification>,
39}
40
41impl SubscribeCOVPropertyMultipleRequest {
42 pub fn encode(&self, buf: &mut BytesMut) {
43 primitives::encode_ctx_unsigned(buf, 0, self.subscriber_process_identifier as u64);
45 if let Some(v) = self.max_notification_delay {
47 primitives::encode_ctx_unsigned(buf, 1, v as u64);
48 }
49 if let Some(v) = self.issue_confirmed_notifications {
51 primitives::encode_ctx_boolean(buf, 2, v);
52 }
53 tags::encode_opening_tag(buf, 3);
55 for spec in &self.list_of_cov_subscription_specifications {
56 primitives::encode_ctx_object_id(buf, 0, &spec.monitored_object_identifier);
58 tags::encode_opening_tag(buf, 1);
60 for cov_ref in &spec.list_of_cov_references {
61 tags::encode_opening_tag(buf, 0);
63 cov_ref.monitored_property.encode(buf);
64 tags::encode_closing_tag(buf, 0);
65 if let Some(inc) = cov_ref.cov_increment {
67 primitives::encode_ctx_real(buf, 1, inc);
68 }
69 if cov_ref.timestamped {
71 primitives::encode_ctx_boolean(buf, 2, true);
72 }
73 }
74 tags::encode_closing_tag(buf, 1);
75 }
76 tags::encode_closing_tag(buf, 3);
77 }
78
79 pub fn decode(data: &[u8]) -> Result<Self, Error> {
80 let mut offset = 0;
81
82 let (tag, pos) = tags::decode_tag(data, offset)?;
84 let end = pos + tag.length as usize;
85 if end > data.len() {
86 return Err(Error::decoding(
87 pos,
88 "SubscribeCOVPropertyMultiple truncated at process-id",
89 ));
90 }
91 let subscriber_process_identifier = primitives::decode_unsigned(&data[pos..end])? as u32;
92 offset = end;
93
94 let mut max_notification_delay = None;
96 if offset < data.len() {
97 let (opt, new_off) = tags::decode_optional_context(data, offset, 1)?;
98 if let Some(content) = opt {
99 max_notification_delay = Some(primitives::decode_unsigned(content)? as u32);
100 offset = new_off;
101 }
102 }
103
104 let mut issue_confirmed_notifications = None;
106 if offset < data.len() {
107 let (opt, new_off) = tags::decode_optional_context(data, offset, 2)?;
108 if let Some(content) = opt {
109 issue_confirmed_notifications = Some(!content.is_empty() && content[0] != 0);
110 offset = new_off;
111 }
112 }
113
114 let (tag, tag_end) = tags::decode_tag(data, offset)?;
116 if !tag.is_opening_tag(3) {
117 return Err(Error::decoding(
118 offset,
119 "SubscribeCOVPropertyMultiple expected opening tag 3",
120 ));
121 }
122 offset = tag_end;
123
124 let mut specs = Vec::new();
125 loop {
126 if offset >= data.len() {
127 return Err(Error::decoding(
128 offset,
129 "SubscribeCOVPropertyMultiple missing closing tag 3",
130 ));
131 }
132 if specs.len() >= MAX_DECODED_ITEMS {
133 return Err(Error::decoding(offset, "too many subscription specs"));
134 }
135 let (tag, tag_end) = tags::decode_tag(data, offset)?;
136 if tag.is_closing_tag(3) {
137 offset = tag_end;
138 break;
139 }
140
141 let end = tag_end + tag.length as usize;
143 if end > data.len() {
144 return Err(Error::decoding(
145 tag_end,
146 "SubscribeCOVPropertyMultiple truncated at object-id",
147 ));
148 }
149 let oid = ObjectIdentifier::decode(&data[tag_end..end])?;
150 offset = end;
151
152 let (tag, tag_end) = tags::decode_tag(data, offset)?;
154 if !tag.is_opening_tag(1) {
155 return Err(Error::decoding(
156 offset,
157 "SubscribeCOVPropertyMultiple expected opening tag 1",
158 ));
159 }
160 offset = tag_end;
161
162 let mut refs = Vec::new();
163 loop {
164 if offset >= data.len() {
165 return Err(Error::decoding(
166 offset,
167 "SubscribeCOVPropertyMultiple missing closing tag 1",
168 ));
169 }
170 if refs.len() >= MAX_DECODED_ITEMS {
171 return Err(Error::decoding(offset, "too many COV references"));
172 }
173 let (tag, tag_end) = tags::decode_tag(data, offset)?;
174 if tag.is_closing_tag(1) {
175 offset = tag_end;
176 break;
177 }
178
179 if !tag.is_opening_tag(0) {
181 return Err(Error::decoding(
182 offset,
183 "SubscribeCOVPropertyMultiple expected opening tag 0 for property ref",
184 ));
185 }
186 let (prop_ref, new_off) = PropertyReference::decode(data, tag_end)?;
187 offset = new_off;
188 let (_tag, tag_end) = tags::decode_tag(data, offset)?;
190 offset = tag_end;
191
192 let mut cov_increment = None;
194 if offset < data.len() {
195 let (opt, new_off) = tags::decode_optional_context(data, offset, 1)?;
196 if let Some(content) = opt {
197 cov_increment = Some(primitives::decode_real(content)?);
198 offset = new_off;
199 }
200 }
201
202 let mut timestamped = false;
204 if offset < data.len() {
205 let (opt, new_off) = tags::decode_optional_context(data, offset, 2)?;
206 if let Some(content) = opt {
207 timestamped = !content.is_empty() && content[0] != 0;
208 offset = new_off;
209 }
210 }
211
212 refs.push(COVReference {
213 monitored_property: prop_ref,
214 cov_increment,
215 timestamped,
216 });
217 }
218
219 specs.push(COVSubscriptionSpecification {
220 monitored_object_identifier: oid,
221 list_of_cov_references: refs,
222 });
223 }
224 let _ = offset;
225
226 Ok(Self {
227 subscriber_process_identifier,
228 max_notification_delay,
229 issue_confirmed_notifications,
230 list_of_cov_subscription_specifications: specs,
231 })
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Eq)]
242pub struct COVNotificationValue {
243 pub property_identifier: PropertyIdentifier,
244 pub property_array_index: Option<u32>,
245 pub value: Vec<u8>,
247 pub time_of_change: Option<Vec<u8>>,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct COVNotificationItem {
253 pub monitored_object_identifier: ObjectIdentifier,
254 pub list_of_values: Vec<COVNotificationValue>,
255}
256
257#[derive(Debug, Clone, PartialEq)]
259pub struct COVNotificationMultipleRequest {
260 pub subscriber_process_identifier: u32,
261 pub initiating_device_identifier: ObjectIdentifier,
262 pub time_remaining: u32,
263 pub timestamp: BACnetTimeStamp,
264 pub list_of_cov_notifications: Vec<COVNotificationItem>,
265}
266
267impl COVNotificationMultipleRequest {
268 pub fn encode(&self, buf: &mut BytesMut) {
269 primitives::encode_ctx_unsigned(buf, 0, self.subscriber_process_identifier as u64);
271 primitives::encode_ctx_object_id(buf, 1, &self.initiating_device_identifier);
273 primitives::encode_ctx_unsigned(buf, 2, self.time_remaining as u64);
275 primitives::encode_timestamp(buf, 3, &self.timestamp);
277 tags::encode_opening_tag(buf, 4);
279 for item in &self.list_of_cov_notifications {
280 primitives::encode_ctx_object_id(buf, 0, &item.monitored_object_identifier);
282 tags::encode_opening_tag(buf, 1);
284 for val in &item.list_of_values {
285 primitives::encode_ctx_unsigned(buf, 0, val.property_identifier.to_raw() as u64);
287 if let Some(idx) = val.property_array_index {
289 primitives::encode_ctx_unsigned(buf, 1, idx as u64);
290 }
291 tags::encode_opening_tag(buf, 2);
293 buf.extend_from_slice(&val.value);
294 tags::encode_closing_tag(buf, 2);
295 if let Some(ref ts) = val.time_of_change {
297 tags::encode_opening_tag(buf, 3);
298 buf.extend_from_slice(ts);
299 tags::encode_closing_tag(buf, 3);
300 }
301 }
302 tags::encode_closing_tag(buf, 1);
303 }
304 tags::encode_closing_tag(buf, 4);
305 }
306
307 pub fn decode(data: &[u8]) -> Result<Self, Error> {
308 let mut offset = 0;
309
310 let (tag, pos) = tags::decode_tag(data, offset)?;
312 let end = pos + tag.length as usize;
313 if end > data.len() {
314 return Err(Error::decoding(
315 pos,
316 "COVNotificationMultiple truncated at process-id",
317 ));
318 }
319 let subscriber_process_identifier = primitives::decode_unsigned(&data[pos..end])? as u32;
320 offset = end;
321
322 let (tag, pos) = tags::decode_tag(data, offset)?;
324 let end = pos + tag.length as usize;
325 if end > data.len() {
326 return Err(Error::decoding(
327 pos,
328 "COVNotificationMultiple truncated at device-id",
329 ));
330 }
331 let initiating_device_identifier = ObjectIdentifier::decode(&data[pos..end])?;
332 offset = end;
333
334 let (tag, pos) = tags::decode_tag(data, offset)?;
336 let end = pos + tag.length as usize;
337 if end > data.len() {
338 return Err(Error::decoding(
339 pos,
340 "COVNotificationMultiple truncated at time-remaining",
341 ));
342 }
343 let time_remaining = primitives::decode_unsigned(&data[pos..end])? as u32;
344 offset = end;
345
346 let (timestamp, new_off) = primitives::decode_timestamp(data, offset, 3)?;
348 offset = new_off;
349
350 let (tag, tag_end) = tags::decode_tag(data, offset)?;
352 if !tag.is_opening_tag(4) {
353 return Err(Error::decoding(
354 offset,
355 "COVNotificationMultiple expected opening tag 4",
356 ));
357 }
358 offset = tag_end;
359
360 let mut items = Vec::new();
361 loop {
362 if offset >= data.len() {
363 return Err(Error::decoding(
364 offset,
365 "COVNotificationMultiple missing closing tag 4",
366 ));
367 }
368 if items.len() >= MAX_DECODED_ITEMS {
369 return Err(Error::decoding(offset, "too many notification items"));
370 }
371 let (tag, tag_end) = tags::decode_tag(data, offset)?;
372 if tag.is_closing_tag(4) {
373 offset = tag_end;
374 break;
375 }
376
377 let end = tag_end + tag.length as usize;
379 if end > data.len() {
380 return Err(Error::decoding(
381 tag_end,
382 "COVNotificationMultiple truncated at monitored-id",
383 ));
384 }
385 let oid = ObjectIdentifier::decode(&data[tag_end..end])?;
386 offset = end;
387
388 let (tag, tag_end) = tags::decode_tag(data, offset)?;
390 if !tag.is_opening_tag(1) {
391 return Err(Error::decoding(
392 offset,
393 "COVNotificationMultiple expected opening tag 1",
394 ));
395 }
396 offset = tag_end;
397
398 let mut values = Vec::new();
399 loop {
400 if offset >= data.len() {
401 return Err(Error::decoding(
402 offset,
403 "COVNotificationMultiple missing closing tag 1",
404 ));
405 }
406 if values.len() >= MAX_DECODED_ITEMS {
407 return Err(Error::decoding(offset, "too many notification values"));
408 }
409 let (tag, tag_end) = tags::decode_tag(data, offset)?;
410 if tag.is_closing_tag(1) {
411 offset = tag_end;
412 break;
413 }
414
415 let end = tag_end + tag.length as usize;
417 if end > data.len() {
418 return Err(Error::decoding(
419 tag_end,
420 "COVNotificationMultiple truncated at property-id",
421 ));
422 }
423 let prop_id = primitives::decode_unsigned(&data[tag_end..end])? as u32;
424 offset = end;
425
426 let mut array_index = None;
428 if offset < data.len() {
429 let (opt, new_off) = tags::decode_optional_context(data, offset, 1)?;
430 if let Some(content) = opt {
431 array_index = Some(primitives::decode_unsigned(content)? as u32);
432 offset = new_off;
433 }
434 }
435
436 let (tag, tag_end) = tags::decode_tag(data, offset)?;
438 if !tag.is_opening_tag(2) {
439 return Err(Error::decoding(
440 offset,
441 "COVNotificationMultiple expected opening tag 2",
442 ));
443 }
444 let (value_bytes, new_off) = tags::extract_context_value(data, tag_end, 2)?;
445 let value = value_bytes.to_vec();
446 offset = new_off;
447
448 let mut time_of_change = None;
450 if offset < data.len() {
451 let (peek, _) = tags::decode_tag(data, offset)?;
452 if peek.is_opening_tag(3) {
453 let (_, inner_start) = tags::decode_tag(data, offset)?;
454 let (ts_bytes, new_off) =
455 tags::extract_context_value(data, inner_start, 3)?;
456 time_of_change = Some(ts_bytes.to_vec());
457 offset = new_off;
458 }
459 }
460
461 values.push(COVNotificationValue {
462 property_identifier: PropertyIdentifier::from_raw(prop_id),
463 property_array_index: array_index,
464 value,
465 time_of_change,
466 });
467 }
468
469 items.push(COVNotificationItem {
470 monitored_object_identifier: oid,
471 list_of_values: values,
472 });
473 }
474 let _ = offset;
475
476 Ok(Self {
477 subscriber_process_identifier,
478 initiating_device_identifier,
479 time_remaining,
480 timestamp,
481 list_of_cov_notifications: items,
482 })
483 }
484}
485
486#[cfg(test)]
491mod tests {
492 use super::*;
493 use bacnet_types::enums::ObjectType;
494 use bacnet_types::primitives::Time;
495
496 #[test]
497 fn subscribe_cov_property_multiple_round_trip() {
498 let req = SubscribeCOVPropertyMultipleRequest {
499 subscriber_process_identifier: 42,
500 max_notification_delay: Some(10),
501 issue_confirmed_notifications: Some(true),
502 list_of_cov_subscription_specifications: vec![COVSubscriptionSpecification {
503 monitored_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1)
504 .unwrap(),
505 list_of_cov_references: vec![
506 COVReference {
507 monitored_property: PropertyReference {
508 property_identifier: PropertyIdentifier::PRESENT_VALUE,
509 property_array_index: None,
510 },
511 cov_increment: Some(1.0),
512 timestamped: true,
513 },
514 COVReference {
515 monitored_property: PropertyReference {
516 property_identifier: PropertyIdentifier::STATUS_FLAGS,
517 property_array_index: None,
518 },
519 cov_increment: None,
520 timestamped: false,
521 },
522 ],
523 }],
524 };
525 let mut buf = BytesMut::new();
526 req.encode(&mut buf);
527 let decoded = SubscribeCOVPropertyMultipleRequest::decode(&buf).unwrap();
528 assert_eq!(req, decoded);
529 }
530
531 #[test]
532 fn subscribe_cov_property_multiple_minimal() {
533 let req = SubscribeCOVPropertyMultipleRequest {
534 subscriber_process_identifier: 1,
535 max_notification_delay: None,
536 issue_confirmed_notifications: None,
537 list_of_cov_subscription_specifications: vec![COVSubscriptionSpecification {
538 monitored_object_identifier: ObjectIdentifier::new(ObjectType::BINARY_INPUT, 5)
539 .unwrap(),
540 list_of_cov_references: vec![COVReference {
541 monitored_property: PropertyReference {
542 property_identifier: PropertyIdentifier::PRESENT_VALUE,
543 property_array_index: None,
544 },
545 cov_increment: None,
546 timestamped: false,
547 }],
548 }],
549 };
550 let mut buf = BytesMut::new();
551 req.encode(&mut buf);
552 let decoded = SubscribeCOVPropertyMultipleRequest::decode(&buf).unwrap();
553 assert_eq!(req, decoded);
554 }
555
556 #[test]
557 fn cov_notification_multiple_round_trip() {
558 let req = COVNotificationMultipleRequest {
559 subscriber_process_identifier: 1,
560 initiating_device_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 100).unwrap(),
561 time_remaining: 60,
562 timestamp: BACnetTimeStamp::SequenceNumber(42),
563 list_of_cov_notifications: vec![COVNotificationItem {
564 monitored_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1)
565 .unwrap(),
566 list_of_values: vec![
567 COVNotificationValue {
568 property_identifier: PropertyIdentifier::PRESENT_VALUE,
569 property_array_index: None,
570 value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
571 time_of_change: None,
572 },
573 COVNotificationValue {
574 property_identifier: PropertyIdentifier::STATUS_FLAGS,
575 property_array_index: None,
576 value: vec![0x82, 0x04, 0x00],
577 time_of_change: Some(vec![0x19, 0x2A]), },
579 ],
580 }],
581 };
582 let mut buf = BytesMut::new();
583 req.encode(&mut buf);
584 let decoded = COVNotificationMultipleRequest::decode(&buf).unwrap();
585 assert_eq!(req, decoded);
586 }
587
588 #[test]
589 fn cov_notification_multiple_with_time_timestamp() {
590 let req = COVNotificationMultipleRequest {
591 subscriber_process_identifier: 5,
592 initiating_device_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 200).unwrap(),
593 time_remaining: 0,
594 timestamp: BACnetTimeStamp::Time(Time {
595 hour: 12,
596 minute: 30,
597 second: 0,
598 hundredths: 0,
599 }),
600 list_of_cov_notifications: vec![COVNotificationItem {
601 monitored_object_identifier: ObjectIdentifier::new(ObjectType::BINARY_VALUE, 3)
602 .unwrap(),
603 list_of_values: vec![COVNotificationValue {
604 property_identifier: PropertyIdentifier::PRESENT_VALUE,
605 property_array_index: None,
606 value: vec![0x91, 0x01],
607 time_of_change: None,
608 }],
609 }],
610 };
611 let mut buf = BytesMut::new();
612 req.encode(&mut buf);
613 let decoded = COVNotificationMultipleRequest::decode(&buf).unwrap();
614 assert_eq!(req, decoded);
615 }
616
617 #[test]
618 fn subscribe_cov_property_multiple_empty_input() {
619 assert!(SubscribeCOVPropertyMultipleRequest::decode(&[]).is_err());
620 }
621
622 #[test]
623 fn cov_notification_multiple_empty_input() {
624 assert!(COVNotificationMultipleRequest::decode(&[]).is_err());
625 }
626}