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
//! Abstract ledger tranport trait with WASM and native HID instantiations.

use crate::{
    common::{APDUAnswer, APDUCommand},
    errors::LedgerError,
};
use async_trait::async_trait;

cfg_if::cfg_if! {
    if #[cfg(target_arch = "wasm32")] {
        /// APDU Transport wrapper for JS/WASM transports.
        pub mod wasm;
        pub use wasm::LedgerTransport as DefaultTransport;

        use log::{debug, error};
    } else {
        /// APDU Transport for native HID. Wraps
        /// [`TransportNativeHID`][native::hid::TransportNativeHID] in a
        /// thread managing the IO.
        pub mod native;
        pub use native::LedgerHandle as DefaultTransport;

        use tracing::{debug, error};
    }
}

/// A Ledger device connection. This wraps the default transport type. In
/// native code, this is the `hidapi` library. When the `node` or `browser`
/// feature is selected, it is a Ledger JS transport library.
#[derive(Debug)]
pub struct Ledger(DefaultTransport);

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
/// An asynchronous interface to the Ledger device. It is critical that the
/// device have only one connection active, so the `init` funnction acquires a
/// lock on the device.
pub trait LedgerAsync: Sized {
    /// Init the connection to the device. This may fail if the device is already in use by some
    /// other process.
    async fn init() -> Result<Self, LedgerError>;

    /// Exchange a packet with the device.
    async fn exchange(&self, packet: &APDUCommand) -> Result<APDUAnswer, LedgerError>;

    /// Consume the connection, and release the resources it holds.
    ///
    /// By default this function simply drops the struct.
    fn close(self) {}
}

#[cfg(not(target_arch = "wasm32"))]
#[async_trait]
impl LedgerAsync for Ledger {
    async fn init() -> Result<Self, LedgerError> {
        Ok(Self(DefaultTransport::init()?))
    }

    async fn exchange(&self, packet: &APDUCommand) -> Result<APDUAnswer, LedgerError> {
        debug!(command = %packet, "dispatching APDU to device");

        // TODO: remove clone
        let resp = self.0.exchange(packet.clone()).await;
        match &resp {
            Ok(resp) => {
                debug!(
                    retcode = resp.retcode(),
                    response = resp.data().map(hex::encode),
                    "Received response from device"
                )
            }
            Err(err) => error!(%err, "Error during communication"),
        }
        resp
    }
}

#[cfg(target_arch = "wasm32")]
#[async_trait(?Send)]
impl LedgerAsync for Ledger {
    async fn init() -> Result<Self, LedgerError> {
        let res: Result<DefaultTransport, wasm_bindgen::JsValue> = DefaultTransport::create().await;
        let res: Result<DefaultTransport, LedgerError> = res.map_err(|err| err.into());
        Ok(Self(res?))
    }

    async fn exchange(&self, packet: &APDUCommand) -> Result<APDUAnswer, LedgerError> {
        debug!("Exchanging Packet {:#?}", packet);
        let resp = self.0.exchange(packet).await;
        match &resp {
            Ok(resp) => debug!("Got response: {:#?}", &resp),
            Err(e) => error!("Got error: {}", e),
        }
        resp
    }
}

/*******************************************************************************
*   (c) 2020 ZondaX GmbH
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
********************************************************************************/