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}