Skip to main content

rustbac_core/services/
read_range.rs

1use crate::apdu::ConfirmedRequestHeader;
2use crate::encoding::{
3    primitives::{
4        encode_app_signed, encode_app_unsigned, encode_ctx_object_id, encode_ctx_unsigned,
5    },
6    tag::{AppTag, Tag},
7    writer::Writer,
8};
9use crate::types::{Date, ObjectId, PropertyId, Time};
10use crate::EncodeError;
11
12#[cfg(feature = "alloc")]
13use crate::encoding::{primitives::decode_unsigned, reader::Reader};
14#[cfg(feature = "alloc")]
15use crate::services::value_codec::decode_application_data_value_from_tag;
16#[cfg(feature = "alloc")]
17use crate::types::{BitString, DataValue};
18#[cfg(feature = "alloc")]
19use crate::DecodeError;
20#[cfg(feature = "alloc")]
21use alloc::vec::Vec;
22
23pub const SERVICE_READ_RANGE: u8 = 0x1A;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ReadRangeSpecifier {
27    ByPosition { reference_index: i32, count: i16 },
28    BySequenceNumber { reference_sequence: u32, count: i16 },
29    ByTime { date: Date, time: Time, count: i16 },
30    ReadAll,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct ReadRangeRequest {
35    pub object_id: ObjectId,
36    pub property_id: PropertyId,
37    pub array_index: Option<u32>,
38    pub range: ReadRangeSpecifier,
39    pub invoke_id: u8,
40}
41
42impl ReadRangeRequest {
43    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
44        ConfirmedRequestHeader {
45            segmented: false,
46            more_follows: false,
47            segmented_response_accepted: true,
48            max_segments: 0,
49            max_apdu: 5,
50            invoke_id: self.invoke_id,
51            sequence_number: None,
52            proposed_window_size: None,
53            service_choice: SERVICE_READ_RANGE,
54        }
55        .encode(w)?;
56
57        encode_ctx_object_id(w, 0, self.object_id.raw())?;
58        encode_ctx_unsigned(w, 1, self.property_id.to_u32())?;
59        if let Some(array_index) = self.array_index {
60            encode_ctx_unsigned(w, 2, array_index)?;
61        }
62
63        match self.range {
64            ReadRangeSpecifier::ByPosition {
65                reference_index,
66                count,
67            } => {
68                let reference_index =
69                    u32::try_from(reference_index).map_err(|_| EncodeError::ValueOutOfRange)?;
70                Tag::Opening { tag_num: 3 }.encode(w)?;
71                encode_app_unsigned(w, reference_index)?;
72                encode_app_signed(w, count as i32)?;
73                Tag::Closing { tag_num: 3 }.encode(w)?;
74            }
75            ReadRangeSpecifier::BySequenceNumber {
76                reference_sequence,
77                count,
78            } => {
79                Tag::Opening { tag_num: 6 }.encode(w)?;
80                encode_app_unsigned(w, reference_sequence)?;
81                encode_app_signed(w, count as i32)?;
82                Tag::Closing { tag_num: 6 }.encode(w)?;
83            }
84            ReadRangeSpecifier::ByTime { date, time, count } => {
85                Tag::Opening { tag_num: 7 }.encode(w)?;
86                Tag::Application {
87                    tag: AppTag::Date,
88                    len: 4,
89                }
90                .encode(w)?;
91                w.write_all(&[date.year_since_1900, date.month, date.day, date.weekday])?;
92                Tag::Application {
93                    tag: AppTag::Time,
94                    len: 4,
95                }
96                .encode(w)?;
97                w.write_all(&[time.hour, time.minute, time.second, time.hundredths])?;
98                encode_app_signed(w, count as i32)?;
99                Tag::Closing { tag_num: 7 }.encode(w)?;
100            }
101            ReadRangeSpecifier::ReadAll => {}
102        }
103
104        Ok(())
105    }
106
107    pub fn by_position(
108        object_id: ObjectId,
109        property_id: PropertyId,
110        array_index: Option<u32>,
111        reference_index: i32,
112        count: i16,
113        invoke_id: u8,
114    ) -> Self {
115        Self {
116            object_id,
117            property_id,
118            array_index,
119            range: ReadRangeSpecifier::ByPosition {
120                reference_index,
121                count,
122            },
123            invoke_id,
124        }
125    }
126
127    pub fn by_sequence_number(
128        object_id: ObjectId,
129        property_id: PropertyId,
130        array_index: Option<u32>,
131        reference_sequence: u32,
132        count: i16,
133        invoke_id: u8,
134    ) -> Self {
135        Self {
136            object_id,
137            property_id,
138            array_index,
139            range: ReadRangeSpecifier::BySequenceNumber {
140                reference_sequence,
141                count,
142            },
143            invoke_id,
144        }
145    }
146
147    pub fn by_time(
148        object_id: ObjectId,
149        property_id: PropertyId,
150        array_index: Option<u32>,
151        date: Date,
152        time: Time,
153        count: i16,
154        invoke_id: u8,
155    ) -> Self {
156        Self {
157            object_id,
158            property_id,
159            array_index,
160            range: ReadRangeSpecifier::ByTime { date, time, count },
161            invoke_id,
162        }
163    }
164
165    pub fn read_all(
166        object_id: ObjectId,
167        property_id: PropertyId,
168        array_index: Option<u32>,
169        invoke_id: u8,
170    ) -> Self {
171        Self {
172            object_id,
173            property_id,
174            array_index,
175            range: ReadRangeSpecifier::ReadAll,
176            invoke_id,
177        }
178    }
179}
180
181#[cfg(feature = "alloc")]
182#[derive(Debug, Clone, PartialEq)]
183pub struct ReadRangeAck<'a> {
184    pub object_id: ObjectId,
185    pub property_id: PropertyId,
186    pub array_index: Option<u32>,
187    pub result_flags: BitString<'a>,
188    pub item_count: u32,
189    pub items: Vec<DataValue<'a>>,
190}
191
192#[cfg(feature = "alloc")]
193impl<'a> ReadRangeAck<'a> {
194    pub fn decode_after_header(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
195        let object_id = match Tag::decode(r)? {
196            Tag::Context { tag_num: 0, len } => {
197                if len != 4 {
198                    return Err(DecodeError::InvalidLength);
199                }
200                ObjectId::from_raw(r.read_be_u32()?)
201            }
202            _ => return Err(DecodeError::InvalidTag),
203        };
204
205        let property_id = match Tag::decode(r)? {
206            Tag::Context { tag_num: 1, len } => {
207                PropertyId::from_u32(decode_unsigned(r, len as usize)?)
208            }
209            _ => return Err(DecodeError::InvalidTag),
210        };
211
212        let next = Tag::decode(r)?;
213        let (array_index, result_flags_tag) = match next {
214            Tag::Context { tag_num: 2, len } => {
215                let idx = decode_unsigned(r, len as usize)?;
216                (Some(idx), Tag::decode(r)?)
217            }
218            other => (None, other),
219        };
220
221        let result_flags = match result_flags_tag {
222            Tag::Context { tag_num: 3, len } => {
223                if len == 0 {
224                    return Err(DecodeError::InvalidLength);
225                }
226                let raw = r.read_exact(len as usize)?;
227                if raw[0] > 7 {
228                    return Err(DecodeError::InvalidValue);
229                }
230                BitString {
231                    unused_bits: raw[0],
232                    data: &raw[1..],
233                }
234            }
235            _ => return Err(DecodeError::InvalidTag),
236        };
237
238        let item_count = match Tag::decode(r)? {
239            Tag::Context { tag_num: 4, len } => decode_unsigned(r, len as usize)?,
240            _ => return Err(DecodeError::InvalidTag),
241        };
242
243        match Tag::decode(r)? {
244            Tag::Opening { tag_num: 5 } => {}
245            _ => return Err(DecodeError::InvalidTag),
246        }
247
248        let mut items = Vec::new();
249        loop {
250            let tag = Tag::decode(r)?;
251            if tag == (Tag::Closing { tag_num: 5 }) {
252                break;
253            }
254
255            let value = match tag {
256                Tag::Application { .. } => decode_application_data_value_from_tag(r, tag)?,
257                Tag::Context { .. } | Tag::Opening { .. } | Tag::Closing { .. } => {
258                    return Err(DecodeError::Unsupported);
259                }
260            };
261            items.push(value);
262        }
263
264        Ok(Self {
265            object_id,
266            property_id,
267            array_index,
268            result_flags,
269            item_count,
270            items,
271        })
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    #[cfg(feature = "alloc")]
278    use super::ReadRangeAck;
279    use super::{ReadRangeRequest, ReadRangeSpecifier, SERVICE_READ_RANGE};
280    #[cfg(feature = "alloc")]
281    use crate::apdu::ComplexAckHeader;
282    use crate::apdu::ConfirmedRequestHeader;
283    #[cfg(feature = "alloc")]
284    use crate::encoding::primitives::{encode_app_real, encode_ctx_object_id, encode_ctx_unsigned};
285    #[cfg(feature = "alloc")]
286    use crate::encoding::tag::Tag;
287    use crate::encoding::{reader::Reader, writer::Writer};
288    use crate::types::{ObjectId, ObjectType, PropertyId};
289
290    #[test]
291    fn encode_read_range_request_by_position() {
292        let req = ReadRangeRequest {
293            object_id: ObjectId::new(ObjectType::TrendLog, 1),
294            property_id: PropertyId::PresentValue,
295            array_index: None,
296            range: ReadRangeSpecifier::ByPosition {
297                reference_index: 1,
298                count: 10,
299            },
300            invoke_id: 3,
301        };
302
303        let mut buf = [0u8; 128];
304        let mut w = Writer::new(&mut buf);
305        req.encode(&mut w).unwrap();
306
307        let mut r = Reader::new(w.as_written());
308        let header = ConfirmedRequestHeader::decode(&mut r).unwrap();
309        assert_eq!(header.service_choice, SERVICE_READ_RANGE);
310        assert_eq!(header.invoke_id, 3);
311    }
312
313    #[cfg(feature = "alloc")]
314    #[test]
315    fn decode_read_range_ack_minimal() {
316        let mut buf = [0u8; 256];
317        let mut w = Writer::new(&mut buf);
318        ComplexAckHeader {
319            segmented: false,
320            more_follows: false,
321            invoke_id: 9,
322            sequence_number: None,
323            proposed_window_size: None,
324            service_choice: SERVICE_READ_RANGE,
325        }
326        .encode(&mut w)
327        .unwrap();
328        encode_ctx_object_id(&mut w, 0, ObjectId::new(ObjectType::TrendLog, 1).raw()).unwrap();
329        encode_ctx_unsigned(&mut w, 1, PropertyId::PresentValue.to_u32()).unwrap();
330        Tag::Context { tag_num: 3, len: 2 }.encode(&mut w).unwrap();
331        w.write_u8(5).unwrap();
332        w.write_u8(0b1110_0000).unwrap();
333        encode_ctx_unsigned(&mut w, 4, 2).unwrap();
334        Tag::Opening { tag_num: 5 }.encode(&mut w).unwrap();
335        encode_app_real(&mut w, 10.0).unwrap();
336        encode_app_real(&mut w, 11.0).unwrap();
337        Tag::Closing { tag_num: 5 }.encode(&mut w).unwrap();
338
339        let mut r = Reader::new(w.as_written());
340        let _ack = ComplexAckHeader::decode(&mut r).unwrap();
341        let parsed = ReadRangeAck::decode_after_header(&mut r).unwrap();
342        assert_eq!(parsed.item_count, 2);
343        assert_eq!(parsed.items.len(), 2);
344    }
345}