ledger_lib/transport/
mod.rs

1//! Low-level transport implementations for communication with ledger devices and nano apps
2//!
3//! Transports are gated by `transport_X` features, while [GenericTransport] and
4//! [GenericDevice] provide an abstraction over enabled transports.
5//!
6//! # Safety
7//! [UsbTransport] (and thus [GenericTransport] when `transport_usb` feature is enabled)
8//! is _not_ `Send` or `Sync`, however this is marked as such to appease `async_trait`...
9//!
10//! Once `async_trait` has stabilised transports can be marked correctly.
11//! (This is also implemented under the `unstable_async_trait` feature)
12//! Until then, use [LedgerProvider](crate::LedgerProvider) for a `Sync + Send` interface or
13//!  be _super sure_ you're not going to call transports from a multi-threaded context.
14
15use std::{fmt::Debug, time::Duration};
16
17use tracing::{debug, warn};
18
19#[cfg(feature = "transport_usb")]
20mod usb;
21#[cfg(feature = "transport_usb")]
22pub use usb::{UsbDevice, UsbInfo, UsbTransport};
23
24#[cfg(feature = "transport_ble")]
25mod ble;
26#[cfg(feature = "transport_ble")]
27pub use ble::{BleDevice, BleInfo, BleTransport};
28
29#[cfg(feature = "transport_tcp")]
30mod tcp;
31#[cfg(feature = "transport_tcp")]
32pub use tcp::{TcpDevice, TcpInfo, TcpTransport};
33
34use crate::{
35    info::{ConnInfo, LedgerInfo},
36    Error, Exchange, Filters,
37};
38
39/// [Transport] trait provides an abstract interface for transport implementations
40#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
41pub trait Transport {
42    /// Connection filters
43    type Filters: Debug;
44    /// Device information, used for listing and connecting
45    type Info: Debug;
46    /// Device handle for interacting with the device
47    type Device: Exchange;
48
49    /// List available devices
50    async fn list(&mut self, filters: Self::Filters) -> Result<Vec<LedgerInfo>, Error>;
51
52    /// Connect to a device using info from a previous list operation
53    async fn connect(&mut self, info: Self::Info) -> Result<Self::Device, Error>;
54}
55
56/// [GenericTransport] for device communication, abstracts underlying transport types
57///
58pub struct GenericTransport {
59    #[cfg(feature = "transport_usb")]
60    usb: UsbTransport,
61
62    #[cfg(feature = "transport_ble")]
63    ble: BleTransport,
64
65    #[cfg(feature = "transport_tcp")]
66    tcp: TcpTransport,
67}
68
69/// [GenericDevice] for communication with ledger devices, abstracts underlying transport types
70///
71pub enum GenericDevice {
72    #[cfg(feature = "transport_usb")]
73    Usb(UsbDevice),
74
75    #[cfg(feature = "transport_ble")]
76    Ble(BleDevice),
77
78    #[cfg(feature = "transport_tcp")]
79    Tcp(TcpDevice),
80}
81
82impl GenericTransport {
83    /// Create a new [GenericTransport] with all endabled transports
84    pub async fn new() -> Result<Self, Error> {
85        debug!("Initialising GenericTransport");
86
87        Ok(Self {
88            #[cfg(feature = "transport_usb")]
89            usb: UsbTransport::new()?,
90
91            #[cfg(feature = "transport_ble")]
92            ble: BleTransport::new().await?,
93
94            #[cfg(feature = "transport_tcp")]
95            tcp: TcpTransport::new()?,
96        })
97    }
98}
99
100#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
101impl Transport for GenericTransport {
102    type Filters = Filters;
103    type Info = LedgerInfo;
104    type Device = GenericDevice;
105
106    /// List available ledger devices using all enabled transports
107    async fn list(&mut self, filters: Filters) -> Result<Vec<LedgerInfo>, Error> {
108        let mut devices = vec![];
109
110        #[cfg(feature = "transport_usb")]
111        if filters == Filters::Any || filters == Filters::Hid {
112            let mut d = self.usb.list(()).await?;
113            devices.append(&mut d);
114        }
115
116        #[cfg(feature = "transport_ble")]
117        if filters == Filters::Any || filters == Filters::Ble {
118            // BLE discovery is allowed to fail if not explictly selected
119            // as dbus does not always provide the relevant service (eg. under WSL)
120            // TODO: work out whether we can detect this to separate no BLE from discovery failure
121
122            match self.ble.list(()).await {
123                Ok(mut d) => devices.append(&mut d),
124                Err(e) if filters == Filters::Any => {
125                    warn!("BLE discovery failed: {e:?}");
126                }
127                Err(e) => return Err(e),
128            }
129        }
130
131        #[cfg(feature = "transport_tcp")]
132        if filters == Filters::Any || filters == Filters::Tcp {
133            let mut d = self.tcp.list(()).await?;
134            devices.append(&mut d);
135        }
136
137        Ok(devices)
138    }
139
140    /// Connect to a ledger device using available transports
141    ///
142    async fn connect(&mut self, info: LedgerInfo) -> Result<GenericDevice, Error> {
143        debug!("Connecting to device: {:?}", info);
144
145        let d = match info.conn {
146            #[cfg(feature = "transport_usb")]
147            ConnInfo::Usb(i) => self.usb.connect(i).await.map(GenericDevice::Usb)?,
148            #[cfg(feature = "transport_tcp")]
149            ConnInfo::Tcp(i) => self.tcp.connect(i).await.map(GenericDevice::Tcp)?,
150            #[cfg(feature = "transport_ble")]
151            ConnInfo::Ble(i) => self.ble.connect(i).await.map(GenericDevice::Ble)?,
152        };
153
154        Ok(d)
155    }
156}
157
158impl GenericDevice {
159    /// Fetch connection info for a device
160    pub fn info(&self) -> ConnInfo {
161        match self {
162            GenericDevice::Usb(d) => d.info.clone().into(),
163            GenericDevice::Ble(d) => d.info.clone().into(),
164            GenericDevice::Tcp(d) => d.info.clone().into(),
165        }
166    }
167
168    pub(crate) async fn is_connected(&self) -> Result<bool, Error> {
169        match self {
170            GenericDevice::Usb(d) => d.is_connected().await,
171            GenericDevice::Ble(d) => d.is_connected().await,
172            GenericDevice::Tcp(d) => d.is_connected().await,
173        }
174    }
175}
176
177#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
178impl Exchange for GenericDevice {
179    /// Exchange an APDU with the [GenericDevice]
180    async fn exchange(&mut self, command: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
181        match self {
182            #[cfg(feature = "transport_usb")]
183            Self::Usb(d) => d.exchange(command, timeout).await,
184            Self::Ble(d) => d.exchange(command, timeout).await,
185            Self::Tcp(d) => d.exchange(command, timeout).await,
186        }
187    }
188}
189
190#[cfg(feature = "transport_usb")]
191impl From<UsbDevice> for GenericDevice {
192    fn from(value: UsbDevice) -> Self {
193        Self::Usb(value)
194    }
195}
196
197#[cfg(feature = "transport_tcp")]
198impl From<TcpDevice> for GenericDevice {
199    fn from(value: TcpDevice) -> Self {
200        Self::Tcp(value)
201    }
202}
203
204#[cfg(feature = "transport_ble")]
205impl From<BleDevice> for GenericDevice {
206    fn from(value: BleDevice) -> Self {
207        Self::Ble(value)
208    }
209}