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
110pub use encdec::{Decode, DecodeOwned, EncDec, Encode};
111
112mod error;
113pub use error::ApduError;
114
115pub mod apdus;
116
117/// APDU command header
118#[derive(Copy, Clone, PartialEq, Debug, Default, Encode, DecodeOwned)]
119#[encdec(error = "ApduError")]
120pub struct ApduHeader {
121 /// Class ID
122 pub cla: u8,
123 /// Instruction ID
124 pub ins: u8,
125 /// Parameter 1
126 pub p1: u8,
127 /// Parameter 2
128 pub p2: u8,
129}
130
131/// Helper trait for defining static APDU commands, automatically
132/// implements [ApduReq].
133///
134/// ```
135/// use ledger_proto::{ApduStatic, ApduError, Encode, Decode};
136///
137/// // App information request APDU (no body)
138/// #[derive(Clone, Debug, PartialEq, Encode, Decode)]
139/// #[encdec(error = "ApduError")]
140/// pub struct AppInfoReq {}
141///
142/// /// Set CLA and INS values for [AppInfoReq]
143/// impl ApduStatic for AppInfoReq {
144/// /// Application Info GET APDU is class `0xb0`
145/// const CLA: u8 = 0xb0;
146///
147/// /// Application Info GET APDU is instruction `0x00`
148/// const INS: u8 = 0x01;
149/// }
150/// ```
151pub trait ApduStatic {
152 /// Class ID for APDU commands
153 const CLA: u8;
154
155 /// Instruction ID for APDU commands
156 const INS: u8;
157
158 /// Fetch P1 value (defaults to `0` if not extended)
159 fn p1(&self) -> u8 {
160 0
161 }
162
163 /// Fetch P2 value (defaults to `0` if not extended)
164 fn p2(&self) -> u8 {
165 0
166 }
167}
168
169/// Generic APDU request trait
170pub trait ApduReq<'a>: EncDec<'a, ApduError> {
171 /// Fetch the [ApduHeader] for a given APDU request
172 fn header(&self) -> ApduHeader;
173}
174
175/// Blanket [ApduReq] impl for [ApduStatic] types
176impl<'a, T: EncDec<'a, ApduError> + ApduStatic> ApduReq<'a> for T {
177 fn header(&self) -> ApduHeader {
178 ApduHeader {
179 cla: T::CLA,
180 ins: T::INS,
181 p1: self.p1(),
182 p2: self.p2(),
183 }
184 }
185}
186
187/// Generic APDU base trait, auto-implemented where `T: EncDec<'a, ApduError>`
188pub trait ApduBase<'a>: EncDec<'a, ApduError> {}
189
190/// Blanket [ApduBase] implementation
191impl<'a, T: EncDec<'a, ApduError>> ApduBase<'a> for T {}
192
193/// Generic APDU object (enabled with `alloc` feature), prefer use of strict APDU types where possible
194#[derive(Clone, Debug, PartialEq)]
195#[cfg(feature = "alloc")]
196pub struct GenericApdu {
197 /// Request APDU Header (uses [Default] for incoming / response APDUs)
198 pub header: ApduHeader,
199 /// APDU data
200 pub data: Vec<u8>,
201}
202
203/// [ApduReq] implementation for [GenericApdu], exposes internal header
204#[cfg(feature = "alloc")]
205impl<'a> ApduReq<'a> for GenericApdu {
206 fn header(&self) -> ApduHeader {
207 self.header
208 }
209}
210
211/// [Encode] implementation for [GenericApdu]
212#[cfg(feature = "alloc")]
213impl Encode for GenericApdu {
214 type Error = ApduError;
215
216 fn encode_len(&self) -> Result<usize, Self::Error> {
217 Ok(self.data.len())
218 }
219
220 fn encode(&self, buff: &mut [u8]) -> Result<usize, Self::Error> {
221 // Check buffer length
222 if buff.len() < self.data.len() {
223 return Err(ApduError::InvalidLength);
224 }
225 // Copy data
226 buff[..self.data.len()].copy_from_slice(&self.data);
227 // Return write length
228 Ok(self.data.len())
229 }
230}
231
232/// [DecodeOwned] implementation for [GenericApdu]
233#[cfg(feature = "alloc")]
234impl DecodeOwned for GenericApdu {
235 type Output = Self;
236
237 type Error = ApduError;
238
239 fn decode_owned(buff: &[u8]) -> Result<(Self::Output, usize), Self::Error> {
240 let data = buff.to_vec();
241 Ok((
242 Self {
243 header: Default::default(),
244 data,
245 },
246 buff.len(),
247 ))
248 }
249}
250
251#[cfg(test)]
252pub(crate) mod tests {
253 use super::*;
254 use encdec::EncDec;
255
256 /// Helper to test round-trip encode / decode for APDUS
257 pub fn encode_decode<'a, A: EncDec<'a, ApduError> + PartialEq>(buff: &'a mut [u8], a: A) {
258 // Test encoding
259 let n = a.encode(buff).unwrap();
260
261 // Test decoding
262 let (a1, n1) = A::decode(&buff[..n]).unwrap();
263
264 // Compare results
265 assert_eq!(n1, n);
266 assert_eq!(a1, a);
267 }
268
269 #[test]
270 fn header_encode_decode() {
271 let h = ApduHeader {
272 cla: 1,
273 ins: 2,
274 p1: 3,
275 p2: 4,
276 };
277
278 let mut b = [0u8; 4];
279
280 encode_decode(&mut b, h);
281
282 assert_eq!(&b, &[1, 2, 3, 4]);
283 }
284}