use super::{ error::Error, protocol::{Link, Protocol, ProtocolV1}, AvailableDeviceTransport, ProtoMessage, Transport, }; use crate::{AvailableDevice, Model}; use std::{fmt, net::UdpSocket, result::Result, time::Duration}; // A collection of constants related to the Emulator Ports. mod constants { pub(crate) const DEFAULT_HOST: &str = "127.0.0.1"; pub(crate) const DEFAULT_PORT: &str = "21324"; pub(crate) const DEFAULT_DEBUG_PORT: &str = "21325"; pub(crate) const LOCAL_LISTENER: &str = "127.0.0.1:0"; } use constants::{DEFAULT_DEBUG_PORT, DEFAULT_HOST, DEFAULT_PORT, LOCAL_LISTENER}; /// The chunk size for the serial protocol. const CHUNK_SIZE: usize = 64; const READ_TIMEOUT_MS: u64 = 100000; const WRITE_TIMEOUT_MS: u64 = 100000; /// An available transport for connecting with a device. #[derive(Debug)] pub struct AvailableUdpTransport { pub host: String, pub port: String, } impl fmt::Display for AvailableUdpTransport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "udp:{}:{}", self.host, self.port) } } /// An actual serial HID USB link to a device over which bytes can be sent. struct UdpLink { socket: UdpSocket, device: (String, String), } // No need to implement drop as every member is owned impl Link for UdpLink { fn write_chunk(&mut self, chunk: Vec) -> Result<(), Error> { debug_assert_eq!(CHUNK_SIZE, chunk.len()); let timeout = Duration::from_millis(WRITE_TIMEOUT_MS); self.socket.set_write_timeout(Some(timeout))?; if let Err(e) = self.socket.send(&chunk) { return Err(e.into()) } Ok(()) } fn read_chunk(&mut self) -> Result, Error> { let mut chunk = vec![0; CHUNK_SIZE]; let timeout = Duration::from_millis(READ_TIMEOUT_MS); self.socket.set_read_timeout(Some(timeout))?; let n = self.socket.recv(&mut chunk)?; if n == CHUNK_SIZE { Ok(chunk) } else { Err(Error::DeviceReadTimeout) } } } impl UdpLink { fn open(path: &str) -> Result { let mut parts = path.split(':'); let link = Self { socket: UdpSocket::bind(LOCAL_LISTENER)?, device: ( parts.next().expect("Incorrect Path").to_owned(), parts.next().expect("Incorrect Path").to_owned(), ), }; link.socket.connect(path)?; Ok(link) } // Ping the port and compare against expected response fn ping(&self) -> Result { let mut resp = [0; CHUNK_SIZE]; self.socket.send("PINGPING".as_bytes())?; let size = self.socket.recv(&mut resp)?; Ok(&resp[..size] == "PONGPONG".as_bytes()) } } /// An implementation of the Transport interface for UDP devices. pub struct UdpTransport { protocol: ProtocolV1, } impl UdpTransport { pub fn find_devices(debug: bool, path: Option<&str>) -> Result, Error> { let mut devices = Vec::new(); let mut dest = String::new(); match path { Some(p) => dest = p.to_owned(), None => { dest.push_str(DEFAULT_HOST); dest.push(':'); dest.push_str(if debug { DEFAULT_DEBUG_PORT } else { DEFAULT_PORT }); } }; let link = UdpLink::open(&dest)?; if link.ping()? { devices.push(AvailableDevice { model: Model::TrezorEmulator, debug, transport: AvailableDeviceTransport::Udp(AvailableUdpTransport { host: link.device.0, port: link.device.1, }), }); } Ok(devices) } /// Connect to a device over the UDP transport. pub fn connect(device: &AvailableDevice) -> Result, Error> { let transport = match device.transport { AvailableDeviceTransport::Udp(ref t) => t, _ => panic!("passed wrong AvailableDevice in UdpTransport::connect"), }; let mut path = String::new(); path.push_str(&transport.host); path.push(':'); path.push_str(&transport.port); let link = UdpLink::open(&path)?; Ok(Box::new(UdpTransport { protocol: ProtocolV1 { link } })) } } impl super::Transport for UdpTransport { fn session_begin(&mut self) -> Result<(), Error> { self.protocol.session_begin() } fn session_end(&mut self) -> Result<(), Error> { self.protocol.session_end() } fn write_message(&mut self, message: ProtoMessage) -> Result<(), Error> { self.protocol.write(message) } fn read_message(&mut self) -> Result { self.protocol.read() } }