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}