1 unstable release
0.1.0 | Jun 16, 2023 |
---|
#1488 in Hardware support
233 downloads per month
Used in 3 crates
19KB
327 lines
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.
Dependencies
~2.5MB
~51K SLoC