1mod errors;
2use std::str;
3
4use async_trait::async_trait;
5pub use errors::*;
6use ledger_sdk_transport::{APDUAnswer, APDUCommand, APDUErrorCode, Exchange};
7use serde::{Deserialize, Serialize};
8
9const INS_GET_VERSION: u8 = 0x00;
11const CLA_APP_INFO: u8 = 0xb0;
12const INS_APP_INFO: u8 = 0x01;
13const CLA_DEVICE_INFO: u8 = 0xe0;
14const INS_DEVICE_INFO: u8 = 0x01;
15const USER_MESSAGE_CHUNK_SIZE: usize = 250;
16
17pub enum ChunkPayloadType {
18 Init = 0x00,
20 Add = 0x01,
22 Last = 0x02,
24}
25
26#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
27pub struct Version {
29 #[serde(rename(serialize = "testMode"))]
31 pub mode: u8,
32 pub major: u16,
34 pub minor: u16,
36 pub patch: u16,
38 pub locked: bool,
40 pub target_id: [u8; 4],
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
45pub struct DeviceInfo {
47 #[serde(rename(serialize = "targetId"))]
49 pub target_id: [u8; 4],
50 #[serde(rename(serialize = "seVersion"))]
52 pub se_version: String,
53 pub flag: Vec<u8>,
55 #[serde(rename(serialize = "mcuVersion"))]
57 pub mcu_version: String,
58}
59
60#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
61pub struct AppInfo {
63 #[serde(rename(serialize = "appName"))]
65 pub app_name: String,
66 #[serde(rename(serialize = "appVersion"))]
68 pub app_version: String,
69 #[serde(rename(serialize = "flagLen"))]
71 pub flag_len: u8,
72 #[serde(rename(serialize = "flagsValue"))]
74 pub flags_value: u8,
75 #[serde(rename(serialize = "flagsRecovery"))]
77 pub flag_recovery: bool,
78 #[serde(rename(serialize = "flagsSignedMCUCode"))]
80 pub flag_signed_mcu_code: bool,
81 #[serde(rename(serialize = "flagsOnboarded"))]
83 pub flag_onboarded: bool,
84 #[serde(rename(serialize = "flagsPINValidated"))]
86 pub flag_pin_validated: bool,
87}
88
89pub trait App {
91 const CLA: u8;
93}
94
95#[async_trait]
96pub trait AppExt<E>: App
97where
98 E: Exchange + Send + Sync,
99 E::Error: std::error::Error,
100{
101 fn handle_response_error(
104 response: &APDUAnswer<E::AnswerType>,
105 ) -> Result<(), LedgerAppError<E::Error>> {
106 match response.error_code() {
107 Ok(APDUErrorCode::NoError) => Ok(()),
108 Ok(err) => Err(LedgerAppError::AppSpecific(err as _, err.description())),
109 Err(err) => Err(LedgerAppError::Unknown(err)),
110 }
111 }
112
113 fn handle_response_error_signature(
115 response: &APDUAnswer<E::AnswerType>,
116 ) -> Result<(), LedgerAppError<E::Error>> {
117 match response.error_code() {
118 Ok(APDUErrorCode::NoError) if response.data().is_empty() => {
119 Err(LedgerAppError::NoSignature)
120 }
121 Ok(APDUErrorCode::NoError) => Ok(()),
122 Ok(err) => Err(LedgerAppError::AppSpecific(err as _, err.description())),
123 Err(err) => Err(LedgerAppError::AppSpecific(
124 err,
125 "[APDU_ERROR] Unknown".to_string(),
126 )),
127 }
128 }
129
130 async fn get_device_info(transport: &E) -> Result<DeviceInfo, LedgerAppError<E::Error>> {
132 let command = APDUCommand {
134 cla: CLA_DEVICE_INFO,
135 ins: INS_DEVICE_INFO,
136 p1: 0x00,
137 p2: 0x00,
138 data: Vec::new(),
139 };
140
141 let response = transport.exchange(&command).await?;
143 match response.error_code() {
144 Ok(APDUErrorCode::NoError) => {}
145 Ok(err) => return Err(LedgerAppError::Unknown(err as _)),
146 Err(err) => return Err(LedgerAppError::Unknown(err)),
147 }
148
149 let response_data = response.data();
151
152 let target_id_slice = &response_data[0..4];
154 let mut idx = 4;
155
156 let se_version_len: usize = response_data[idx] as usize;
158 idx += 1;
159 let se_version_bytes = &response_data[idx..(idx + se_version_len)];
160
161 idx += se_version_len;
162
163 let flags_len: usize = response_data[idx] as usize;
165 idx += 1;
166 let flag = &response_data[idx..idx + flags_len];
167 idx += flags_len;
168
169 let mcu_version_len: usize = response_data[idx] as usize;
171 idx += 1;
172 let mut tmp = &response_data[idx..idx + mcu_version_len];
173 if tmp[mcu_version_len - 1] == 0 {
174 tmp = &response_data[idx..idx + mcu_version_len - 1];
175 }
176
177 let mut target_id = [Default::default(); 4];
179 target_id.copy_from_slice(target_id_slice);
180
181 let se_version = str::from_utf8(se_version_bytes).map_err(|_e| LedgerAppError::Utf8)?;
183 let mcu_version = str::from_utf8(tmp).map_err(|_e| LedgerAppError::Utf8)?;
184
185 let device_info = DeviceInfo {
187 target_id,
188 se_version: se_version.to_string(),
189 flag: flag.to_vec(),
190 mcu_version: mcu_version.to_string(),
191 };
192
193 Ok(device_info)
194 }
195
196 async fn get_app_info(transport: &E) -> Result<AppInfo, LedgerAppError<E::Error>> {
198 let command = APDUCommand {
199 cla: CLA_APP_INFO,
200 ins: INS_APP_INFO,
201 p1: 0x00,
202 p2: 0x00,
203 data: Vec::new(),
204 };
205
206 let response = transport.exchange(&command).await?;
207 match response.error_code() {
208 Ok(APDUErrorCode::NoError) => {}
209 Ok(err) => return Err(LedgerAppError::AppSpecific(err as _, err.description())),
210 Err(err) => return Err(LedgerAppError::Unknown(err as _)),
211 }
212
213 let response_data = response.data();
214
215 if response_data[0] != 1 {
216 return Err(LedgerAppError::InvalidFormatID);
217 }
218
219 let app_name_len: usize = response_data[1] as usize;
220 let app_name_bytes = &response_data[2..app_name_len];
221
222 let mut idx = 2 + app_name_len;
223 let app_version_len: usize = response_data[idx] as usize;
224 idx += 1;
225 let app_version_bytes = &response_data[idx..idx + app_version_len];
226
227 idx += app_version_len;
228
229 let app_flags_len = response_data[idx];
230 idx += 1;
231 let flags_value = response_data[idx];
232
233 let app_name = str::from_utf8(app_name_bytes).map_err(|_e| LedgerAppError::Utf8)?;
234 let app_version = str::from_utf8(app_version_bytes).map_err(|_e| LedgerAppError::Utf8)?;
235
236 let app_info = AppInfo {
237 app_name: app_name.to_string(),
238 app_version: app_version.to_string(),
239 flag_len: app_flags_len,
240 flags_value,
241 flag_recovery: (flags_value & 1) != 0,
242 flag_signed_mcu_code: (flags_value & 2) != 0,
243 flag_onboarded: (flags_value & 4) != 0,
244 flag_pin_validated: (flags_value & 128) != 0,
245 };
246
247 Ok(app_info)
248 }
249
250 async fn get_version(transport: &E) -> Result<Version, LedgerAppError<E::Error>> {
252 let command = APDUCommand {
253 cla: Self::CLA,
254 ins: INS_GET_VERSION,
255 p1: 0x00,
256 p2: 0x00,
257 data: Vec::new(),
258 };
259
260 let response = transport.exchange(&command).await?;
261 match response.error_code() {
262 Ok(APDUErrorCode::NoError) => {}
263 Ok(err) => return Err(LedgerAppError::Unknown(err as _)),
264 Err(err) => return Err(LedgerAppError::Unknown(err)),
265 }
266
267 let response_data = response.data();
268
269 let version = match response_data.len() {
270 4 => Version {
272 mode: response_data[0],
273 major: response_data[1] as u16,
274 minor: response_data[2] as u16,
275 patch: response_data[3] as u16,
276 locked: false,
277 target_id: [0, 0, 0, 0],
278 },
279 7 => Version {
281 mode: response_data[0],
282 major: response_data[1] as u16 * 256 + response_data[2] as u16,
283 minor: response_data[3] as u16 * 256 + response_data[4] as u16,
284 patch: response_data[5] as u16 * 256 + response_data[6] as u16,
285 locked: false,
286 target_id: [0, 0, 0, 0],
287 },
288 9 => Version {
290 mode: response_data[0],
291 major: response_data[1] as u16,
292 minor: response_data[2] as u16,
293 patch: response_data[3] as u16,
294 locked: response_data[4] != 0,
295 target_id: [
296 response_data[5],
297 response_data[6],
298 response_data[7],
299 response_data[8],
300 ],
301 },
302 12 => Version {
304 mode: response_data[0],
305 major: response_data[1] as u16 * 256 + response_data[2] as u16,
306 minor: response_data[3] as u16 * 256 + response_data[4] as u16,
307 patch: response_data[5] as u16 * 256 + response_data[6] as u16,
308 locked: response_data[7] != 0,
309 target_id: [
310 response_data[8],
311 response_data[9],
312 response_data[10],
313 response_data[11],
314 ],
315 },
316 _ => return Err(LedgerAppError::InvalidVersion),
317 };
318 Ok(version)
319 }
320
321 async fn send_chunks<I: std::ops::Deref<Target = [u8]> + Send + Sync>(
323 transport: &E,
324 command: APDUCommand<I>,
325 message: &[u8],
326 ) -> Result<APDUAnswer<E::AnswerType>, LedgerAppError<E::Error>> {
327 let chunks = message.chunks(USER_MESSAGE_CHUNK_SIZE);
328 match chunks.len() {
329 0 => return Err(LedgerAppError::InvalidEmptyMessage),
330 n if n > 255 => return Err(LedgerAppError::InvalidMessageSize),
331 _ => (),
332 }
333
334 if command.p1 != ChunkPayloadType::Init as u8 {
335 return Err(LedgerAppError::InvalidChunkPayloadType);
336 }
337
338 let mut response = transport.exchange(&command).await?;
339 Self::handle_response_error(&response)?;
340
341 let last_chunk_index = chunks.len() - 1;
343 for (packet_idx, chunk) in chunks.enumerate() {
344 let mut p1 = ChunkPayloadType::Add as u8;
345 if packet_idx == last_chunk_index {
346 p1 = ChunkPayloadType::Last as u8;
347 }
348
349 let command = APDUCommand {
350 cla: command.cla,
351 ins: command.ins,
352 p1,
353 p2: command.p2,
354 data: chunk.to_vec(),
355 };
356
357 response = transport.exchange(&command).await?;
358 Self::handle_response_error(&response)?;
359 }
360
361 Ok(response)
362 }
363}
364
365impl<T, E> AppExt<E> for T
366where
367 T: App,
368 E: Exchange + Send + Sync,
369 E::Error: std::error::Error,
370{
371}