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