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