nam_ledger_proto/lib.rs
1//! Ledger Hardware Wallet APDU traits and shared types.
2//!
3//! This provides abstractions for encoding and decoding APDUs for to
4//! support interaction with Ledger devices.
5//!
6//! APDUs must implement [ApduBase] as well as [encdec::Encode] and [encdec::Decode]
7//! (or [encdec::DecodeOwned]) for binary serialisation, with commands providing
8//! header information via [ApduReq].
9//! [encdec::Encode] and [encdec::Decode] can be automatically derived using `encdec` macros,
10//! or manually implemented over existing objects / encodings.
11//!
12//! An [ApduStatic] helper is provided to automatically implement [ApduReq] for APDU requests
13//! with static headers and a common [ApduError] type is provided to unify serialisation and
14//! deserialisation errors across APDU objects.
15//!
16//!
17//! ## Examples
18//!
19//! Command APDU (no body) using [ApduStatic]:
20//!
21//! ```
22//! use ledger_proto::{ApduStatic, ApduError, Encode, DecodeOwned};
23//!
24//! /// Application information request APDU
25//! #[derive(Clone, Debug, PartialEq, Encode, DecodeOwned)]
26//! #[encdec(error = "ApduError")]
27//! pub struct AppInfoReq {}
28//!
29//! /// Set CLA and INS values for [AppInfoReq]
30//! impl ApduStatic for AppInfoReq {
31//! /// Application Info GET APDU is class `0xb0`
32//! const CLA: u8 = 0xb0;
33//! /// Application Info GET APDU is instruction `0x00`
34//! const INS: u8 = 0x01;
35//! }
36//! ```
37//!
38//! Manual response APDU implementation
39//!
40//! ```
41//! use ledger_proto::{ApduStatic, ApduError, Encode, Decode};
42//!
43//! /// Example response APDU
44//! #[derive(Clone, Debug, PartialEq)]
45//! pub struct StringResp<'a> {
46//! pub value: &'a str,
47//! }
48//!
49//! /// [Encode] implementation for [StringResp]
50//! impl <'a> Encode for StringResp<'a> {
51//! type Error = ApduError;
52//!
53//! /// Fetch encoded length
54//! fn encode_len(&self) -> Result<usize, Self::Error> {
55//! Ok(1 + self.value.as_bytes().len())
56//! }
57//!
58//! /// Encode to bytes
59//! fn encode(&self, buff: &mut [u8]) -> Result<usize, Self::Error> {
60//! let b = self.value.as_bytes();
61//!
62//! // Check buffer length is valid
63//! if buff.len() < self.encode_len()?
64//! || b.len() > u8::MAX as usize {
65//! return Err(ApduError::InvalidLength);
66//! }
67//!
68//! // Write value length
69//! buff[0] = b.len() as u8;
70//!
71//! // Write value
72//! buff[1..][..b.len()]
73//! .copy_from_slice(b);
74//!
75//! Ok(1 + b.len())
76//! }
77//! }
78//!
79//! impl <'a> Decode<'a> for StringResp<'a> {
80//! type Output = Self;
81//! type Error = ApduError;
82//!
83//! fn decode(buff: &'a [u8]) -> Result<(Self::Output, usize), Self::Error> {
84//! // Check buffer length
85//! if buff.len() < 1 {
86//! return Err(ApduError::InvalidLength);
87//! }
88//! let n = buff[0]as usize;
89//! if n + 1 > buff.len() {
90//! return Err(ApduError::InvalidLength);
91//! }
92//!
93//! // Parse string value
94//! let s = match core::str::from_utf8(&buff[1..][..n]) {
95//! Ok(v) => v,
96//! Err(_) => return Err(ApduError::InvalidUtf8),
97//! };
98//!
99//! // Return object and parsed length
100//! Ok((Self{ value: s}, n + 1))
101//! }
102//! }
103//! ```
104//!
105//! For more examples, see the shared APDUs provided in the [apdus] module.
106//!
107
108#![cfg_attr(not(feature = "std"), no_std)]
109
110#[cfg(feature = "alloc")]
111extern crate alloc;
112#[cfg(feature = "alloc")]
113use alloc::vec::Vec;
114
115pub use encdec::{Decode, DecodeOwned, EncDec, Encode};
116
117mod error;
118pub use error::ApduError;
119
120pub mod apdus;
121
122mod status;
123pub use status::StatusCode;
124
125/// APDU command header
126#[derive(Copy, Clone, PartialEq, Debug, Default, Encode, DecodeOwned)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128#[encdec(error = "ApduError")]
129pub struct ApduHeader {
130 /// Class ID
131 pub cla: u8,
132 /// Instruction ID
133 pub ins: u8,
134 /// Parameter 1
135 pub p1: u8,
136 /// Parameter 2
137 pub p2: u8,
138}
139
140/// Helper trait for defining static APDU commands, automatically
141/// implements [ApduReq].
142///
143/// ```
144/// use ledger_proto::{ApduStatic, ApduError, Encode, Decode};
145///
146/// // App information request APDU (no body)
147/// #[derive(Clone, Debug, PartialEq, Encode, Decode)]
148/// #[encdec(error = "ApduError")]
149/// pub struct AppInfoReq {}
150///
151/// /// Set CLA and INS values for [AppInfoReq]
152/// impl ApduStatic for AppInfoReq {
153/// /// Application Info GET APDU is class `0xb0`
154/// const CLA: u8 = 0xb0;
155///
156/// /// Application Info GET APDU is instruction `0x00`
157/// const INS: u8 = 0x01;
158/// }
159/// ```
160pub trait ApduStatic {
161 /// Class ID for APDU commands
162 const CLA: u8;
163
164 /// Instruction ID for APDU commands
165 const INS: u8;
166
167 /// Fetch P1 value (defaults to `0` if not extended)
168 fn p1(&self) -> u8 {
169 0
170 }
171
172 /// Fetch P2 value (defaults to `0` if not extended)
173 fn p2(&self) -> u8 {
174 0
175 }
176}
177
178/// Generic APDU request trait
179pub trait ApduReq<'a>: EncDec<'a, ApduError> {
180 /// Fetch the [ApduHeader] for a given APDU request
181 fn header(&self) -> ApduHeader;
182}
183
184/// Blanket [ApduReq] impl for [ApduStatic] types
185impl<'a, T: EncDec<'a, ApduError> + ApduStatic> ApduReq<'a> for T {
186 fn header(&self) -> ApduHeader {
187 ApduHeader {
188 cla: T::CLA,
189 ins: T::INS,
190 p1: self.p1(),
191 p2: self.p2(),
192 }
193 }
194}
195
196/// Generic APDU base trait, auto-implemented where `T: EncDec<'a, ApduError>`
197pub trait ApduBase<'a>: EncDec<'a, ApduError> {}
198
199/// Blanket [ApduBase] implementation
200impl<'a, T: EncDec<'a, ApduError>> ApduBase<'a> for T {}
201
202/// Generic APDU object (enabled with `alloc` feature), prefer use of strict APDU types where possible
203#[derive(Clone, Debug, PartialEq)]
204#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
205#[cfg(feature = "alloc")]
206pub struct GenericApdu {
207 /// Request APDU Header (uses [Default] for incoming / response APDUs)
208 pub header: ApduHeader,
209 /// APDU data
210 #[cfg_attr(feature = "serde", serde(with = "hex::serde"))]
211 pub data: Vec<u8>,
212}
213
214/// [ApduReq] implementation for [GenericApdu], exposes internal header
215#[cfg(feature = "alloc")]
216impl ApduReq<'_> for GenericApdu {
217 fn header(&self) -> ApduHeader {
218 self.header
219 }
220}
221
222/// [Encode] implementation for [GenericApdu]
223#[cfg(feature = "alloc")]
224impl Encode for GenericApdu {
225 type Error = ApduError;
226
227 fn encode_len(&self) -> Result<usize, Self::Error> {
228 Ok(self.data.len())
229 }
230
231 fn encode(&self, buff: &mut [u8]) -> Result<usize, Self::Error> {
232 // Check buffer length
233 if buff.len() < self.data.len() {
234 return Err(ApduError::InvalidLength);
235 }
236 // Copy data
237 buff[..self.data.len()].copy_from_slice(&self.data);
238 // Return write length
239 Ok(self.data.len())
240 }
241}
242
243/// [DecodeOwned] implementation for [GenericApdu]
244#[cfg(feature = "alloc")]
245impl DecodeOwned for GenericApdu {
246 type Output = Self;
247
248 type Error = ApduError;
249
250 fn decode_owned(buff: &[u8]) -> Result<(Self::Output, usize), Self::Error> {
251 let data = buff.to_vec();
252 Ok((
253 Self {
254 header: Default::default(),
255 data,
256 },
257 buff.len(),
258 ))
259 }
260}
261
262#[cfg(test)]
263pub(crate) mod tests {
264 use super::*;
265 use encdec::EncDec;
266
267 /// Helper to test round-trip encode / decode for APDUS
268 pub fn encode_decode<'a, A: EncDec<'a, ApduError> + PartialEq>(buff: &'a mut [u8], a: A) {
269 // Test encoding
270 let n = a.encode(buff).unwrap();
271
272 // Test decoding
273 let (a1, n1) = A::decode(&buff[..n]).unwrap();
274
275 // Compare results
276 assert_eq!(n1, n);
277 assert_eq!(a1, a);
278 }
279
280 #[test]
281 fn header_encode_decode() {
282 let h = ApduHeader {
283 cla: 1,
284 ins: 2,
285 p1: 3,
286 p2: 4,
287 };
288
289 let mut b = [0u8; 4];
290
291 encode_decode(&mut b, h);
292
293 assert_eq!(&b, &[1, 2, 3, 4]);
294 }
295}