Skip to main content

rustbac_core/services/
event_notification.rs

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}