Skip to main content

rustbac_core/services/
atomic_write_file.rs

1use crate::apdu::ConfirmedRequestHeader;
2use crate::encoding::{
3    primitives::{decode_signed, encode_app_object_id, encode_app_signed, encode_app_unsigned},
4    reader::Reader,
5    tag::{AppTag, Tag},
6    writer::Writer,
7};
8use crate::types::ObjectId;
9use crate::{DecodeError, EncodeError};
10
11pub const SERVICE_ATOMIC_WRITE_FILE: u8 = 0x07;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum AtomicWriteFileAccessMethod<'a> {
15    Stream {
16        file_start_position: i32,
17        file_data: &'a [u8],
18    },
19    Record {
20        file_start_record: i32,
21        file_record_data: &'a [&'a [u8]],
22    },
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct AtomicWriteFileRequest<'a> {
27    pub file_object_id: ObjectId,
28    pub access_method: AtomicWriteFileAccessMethod<'a>,
29    pub invoke_id: u8,
30}
31
32impl<'a> AtomicWriteFileRequest<'a> {
33    pub fn stream(
34        file_object_id: ObjectId,
35        file_start_position: i32,
36        file_data: &'a [u8],
37        invoke_id: u8,
38    ) -> Self {
39        Self {
40            file_object_id,
41            access_method: AtomicWriteFileAccessMethod::Stream {
42                file_start_position,
43                file_data,
44            },
45            invoke_id,
46        }
47    }
48
49    pub fn record(
50        file_object_id: ObjectId,
51        file_start_record: i32,
52        file_record_data: &'a [&'a [u8]],
53        invoke_id: u8,
54    ) -> Self {
55        Self {
56            file_object_id,
57            access_method: AtomicWriteFileAccessMethod::Record {
58                file_start_record,
59                file_record_data,
60            },
61            invoke_id,
62        }
63    }
64
65    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
66        ConfirmedRequestHeader {
67            segmented: false,
68            more_follows: false,
69            segmented_response_accepted: false,
70            max_segments: 0,
71            max_apdu: 5,
72            invoke_id: self.invoke_id,
73            sequence_number: None,
74            proposed_window_size: None,
75            service_choice: SERVICE_ATOMIC_WRITE_FILE,
76        }
77        .encode(w)?;
78
79        encode_app_object_id(w, self.file_object_id.raw())?;
80        match self.access_method {
81            AtomicWriteFileAccessMethod::Stream {
82                file_start_position,
83                file_data,
84            } => {
85                Tag::Opening { tag_num: 0 }.encode(w)?;
86                encode_app_signed(w, file_start_position)?;
87                Tag::Application {
88                    tag: AppTag::OctetString,
89                    len: file_data.len() as u32,
90                }
91                .encode(w)?;
92                w.write_all(file_data)?;
93                Tag::Closing { tag_num: 0 }.encode(w)?;
94            }
95            AtomicWriteFileAccessMethod::Record {
96                file_start_record,
97                file_record_data,
98            } => {
99                Tag::Opening { tag_num: 1 }.encode(w)?;
100                encode_app_signed(w, file_start_record)?;
101                encode_app_unsigned(w, file_record_data.len() as u32)?;
102                for record in file_record_data {
103                    Tag::Application {
104                        tag: AppTag::OctetString,
105                        len: record.len() as u32,
106                    }
107                    .encode(w)?;
108                    w.write_all(record)?;
109                }
110                Tag::Closing { tag_num: 1 }.encode(w)?;
111            }
112        }
113        Ok(())
114    }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum AtomicWriteFileAck {
119    Stream { file_start_position: i32 },
120    Record { file_start_record: i32 },
121}
122
123impl AtomicWriteFileAck {
124    pub fn decode_after_header(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
125        match Tag::decode(r)? {
126            Tag::Context { tag_num: 0, len } => Ok(Self::Stream {
127                file_start_position: decode_signed(r, len as usize)?,
128            }),
129            Tag::Context { tag_num: 1, len } => Ok(Self::Record {
130                file_start_record: decode_signed(r, len as usize)?,
131            }),
132            Tag::Opening { tag_num: 0 } => {
133                let start = decode_required_ctx_signed(r, 0)?;
134                match Tag::decode(r)? {
135                    Tag::Closing { tag_num: 0 } => Ok(Self::Stream {
136                        file_start_position: start,
137                    }),
138                    _ => Err(DecodeError::InvalidTag),
139                }
140            }
141            Tag::Opening { tag_num: 1 } => {
142                let start = decode_required_ctx_signed(r, 0)?;
143                match Tag::decode(r)? {
144                    Tag::Closing { tag_num: 1 } => Ok(Self::Record {
145                        file_start_record: start,
146                    }),
147                    _ => Err(DecodeError::InvalidTag),
148                }
149            }
150            _ => Err(DecodeError::InvalidTag),
151        }
152    }
153}
154
155fn decode_required_ctx_signed(
156    r: &mut Reader<'_>,
157    expected_tag_num: u8,
158) -> Result<i32, DecodeError> {
159    match Tag::decode(r)? {
160        Tag::Context { tag_num, len } if tag_num == expected_tag_num => {
161            decode_signed(r, len as usize)
162        }
163        _ => Err(DecodeError::InvalidTag),
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::{AtomicWriteFileAck, AtomicWriteFileRequest, SERVICE_ATOMIC_WRITE_FILE};
170    use crate::apdu::{ComplexAckHeader, ConfirmedRequestHeader};
171    use crate::encoding::{reader::Reader, tag::Tag, writer::Writer};
172    use crate::types::{ObjectId, ObjectType};
173
174    #[test]
175    fn encode_atomic_write_file_stream_request() {
176        let req = AtomicWriteFileRequest::stream(
177            ObjectId::new(ObjectType::File, 3),
178            128,
179            &[0xAA, 0xBB, 0xCC],
180            5,
181        );
182        let mut buf = [0u8; 128];
183        let mut w = Writer::new(&mut buf);
184        req.encode(&mut w).unwrap();
185
186        let mut r = Reader::new(w.as_written());
187        let hdr = ConfirmedRequestHeader::decode(&mut r).unwrap();
188        assert_eq!(hdr.service_choice, SERVICE_ATOMIC_WRITE_FILE);
189    }
190
191    #[test]
192    fn decode_atomic_write_file_ack_stream() {
193        let mut buf = [0u8; 32];
194        let mut w = Writer::new(&mut buf);
195        ComplexAckHeader {
196            segmented: false,
197            more_follows: false,
198            invoke_id: 2,
199            sequence_number: None,
200            proposed_window_size: None,
201            service_choice: SERVICE_ATOMIC_WRITE_FILE,
202        }
203        .encode(&mut w)
204        .unwrap();
205        Tag::Context { tag_num: 0, len: 2 }.encode(&mut w).unwrap();
206        w.write_all(&0x0080u16.to_be_bytes()).unwrap();
207
208        let mut r = Reader::new(w.as_written());
209        let _ack = ComplexAckHeader::decode(&mut r).unwrap();
210        let parsed = AtomicWriteFileAck::decode_after_header(&mut r).unwrap();
211        assert_eq!(
212            parsed,
213            AtomicWriteFileAck::Stream {
214                file_start_position: 128
215            }
216        );
217    }
218}