rustbac_core/services/
cov_notification.rs1#[cfg(feature = "alloc")]
2use crate::encoding::{primitives::decode_unsigned, reader::Reader, tag::Tag};
3#[cfg(feature = "alloc")]
4use crate::services::value_codec::decode_application_data_value_from_tag;
5#[cfg(feature = "alloc")]
6use crate::services::{decode_required_ctx_object_id, decode_required_ctx_unsigned};
7#[cfg(feature = "alloc")]
8use crate::types::{DataValue, ObjectId, PropertyId};
9#[cfg(feature = "alloc")]
10use crate::DecodeError;
11#[cfg(feature = "alloc")]
12use alloc::vec::Vec;
13
14pub const SERVICE_CONFIRMED_COV_NOTIFICATION: u8 = 0x01;
15pub const SERVICE_UNCONFIRMED_COV_NOTIFICATION: u8 = 0x02;
16
17#[cfg(feature = "alloc")]
18#[derive(Debug, Clone, PartialEq)]
19pub struct CovPropertyValue<'a> {
20 pub property_id: PropertyId,
21 pub array_index: Option<u32>,
22 pub value: DataValue<'a>,
23 pub priority: Option<u8>,
24}
25
26#[cfg(feature = "alloc")]
27#[derive(Debug, Clone, PartialEq)]
28pub struct CovNotificationRequest<'a> {
29 pub subscriber_process_id: u32,
30 pub initiating_device_id: ObjectId,
31 pub monitored_object_id: ObjectId,
32 pub time_remaining_seconds: u32,
33 pub values: Vec<CovPropertyValue<'a>>,
34}
35
36#[cfg(feature = "alloc")]
37impl<'a> CovNotificationRequest<'a> {
38 pub fn decode_after_header(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
39 let subscriber_process_id = decode_required_ctx_unsigned(r, 0)?;
40 let initiating_device_id = decode_required_ctx_object_id(r, 1)?;
41 let monitored_object_id = decode_required_ctx_object_id(r, 2)?;
42 let time_remaining_seconds = decode_required_ctx_unsigned(r, 3)?;
43
44 match Tag::decode(r)? {
45 Tag::Opening { tag_num: 4 } => {}
46 _ => return Err(DecodeError::InvalidTag),
47 }
48
49 let mut values = Vec::new();
50 loop {
51 let property_start = Tag::decode(r)?;
52 if property_start == (Tag::Closing { tag_num: 4 }) {
53 break;
54 }
55
56 let property_id = match property_start {
57 Tag::Context { tag_num: 0, len } => {
58 PropertyId::from_u32(decode_unsigned(r, len as usize)?)
59 }
60 _ => return Err(DecodeError::InvalidTag),
61 };
62
63 let next = Tag::decode(r)?;
64 let (array_index, value_open_tag) = match next {
65 Tag::Context { tag_num: 1, len } => {
66 let idx = decode_unsigned(r, len as usize)?;
67 (Some(idx), Tag::decode(r)?)
68 }
69 other => (None, other),
70 };
71 if value_open_tag != (Tag::Opening { tag_num: 2 }) {
72 return Err(DecodeError::InvalidTag);
73 }
74
75 let value_tag = Tag::decode(r)?;
76 let value = decode_application_data_value_from_tag(r, value_tag)?;
77 match Tag::decode(r)? {
78 Tag::Closing { tag_num: 2 } => {}
79 _ => return Err(DecodeError::InvalidTag),
80 }
81
82 let checkpoint = *r;
83 let priority = match Tag::decode(r)? {
84 Tag::Context { tag_num: 3, len } => {
85 let p = decode_unsigned(r, len as usize)?;
86 if p > u8::MAX as u32 {
87 return Err(DecodeError::InvalidValue);
88 }
89 Some(p as u8)
90 }
91 _ => {
92 *r = checkpoint;
93 None
94 }
95 };
96
97 values.push(CovPropertyValue {
98 property_id,
99 array_index,
100 value,
101 priority,
102 });
103 }
104
105 Ok(Self {
106 subscriber_process_id,
107 initiating_device_id,
108 monitored_object_id,
109 time_remaining_seconds,
110 values,
111 })
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 #[cfg(feature = "alloc")]
118 use super::{CovNotificationRequest, SERVICE_UNCONFIRMED_COV_NOTIFICATION};
119 #[cfg(feature = "alloc")]
120 use crate::apdu::UnconfirmedRequestHeader;
121 #[cfg(feature = "alloc")]
122 use crate::encoding::{
123 primitives::{encode_app_real, encode_ctx_unsigned},
124 tag::Tag,
125 writer::Writer,
126 };
127 #[cfg(feature = "alloc")]
128 use crate::types::{ObjectId, ObjectType, PropertyId};
129
130 #[cfg(feature = "alloc")]
131 #[test]
132 fn decode_cov_notification_after_header() {
133 let mut buf = [0u8; 256];
134 let mut w = Writer::new(&mut buf);
135 UnconfirmedRequestHeader {
136 service_choice: SERVICE_UNCONFIRMED_COV_NOTIFICATION,
137 }
138 .encode(&mut w)
139 .unwrap();
140 encode_ctx_unsigned(&mut w, 0, 77).unwrap();
141 encode_ctx_unsigned(&mut w, 1, ObjectId::new(ObjectType::Device, 1).raw()).unwrap();
142 encode_ctx_unsigned(&mut w, 2, ObjectId::new(ObjectType::AnalogInput, 2).raw()).unwrap();
143 encode_ctx_unsigned(&mut w, 3, 120).unwrap();
144 Tag::Opening { tag_num: 4 }.encode(&mut w).unwrap();
145 encode_ctx_unsigned(&mut w, 0, PropertyId::PresentValue.to_u32()).unwrap();
146 Tag::Opening { tag_num: 2 }.encode(&mut w).unwrap();
147 encode_app_real(&mut w, 42.25).unwrap();
148 Tag::Closing { tag_num: 2 }.encode(&mut w).unwrap();
149 encode_ctx_unsigned(&mut w, 3, 8).unwrap();
150 Tag::Closing { tag_num: 4 }.encode(&mut w).unwrap();
151
152 let encoded = w.as_written();
153 let mut r = crate::encoding::reader::Reader::new(encoded);
154 let _header = UnconfirmedRequestHeader::decode(&mut r).unwrap();
155 let cov = CovNotificationRequest::decode_after_header(&mut r).unwrap();
156 assert_eq!(cov.subscriber_process_id, 77);
157 assert_eq!(
158 cov.initiating_device_id,
159 ObjectId::new(ObjectType::Device, 1)
160 );
161 assert_eq!(
162 cov.monitored_object_id,
163 ObjectId::new(ObjectType::AnalogInput, 2)
164 );
165 assert_eq!(cov.values.len(), 1);
166 assert_eq!(cov.values[0].property_id, PropertyId::PresentValue);
167 assert_eq!(cov.values[0].priority, Some(8));
168 }
169}