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)?;
189 offset = tag_end;
190
191 let mut cov_increment = None;
193 if offset < data.len() {
194 let (opt, new_off) = tags::decode_optional_context(data, offset, 1)?;
195 if let Some(content) = opt {
196 cov_increment = Some(primitives::decode_real(content)?);
197 offset = new_off;
198 }
199 }
200
201 let mut timestamped = false;
203 if offset < data.len() {
204 let (opt, new_off) = tags::decode_optional_context(data, offset, 2)?;
205 if let Some(content) = opt {
206 timestamped = !content.is_empty() && content[0] != 0;
207 offset = new_off;
208 }
209 }
210
211 refs.push(COVReference {
212 monitored_property: prop_ref,
213 cov_increment,
214 timestamped,
215 });
216 }
217
218 specs.push(COVSubscriptionSpecification {
219 monitored_object_identifier: oid,
220 list_of_cov_references: refs,
221 });
222 }
223 let _ = offset;
224
225 Ok(Self {
226 subscriber_process_identifier,
227 max_notification_delay,
228 issue_confirmed_notifications,
229 list_of_cov_subscription_specifications: specs,
230 })
231 }
232}
233
234#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct COVNotificationValue {
241 pub property_identifier: PropertyIdentifier,
242 pub property_array_index: Option<u32>,
243 pub value: Vec<u8>,
245 pub time_of_change: Option<Vec<u8>>,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq)]
250pub struct COVNotificationItem {
251 pub monitored_object_identifier: ObjectIdentifier,
252 pub list_of_values: Vec<COVNotificationValue>,
253}
254
255#[derive(Debug, Clone, PartialEq)]
257pub struct COVNotificationMultipleRequest {
258 pub subscriber_process_identifier: u32,
259 pub initiating_device_identifier: ObjectIdentifier,
260 pub time_remaining: u32,
261 pub timestamp: BACnetTimeStamp,
262 pub list_of_cov_notifications: Vec<COVNotificationItem>,
263}
264
265impl COVNotificationMultipleRequest {
266 pub fn encode(&self, buf: &mut BytesMut) {
267 primitives::encode_ctx_unsigned(buf, 0, self.subscriber_process_identifier as u64);
269 primitives::encode_ctx_object_id(buf, 1, &self.initiating_device_identifier);
271 primitives::encode_ctx_unsigned(buf, 2, self.time_remaining as u64);
273 primitives::encode_timestamp(buf, 3, &self.timestamp);
275 tags::encode_opening_tag(buf, 4);
277 for item in &self.list_of_cov_notifications {
278 primitives::encode_ctx_object_id(buf, 0, &item.monitored_object_identifier);
280 tags::encode_opening_tag(buf, 1);
282 for val in &item.list_of_values {
283 primitives::encode_ctx_unsigned(buf, 0, val.property_identifier.to_raw() as u64);
285 if let Some(idx) = val.property_array_index {
287 primitives::encode_ctx_unsigned(buf, 1, idx as u64);
288 }
289 tags::encode_opening_tag(buf, 2);
291 buf.extend_from_slice(&val.value);
292 tags::encode_closing_tag(buf, 2);
293 if let Some(ref ts) = val.time_of_change {
295 tags::encode_opening_tag(buf, 3);
296 buf.extend_from_slice(ts);
297 tags::encode_closing_tag(buf, 3);
298 }
299 }
300 tags::encode_closing_tag(buf, 1);
301 }
302 tags::encode_closing_tag(buf, 4);
303 }
304
305 pub fn decode(data: &[u8]) -> Result<Self, Error> {
306 let mut offset = 0;
307
308 let (tag, pos) = tags::decode_tag(data, offset)?;
310 let end = pos + tag.length as usize;
311 if end > data.len() {
312 return Err(Error::decoding(
313 pos,
314 "COVNotificationMultiple truncated at process-id",
315 ));
316 }
317 let subscriber_process_identifier = primitives::decode_unsigned(&data[pos..end])? as u32;
318 offset = end;
319
320 let (tag, pos) = tags::decode_tag(data, offset)?;
322 let end = pos + tag.length as usize;
323 if end > data.len() {
324 return Err(Error::decoding(
325 pos,
326 "COVNotificationMultiple truncated at device-id",
327 ));
328 }
329 let initiating_device_identifier = ObjectIdentifier::decode(&data[pos..end])?;
330 offset = end;
331
332 let (tag, pos) = tags::decode_tag(data, offset)?;
334 let end = pos + tag.length as usize;
335 if end > data.len() {
336 return Err(Error::decoding(
337 pos,
338 "COVNotificationMultiple truncated at time-remaining",
339 ));
340 }
341 let time_remaining = primitives::decode_unsigned(&data[pos..end])? as u32;
342 offset = end;
343
344 let (timestamp, new_off) = primitives::decode_timestamp(data, offset, 3)?;
346 offset = new_off;
347
348 let (tag, tag_end) = tags::decode_tag(data, offset)?;
350 if !tag.is_opening_tag(4) {
351 return Err(Error::decoding(
352 offset,
353 "COVNotificationMultiple expected opening tag 4",
354 ));
355 }
356 offset = tag_end;
357
358 let mut items = Vec::new();
359 loop {
360 if offset >= data.len() {
361 return Err(Error::decoding(
362 offset,
363 "COVNotificationMultiple missing closing tag 4",
364 ));
365 }
366 if items.len() >= MAX_DECODED_ITEMS {
367 return Err(Error::decoding(offset, "too many notification items"));
368 }
369 let (tag, tag_end) = tags::decode_tag(data, offset)?;
370 if tag.is_closing_tag(4) {
371 offset = tag_end;
372 break;
373 }
374
375 let end = tag_end + tag.length as usize;
377 if end > data.len() {
378 return Err(Error::decoding(
379 tag_end,
380 "COVNotificationMultiple truncated at monitored-id",
381 ));
382 }
383 let oid = ObjectIdentifier::decode(&data[tag_end..end])?;
384 offset = end;
385
386 let (tag, tag_end) = tags::decode_tag(data, offset)?;
388 if !tag.is_opening_tag(1) {
389 return Err(Error::decoding(
390 offset,
391 "COVNotificationMultiple expected opening tag 1",
392 ));
393 }
394 offset = tag_end;
395
396 let mut values = Vec::new();
397 loop {
398 if offset >= data.len() {
399 return Err(Error::decoding(
400 offset,
401 "COVNotificationMultiple missing closing tag 1",
402 ));
403 }
404 if values.len() >= MAX_DECODED_ITEMS {
405 return Err(Error::decoding(offset, "too many notification values"));
406 }
407 let (tag, tag_end) = tags::decode_tag(data, offset)?;
408 if tag.is_closing_tag(1) {
409 offset = tag_end;
410 break;
411 }
412
413 let end = tag_end + tag.length as usize;
415 if end > data.len() {
416 return Err(Error::decoding(
417 tag_end,
418 "COVNotificationMultiple truncated at property-id",
419 ));
420 }
421 let prop_id = primitives::decode_unsigned(&data[tag_end..end])? as u32;
422 offset = end;
423
424 let mut array_index = None;
426 if offset < data.len() {
427 let (opt, new_off) = tags::decode_optional_context(data, offset, 1)?;
428 if let Some(content) = opt {
429 array_index = Some(primitives::decode_unsigned(content)? as u32);
430 offset = new_off;
431 }
432 }
433
434 let (tag, tag_end) = tags::decode_tag(data, offset)?;
436 if !tag.is_opening_tag(2) {
437 return Err(Error::decoding(
438 offset,
439 "COVNotificationMultiple expected opening tag 2",
440 ));
441 }
442 let (value_bytes, new_off) = tags::extract_context_value(data, tag_end, 2)?;
443 let value = value_bytes.to_vec();
444 offset = new_off;
445
446 let mut time_of_change = None;
448 if offset < data.len() {
449 let (peek, _) = tags::decode_tag(data, offset)?;
450 if peek.is_opening_tag(3) {
451 let (_, inner_start) = tags::decode_tag(data, offset)?;
452 let (ts_bytes, new_off) =
453 tags::extract_context_value(data, inner_start, 3)?;
454 time_of_change = Some(ts_bytes.to_vec());
455 offset = new_off;
456 }
457 }
458
459 values.push(COVNotificationValue {
460 property_identifier: PropertyIdentifier::from_raw(prop_id),
461 property_array_index: array_index,
462 value,
463 time_of_change,
464 });
465 }
466
467 items.push(COVNotificationItem {
468 monitored_object_identifier: oid,
469 list_of_values: values,
470 });
471 }
472 let _ = offset;
473
474 Ok(Self {
475 subscriber_process_identifier,
476 initiating_device_identifier,
477 time_remaining,
478 timestamp,
479 list_of_cov_notifications: items,
480 })
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487 use bacnet_types::enums::ObjectType;
488 use bacnet_types::primitives::Time;
489
490 #[test]
491 fn subscribe_cov_property_multiple_round_trip() {
492 let req = SubscribeCOVPropertyMultipleRequest {
493 subscriber_process_identifier: 42,
494 max_notification_delay: Some(10),
495 issue_confirmed_notifications: Some(true),
496 list_of_cov_subscription_specifications: vec![COVSubscriptionSpecification {
497 monitored_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1)
498 .unwrap(),
499 list_of_cov_references: vec![
500 COVReference {
501 monitored_property: PropertyReference {
502 property_identifier: PropertyIdentifier::PRESENT_VALUE,
503 property_array_index: None,
504 },
505 cov_increment: Some(1.0),
506 timestamped: true,
507 },
508 COVReference {
509 monitored_property: PropertyReference {
510 property_identifier: PropertyIdentifier::STATUS_FLAGS,
511 property_array_index: None,
512 },
513 cov_increment: None,
514 timestamped: false,
515 },
516 ],
517 }],
518 };
519 let mut buf = BytesMut::new();
520 req.encode(&mut buf);
521 let decoded = SubscribeCOVPropertyMultipleRequest::decode(&buf).unwrap();
522 assert_eq!(req, decoded);
523 }
524
525 #[test]
526 fn subscribe_cov_property_multiple_minimal() {
527 let req = SubscribeCOVPropertyMultipleRequest {
528 subscriber_process_identifier: 1,
529 max_notification_delay: None,
530 issue_confirmed_notifications: None,
531 list_of_cov_subscription_specifications: vec![COVSubscriptionSpecification {
532 monitored_object_identifier: ObjectIdentifier::new(ObjectType::BINARY_INPUT, 5)
533 .unwrap(),
534 list_of_cov_references: vec![COVReference {
535 monitored_property: PropertyReference {
536 property_identifier: PropertyIdentifier::PRESENT_VALUE,
537 property_array_index: None,
538 },
539 cov_increment: None,
540 timestamped: false,
541 }],
542 }],
543 };
544 let mut buf = BytesMut::new();
545 req.encode(&mut buf);
546 let decoded = SubscribeCOVPropertyMultipleRequest::decode(&buf).unwrap();
547 assert_eq!(req, decoded);
548 }
549
550 #[test]
551 fn cov_notification_multiple_round_trip() {
552 let req = COVNotificationMultipleRequest {
553 subscriber_process_identifier: 1,
554 initiating_device_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 100).unwrap(),
555 time_remaining: 60,
556 timestamp: BACnetTimeStamp::SequenceNumber(42),
557 list_of_cov_notifications: vec![COVNotificationItem {
558 monitored_object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1)
559 .unwrap(),
560 list_of_values: vec![
561 COVNotificationValue {
562 property_identifier: PropertyIdentifier::PRESENT_VALUE,
563 property_array_index: None,
564 value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
565 time_of_change: None,
566 },
567 COVNotificationValue {
568 property_identifier: PropertyIdentifier::STATUS_FLAGS,
569 property_array_index: None,
570 value: vec![0x82, 0x04, 0x00],
571 time_of_change: Some(vec![0x19, 0x2A]), },
573 ],
574 }],
575 };
576 let mut buf = BytesMut::new();
577 req.encode(&mut buf);
578 let decoded = COVNotificationMultipleRequest::decode(&buf).unwrap();
579 assert_eq!(req, decoded);
580 }
581
582 #[test]
583 fn cov_notification_multiple_with_time_timestamp() {
584 let req = COVNotificationMultipleRequest {
585 subscriber_process_identifier: 5,
586 initiating_device_identifier: ObjectIdentifier::new(ObjectType::DEVICE, 200).unwrap(),
587 time_remaining: 0,
588 timestamp: BACnetTimeStamp::Time(Time {
589 hour: 12,
590 minute: 30,
591 second: 0,
592 hundredths: 0,
593 }),
594 list_of_cov_notifications: vec![COVNotificationItem {
595 monitored_object_identifier: ObjectIdentifier::new(ObjectType::BINARY_VALUE, 3)
596 .unwrap(),
597 list_of_values: vec![COVNotificationValue {
598 property_identifier: PropertyIdentifier::PRESENT_VALUE,
599 property_array_index: None,
600 value: vec![0x91, 0x01],
601 time_of_change: None,
602 }],
603 }],
604 };
605 let mut buf = BytesMut::new();
606 req.encode(&mut buf);
607 let decoded = COVNotificationMultipleRequest::decode(&buf).unwrap();
608 assert_eq!(req, decoded);
609 }
610
611 #[test]
612 fn subscribe_cov_property_multiple_empty_input() {
613 assert!(SubscribeCOVPropertyMultipleRequest::decode(&[]).is_err());
614 }
615
616 #[test]
617 fn cov_notification_multiple_empty_input() {
618 assert!(COVNotificationMultipleRequest::decode(&[]).is_err());
619 }
620}