1#[cfg(feature = "alloc")]
2use crate::encoding::{
3 primitives::{decode_ctx_character_string, decode_unsigned},
4 reader::Reader,
5 tag::{AppTag, Tag},
6};
7#[cfg(feature = "alloc")]
8use crate::services::acknowledge_alarm::TimeStamp;
9#[cfg(feature = "alloc")]
10use crate::services::{decode_required_ctx_object_id, decode_required_ctx_unsigned};
11#[cfg(feature = "alloc")]
12use crate::types::{Date, ObjectId, Time};
13#[cfg(feature = "alloc")]
14use crate::DecodeError;
15
16pub const SERVICE_CONFIRMED_EVENT_NOTIFICATION: u8 = 0x02;
17pub const SERVICE_UNCONFIRMED_EVENT_NOTIFICATION: u8 = 0x03;
18
19#[cfg(feature = "alloc")]
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct EventNotificationRequest<'a> {
22 pub process_id: u32,
23 pub initiating_device_id: ObjectId,
24 pub event_object_id: ObjectId,
25 pub timestamp: TimeStamp,
26 pub notification_class: u32,
27 pub priority: u32,
28 pub event_type: u32,
29 pub message_text: Option<&'a str>,
30 pub notify_type: u32,
31 pub ack_required: Option<bool>,
32 pub from_state: u32,
33 pub to_state: u32,
34}
35
36#[cfg(feature = "alloc")]
37impl<'a> EventNotificationRequest<'a> {
38 pub fn decode_after_header(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
39 let process_id = decode_required_ctx_unsigned(r, 0)?;
40 let initiating_device_id = decode_required_ctx_object_id(r, 1)?;
41 let event_object_id = decode_required_ctx_object_id(r, 2)?;
42 let timestamp = decode_required_ctx_timestamp(r, 3)?;
43 let notification_class = decode_required_ctx_unsigned(r, 4)?;
44 let priority = decode_required_ctx_unsigned(r, 5)?;
45 let event_type = decode_required_ctx_unsigned(r, 6)?;
46
47 let checkpoint = *r;
48 let message_text = match Tag::decode(r)? {
49 Tag::Context { tag_num: 7, len } => Some(decode_ctx_character_string(r, len as usize)?),
50 _ => {
51 *r = checkpoint;
52 None
53 }
54 };
55
56 let notify_type = decode_required_ctx_unsigned(r, 8)?;
57
58 let checkpoint = *r;
59 let ack_required = match Tag::decode(r)? {
60 Tag::Context { tag_num: 9, len } => Some(len != 0),
61 _ => {
62 *r = checkpoint;
63 None
64 }
65 };
66
67 let from_state = decode_required_ctx_unsigned(r, 10)?;
68 let to_state = decode_required_ctx_unsigned(r, 11)?;
69
70 let checkpoint = *r;
71 if Tag::decode(r)? == (Tag::Opening { tag_num: 12 }) {
72 skip_constructed(r, 12)?;
73 } else {
74 *r = checkpoint;
75 }
76
77 Ok(Self {
78 process_id,
79 initiating_device_id,
80 event_object_id,
81 timestamp,
82 notification_class,
83 priority,
84 event_type,
85 message_text,
86 notify_type,
87 ack_required,
88 from_state,
89 to_state,
90 })
91 }
92}
93
94#[cfg(feature = "alloc")]
95fn decode_required_ctx_timestamp(
96 r: &mut Reader<'_>,
97 expected_tag_num: u8,
98) -> Result<TimeStamp, DecodeError> {
99 match Tag::decode(r)? {
100 Tag::Opening { tag_num } if tag_num == expected_tag_num => {}
101 _ => return Err(DecodeError::InvalidTag),
102 }
103
104 let timestamp = match Tag::decode(r)? {
105 Tag::Context { tag_num: 0, len: 4 } => {
106 let raw = r.read_exact(4)?;
107 TimeStamp::Time(Time {
108 hour: raw[0],
109 minute: raw[1],
110 second: raw[2],
111 hundredths: raw[3],
112 })
113 }
114 Tag::Context { tag_num: 1, len } => {
115 TimeStamp::SequenceNumber(decode_unsigned(r, len as usize)?)
116 }
117 Tag::Opening { tag_num: 2 } => {
118 let date = decode_app_date(r)?;
119 let time = decode_app_time(r)?;
120 if Tag::decode(r)? != (Tag::Closing { tag_num: 2 }) {
121 return Err(DecodeError::InvalidTag);
122 }
123 TimeStamp::DateTime { date, time }
124 }
125 _ => return Err(DecodeError::InvalidTag),
126 };
127
128 match Tag::decode(r)? {
129 Tag::Closing { tag_num } if tag_num == expected_tag_num => Ok(timestamp),
130 _ => Err(DecodeError::InvalidTag),
131 }
132}
133
134#[cfg(feature = "alloc")]
135fn decode_app_date(r: &mut Reader<'_>) -> Result<Date, DecodeError> {
136 match Tag::decode(r)? {
137 Tag::Application {
138 tag: AppTag::Date,
139 len: 4,
140 } => {
141 let raw = r.read_exact(4)?;
142 Ok(Date {
143 year_since_1900: raw[0],
144 month: raw[1],
145 day: raw[2],
146 weekday: raw[3],
147 })
148 }
149 _ => Err(DecodeError::InvalidTag),
150 }
151}
152
153#[cfg(feature = "alloc")]
154fn decode_app_time(r: &mut Reader<'_>) -> Result<Time, DecodeError> {
155 match Tag::decode(r)? {
156 Tag::Application {
157 tag: AppTag::Time,
158 len: 4,
159 } => {
160 let raw = r.read_exact(4)?;
161 Ok(Time {
162 hour: raw[0],
163 minute: raw[1],
164 second: raw[2],
165 hundredths: raw[3],
166 })
167 }
168 _ => Err(DecodeError::InvalidTag),
169 }
170}
171
172#[cfg(feature = "alloc")]
173fn skip_constructed(r: &mut Reader<'_>, tag_num: u8) -> Result<(), DecodeError> {
174 loop {
175 let tag = Tag::decode(r)?;
176 match tag {
177 Tag::Closing { tag_num: closing } if closing == tag_num => return Ok(()),
178 Tag::Opening { tag_num: nested } => skip_constructed(r, nested)?,
179 Tag::Application { len, .. } | Tag::Context { len, .. } => {
180 r.read_exact(len as usize)?;
181 }
182 Tag::Closing { .. } => return Err(DecodeError::InvalidTag),
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 #[cfg(feature = "alloc")]
190 use super::{EventNotificationRequest, SERVICE_UNCONFIRMED_EVENT_NOTIFICATION};
191 #[cfg(feature = "alloc")]
192 use crate::apdu::UnconfirmedRequestHeader;
193 #[cfg(feature = "alloc")]
194 use crate::encoding::{
195 primitives::{encode_ctx_character_string, encode_ctx_object_id, encode_ctx_unsigned},
196 reader::Reader,
197 tag::Tag,
198 writer::Writer,
199 };
200 #[cfg(feature = "alloc")]
201 use crate::services::acknowledge_alarm::TimeStamp;
202 #[cfg(feature = "alloc")]
203 use crate::types::{ObjectId, ObjectType};
204
205 #[cfg(feature = "alloc")]
206 #[test]
207 fn decode_event_notification_after_header() {
208 let mut buf = [0u8; 256];
209 let mut w = Writer::new(&mut buf);
210 UnconfirmedRequestHeader {
211 service_choice: SERVICE_UNCONFIRMED_EVENT_NOTIFICATION,
212 }
213 .encode(&mut w)
214 .unwrap();
215 encode_ctx_unsigned(&mut w, 0, 19).unwrap();
216 encode_ctx_object_id(&mut w, 1, ObjectId::new(ObjectType::Device, 1).raw()).unwrap();
217 encode_ctx_object_id(&mut w, 2, ObjectId::new(ObjectType::AnalogInput, 3).raw()).unwrap();
218 Tag::Opening { tag_num: 3 }.encode(&mut w).unwrap();
219 encode_ctx_unsigned(&mut w, 1, 42).unwrap();
220 Tag::Closing { tag_num: 3 }.encode(&mut w).unwrap();
221 encode_ctx_unsigned(&mut w, 4, 7).unwrap();
222 encode_ctx_unsigned(&mut w, 5, 100).unwrap();
223 encode_ctx_unsigned(&mut w, 6, 2).unwrap();
224 encode_ctx_character_string(&mut w, 7, "alarm message").unwrap();
225 encode_ctx_unsigned(&mut w, 8, 0).unwrap();
226 Tag::Context { tag_num: 9, len: 1 }.encode(&mut w).unwrap();
227 encode_ctx_unsigned(&mut w, 10, 2).unwrap();
228 encode_ctx_unsigned(&mut w, 11, 0).unwrap();
229 Tag::Opening { tag_num: 12 }.encode(&mut w).unwrap();
230 Tag::Opening { tag_num: 0 }.encode(&mut w).unwrap();
231 encode_ctx_unsigned(&mut w, 0, 1).unwrap();
232 Tag::Closing { tag_num: 0 }.encode(&mut w).unwrap();
233 Tag::Closing { tag_num: 12 }.encode(&mut w).unwrap();
234
235 let mut r = Reader::new(w.as_written());
236 let _header = UnconfirmedRequestHeader::decode(&mut r).unwrap();
237 let notification = EventNotificationRequest::decode_after_header(&mut r).unwrap();
238 assert_eq!(notification.process_id, 19);
239 assert_eq!(
240 notification.initiating_device_id,
241 ObjectId::new(ObjectType::Device, 1)
242 );
243 assert_eq!(
244 notification.event_object_id,
245 ObjectId::new(ObjectType::AnalogInput, 3)
246 );
247 assert_eq!(notification.timestamp, TimeStamp::SequenceNumber(42));
248 assert_eq!(notification.message_text, Some("alarm message"));
249 assert_eq!(notification.ack_required, Some(true));
250 assert_eq!(notification.from_state, 2);
251 assert_eq!(notification.to_state, 0);
252 }
253}