Skip to main content

rustbac_core/services/
cov_notification.rs

1#[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}