1use crate::apdu::ConfirmedRequestHeader;
2use crate::encoding::{
3 primitives::{
4 decode_signed, decode_unsigned, encode_app_object_id, encode_app_signed,
5 encode_app_unsigned,
6 },
7 tag::Tag,
8 writer::Writer,
9};
10use crate::types::ObjectId;
11use crate::EncodeError;
12
13#[cfg(feature = "alloc")]
14use crate::encoding::reader::Reader;
15#[cfg(feature = "alloc")]
16use crate::encoding::tag::AppTag;
17#[cfg(feature = "alloc")]
18use crate::DecodeError;
19#[cfg(feature = "alloc")]
20use alloc::vec::Vec;
21
22pub const SERVICE_ATOMIC_READ_FILE: u8 = 0x06;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum AtomicReadFileAccessMethod {
26 Stream {
27 file_start_position: i32,
28 requested_octet_count: u32,
29 },
30 Record {
31 file_start_record: i32,
32 requested_record_count: u32,
33 },
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct AtomicReadFileRequest {
38 pub file_object_id: ObjectId,
39 pub access_method: AtomicReadFileAccessMethod,
40 pub invoke_id: u8,
41}
42
43impl AtomicReadFileRequest {
44 pub fn stream(
45 file_object_id: ObjectId,
46 file_start_position: i32,
47 requested_octet_count: u32,
48 invoke_id: u8,
49 ) -> Self {
50 Self {
51 file_object_id,
52 access_method: AtomicReadFileAccessMethod::Stream {
53 file_start_position,
54 requested_octet_count,
55 },
56 invoke_id,
57 }
58 }
59
60 pub fn record(
61 file_object_id: ObjectId,
62 file_start_record: i32,
63 requested_record_count: u32,
64 invoke_id: u8,
65 ) -> Self {
66 Self {
67 file_object_id,
68 access_method: AtomicReadFileAccessMethod::Record {
69 file_start_record,
70 requested_record_count,
71 },
72 invoke_id,
73 }
74 }
75
76 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
77 ConfirmedRequestHeader {
78 segmented: false,
79 more_follows: false,
80 segmented_response_accepted: true,
81 max_segments: 0,
82 max_apdu: 5,
83 invoke_id: self.invoke_id,
84 sequence_number: None,
85 proposed_window_size: None,
86 service_choice: SERVICE_ATOMIC_READ_FILE,
87 }
88 .encode(w)?;
89
90 encode_app_object_id(w, self.file_object_id.raw())?;
91 match self.access_method {
92 AtomicReadFileAccessMethod::Stream {
93 file_start_position,
94 requested_octet_count,
95 } => {
96 Tag::Opening { tag_num: 0 }.encode(w)?;
97 encode_app_signed(w, file_start_position)?;
98 encode_app_unsigned(w, requested_octet_count)?;
99 Tag::Closing { tag_num: 0 }.encode(w)?;
100 }
101 AtomicReadFileAccessMethod::Record {
102 file_start_record,
103 requested_record_count,
104 } => {
105 Tag::Opening { tag_num: 1 }.encode(w)?;
106 encode_app_signed(w, file_start_record)?;
107 encode_app_unsigned(w, requested_record_count)?;
108 Tag::Closing { tag_num: 1 }.encode(w)?;
109 }
110 }
111 Ok(())
112 }
113}
114
115#[cfg(feature = "alloc")]
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub enum AtomicReadFileAckAccess<'a> {
118 Stream {
119 file_start_position: i32,
120 file_data: &'a [u8],
121 },
122 Record {
123 file_start_record: i32,
124 returned_record_count: u32,
125 file_record_data: Vec<&'a [u8]>,
126 },
127}
128
129#[cfg(feature = "alloc")]
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct AtomicReadFileAck<'a> {
132 pub end_of_file: bool,
133 pub access_method: AtomicReadFileAckAccess<'a>,
134}
135
136#[cfg(feature = "alloc")]
137impl<'a> AtomicReadFileAck<'a> {
138 pub fn decode_after_header(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
139 let end_of_file = match Tag::decode(r)? {
140 Tag::Application {
141 tag: AppTag::Boolean,
142 len,
143 } => len != 0,
144 _ => return Err(DecodeError::InvalidTag),
145 };
146
147 let access_method = match Tag::decode(r)? {
148 Tag::Opening { tag_num: 0 } => {
149 let file_start_position = decode_required_app_signed(r)?;
150 let file_data = decode_required_app_octets(r)?;
151 match Tag::decode(r)? {
152 Tag::Closing { tag_num: 0 } => {}
153 _ => return Err(DecodeError::InvalidTag),
154 }
155 AtomicReadFileAckAccess::Stream {
156 file_start_position,
157 file_data,
158 }
159 }
160 Tag::Opening { tag_num: 1 } => {
161 let file_start_record = decode_required_app_signed(r)?;
162 let returned_record_count = decode_required_app_unsigned(r)?;
163
164 let mut file_record_data = Vec::new();
165 loop {
166 let tag = Tag::decode(r)?;
167 if tag == (Tag::Closing { tag_num: 1 }) {
168 break;
169 }
170 match tag {
171 Tag::Application {
172 tag: AppTag::OctetString,
173 len,
174 } => file_record_data.push(r.read_exact(len as usize)?),
175 _ => return Err(DecodeError::InvalidTag),
176 }
177 }
178 AtomicReadFileAckAccess::Record {
179 file_start_record,
180 returned_record_count,
181 file_record_data,
182 }
183 }
184 _ => return Err(DecodeError::InvalidTag),
185 };
186
187 Ok(Self {
188 end_of_file,
189 access_method,
190 })
191 }
192}
193
194#[cfg(feature = "alloc")]
195fn decode_required_app_unsigned(r: &mut Reader<'_>) -> Result<u32, DecodeError> {
196 match Tag::decode(r)? {
197 Tag::Application {
198 tag: AppTag::UnsignedInt,
199 len,
200 } => decode_unsigned(r, len as usize),
201 _ => Err(DecodeError::InvalidTag),
202 }
203}
204
205#[cfg(feature = "alloc")]
206fn decode_required_app_signed(r: &mut Reader<'_>) -> Result<i32, DecodeError> {
207 match Tag::decode(r)? {
208 Tag::Application {
209 tag: AppTag::SignedInt,
210 len,
211 } => decode_signed(r, len as usize),
212 _ => Err(DecodeError::InvalidTag),
213 }
214}
215
216#[cfg(feature = "alloc")]
217fn decode_required_app_octets<'a>(r: &mut Reader<'a>) -> Result<&'a [u8], DecodeError> {
218 match Tag::decode(r)? {
219 Tag::Application {
220 tag: AppTag::OctetString,
221 len,
222 } => r.read_exact(len as usize),
223 _ => Err(DecodeError::InvalidTag),
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 #[cfg(feature = "alloc")]
230 use super::{AtomicReadFileAck, AtomicReadFileAckAccess};
231 use super::{AtomicReadFileRequest, SERVICE_ATOMIC_READ_FILE};
232 #[cfg(feature = "alloc")]
233 use crate::apdu::ComplexAckHeader;
234 use crate::apdu::ConfirmedRequestHeader;
235 #[cfg(feature = "alloc")]
236 use crate::encoding::tag::{AppTag, Tag};
237 use crate::encoding::{reader::Reader, writer::Writer};
238 use crate::types::{ObjectId, ObjectType};
239
240 #[test]
241 fn encode_atomic_read_file_stream_request() {
242 let req = AtomicReadFileRequest::stream(ObjectId::new(ObjectType::File, 7), 0, 512, 4);
243 let mut buf = [0u8; 128];
244 let mut w = Writer::new(&mut buf);
245 req.encode(&mut w).unwrap();
246
247 let mut r = Reader::new(w.as_written());
248 let hdr = ConfirmedRequestHeader::decode(&mut r).unwrap();
249 assert_eq!(hdr.service_choice, SERVICE_ATOMIC_READ_FILE);
250 }
251
252 #[cfg(feature = "alloc")]
253 #[test]
254 fn decode_atomic_read_file_ack_stream() {
255 let mut buf = [0u8; 128];
256 let mut w = Writer::new(&mut buf);
257 ComplexAckHeader {
258 segmented: false,
259 more_follows: false,
260 invoke_id: 9,
261 sequence_number: None,
262 proposed_window_size: None,
263 service_choice: SERVICE_ATOMIC_READ_FILE,
264 }
265 .encode(&mut w)
266 .unwrap();
267 Tag::Application {
268 tag: AppTag::Boolean,
269 len: 1,
270 }
271 .encode(&mut w)
272 .unwrap();
273 Tag::Opening { tag_num: 0 }.encode(&mut w).unwrap();
274 Tag::Application {
275 tag: AppTag::SignedInt,
276 len: 1,
277 }
278 .encode(&mut w)
279 .unwrap();
280 w.write_u8(0).unwrap();
281 Tag::Application {
282 tag: AppTag::OctetString,
283 len: 4,
284 }
285 .encode(&mut w)
286 .unwrap();
287 w.write_all(&[1, 2, 3, 4]).unwrap();
288 Tag::Closing { tag_num: 0 }.encode(&mut w).unwrap();
289
290 let mut r = Reader::new(w.as_written());
291 let _ack = ComplexAckHeader::decode(&mut r).unwrap();
292 let parsed = AtomicReadFileAck::decode_after_header(&mut r).unwrap();
293 assert!(parsed.end_of_file);
294 match parsed.access_method {
295 AtomicReadFileAckAccess::Stream {
296 file_start_position,
297 file_data,
298 } => {
299 assert_eq!(file_start_position, 0);
300 assert_eq!(file_data, &[1, 2, 3, 4]);
301 }
302 other => panic!("unexpected ack access: {other:?}"),
303 }
304 }
305}