feat: rework usb backend
This commit is contained in:
@@ -11,7 +11,7 @@ rust-version.workspace = true
|
|||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
adb_client = { version = "^2.1.17" }
|
adb_client = { version = "^2.1.17", features = ["mdns", "rusb"] }
|
||||||
anyhow = { version = "1.0.100" }
|
anyhow = { version = "1.0.100" }
|
||||||
clap = { version = "4.5.49", features = ["derive"] }
|
clap = { version = "4.5.49", features = ["derive"] }
|
||||||
env_logger = { version = "0.11.8" }
|
env_logger = { version = "0.11.8" }
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
mdns = ["dep:mdns-sd"]
|
mdns = ["dep:mdns-sd"]
|
||||||
usb = ["dep:rsa", "dep:rusb"]
|
rusb = ["dep:rsa", "dep:rusb"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = { version = "0.22.1" }
|
base64 = { version = "0.22.1" }
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ adb_client = "*"
|
|||||||
| Feature | Description | Default? |
|
| Feature | Description | Default? |
|
||||||
| :-----: | :---------------------------------------------: | :------: |
|
| :-----: | :---------------------------------------------: | :------: |
|
||||||
| `mdns` | Enables mDNS device discovery on local network. | No |
|
| `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:
|
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::models::AdbStatResponse;
|
||||||
use crate::{RebootType, Result};
|
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 {
|
pub trait ADBDeviceExt {
|
||||||
/// Runs command in a shell on the device, and write its output and error streams into output.
|
/// 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<()>;
|
fn shell_command(&mut self, command: &[&str], output: &mut dyn Write) -> Result<()>;
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ pub enum RustADBError {
|
|||||||
#[error("Cannot get home directory")]
|
#[error("Cannot get home directory")]
|
||||||
NoHomeDirectory,
|
NoHomeDirectory,
|
||||||
/// Generic USB error
|
/// Generic USB error
|
||||||
#[cfg(feature = "usb")]
|
#[cfg(feature = "rusb")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "usb")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
|
||||||
#[error("USB Error: {0}")]
|
#[error("USB Error: {0}")]
|
||||||
UsbError(#[from] rusb::Error),
|
UsbError(#[from] rusb::Error),
|
||||||
/// USB device not found
|
/// USB device not found
|
||||||
@@ -90,8 +90,8 @@ pub enum RustADBError {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Base64EncodeError(#[from] base64::EncodeSliceError),
|
Base64EncodeError(#[from] base64::EncodeSliceError),
|
||||||
/// An error occurred with RSA engine
|
/// An error occurred with RSA engine
|
||||||
#[cfg(feature = "usb")]
|
#[cfg(feature = "rusb")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "usb")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
RSAError(#[from] rsa::errors::Error),
|
RSAError(#[from] rsa::errors::Error),
|
||||||
/// Cannot convert given data from slice
|
/// Cannot convert given data from slice
|
||||||
@@ -101,8 +101,8 @@ pub enum RustADBError {
|
|||||||
#[error("wrong file extension: {0}")]
|
#[error("wrong file extension: {0}")]
|
||||||
WrongFileExtension(String),
|
WrongFileExtension(String),
|
||||||
/// An error occurred with PKCS8 data
|
/// An error occurred with PKCS8 data
|
||||||
#[cfg(feature = "usb")]
|
#[cfg(feature = "rusb")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "usb")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
|
||||||
#[error("error with pkcs8: {0}")]
|
#[error("error with pkcs8: {0}")]
|
||||||
RsaPkcs8Error(#[from] rsa::pkcs8::Error),
|
RsaPkcs8Error(#[from] rsa::pkcs8::Error),
|
||||||
/// Error during certificate generation
|
/// Error during certificate generation
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
/// USB-related definitions
|
/// USB-related definitions
|
||||||
#[cfg(feature = "usb")]
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "usb")))]
|
|
||||||
pub mod usb;
|
pub mod usb;
|
||||||
|
|
||||||
/// Device reachable over TCP related definition
|
/// 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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TcpTransport {
|
pub struct TcpTransport {
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ use std::path::Path;
|
|||||||
let vendor_id = 0x04e8;
|
let vendor_id = 0x04e8;
|
||||||
let product_id = 0x6860;
|
let product_id = 0x6860;
|
||||||
let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find device");
|
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");
|
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::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::ADBDeviceExt;
|
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::adb_transport_message::ADBTransportMessage;
|
||||||
use crate::message_devices::message_commands::MessageCommand;
|
use crate::message_devices::message_commands::MessageCommand;
|
||||||
use crate::usb::adb_rsa_key::ADBRsaKey;
|
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;
|
use crate::utils::get_default_adb_key_path;
|
||||||
|
|
||||||
const AUTH_TOKEN: u32 = 1;
|
const AUTH_TOKEN: u32 = 1;
|
||||||
const AUTH_SIGNATURE: u32 = 2;
|
const AUTH_SIGNATURE: u32 = 2;
|
||||||
const AUTH_RSAPUBLICKEY: u32 = 3;
|
const AUTH_RSAPUBLICKEY: u32 = 3;
|
||||||
|
|
||||||
pub fn read_adb_private_key<P: AsRef<Path>>(private_key_path: P) -> Result<Option<ADBRsaKey>> {
|
/// Implement Android USB device
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represent a device reached and available over USB.
|
/// Represent a device reached and available over USB.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ADBUSBDevice {
|
pub struct ADBUSBDevice {
|
||||||
private_key: ADBRsaKey,
|
private_key: ADBRsaKey,
|
||||||
inner: ADBMessageDevice<USBTransport>,
|
inner: ADBMessageDevice<RusbTransport>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ADBUSBDevice {
|
impl ADBUSBDevice {
|
||||||
@@ -118,12 +40,15 @@ impl ADBUSBDevice {
|
|||||||
product_id: u16,
|
product_id: u16,
|
||||||
private_key_path: PathBuf,
|
private_key_path: PathBuf,
|
||||||
) -> Result<Self> {
|
) -> 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(
|
pub fn new_from_transport(
|
||||||
transport: USBTransport,
|
transport: RusbTransport,
|
||||||
private_key_path: Option<PathBuf>,
|
private_key_path: Option<PathBuf>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let private_key_path = match private_key_path {
|
let private_key_path = match private_key_path {
|
||||||
@@ -135,7 +60,7 @@ impl ADBUSBDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_from_transport_inner(
|
fn new_from_transport_inner(
|
||||||
transport: USBTransport,
|
transport: RusbTransport,
|
||||||
private_key_path: &PathBuf,
|
private_key_path: &PathBuf,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let private_key = if let Some(private_key) = read_adb_private_key(private_key_path)? {
|
let private_key = if let Some(private_key) = read_adb_private_key(private_key_path)? {
|
||||||
@@ -246,7 +171,7 @@ impl ADBUSBDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_transport_mut(&mut self) -> &mut USBTransport {
|
fn get_transport_mut(&mut self) -> &mut RusbTransport {
|
||||||
self.inner.get_transport_mut()
|
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},
|
adb_transport_message::{ADBTransportMessage, ADBTransportMessageHeader},
|
||||||
message_commands::MessageCommand,
|
message_commands::MessageCommand,
|
||||||
},
|
},
|
||||||
|
usb::constants::class_codes::ADB_SUBCLASS,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -22,18 +23,18 @@ struct Endpoint {
|
|||||||
max_packet_size: usize,
|
max_packet_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transport running on USB
|
/// Transport running on USB using `rusb` as a backend.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct USBTransport {
|
pub struct RusbTransport {
|
||||||
device: Device<GlobalContext>,
|
device: Device<GlobalContext>,
|
||||||
handle: Option<Arc<DeviceHandle<GlobalContext>>>,
|
handle: Option<Arc<DeviceHandle<GlobalContext>>>,
|
||||||
read_endpoint: Option<Endpoint>,
|
read_endpoint: Option<Endpoint>,
|
||||||
write_endpoint: Option<Endpoint>,
|
write_endpoint: Option<Endpoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl USBTransport {
|
impl RusbTransport {
|
||||||
/// Instantiate a new [`USBTransport`].
|
/// Instantiate a new [`RusbTransport`].
|
||||||
/// Only the first device with given `vendor_id` and `product_id` is returned.
|
/// Only the first device with given vendor_id and product_id is returned.
|
||||||
pub fn new(vendor_id: u16, product_id: u16) -> Result<Self> {
|
pub fn new(vendor_id: u16, product_id: u16) -> Result<Self> {
|
||||||
for device in rusb::devices()?.iter() {
|
for device in rusb::devices()?.iter() {
|
||||||
if let Ok(descriptor) = device.device_descriptor() {
|
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.
|
/// 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 {
|
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() {
|
for endpoint_desc in interface_desc.endpoint_descriptors() {
|
||||||
if endpoint_desc.transfer_type() == TransferType::Bulk
|
if endpoint_desc.transfer_type() == TransferType::Bulk
|
||||||
&& interface_desc.class_code() == LIBUSB_CLASS_VENDOR_SPEC
|
&& 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
|
&& interface_desc.protocol_code() == 0x01
|
||||||
{
|
{
|
||||||
let endpoint = Endpoint {
|
let endpoint = Endpoint {
|
||||||
@@ -163,7 +164,7 @@ impl USBTransport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ADBTransport for USBTransport {
|
impl ADBTransport for RusbTransport {
|
||||||
fn connect(&mut self) -> crate::Result<()> {
|
fn connect(&mut self) -> crate::Result<()> {
|
||||||
let device = self.device.open()?;
|
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(
|
fn write_message_with_timeout(
|
||||||
&mut self,
|
&mut self,
|
||||||
message: ADBTransportMessage,
|
message: ADBTransportMessage,
|
||||||
@@ -1,8 +1,59 @@
|
|||||||
#![doc = include_str!("./README.md")]
|
#![doc = include_str!("./README.md")]
|
||||||
|
|
||||||
mod adb_rsa_key;
|
/// Common USB constants for Android Debug Bridge
|
||||||
mod adb_usb_device;
|
pub mod constants {
|
||||||
mod usb_transport;
|
/// 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 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 server = ADBServer::default();
|
||||||
let mut device = server.get_device().expect("cannot get device");
|
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");
|
device.push(&mut input, "/data/local/tmp");
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user