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}