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
17#[cfg(feature = "transport_ble")]
18use tracing::warn;
19
20use tracing::debug;
21
22#[cfg(feature = "transport_usb")]
23mod usb;
24#[cfg(feature = "transport_usb")]
25pub use usb::{UsbDevice, UsbInfo, UsbTransport};
26
27#[cfg(feature = "transport_ble")]
28mod ble;
29#[cfg(feature = "transport_ble")]
30pub use ble::{BleDevice, BleInfo, BleTransport};
31
32#[cfg(feature = "transport_tcp")]
33mod tcp;
34#[cfg(feature = "transport_tcp")]
35pub use tcp::{TcpDevice, TcpInfo, TcpTransport};
36
37use crate::{
38    info::{ConnInfo, LedgerInfo},
39    Error, Exchange, Filters,
40};
41
42/// [Transport] trait provides an abstract interface for transport implementations
43#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
44pub trait Transport {
45    /// Connection filters
46    type Filters: Default + Debug;
47    /// Device information, used for listing and connecting
48    type Info: Debug;
49    /// Device handle for interacting with the device
50    type Device: Exchange;
51
52    /// List available devices
53    async fn list(&mut self, filters: Self::Filters) -> Result<Vec<LedgerInfo>, Error>;
54
55    /// Connect to a device using info from a previous list operation
56    async fn connect(&mut self, info: Self::Info) -> Result<Self::Device, Error>;
57}
58
59/// Blanket [Transport] implementation for references types
60#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
61impl<T: Transport + Send> Transport for &mut T
62where
63    <T as Transport>::Device: Send,
64    <T as Transport>::Filters: Send,
65    <T as Transport>::Info: Send,
66{
67    type Filters = <T as Transport>::Filters;
68    type Info = <T as Transport>::Info;
69    type Device = <T as Transport>::Device;
70
71    async fn list(&mut self, filters: Self::Filters) -> Result<Vec<LedgerInfo>, Error> {
72        <T as Transport>::list(self, filters).await
73    }
74    async fn connect(&mut self, info: Self::Info) -> Result<Self::Device, Error> {
75        <T as Transport>::connect(self, info).await
76    }
77}
78
79/// [GenericTransport] for device communication, abstracts underlying transport types
80///
81pub struct GenericTransport {
82    #[cfg(feature = "transport_usb")]
83    usb: UsbTransport,
84
85    #[cfg(feature = "transport_ble")]
86    ble: BleTransport,
87
88    #[cfg(feature = "transport_tcp")]
89    tcp: TcpTransport,
90}
91
92/// [GenericDevice] for communication with ledger devices, abstracts underlying transport types
93///
94pub enum GenericDevice {
95    #[cfg(feature = "transport_usb")]
96    Usb(UsbDevice),
97
98    #[cfg(feature = "transport_ble")]
99    Ble(BleDevice),
100
101    #[cfg(feature = "transport_tcp")]
102    Tcp(TcpDevice),
103}
104
105impl GenericTransport {
106    /// Create a new [GenericTransport] with all endabled transports
107    pub async fn new() -> Result<Self, Error> {
108        debug!("Initialising GenericTransport");
109
110        Ok(Self {
111            #[cfg(feature = "transport_usb")]
112            usb: UsbTransport::new()?,
113
114            #[cfg(feature = "transport_ble")]
115            ble: BleTransport::new().await?,
116
117            #[cfg(feature = "transport_tcp")]
118            tcp: TcpTransport::new()?,
119        })
120    }
121}
122
123#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
124impl Transport for GenericTransport {
125    type Filters = Filters;
126    type Info = LedgerInfo;
127    type Device = GenericDevice;
128
129    /// List available ledger devices using all enabled transports
130    async fn list(&mut self, filters: Filters) -> Result<Vec<LedgerInfo>, Error> {
131        let mut devices = vec![];
132
133        #[cfg(feature = "transport_usb")]
134        if filters == Filters::Any || filters == Filters::Hid {
135            let mut d = self.usb.list(()).await?;
136            devices.append(&mut d);
137        }
138
139        #[cfg(feature = "transport_ble")]
140        if filters == Filters::Any || filters == Filters::Ble {
141            // BLE discovery is allowed to fail if not exclusively selected
142            // as dbus does not always provide the relevant service (eg. under WSL)
143            // TODO: work out whether we can detect this to separate no BLE from discovery failure
144            match self.ble.list(()).await {
145                Ok(mut d) => devices.append(&mut d),
146                Err(e) if filters == Filters::Any => {
147                    warn!("BLE discovery failed: {e:?}");
148                }
149                Err(e) => return Err(e),
150            }
151        }
152
153        #[cfg(feature = "transport_tcp")]
154        if filters == Filters::Any || filters == Filters::Tcp {
155            let mut d = self.tcp.list(()).await?;
156            devices.append(&mut d);
157        }
158
159        Ok(devices)
160    }
161
162    /// Connect to a ledger device using available transports
163    ///
164    async fn connect(&mut self, info: LedgerInfo) -> Result<GenericDevice, Error> {
165        debug!("Connecting to device: {:?}", info);
166
167        let d = match info.conn {
168            #[cfg(feature = "transport_usb")]
169            ConnInfo::Usb(i) => self.usb.connect(i).await.map(GenericDevice::Usb)?,
170            #[cfg(feature = "transport_tcp")]
171            ConnInfo::Tcp(i) => self.tcp.connect(i).await.map(GenericDevice::Tcp)?,
172            #[cfg(feature = "transport_ble")]
173            ConnInfo::Ble(i) => self.ble.connect(i).await.map(GenericDevice::Ble)?,
174        };
175
176        Ok(d)
177    }
178}
179
180impl GenericDevice {
181    /// Fetch connection info for a device
182    pub fn info(&self) -> ConnInfo {
183        match self {
184            #[cfg(feature = "transport_usb")]
185            GenericDevice::Usb(d) => d.info.clone().into(),
186            #[cfg(feature = "transport_ble")]
187            GenericDevice::Ble(d) => d.info.clone().into(),
188            #[cfg(feature = "transport_tcp")]
189            GenericDevice::Tcp(d) => d.info.clone().into(),
190        }
191    }
192
193    pub(crate) async fn is_connected(&self) -> Result<bool, Error> {
194        match self {
195            #[cfg(feature = "transport_usb")]
196            GenericDevice::Usb(d) => d.is_connected().await,
197            #[cfg(feature = "transport_ble")]
198            GenericDevice::Ble(d) => d.is_connected().await,
199            #[cfg(feature = "transport_tcp")]
200            GenericDevice::Tcp(d) => d.is_connected().await,
201        }
202    }
203}
204
205#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
206impl Exchange for GenericDevice {
207    /// Exchange an APDU with the [GenericDevice]
208    async fn exchange(&mut self, command: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
209        match self {
210            #[cfg(feature = "transport_usb")]
211            Self::Usb(d) => d.exchange(command, timeout).await,
212            #[cfg(feature = "transport_ble")]
213            Self::Ble(d) => d.exchange(command, timeout).await,
214            #[cfg(feature = "transport_tcp")]
215            Self::Tcp(d) => d.exchange(command, timeout).await,
216        }
217    }
218}
219
220#[cfg(feature = "transport_usb")]
221impl From<UsbDevice> for GenericDevice {
222    fn from(value: UsbDevice) -> Self {
223        Self::Usb(value)
224    }
225}
226
227#[cfg(feature = "transport_tcp")]
228impl From<TcpDevice> for GenericDevice {
229    fn from(value: TcpDevice) -> Self {
230        Self::Tcp(value)
231    }
232}
233
234#[cfg(feature = "transport_ble")]
235impl From<BleDevice> for GenericDevice {
236    fn from(value: BleDevice) -> Self {
237        Self::Ble(value)
238    }
239}