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