lwk_ledger 0.17.0

Liquid Wallet Kit - Interact with Ledger Hardware Wallet
Documentation
//! Note this is a duplication of crate::transport_tcp::Transport with the exception of implementing
//! the asyncronous transport instead of the syncronous one

use std::convert::TryFrom;
use std::error::Error;
use std::io::{Read, Write};
use std::net::TcpStream;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Mutex;

use crate::apdu::APDUCmdVec;
use crate::apdu::StatusWord;
use ledger_apdu::APDUAnswer;

use super::Transport;

/// Transport to communicate with the Ledger Speculos simulator.
#[derive(Debug)]
pub struct TransportTcp {
    connection: Mutex<TcpStream>,
}

impl TransportTcp {
    pub fn new(port: u16) -> Result<Self, Box<dyn Error>> {
        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
        let stream = TcpStream::connect(addr)?;
        Ok(Self {
            connection: Mutex::new(stream),
        })
    }
}

impl Transport for TransportTcp {
    type Error = Box<dyn Error>;
    async fn exchange(&self, command: &APDUCmdVec) -> Result<(StatusWord, Vec<u8>), Self::Error> {
        if let Ok(mut stream) = self.connection.lock() {
            let command_bytes = command.serialize();

            let mut req = vec![0u8; command_bytes.len() + 4];
            req[..4].copy_from_slice(&(command_bytes.len() as u32).to_be_bytes());
            req[4..].copy_from_slice(&command_bytes);
            stream.write_all(&req)?;

            let mut buff = [0u8; 4];
            let len = match stream.read(&mut buff)? {
                4 => u32::from_be_bytes(buff),
                _ => return Err("Invalid Length".into()),
            };

            let mut resp = vec![0u8; len as usize + 2];
            stream.read_exact(&mut resp)?;

            let answer = APDUAnswer::from_answer(resp).map_err(|_| "Invalid Answer")?;
            Ok((
                StatusWord::try_from(answer.retcode()).unwrap_or(StatusWord::Unknown),
                answer.data().to_vec(),
            ))
        } else {
            Err("unable to get lock".into())
        }
    }
}