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));
    }
}