ledger_lib/provider/
mod.rs

1//! [LedgerProvider] provides a tokio-based thread-safe interface for
2//! interacting with ledger devices.
3
4use std::time::Duration;
5
6use tokio::sync::{
7    mpsc::{unbounded_channel, UnboundedSender},
8    OnceCell,
9};
10
11mod context;
12use context::ProviderContext;
13
14use crate::{error::Error, info::LedgerInfo, transport::Transport, Exchange, Filters};
15
16/// Ledger provider manages device discovery and connection
17pub struct LedgerProvider {
18    req_tx: ReqChannel,
19}
20
21/// Ledger device handle for interacting with [LedgerProvider] backed devices
22#[derive(Debug)]
23pub struct LedgerHandle {
24    pub info: LedgerInfo,
25
26    /// Device index in provider map
27    index: usize,
28
29    /// Channel for issuing requests to the provider task
30    req_tx: ReqChannel,
31}
32
33/// Request object for communication to the provider task
34#[derive(Clone, Debug, PartialEq)]
35pub enum LedgerReq {
36    /// List available devices
37    List(Filters),
38
39    /// Connect to a specific device
40    Connect(LedgerInfo),
41
42    /// APDU request issued to a device handle
43    Req(usize, Vec<u8>, Duration),
44
45    /// Close the device handle
46    Close(usize),
47}
48
49/// Request object for communication from the provider task
50#[derive(Debug)]
51pub enum LedgerResp {
52    /// List of available ledger devices
53    Devices(Vec<LedgerInfo>),
54
55    /// Device handle following connection
56    Handle(usize),
57
58    /// APDU response from a device handle
59    Resp(Vec<u8>),
60
61    /// Error / operation failure
62    Error(Error),
63}
64
65/// Helper type alias for [LedgerProvider] requests
66pub type ReqChannel = UnboundedSender<(LedgerReq, UnboundedSender<LedgerResp>)>;
67
68/// Global provider context, handle for pinned thread used for device communication
69static PROVIDER_CTX: OnceCell<ProviderContext> = OnceCell::const_new();
70
71impl LedgerProvider {
72    /// Create or connect to the ledger provider instance
73    pub async fn init() -> Self {
74        // Fetch or create the provider context
75        let ctx = PROVIDER_CTX
76            .get_or_init(|| async { ProviderContext::new().await })
77            .await;
78
79        // Return handle to request channel
80        Self {
81            req_tx: ctx.req_tx(),
82        }
83    }
84}
85
86/// [Transport] implementation for high-level [LedgerProvider]
87#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
88impl Transport for LedgerProvider {
89    type Device = LedgerHandle;
90    type Info = LedgerInfo;
91    type Filters = Filters;
92
93    /// List available devices using the specified filter
94    async fn list(&mut self, filters: Filters) -> Result<Vec<LedgerInfo>, Error> {
95        let (tx, mut rx) = unbounded_channel::<LedgerResp>();
96
97        // Send control request
98        self.req_tx
99            .send((LedgerReq::List(filters), tx))
100            .map_err(|_| Error::Unknown)?;
101
102        // Await resposne
103        match rx.recv().await {
104            Some(LedgerResp::Devices(i)) => Ok(i),
105            Some(LedgerResp::Error(e)) => Err(e),
106            _ => Err(Error::Unknown),
107        }
108    }
109
110    /// Connect to an available device
111    async fn connect(&mut self, info: LedgerInfo) -> Result<LedgerHandle, Error> {
112        let (tx, mut rx) = unbounded_channel::<LedgerResp>();
113
114        // Send control request
115        self.req_tx
116            .send((LedgerReq::Connect(info.clone()), tx))
117            .map_err(|_| Error::Unknown)?;
118
119        // Await resposne
120        match rx.recv().await {
121            Some(LedgerResp::Handle(index)) => Ok(LedgerHandle {
122                info,
123                index,
124                req_tx: self.req_tx.clone(),
125            }),
126            Some(LedgerResp::Error(e)) => Err(e),
127            _ => Err(Error::Unknown),
128        }
129    }
130}
131
132/// [Exchange] implementation for [LedgerProvider] backed [LedgerHandle]
133#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
134impl Exchange for LedgerHandle {
135    async fn exchange(&mut self, command: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
136        let (tx, mut rx) = unbounded_channel::<LedgerResp>();
137
138        // Send APDU request
139        self.req_tx
140            .send((LedgerReq::Req(self.index, command.to_vec(), timeout), tx))
141            .map_err(|_| Error::Unknown)?;
142
143        // Await APDU response
144        match rx.recv().await {
145            Some(LedgerResp::Resp(data)) => Ok(data),
146            Some(LedgerResp::Error(e)) => Err(e),
147            _ => Err(Error::Unknown),
148        }
149    }
150}
151
152/// [Drop] impl sends close message to provider when [LedgerHandle] is dropped
153impl Drop for LedgerHandle {
154    fn drop(&mut self) {
155        let (tx, _rx) = unbounded_channel::<LedgerResp>();
156        let _ = self.req_tx.send((LedgerReq::Close(self.index), tx));
157    }
158}