ledger_lib/provider/mod.rs
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
//! [LedgerProvider] provides a tokio-based thread-safe interface for
//! interacting with ledger devices.
use std::time::Duration;
use tokio::sync::{
mpsc::{unbounded_channel, UnboundedSender},
OnceCell,
};
mod context;
use context::ProviderContext;
use crate::{error::Error, info::LedgerInfo, transport::Transport, Exchange, Filters};
/// Ledger provider manages device discovery and connection
pub struct LedgerProvider {
req_tx: ReqChannel,
}
/// Ledger device handle for interacting with [LedgerProvider] backed devices
#[derive(Debug)]
pub struct LedgerHandle {
pub info: LedgerInfo,
/// Device index in provider map
index: usize,
/// Channel for issuing requests to the provider task
req_tx: ReqChannel,
}
/// Request object for communication to the provider task
#[derive(Clone, Debug, PartialEq)]
pub enum LedgerReq {
/// List available devices
List(Filters),
/// Connect to a specific device
Connect(LedgerInfo),
/// APDU request issued to a device handle
Req(usize, Vec<u8>, Duration),
/// Close the device handle
Close(usize),
}
/// Request object for communication from the provider task
#[derive(Debug)]
pub enum LedgerResp {
/// List of available ledger devices
Devices(Vec<LedgerInfo>),
/// Device handle following connection
Handle(usize),
/// APDU response from a device handle
Resp(Vec<u8>),
/// Error / operation failure
Error(Error),
}
/// Helper type alias for [LedgerProvider] requests
pub type ReqChannel = UnboundedSender<(LedgerReq, UnboundedSender<LedgerResp>)>;
/// Global provider context, handle for pinned thread used for device communication
static PROVIDER_CTX: OnceCell<ProviderContext> = OnceCell::const_new();
impl LedgerProvider {
/// Create or connect to the ledger provider instance
pub async fn init() -> Self {
// Fetch or create the provider context
let ctx = PROVIDER_CTX
.get_or_init(|| async { ProviderContext::new().await })
.await;
// Return handle to request channel
Self {
req_tx: ctx.req_tx(),
}
}
}
/// [Transport] implementation for high-level [LedgerProvider]
#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
impl Transport for LedgerProvider {
type Device = LedgerHandle;
type Info = LedgerInfo;
type Filters = Filters;
/// List available devices using the specified filter
async fn list(&mut self, filters: Filters) -> Result<Vec<LedgerInfo>, Error> {
let (tx, mut rx) = unbounded_channel::<LedgerResp>();
// Send control request
self.req_tx
.send((LedgerReq::List(filters), tx))
.map_err(|_| Error::Unknown)?;
// Await resposne
match rx.recv().await {
Some(LedgerResp::Devices(i)) => Ok(i),
Some(LedgerResp::Error(e)) => Err(e),
_ => Err(Error::Unknown),
}
}
/// Connect to an available device
async fn connect(&mut self, info: LedgerInfo) -> Result<LedgerHandle, Error> {
let (tx, mut rx) = unbounded_channel::<LedgerResp>();
// Send control request
self.req_tx
.send((LedgerReq::Connect(info.clone()), tx))
.map_err(|_| Error::Unknown)?;
// Await resposne
match rx.recv().await {
Some(LedgerResp::Handle(index)) => Ok(LedgerHandle {
info,
index,
req_tx: self.req_tx.clone(),
}),
Some(LedgerResp::Error(e)) => Err(e),
_ => Err(Error::Unknown),
}
}
}
/// [Exchange] implementation for [LedgerProvider] backed [LedgerHandle]
#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
impl Exchange for LedgerHandle {
async fn exchange(&mut self, command: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
let (tx, mut rx) = unbounded_channel::<LedgerResp>();
// Send APDU request
self.req_tx
.send((LedgerReq::Req(self.index, command.to_vec(), timeout), tx))
.map_err(|_| Error::Unknown)?;
// Await APDU response
match rx.recv().await {
Some(LedgerResp::Resp(data)) => Ok(data),
Some(LedgerResp::Error(e)) => Err(e),
_ => Err(Error::Unknown),
}
}
}
/// [Drop] impl sends close message to provider when [LedgerHandle] is dropped
impl Drop for LedgerHandle {
fn drop(&mut self) {
let (tx, _rx) = unbounded_channel::<LedgerResp>();
let _ = self.req_tx.send((LedgerReq::Close(self.index), tx));
}
}