feat: rework usb backend
This commit is contained in:
@@ -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" }
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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<()>;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -79,7 +79,7 @@ impl Write for CurrentConnection {
|
||||
}
|
||||
}
|
||||
|
||||
/// Transport running on USB
|
||||
/// Transport running on TCP
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TcpTransport {
|
||||
address: SocketAddr,
|
||||
|
||||
@@ -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");
|
||||
```
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
3
adb_client/src/message_devices/usb/backends/mod.rs
Normal file
3
adb_client/src/message_devices/usb/backends/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
#[cfg(feature = "rusb")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
|
||||
pub mod rusb_transport;
|
||||
@@ -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,
|
||||
@@ -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};
|
||||
|
||||
101
adb_client/src/message_devices/usb/utils.rs
Normal file
101
adb_client/src/message_devices/usb/utils.rs
Normal 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
|
||||
}
|
||||
@@ -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");
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user