Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion adb_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ rust-version.workspace = true
version.workspace = true

[dependencies]
adb_client = { version = "^2.0.0", features = ["mdns", "usb"] }
adb_client = { version = "^2.0.0", features = ["mdns", "rusb"] }
anyhow = { version = "1.0.94" }
clap = { version = "4.5.23", features = ["derive"] }
env_logger = { version = "0.11.5" }
Expand Down
2 changes: 1 addition & 1 deletion adb_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
2 changes: 1 addition & 1 deletion adb_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
5 changes: 4 additions & 1 deletion adb_client/src/adb_device_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()>;
Expand Down
12 changes: 6 additions & 6 deletions adb_client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,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
Expand All @@ -87,8 +87,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
Expand All @@ -98,8 +98,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
Expand Down
2 changes: 0 additions & 2 deletions adb_client/src/message_devices/mod.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion adb_client/src/message_devices/tcp/tcp_transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl Write for CurrentConnection {
}
}

/// Transport running on USB
/// Transport running on TCP
#[derive(Clone, Debug)]
pub struct TcpTransport {
address: SocketAddr,
Expand Down
2 changes: 1 addition & 1 deletion adb_client/src/message_devices/usb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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");
```
98 changes: 10 additions & 88 deletions adb_client/src/message_devices/usb/adb_usb_device.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -118,12 +40,12 @@ 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 {
Expand All @@ -135,7 +57,7 @@ impl ADBUSBDevice {
}

fn new_from_transport_inner(
transport: USBTransport,
transport: RusbTransport,
private_key_path: PathBuf,
) -> Result<Self> {
let private_key = match read_adb_private_key(&private_key_path)? {
Expand Down Expand Up @@ -247,7 +169,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()
}
}
Expand Down
3 changes: 3 additions & 0 deletions adb_client/src/message_devices/usb/backends/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#[cfg(feature = "rusb")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusb")))]
pub mod rusb_transport;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
adb_transport_message::{ADBTransportMessage, ADBTransportMessageHeader},
message_commands::MessageCommand,
},
usb::constants::class_codes::ADB_SUBCLASS,
};

#[derive(Clone, Debug)]
Expand All @@ -22,17 +23,17 @@ 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`].
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() {
Expand All @@ -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 {
Expand Down Expand Up @@ -109,7 +110,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 {
Expand Down Expand Up @@ -166,7 +167,7 @@ impl USBTransport {
}
}

impl ADBTransport for USBTransport {
impl ADBTransport for RusbTransport {
fn connect(&mut self) -> crate::Result<()> {
let device = self.device.open()?;

Expand Down Expand Up @@ -205,7 +206,7 @@ impl ADBTransport for USBTransport {
}
}

impl ADBMessageTransport for USBTransport {
impl ADBMessageTransport for RusbTransport {
fn write_message_with_timeout(
&mut self,
message: ADBTransportMessage,
Expand Down
55 changes: 53 additions & 2 deletions adb_client/src/message_devices/usb/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,59 @@
#![doc = include_str!("./README.md")]

/// 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 usb_transport;

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};
Loading
Loading