ledger_lib/transport/
tcp.rs

1use std::{
2    fmt::Display,
3    net::{Ipv4Addr, SocketAddr, SocketAddrV4},
4    time::Duration,
5};
6
7use tokio::{
8    io::{AsyncReadExt, AsyncWriteExt, Interest},
9    net::{TcpListener, TcpStream},
10};
11use tracing::{debug, error};
12
13use crate::{
14    info::{LedgerInfo, Model},
15    Error, Exchange, Transport,
16};
17
18/// TCP transport implementation for interacting with Speculos via the TCP APDU socket
19#[derive(Default)]
20pub struct TcpTransport {}
21
22/// TCP based device
23pub struct TcpDevice {
24    s: TcpStream,
25    pub info: TcpInfo,
26}
27
28/// TCP device information
29#[derive(Clone, PartialEq, Debug)]
30pub struct TcpInfo {
31    pub addr: SocketAddr,
32}
33
34impl Default for TcpInfo {
35    fn default() -> Self {
36        Self {
37            addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1237)),
38        }
39    }
40}
41
42impl Display for TcpInfo {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        write!(f, "{}", self.addr)
45    }
46}
47
48impl TcpTransport {
49    /// Create a new [TcpTransport] instance
50    pub fn new() -> Result<Self, Error> {
51        Ok(Self {})
52    }
53}
54
55impl Transport for TcpTransport {
56    type Filters = ();
57    type Info = TcpInfo;
58    type Device = TcpDevice;
59
60    /// List available devices using the [TcpTransport]
61    ///
62    /// (This looks for a speculos socket on the default port and returns a device if found,
63    /// if you want to connect to a specific device use [TcpTransport::connect])
64    async fn list(&mut self, _filters: Self::Filters) -> Result<Vec<LedgerInfo>, Error> {
65        let mut devices = vec![];
66
67        // Check whether a speculos socket is open on the default port
68        let addr = SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 1237);
69
70        // We can't -connect- to speculos as this does not handle multiple TCP connections
71        // so instead we attempt to bind to the socket we expect speculos to occupy.
72        match TcpListener::bind(addr).await {
73            Ok(_) => (),
74            // A failure indicates this is in use and we should report a device available for connection
75            Err(_) => {
76                devices.push(LedgerInfo {
77                    conn: TcpInfo { addr }.into(),
78                    model: Model::Unknown { usb_pid: None },
79                });
80            }
81        }
82
83        Ok(devices)
84    }
85
86    /// Connect to a TCP device using the provided [TcpInfo]
87    async fn connect(&mut self, info: TcpInfo) -> Result<TcpDevice, Error> {
88        debug!("Connecting to: {:?}", info);
89
90        // Connect to provided TCP socket
91        let s = match TcpStream::connect(info.addr).await {
92            Ok(v) => v,
93            Err(e) => {
94                error!("TCP connection failed: {:?}", e);
95                return Err(e.into());
96            }
97        };
98
99        // Return TCP device handle
100        Ok(TcpDevice { s, info })
101    }
102}
103
104impl TcpDevice {
105    /// Internal helper to write command data
106    async fn write_command(&mut self, req: &[u8]) -> Result<(), Error> {
107        // Setup data buffer to send
108        let mut buff = vec![0; 4 + req.len()];
109
110        // Write APDU length
111        buff[0..4].copy_from_slice(&(req.len() as u32).to_be_bytes());
112
113        // Write APDU data
114        buff[4..].copy_from_slice(req);
115
116        debug!("TX: {:02x?}", buff);
117
118        // Send APDU request
119        if let Err(e) = self.s.write_all(&buff).await {
120            error!("Failed to write request APDU: {:?}", e);
121            return Err(e.into());
122        }
123
124        Ok(())
125    }
126
127    /// Internal helper to read response data
128    async fn read_data(&mut self) -> Result<Vec<u8>, Error> {
129        let mut buff = vec![0u8; 4];
130
131        // Read response length (u32 big endian + 2 bytes for status)
132        let n = match self.s.read_exact(&mut buff[..4]).await {
133            Ok(_) => u32::from_be_bytes(buff[..4].try_into().unwrap()) as usize + 2,
134            Err(e) => {
135                error!("Failed to read response APDU length: {:?}", e);
136                return Err(e.into());
137            }
138        };
139
140        // Read response data
141        buff.resize(n + 4, 0);
142        if let Err(e) = self.s.read_exact(&mut buff[4..][..n]).await {
143            error!("Failed to read response APDU data: {:?}", e);
144            return Err(e.into());
145        }
146
147        debug!("RX: {:02x?}", buff);
148
149        // Return response data
150        Ok(buff[4..].to_vec())
151    }
152
153    pub(crate) async fn is_connected(&self) -> Result<bool, Error> {
154        let r = self.s.ready(Interest::WRITABLE).await?;
155        Ok(!r.is_read_closed() || !r.is_write_closed())
156    }
157}
158
159/// [Exchange] implementation for the TCP transport
160impl Exchange for TcpDevice {
161    async fn exchange(&mut self, req: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
162        // Write APDU request
163        self.write_command(req).await?;
164
165        // Await APDU response with timeout
166        let d = match tokio::time::timeout(timeout, self.read_data()).await {
167            Ok(Ok(d)) => d,
168            Ok(Err(e)) => return Err(e),
169            Err(e) => return Err(e.into()),
170        };
171
172        // Return response data
173        Ok(d)
174    }
175}