Skip to main content

rustbac_core/services/
atomic_read_file.rs

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}