ledger_lib/
device.rs

1//! High-level Ledger [Device] abstraction for application development
2
3use std::time::Duration;
4
5use encdec::{EncDec, Encode};
6use tracing::{debug, error};
7
8use ledger_proto::{
9    apdus::{
10        decode_app_data, AppData, AppInfoReq, AppInfoResp, AppListNextReq, AppListStartReq,
11        DeviceInfoReq, DeviceInfoResp,
12    },
13    ApduError, ApduReq, GenericApdu, StatusCode,
14};
15
16use crate::{
17    info::{AppInfo, DeviceInfo},
18    Error, Exchange,
19};
20
21const APDU_BUFF_LEN: usize = 256;
22
23/// [Device] provides a high-level interface exchanging APDU objects with implementers of [Exchange]
24#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
25pub trait Device {
26    /// Issue a request APDU, returning a reponse APDU
27    async fn request<'a, 'b, RESP: EncDec<'b, ApduError>>(
28        &mut self,
29        request: impl ApduReq<'a> + Send,
30        buff: &'b mut [u8],
31        timeout: Duration,
32    ) -> Result<RESP, Error>;
33
34    /// Fetch application information
35    async fn app_info(&mut self, timeout: Duration) -> Result<AppInfo, Error> {
36        let mut buff = [0u8; APDU_BUFF_LEN];
37
38        let r = self
39            .request::<AppInfoResp>(AppInfoReq {}, &mut buff[..], timeout)
40            .await?;
41
42        Ok(AppInfo {
43            name: r.name.to_string(),
44            version: r.version.to_string(),
45            flags: r.flags,
46        })
47    }
48
49    /// Fetch device information
50    async fn device_info(&mut self, timeout: Duration) -> Result<DeviceInfo, Error> {
51        let mut buff = [0u8; APDU_BUFF_LEN];
52
53        let r = self
54            .request::<DeviceInfoResp>(DeviceInfoReq {}, &mut buff[..], timeout)
55            .await?;
56
57        Ok(DeviceInfo {
58            target_id: r.target_id,
59            se_version: r.se_version.to_string(),
60            mcu_version: r.mcu_version.to_string(),
61            flags: r.flags.to_vec(),
62        })
63    }
64
65    /// Fetch list of installed apps
66    async fn app_list(&mut self, timeout: Duration) -> Result<Vec<AppData>, Error> {
67        let mut buff = [0u8; APDU_BUFF_LEN];
68
69        let mut app_data_list: Vec<AppData> = Default::default();
70
71        let mut start: bool = true;
72
73        loop {
74            let r = match start {
75                true => {
76                    self.request::<GenericApdu>(AppListStartReq {}, &mut buff[..], timeout)
77                        .await
78                }
79                false => {
80                    self.request::<GenericApdu>(AppListNextReq {}, &mut buff[..], timeout)
81                        .await
82                }
83            };
84
85            start = false;
86
87            match r {
88                Ok(apdu_output) => {
89                    let mut offset: usize = 1;
90                    while offset < apdu_output.data.len() - 2 {
91                        let data = decode_app_data(apdu_output.data.as_slice(), &mut offset)
92                            .map_err(Error::from)?;
93                        app_data_list.push(data);
94                    }
95                }
96                Err(Error::Status(StatusCode::Ok)) => {
97                    break;
98                }
99                Err(e) => {
100                    error!("Command failed: {e:?}");
101                    return Err(e);
102                }
103            }
104        }
105        Ok(app_data_list)
106    }
107}
108
109/// Generic [Device] implementation for types supporting [Exchange]
110#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
111impl<T: Exchange + Send> Device for T {
112    /// Issue a request APDU to a device, encoding and decoding internally then returning a response APDU
113    async fn request<'a, 'b, RESP: EncDec<'b, ApduError>>(
114        &mut self,
115        req: impl ApduReq<'a> + Send,
116        buff: &'b mut [u8],
117        timeout: Duration,
118    ) -> Result<RESP, Error> {
119        debug!("TX: {req:?}");
120
121        // Encode request
122        let n = encode_request(req, buff)?;
123
124        // Send request to device
125        let resp_bytes = self.exchange(&buff[..n], timeout).await?;
126
127        // Copy response back to buffer prior to decode
128        // (these hijinks are required to allow devices to avoid ownership of APDU data)
129        let n = resp_bytes.len();
130        if n > buff.len() {
131            error!(
132                "Response length exceeds buffer length ({} > {})",
133                n,
134                buff.len()
135            );
136            return Err(ApduError::InvalidLength.into());
137        }
138        buff[..n].copy_from_slice(&resp_bytes[..]);
139
140        // Handle error responses (2 bytes long, only a status)
141        if n == 2 {
142            // Return status code if matched, unknown otherwise
143            let v = u16::from_be_bytes([resp_bytes[0], resp_bytes[1]]);
144            match StatusCode::try_from(v) {
145                Ok(c) => return Err(Error::Status(c)),
146                Err(_) => return Err(Error::UnknownStatus(resp_bytes[0], resp_bytes[1])),
147            }
148        }
149
150        // Decode response data - status bytes
151        let (resp, _) = RESP::decode(&buff[..n - 2])?;
152
153        debug!("RX: {resp:?}");
154
155        // Return decode response
156        Ok(resp)
157    }
158}
159
160/// Helper to perform APDU request encoding including the header, length, and body
161fn encode_request<'a, REQ: ApduReq<'a>>(req: REQ, buff: &mut [u8]) -> Result<usize, Error> {
162    let mut index = 0;
163
164    let data_len = req.encode_len()?;
165
166    // Check buffer length is reasonable
167    if buff.len() < 5 + data_len {
168        return Err(ApduError::InvalidLength.into());
169    }
170
171    // Encode request object
172
173    // First the header
174    let h = req.header();
175    index += h.encode(&mut buff[index..])?;
176
177    // Then the data length
178    if data_len > u8::MAX as usize {
179        return Err(ApduError::InvalidLength.into());
180    }
181    buff[index] = data_len as u8;
182    index += 1;
183
184    // Then finally the data
185    index += req.encode(&mut buff[index..])?;
186
187    Ok(index)
188}
189
190#[cfg(test)]
191mod tests {
192    use ledger_proto::{apdus::AppInfoReq, ApduStatic};
193
194    use super::encode_request;
195
196    #[test]
197    fn test_encode_requests() {
198        let mut buff = [0u8; 256];
199
200        let req = AppInfoReq {};
201        let n = encode_request(req, &mut buff).unwrap();
202        assert_eq!(n, 5);
203        assert_eq!(
204            &buff[..n],
205            &[AppInfoReq::CLA, AppInfoReq::INS, 0x00, 0x00, 0x00]
206        );
207    }
208}