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}