feat: rework usb backend

This commit is contained in:
Corentin LIAUD
2025-09-21 10:25:19 +02:00
parent 2eed32649c
commit b916bafa5a
14 changed files with 198 additions and 116 deletions

View File

@@ -11,7 +11,7 @@ rust-version.workspace = true
version.workspace = true
[dependencies]
adb_client = { version = "^2.1.17" }
adb_client = { version = "^2.1.17", features = ["mdns", "rusb"] }
anyhow = { version = "1.0.100" }
clap = { version = "4.5.49", features = ["derive"] }
env_logger = { version = "0.11.8" }

View File

@@ -17,7 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = []
mdns = ["dep:mdns-sd"]
usb = ["dep:rsa", "dep:rusb"]
rusb = ["dep:rsa", "dep:rusb"]
[dependencies]
base64 = { version = "0.22.1" }

View File

@@ -21,7 +21,7 @@ adb_client = "*"
| Feature | Description | Default? |
| :-----: | :---------------------------------------------: | :------: |
| `mdns` | Enables mDNS device discovery on local network. | No |
| `usb` | Enables interactions with USB devices. | No |
| `rusb` | Enables interactions with USB devices. | No |
To deactivate some features you can use the `default-features = false` option in your `Cargo.toml` file and manually specify the features you want to activate:

View File

