feat: create an USBTransport from a rusb::Device (#66)

This allows to be more flexible on USB device we want to connect to,
without making public API to complex
This commit is contained in:
cli
2024-11-30 10:21:21 +01:00
committed by GitHub
parent 9eeb8f7da8
commit 2f4d13bd29
8 changed files with 70 additions and 44 deletions

View File

@@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4
- run: rustup component add clippy
- name: Run clippy
run : cargo clippy --all-features
run: cargo clippy --all-features
fmt:
name: "fmt"
@@ -21,7 +21,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Run formatter
run : cargo fmt --all --check
run: cargo fmt --all --check
doc:
name: "doc"
@@ -29,7 +29,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Run doc
run : cargo doc --all-features --keep-going
run: cargo doc --all-features --no-deps
env:
RUSTDOCFLAGS: "-D warnings"
tests:
name: "tests"
@@ -37,4 +39,4 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Run tests
run: cargo test --verbose
run: cargo test --verbose

View File

@@ -4,18 +4,18 @@ use std::path::Path;
use crate::models::AdbStatResponse;
use crate::{RebootType, Result};
/// Trait representing all features available on devices.
/// Trait representing all features available on both [`crate::ADBServerDevice`] and [`crate::ADBUSBDevice`]
pub trait ADBDeviceExt {
/// Run '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<S: ToString, W: Write>(
&mut self,
command: impl IntoIterator<Item = S>,
output: W,
) -> Result<()>;
/// Start an interactive shell session on the device.
/// Input data is read from `reader` and write to `writer`.
/// `W` has a 'static bound as it is internally used in a thread.
/// Starts an interactive shell session on the device.
/// Input data is read from reader and write to writer.
/// W has a 'static bound as it is internally used in a thread.
fn shell<R: Read, W: Write + Send + 'static>(&mut self, reader: R, writer: W) -> Result<()>;
/// Display the stat information for a remote file

View File

@@ -15,13 +15,6 @@ use crate::ADBMessageTransport;
use crate::ADBTransport;
use crate::{Result, RustADBError, USBTransport};
/// Represent a device reached and available over USB.
#[derive(Debug)]
pub struct ADBUSBDevice {
private_key: ADBRsaKey,
inner: ADBMessageDevice<USBTransport>,
}
pub fn read_adb_private_key<P: AsRef<Path>>(private_key_path: P) -> Result<Option<ADBRsaKey>> {
Ok(read_to_string(private_key_path.as_ref()).map(|pk| {
match ADBRsaKey::new_from_pkcs8(&pk) {
@@ -101,6 +94,13 @@ pub fn get_default_adb_key_path() -> Result<PathBuf> {
.ok_or(RustADBError::NoHomeDirectory)
}
/// Represent a device reached and available over USB.
#[derive(Debug)]
pub struct ADBUSBDevice {
private_key: ADBRsaKey,
inner: ADBMessageDevice<USBTransport>,
}
impl ADBUSBDevice {
/// Instantiate a new [`ADBUSBDevice`]
pub fn new(vendor_id: u16, product_id: u16) -> Result<Self> {
@@ -112,6 +112,26 @@ impl ADBUSBDevice {
vendor_id: u16,
product_id: u16,
private_key_path: PathBuf,
) -> Result<Self> {
Self::new_from_transport_inner(USBTransport::new(vendor_id, product_id)?, private_key_path)
}
/// Instantiate a new [`ADBUSBDevice`] from a [`USBTransport`] and an optional private key path.
pub fn new_from_transport(
transport: USBTransport,
private_key_path: Option<PathBuf>,
) -> Result<Self> {
let private_key_path = match private_key_path {
Some(private_key_path) => private_key_path,
None => get_default_adb_key_path()?,
};
Self::new_from_transport_inner(transport, private_key_path)
}
fn new_from_transport_inner(
transport: USBTransport,
private_key_path: PathBuf,
) -> Result<Self> {
let private_key = match read_adb_private_key(private_key_path)? {
Some(pk) => pk,
@@ -120,7 +140,7 @@ impl ADBUSBDevice {
let mut s = Self {
private_key,
inner: ADBMessageDevice::new(USBTransport::new(vendor_id, product_id)),
inner: ADBMessageDevice::new(transport),
};
s.connect()?;

View File

@@ -9,7 +9,7 @@ use std::{
};
impl ADBServerDevice {
/// Lists files in `path` on the device.
/// Lists files in path on the device.
pub fn list<A: AsRef<str>>(&mut self, path: A) -> Result<()> {
let serial = self.identifier.clone();
self.connect()?

View File

@@ -69,7 +69,7 @@ impl<R: Read> Read for ADBRecvCommandReader<R> {
}
impl ADBServerDevice {
/// Receives `path` to `stream` from the device.
/// Receives path to stream from the device.
pub fn pull<A: AsRef<str>>(&mut self, path: A, stream: &mut dyn Write) -> Result<()> {
let serial = self.identifier.clone();
self.connect()?

View File

@@ -42,7 +42,7 @@ impl<W: Write> Write for ADBSendCommandWriter<W> {
}
impl ADBServerDevice {
/// Send `stream` to `path` on the device.
/// Send stream to path on the device.
pub fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> {
log::info!("Sending data to {}", path.as_ref());
let serial = self.identifier.clone();

View File

@@ -41,7 +41,7 @@ impl ADBServerDevice {
}
}
/// Stat file given as `path` on the device.
/// Stat file given as path on the device.
pub fn stat<A: AsRef<str>>(&mut self, path: A) -> Result<AdbStatResponse> {
let serial = self.identifier.clone();
self.connect()?

View File

@@ -1,7 +1,8 @@
use std::{sync::Arc, time::Duration};
use rusb::{
constants::LIBUSB_CLASS_VENDOR_SPEC, DeviceHandle, Direction, GlobalContext, TransferType,
constants::LIBUSB_CLASS_VENDOR_SPEC, Device, DeviceHandle, Direction, GlobalContext,
TransferType,
};
use super::{ADBMessageTransport, ADBTransport};
@@ -19,18 +20,35 @@ struct Endpoint {
/// Transport running on USB
#[derive(Debug, Clone)]
pub struct USBTransport {
vendor_id: u16,
product_id: u16,
device: Device<GlobalContext>,
handle: Option<Arc<DeviceHandle<GlobalContext>>>,
}
impl USBTransport {
/// Instantiate a new [USBTransport]
pub fn new(vendor_id: u16, product_id: u16) -> Self {
/// Instantiate a new [`USBTransport`].
/// 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() {
if descriptor.vendor_id() == vendor_id && descriptor.product_id() == product_id {
return Ok(Self::new_from_device(device));
}
}
}
Err(RustADBError::DeviceNotFound(format!(
"cannot find USB device with vendor_id={} and product_id={}",
vendor_id, product_id
)))
}
/// Instantiate a new [`USBTransport`] 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 {
Self {
device: rusb_device,
handle: None,
vendor_id,
product_id,
}
}
@@ -112,22 +130,8 @@ impl USBTransport {
impl ADBTransport for USBTransport {
fn connect(&mut self) -> crate::Result<()> {
for d in rusb::devices()?.iter() {
if let Ok(descriptor) = d.device_descriptor() {
if descriptor.vendor_id() == self.vendor_id
&& descriptor.product_id() == self.product_id
{
self.handle = Some(Arc::new(d.open()?));
return Ok(());
}
}
}
Err(RustADBError::DeviceNotFound(format!(
"Cannot find device with vendor id {} and product id {}",
self.vendor_id, self.product_id
)))
self.handle = Some(Arc::new(self.device.open()?));
Ok(())
}
fn disconnect(&mut self) -> crate::Result<()> {