use async_trait::async_trait;
pub use bhwi::ledger::LEDGER_DEVICE_ID;
use byteorder::{BigEndian, ReadBytesExt};
use std::io::Cursor;
use crate::{
ErrorKind, Transport,
transport::{Channel, io_error_kind},
};
pub const LEDGER_CHANNEL: u16 = 0x0101;
const LEDGER_PACKET_WRITE_SIZE: u8 = 64;
const LEDGER_PACKET_READ_SIZE: u8 = 64;
#[derive(Debug, thiserror::Error)]
pub enum LedgerHIDError {
#[error("communication error: {0}")]
Comm(
&'static str,
),
#[error("HID IO error: {0}")]
Hid(
#[from]
std::io::Error,
),
}
pub struct LedgerTransportHID<C: Channel> {
channel: C,
}
impl<C: Channel> LedgerTransportHID<C> {
pub fn new(channel: C) -> Self {
Self { channel }
}
}
#[async_trait(?Send)]
impl<C: Channel> Transport for LedgerTransportHID<C> {
type Error = LedgerHIDError;
async fn exchange(
&mut self,
apdu_command: &[u8],
_encrypted: bool,
) -> Result<Vec<u8>, Self::Error> {
let command_length = apdu_command.len();
let mut in_data = Vec::with_capacity(command_length + 2);
in_data.push(((command_length >> 8) & 0xFF) as u8);
in_data.push((command_length & 0xFF) as u8);
in_data.extend_from_slice(apdu_command);
let mut buffer = vec![0u8; LEDGER_PACKET_WRITE_SIZE as usize];
buffer[0] = ((LEDGER_CHANNEL >> 8) & 0xFF) as u8; buffer[1] = (LEDGER_CHANNEL & 0xFF) as u8; buffer[2] = 0x05u8;
for (sequence_idx, chunk) in in_data
.chunks((LEDGER_PACKET_WRITE_SIZE - 5) as usize)
.enumerate()
{
buffer[3] = ((sequence_idx >> 8) & 0xFF) as u8; buffer[4] = (sequence_idx & 0xFF) as u8; buffer[5..5 + chunk.len()].copy_from_slice(chunk);
match self.channel.send(&buffer).await {
Ok(size) => {
if size < buffer.len() {
return Err(LedgerHIDError::Comm(
"USB write error. Could not send whole message",
));
}
}
Err(e) => {
return Err(LedgerHIDError::Hid(e));
}
}
}
let mut apdu_answer: Vec<u8> = Vec::with_capacity(256);
let mut buffer = vec![0u8; LEDGER_PACKET_READ_SIZE as usize];
let mut sequence_idx = 0u16;
let mut expected_apdu_len = 0usize;
loop {
let res = self.channel.receive(&mut buffer).await?;
if (sequence_idx == 0 && res < 7) || res < 5 {
return Err(LedgerHIDError::Comm("Read error. Incomplete header"));
}
let mut rdr = Cursor::new(&buffer);
let rcv_channel = rdr.read_u16::<BigEndian>()?;
let rcv_tag = rdr.read_u8()?;
let rcv_seq_idx = rdr.read_u16::<BigEndian>()?;
if rcv_channel != LEDGER_CHANNEL {
return Err(LedgerHIDError::Comm("Invalid channel"));
}
if rcv_tag != 0x05u8 {
return Err(LedgerHIDError::Comm("Invalid tag"));
}
if rcv_seq_idx != sequence_idx {
return Err(LedgerHIDError::Comm("Invalid sequence idx"));
}
if rcv_seq_idx == 0 {
expected_apdu_len = rdr.read_u16::<BigEndian>()? as usize;
}
let available: usize = buffer.len() - rdr.position() as usize;
let missing: usize = expected_apdu_len - apdu_answer.len();
let end_p = rdr.position() as usize + std::cmp::min(available, missing);
let new_chunk = &buffer[rdr.position() as usize..end_p];
apdu_answer.extend_from_slice(new_chunk);
if apdu_answer.len() >= expected_apdu_len {
break;
}
sequence_idx += 1;
}
Ok(apdu_answer)
}
fn error_kind(&self, error: &Self::Error) -> ErrorKind {
match error {
LedgerHIDError::Hid(error) => io_error_kind(error),
LedgerHIDError::Comm(_) => ErrorKind::Transport,
}
}
}