@@ -6,7 +6,10 @@ use image::{ImageBuffer, ImageFormat, Rgba};
use crate::models::AdbStatResponse;
use crate::{RebootType, Result};
/// Trait representing all features available on both [`crate::server_device::ADBServerDevice`] and [`crate::usb::ADBUSBDevice`]
/// Trait representing all features available on an ADB device, currently used by:
/// - [`crate::server_device::ADBServerDevice`]
/// - [`crate::usb::ADBUSBDevice`]
/// - [`crate::tcp::ADBTcpDevice`]
pub trait ADBDeviceExt {
/// Runs command in a shell on the device, and write its output and error streams into output.
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()>;

View File

@@ -70,8 +70,8 @@ pub enum RustADBError {
#[error("Cannot get home directory")]
NoHomeDirectory,
/// Generic USB error
#[cfg(feature = "usb")]
#[cfg_attr(docsrs, doc(cfg(feature = "usb")))]
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
#[error("USB Error: {0}")]
UsbError(#[from] rusb::Error),
/// USB device not found
@@ -90,8 +90,8 @@ pub enum RustADBError {
#[error(transparent)]
Base64EncodeError(#[from] base64::EncodeSliceError),
/// An error occurred with RSA engine
#[cfg(feature = "usb")]
#[cfg_attr(docsrs, doc(cfg(feature = "usb")))]
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
#[error(transparent)]
RSAError(#[from] rsa::errors::Error),
/// Cannot convert given data from slice
@@ -101,8 +101,8 @@ pub enum RustADBError {
#[error("wrong file extension: {0}")]
WrongFileExtension(String),
/// An error occurred with PKCS8 data
#[cfg(feature = "usb")]
#[cfg_attr(docsrs, doc(cfg(feature = "usb")))]
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
#[error("error with pkcs8: {0}")]
RsaPkcs8Error(#[from] rsa::pkcs8::Error),
/// Error during certificate generation

View File

@@ -1,6 +1,4 @@
/// USB-related definitions
#[cfg(feature = "usb")]
#[cfg_attr(docsrs, doc(cfg(feature = "usb")))]
pub mod usb;
/// Device reachable over TCP related definition

View File

@@ -79,7 +79,7 @@ impl Write for CurrentConnection {
}
}
/// Transport running on USB
/// Transport running on TCP
#[derive(Clone, Debug)]
pub struct TcpTransport {
address: SocketAddr,

View File

@@ -21,6 +21,6 @@ use std::path::Path;
let vendor_id = 0x04e8;
let product_id = 0x6860;
let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find device");
let mut input = File::open(Path::new("/tmp/file.txt")).expect("Cannot open file");
let mut input = File::open("/tmp/file.txt").expect("Cannot open file");
device.push(&mut input, &"/data/local/tmp");
```

View File

@@ -1,12 +1,6 @@
use rusb::Device;
use rusb::DeviceDescriptor;
use rusb::UsbContext;
use rusb::constants::LIBUSB_CLASS_VENDOR_SPEC;
use std::fs::read_to_string;
use std::io::Read;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::ADBDeviceExt;
@@ -18,92 +12,20 @@ use crate::message_devices::adb_message_transport::ADBMessageTransport;
use crate::message_devices::adb_transport_message::ADBTransportMessage;
use crate::message_devices::message_commands::MessageCommand;
use crate::usb::adb_rsa_key::ADBRsaKey;
use crate::usb::usb_transport::USBTransport;
use crate::usb::backends::rusb_transport::RusbTransport;
use crate::usb::{read_adb_private_key, search_adb_devices};
use crate::utils::get_default_adb_key_path;
const AUTH_TOKEN: u32 = 1;
const AUTH_SIGNATURE: u32 = 2;
const AUTH_RSAPUBLICKEY: u32 = 3;
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
// If there is another error while reading the file, return this error
// Else, return the private key content
let pk = match read_to_string(private_key_path.as_ref()) {
Ok(pk) => pk,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(e.into()),
};
match ADBRsaKey::new_from_pkcs8(&pk) {
Ok(pk) => Ok(Some(pk)),
Err(e) => Err(e),
}
}
/// Search for adb devices with known interface class and subclass values
pub fn search_adb_devices() -> Result<Option<(u16, u16)>> {
let mut found_devices = vec![];
for device in rusb::devices()?.iter() {
let Ok(des) = device.device_descriptor() else {
continue;
};
if is_adb_device(&device, &des) {
log::debug!(
"Autodetect device {:04x}:{:04x}",
des.vendor_id(),
des.product_id()
);
found_devices.push((des.vendor_id(), des.product_id()));
}
}
match (found_devices.first(), found_devices.get(1)) {
(None, _) => Ok(None),
(Some(identifiers), None) => Ok(Some(*identifiers)),
(Some((vid1, pid1)), Some((vid2, pid2))) => Err(RustADBError::DeviceNotFound(format!(
"Found two Android devices {vid1:04x}:{pid1:04x} and {vid2:04x}:{pid2:04x}",
))),
}
}
/// Check whether a device with given descriptor is an ADB device
pub fn is_adb_device<T: UsbContext>(device: &Device<T>, des: &DeviceDescriptor) -> bool {
const ADB_SUBCLASS: u8 = 0x42;
const ADB_PROTOCOL: u8 = 0x1;
// Some devices require choosing the file transfer mode
// for usb debugging to take effect.
const BULK_CLASS: u8 = 0xdc;
const BULK_ADB_SUBCLASS: u8 = 2;
for n in 0..des.num_configurations() {
let Ok(config_des) = device.config_descriptor(n) else {
continue;
};
for interface in config_des.interfaces() {
for interface_des in interface.descriptors() {
let proto = interface_des.protocol_code();
let class = interface_des.class_code();
let subcl = interface_des.sub_class_code();
if proto == ADB_PROTOCOL
&& ((class == LIBUSB_CLASS_VENDOR_SPEC && subcl == ADB_SUBCLASS)
|| (class == BULK_CLASS && subcl == BULK_ADB_SUBCLASS))
{
return true;
}
}
}
}
false
}
/// Implement Android USB device
/// Represent a device reached and available over USB.
#[derive(Debug)]
pub struct ADBUSBDevice {
private_key: ADBRsaKey,
inner: ADBMessageDevice<USBTransport>,
inner: ADBMessageDevice<RusbTransport>,
}
impl ADBUSBDevice {
@@ -118,12 +40,15 @@ impl ADBUSBDevice {
product_id: u16,
private_key_path: PathBuf,
) -> Result<Self> {
Self::new_from_transport_inner(USBTransport::new(vendor_id, product_id)?, &private_key_path)
Self::new_from_transport_inner(
RusbTransport::new(vendor_id, product_id)?,
&private_key_path,
)
}
/// Instantiate a new [`ADBUSBDevice`] from a [`USBTransport`] and an optional private key path.
/// Instantiate a new [`ADBUSBDevice`] from a [`RusbTransport`] and an optional private key path.
pub fn new_from_transport(
transport: USBTransport,
transport: RusbTransport,
private_key_path: Option<PathBuf>,
) -> Result<Self> {
let private_key_path = match private_key_path {
@@ -135,7 +60,7 @@ impl ADBUSBDevice {
}
fn new_from_transport_inner(
transport: USBTransport,
transport: RusbTransport,
private_key_path: &PathBuf,
) -> Result<Self> {
let private_key = if let Some(private_key) = read_adb_private_key(private_key_path)? {
@@ -246,7 +171,7 @@ impl ADBUSBDevice {
}
#[inline]
fn get_transport_mut(&mut self) -> &mut USBTransport {
fn get_transport_mut(&mut self) -> &mut RusbTransport {
self.inner.get_transport_mut()
}
}

View File

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

View File

@@ -13,6 +13,7 @@ use crate::{
adb_transport_message::{ADBTransportMessage, ADBTransportMessageHeader},
message_commands::MessageCommand,
},
usb::constants::class_codes::ADB_SUBCLASS,
};
#[derive(Clone, Debug)]
@@ -22,18 +23,18 @@ struct Endpoint {
max_packet_size: usize,
}
/// Transport running on USB
/// Transport running on USB using `rusb` as a backend.
#[derive(Debug, Clone)]
pub struct USBTransport {
pub struct RusbTransport {
device: Device<GlobalContext>,
handle: Option<Arc<DeviceHandle<GlobalContext>>>,
read_endpoint: Option<Endpoint>,
write_endpoint: Option<Endpoint>,
}
impl USBTransport {
/// Instantiate a new [`USBTransport`].
/// Only the first device with given `vendor_id` and `product_id` is returned.
impl RusbTransport {
/// Instantiate a new [`RusbTransport`].
/// Only the first device with given vendor_id and product_id is returned.
pub fn new(vendor_id: u16, product_id: u16) -> Result<Self> {
for device in rusb::devices()?.iter() {
if let Ok(descriptor) = device.device_descriptor() {
@@ -48,7 +49,7 @@ impl USBTransport {
)))
}
/// Instantiate a new [`USBTransport`] from a [`rusb::Device`].
/// Instantiate a new [`RusbTransport`] from a [`rusb::Device`].
///
/// Devices can be enumerated using [`rusb::devices()`] and then filtered out to get desired device.
pub fn new_from_device(rusb_device: rusb::Device<GlobalContext>) -> Self {
@@ -108,7 +109,7 @@ impl USBTransport {
for endpoint_desc in interface_desc.endpoint_descriptors() {
if endpoint_desc.transfer_type() == TransferType::Bulk
&& interface_desc.class_code() == LIBUSB_CLASS_VENDOR_SPEC
&& interface_desc.sub_class_code() == 0x42
&& interface_desc.sub_class_code() == ADB_SUBCLASS
&& interface_desc.protocol_code() == 0x01
{
let endpoint = Endpoint {
@@ -163,7 +164,7 @@ impl USBTransport {
}
}
impl ADBTransport for USBTransport {
impl ADBTransport for RusbTransport {
fn connect(&mut self) -> crate::Result<()> {
let device = self.device.open()?;
@@ -202,7 +203,7 @@ impl ADBTransport for USBTransport {
}
}
impl ADBMessageTransport for USBTransport {
impl ADBMessageTransport for RusbTransport {
fn write_message_with_timeout(
&mut self,
message: ADBTransportMessage,

View File

@@ -1,8 +1,59 @@
#![doc = include_str!("./README.md")]
mod adb_rsa_key;
mod adb_usb_device;
mod usb_transport;
/// Common USB constants for Android Debug Bridge
pub mod constants {
/// Standard Android vendor ID
pub const ANDROID_VENDOR_ID: u16 = 0x18d1;
/// Common ADB product IDs
pub mod product_ids {
/// ADB interface
pub const ADB: u16 = 0x4ee7;
/// ADB + MTP
pub const ADB_MTP: u16 = 0x4ee2;
/// ADB + RNDIS
pub const ADB_RNDIS: u16 = 0x4ee4;
/// Fastboot interface
pub const FASTBOOT: u16 = 0x4ee0;
}
/// USB class codes for ADB detection
pub mod class_codes {
/// ADB subclass code
pub const ADB_SUBCLASS: u8 = 0x42;
/// ADB protocol code
pub const ADB_PROTOCOL: u8 = 0x1;
/// Bulk transfer class
pub const BULK_CLASS: u8 = 0xdc;
/// Bulk ADB subclass
pub const BULK_ADB_SUBCLASS: u8 = 2;
}
}
#[cfg(feature = "rusb")]
mod adb_rsa_key;
#[cfg(feature = "rusb")]
mod adb_usb_device;
mod backends;
mod utils;
// Device implementations
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
pub use adb_usb_device::ADBUSBDevice;
pub use usb_transport::USBTransport;
// Transport implementations
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
pub use backends::rusb_transport::RusbTransport;
// Utility functions
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
pub use utils::read_adb_private_key;
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
pub use utils::{is_adb_device, search_adb_devices};

View File

@@ -0,0 +1,101 @@
//! USB utilities that are independent of specific transport implementations
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")]
use rusb::{Device, DeviceDescriptor, UsbContext};
#[cfg(feature = "rusb")]
use rusb::constants::LIBUSB_CLASS_VENDOR_SPEC;
use crate::usb::constants::class_codes::{
ADB_PROTOCOL, ADB_SUBCLASS, BULK_ADB_SUBCLASS, BULK_CLASS,
};
/// Read an ADB private key from a file path
///
/// 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
// If there is another error while reading the file, return this error
// Else, return the private key content
let pk = match read_to_string(private_key_path.as_ref()) {
Ok(pk) => pk,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(e.into()),
};
match ADBRsaKey::new_from_pkcs8(&pk) {
Ok(pk) => Ok(Some(pk)),
Err(e) => Err(e),
}
}
/// Search for ADB devices connected via USB
///
/// Returns the vendor_id and product_id of the first ADB device found,
/// or `None` if no devices are found.
#[cfg(feature = "rusb")]
pub fn search_adb_devices() -> Result<Option<(u16, u16)>> {
let mut found_devices = vec![];
for device in rusb::devices()?.iter() {
let Ok(des) = device.device_descriptor() else {
continue;
};
if is_adb_device(&device, &des) {
log::debug!(
"Autodetect device {:04x}:{:04x}",
des.vendor_id(),
des.product_id()
);
found_devices.push((des.vendor_id(), des.product_id()));
}
}
match (found_devices.first(), found_devices.get(1)) {
(None, _) => Ok(None),
(Some(identifiers), None) => Ok(Some(*identifiers)),
(Some((vid1, pid1)), Some((vid2, pid2))) => Err(RustADBError::DeviceNotFound(format!(
"Found two Android devices {vid1:04x}:{pid1:04x} and {vid2:04x}:{pid2:04x}",
))),
}
}
/// Check if a USB device is an ADB device
///
/// This function inspects the device descriptor and configuration to determine
/// if it's an Android Debug Bridge device.
#[cfg(feature = "rusb")]
pub fn is_adb_device<T: UsbContext>(device: &Device<T>, des: &DeviceDescriptor) -> bool {
// Some devices require choosing the file transfer mode
// for usb debugging to take effect.
for n in 0..des.num_configurations() {
let Ok(config_des) = device.config_descriptor(n) else {
continue;
};
for interface in config_des.interfaces() {
for interface_des in interface.descriptors() {
let proto = interface_des.protocol_code();
let class = interface_des.class_code();
let subcl = interface_des.sub_class_code();
if proto == ADB_PROTOCOL
&& ((class == LIBUSB_CLASS_VENDOR_SPEC && subcl == ADB_SUBCLASS)
|| (class == BULK_CLASS && subcl == BULK_ADB_SUBCLASS))
{
return true;
}
}
}
}
false
}

View File

@@ -20,6 +20,6 @@ use std::path::Path;
let mut server = ADBServer::default();
let mut device = server.get_device().expect("cannot get device");
let mut input = File::open(Path::new("/tmp/file.txt")).expect("Cannot open file");
let mut input = File::open("/tmp/file.txt").expect("Cannot open file");
device.push(&mut input, "/data/local/tmp");
```