feat: support wasm build for adb_client

This commit is contained in:
Corentin LIAUD
2025-11-10 11:39:28 +01:00
parent c453c544e2
commit 267d740321
12 changed files with 171 additions and 37 deletions

View File

@@ -18,26 +18,26 @@ rustdoc-args = ["--cfg", "docsrs"]
default = []
mdns = ["dep:mdns-sd"]
rusb = ["dep:rsa", "dep:rusb"]
webusb = ["dep:webusb-web", "dep:rsa"]
[dependencies]
base64 = { version = "0.22.1" }
bincode = { version = "1.3.3" }
byteorder = { version = "1.5.0" }
chrono = { version = "0.4.42", default-features = false, features = ["std"] }
homedir = { version = "=0.3.4" }
image = { version = "0.25.8", default-features = false }
log = { version = "0.4.28" }
num-bigint = { version = "0.8.4", package = "num-bigint-dig" }
num-traits = { version = "0.2.19" }
quick-protobuf = { version = "0.8.1" }
rand = { version = "0.9.2" }
rand = { version = "0.8.5" }
rcgen = { version = "0.13.2", default-features = false, features = [
"aws_lc_rs",
"pem",
"ring"
] }
regex = { version = "1.12.2", features = ["perf", "std", "unicode"] }
rustls = { version = "0.23.33" }
rustls-pki-types = { version = "1.12.0" }
rustls = { version = "0.23.33", default-features = false, features = ["logging", "ring", "std", "tls12"] }
rustls-pki-types = { version = "1.12.0", features = ["web"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_repr = { version = "0.1.20" }
sha1 = { version = "0.10.6", features = ["oid"] }
@@ -54,6 +54,12 @@ mdns-sd = { version = "0.13.11", default-features = false, features = [
rsa = { version = "0.9.7", optional = true }
rusb = { version = "0.9.4", features = ["vendored"], optional = true }
#########
#########
# webusb dependencies
webusb-web = { version = "0.4.1", optional = true }
getrandom = { version = "0.2.16", features = ["js"] }
ring = { version = "0.17.14", features = ["wasm32_unknown_unknown_js"] }
[dev-dependencies]
anyhow = { version = "1.0.100" }

View File

@@ -8,7 +8,7 @@ use crate::{RebootType, Result};
/// Trait representing all features available on an ADB device, currently used by:
/// - [`crate::server_device::ADBServerDevice`]
/// - [`crate::usb::ADBUSBDevice`]
/// - [`crate::usb::ADBRusbDevice`]
/// - [`crate::tcp::ADBTcpDevice`]
pub trait ADBDeviceExt {
/// Runs command in a shell on the device, and write its output and error streams into output.

View File

@@ -4,8 +4,6 @@ use std::{
net::{SocketAddrV4, TcpStream},
};
use homedir::my_home;
use crate::{
Result, RustADBError, adb_transport::ADBTransport, emulator::models::ADBEmulatorCommand,
};
@@ -37,7 +35,7 @@ impl TCPEmulatorTransport {
/// Return authentication token stored in `$HOME/.emulator_console_auth_token`
pub fn get_authentication_token(&mut self) -> Result<String> {
let Some(home) = my_home()? else {
let Some(home) = std::env::home_dir() else {
return Err(RustADBError::NoHomeDirectory);
};

View File

@@ -63,9 +63,6 @@ pub enum RustADBError {
/// Unimplemented framebuffer image version
#[error("Unimplemented framebuffer image version: {0}")]
UnimplementedFramebufferImageVersion(u32),
/// An error occurred while getting user's home directory
#[error(transparent)]
HomeError(#[from] homedir::GetHomeError),
/// Cannot get home directory
#[error("Cannot get home directory")]
NoHomeDirectory,
@@ -90,8 +87,8 @@ pub enum RustADBError {
#[error(transparent)]
Base64EncodeError(#[from] base64::EncodeSliceError),
/// An error occurred with RSA engine
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
#[cfg(any(feature = "rusb", feature = "webusb"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "rusb", feature = "webusb"))))]
#[error(transparent)]
RSAError(#[from] rsa::errors::Error),
/// Cannot convert given data from slice
@@ -101,8 +98,8 @@ pub enum RustADBError {
#[error("wrong file extension: {0}")]
WrongFileExtension(String),
/// An error occurred with PKCS8 data
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
#[cfg(any(feature = "rusb", feature = "webusb"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "rusb", feature = "webusb"))))]
#[error("error with pkcs8: {0}")]
RsaPkcs8Error(#[from] rsa::pkcs8::Error),
/// Error during certificate generation

View File

@@ -1,5 +1,4 @@
use byteorder::{LittleEndian, ReadBytesExt};
use rand::Rng;
use std::io::{Cursor, Read, Seek};
use crate::{
@@ -219,11 +218,9 @@ impl<T: ADBMessageTransport> ADBMessageDevice<T> {
}
pub(crate) fn open_session(&mut self, data: &[u8]) -> Result<ADBTransportMessage> {
let mut rng = rand::rng();
let message = ADBTransportMessage::new(
MessageCommand::Open,
rng.random(), // Our 'local-id'
rand::random::<u32>(), // Our 'local-id'
0,
data,
);

View File

@@ -16,7 +16,7 @@ pub struct ADBRusbDevice {
}
impl ADBRusbDevice {
/// Instantiate a new [`ADBRusb`]
/// Instantiate a new [`ADBRusbDevice`]
pub fn new(vendor_id: u16, product_id: u16) -> Result<Self> {
Self::new_with_custom_private_key(vendor_id, product_id, get_default_adb_key_path()?)
}

View File

@@ -0,0 +1,69 @@
use std::{
io::{Read, Write},
path::Path,
};
use crate::{
ADBDeviceExt, Result,
usb::{WebUsbTransport, adb_usb_device::ADBUSBDevice},
};
/// Implement Android USB device reachable over wired USB
#[derive(Debug)]
pub struct ADBWebUsbDevice {
inner: ADBUSBDevice<WebUsbTransport>,
}
impl ADBWebUsbDevice {
/// Instantiate a new [`ADBRusb`]
pub fn new(_vendor_id: u16, _product_id: u16) -> Result<Self> {
todo!()
}
}
impl ADBDeviceExt for ADBWebUsbDevice {
#[inline]
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()> {
self.inner.shell_command(command, output)
}
#[inline]
fn shell<'a>(&mut self, reader: &mut dyn Read, writer: Box<dyn Write + Send>) -> Result<()> {
self.inner.shell(reader, writer)
}
#[inline]
fn stat(&mut self, remote_path: &str) -> Result<crate::AdbStatResponse> {
self.inner.stat(remote_path)
}
#[inline]
fn pull(&mut self, source: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
self.inner.pull(source, output)
}
#[inline]
fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()> {
self.inner.push(stream, path)
}
#[inline]
fn reboot(&mut self, reboot_type: crate::RebootType) -> Result<()> {
self.inner.reboot(reboot_type)
}
#[inline]
fn install(&mut self, apk_path: &dyn AsRef<Path>) -> Result<()> {
self.inner.install(apk_path)
}
#[inline]
fn uninstall(&mut self, package: &str) -> Result<()> {
self.inner.uninstall(package)
}
#[inline]
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
self.inner.framebuffer_inner()
}
}

View File

@@ -1,3 +1,7 @@
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
pub mod rusb_transport;
#[cfg(feature = "webusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "webusb")))]
pub mod webusb_transport;

View File

@@ -0,0 +1,48 @@
use std::time::Duration;
use crate::{
Result,
adb_transport::ADBTransport,
message_devices::{
adb_message_transport::ADBMessageTransport, adb_transport_message::ADBTransportMessage,
},
};
/// Transport running on USB using `webusb` as a backend.
#[derive(Clone, Debug)]
pub struct WebUsbTransport {}
impl WebUsbTransport {
/// Instantiate a new [`WebUsbTransport`].
/// Only the first device with given vendor_id and product_id is returned.
pub fn new() -> Self {
WebUsbTransport {}
}
}
impl ADBTransport for WebUsbTransport {
fn connect(&mut self) -> crate::Result<()> {
todo!()
}
fn disconnect(&mut self) -> crate::Result<()> {
todo!()
}
}
impl ADBMessageTransport for WebUsbTransport {
fn read_message_with_timeout(
&mut self,
_read_timeout: Duration,
) -> Result<ADBTransportMessage> {
todo!()
}
fn write_message_with_timeout(
&mut self,
message: ADBTransportMessage,
write_timeout: std::time::Duration,
) -> Result<()> {
todo!()
}
}

View File

@@ -30,18 +30,11 @@ pub mod constants {
}
}
#[cfg(feature = "rusb")]
mod adb_rsa_key;
// ###################################################
// rusb specific modules
#[cfg(feature = "rusb")]
mod adb_rusb_device;
#[cfg(feature = "rusb")]
mod adb_usb_device;
mod backends;
mod utils;
// Device implementations
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
@@ -51,10 +44,36 @@ pub use adb_rusb_device::ADBRusbDevice;
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
pub use backends::rusb_transport::RusbTransport;
// ###################################################
// ###################################################
// webusb specific modules
#[cfg(feature = "webusb")]
mod adb_webusb_device;
// Device implementations
#[cfg(feature = "webusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "webusb")))]
pub use adb_webusb_device::ADBWebUsbDevice;
// Transport implementations
#[cfg(feature = "webusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "webusb")))]
pub use backends::webusb_transport::WebUsbTransport;
// ###################################################
mod backends;
mod utils;
#[cfg(any(feature = "rusb", feature = "webusb"))]
mod adb_rsa_key;
#[cfg(any(feature = "rusb", feature = "webusb"))]
mod adb_usb_device;
// Utility functions
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
#[cfg(any(feature = "rusb", feature = "webusb"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "rusb", feature = "webusb"))))]
pub use utils::read_adb_private_key;
#[cfg(feature = "rusb")]

View File

@@ -4,7 +4,6 @@ use crate::{Result, RustADBError};
use std::fs::read_to_string;
use std::path::Path;
#[cfg(feature = "rusb")]
use crate::usb::adb_rsa_key::ADBRsaKey;
#[cfg(feature = "rusb")]
@@ -21,7 +20,6 @@ use crate::usb::constants::class_codes::{
///
/// Returns `Ok(None)` if the file doesn't exist, `Ok(Some(key))` if the key was successfully loaded,
/// or an error if there was a problem reading the file.
#[cfg(feature = "rusb")]
pub fn read_adb_private_key<P: AsRef<Path>>(private_key_path: P) -> Result<Option<ADBRsaKey>> {
// Try to read the private key file from given path
// If the file is not found, return None

View File

@@ -21,9 +21,7 @@ pub fn check_extension_is_apk<P: AsRef<Path>>(path: P) -> Result<()> {
}
pub fn get_default_adb_key_path() -> Result<PathBuf> {
homedir::my_home()
.ok()
.flatten()
std::env::home_dir()
.map(|home| home.join(".android").join("adbkey"))
.ok_or(RustADBError::NoHomeDirectory)
}