Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb23fd6155 | ||
|
|
d932e93d0b | ||
|
|
e60ae7434b | ||
|
|
005d864609 | ||
|
|
4d8e5d9367 | ||
|
|
cff0e68f46 |
2
.github/workflows/rust-build.yml
vendored
2
.github/workflows/rust-build.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Rust - Build
|
||||
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
2
.github/workflows/rust-quality.yml
vendored
2
.github/workflows/rust-quality.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Rust - Quality
|
||||
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
50
.github/workflows/rust-release.yml
vendored
Normal file
50
.github/workflows/rust-release.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Rust - Release creation
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: "Checkout repository"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Set up Rust"
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y rpm
|
||||
cargo install cargo-deb
|
||||
cargo install cargo-generate-rpm
|
||||
|
||||
- name: "build-release"
|
||||
run: cargo build --release
|
||||
|
||||
- name: "Build DEB package"
|
||||
run: cargo deb -p adb_cli
|
||||
|
||||
- name: "Build RPM package"
|
||||
run: cargo generate-rpm -p adb_cli
|
||||
|
||||
- name: "Publish GitHub artefacts"
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
target/debian/*.deb
|
||||
target/generate-rpm/*.rpm
|
||||
target/release/adb_cli
|
||||
|
||||
- name: "Publish crates"
|
||||
run: |
|
||||
cargo publish -p adb_client --token ${CRATES_IO_TOKEN}
|
||||
cargo publish -p adb_cli --token ${CRATES_IO_TOKEN}
|
||||
env:
|
||||
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||
@@ -3,11 +3,13 @@ members = ["adb_cli", "adb_client"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Corentin LIAUD"]
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/cocool97/adb_client"
|
||||
keywords = ["adb", "android", "tcp", "usb"]
|
||||
license = "MIT"
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/cocool97/adb_client"
|
||||
version = "2.0.0"
|
||||
version = "2.0.2"
|
||||
|
||||
# To build locally when working on a new release
|
||||
[patch.crates-io]
|
||||
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) [year] [fullname]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,15 +1,16 @@
|
||||
[package]
|
||||
authors.workspace = true
|
||||
description = "Rust ADB (Android Debug Bridge) CLI"
|
||||
edition.workspace = true
|
||||
keywords.workspace = true
|
||||
license.workspace = true
|
||||
license-file.workspace = true
|
||||
name = "adb_cli"
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
adb_client = { version = "2.0.0" }
|
||||
adb_client = { version = "2.0.1" }
|
||||
anyhow = { version = "1.0.89" }
|
||||
clap = { version = "4.5.18", features = ["derive"] }
|
||||
env_logger = { version = "0.11.5" }
|
||||
@@ -17,3 +18,22 @@ log = { version = "0.4.22" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
termios = { version = "0.3.3" }
|
||||
|
||||
#####################################
|
||||
# Debian package build instructions #
|
||||
#####################################
|
||||
[package.metadata.deb]
|
||||
assets = [
|
||||
{ source = "target/release/adb_cli", dest = "usr/bin/", mode = "755" },
|
||||
]
|
||||
priority = "optional"
|
||||
section = "utility"
|
||||
|
||||
##################################
|
||||
# RPM package build instructions #
|
||||
##################################
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [
|
||||
{ source = "target/release/adb_cli", dest = "/usr/bin/adb_cli", mode = "755" },
|
||||
]
|
||||
license = "MIT"
|
||||
|
||||
@@ -13,10 +13,10 @@ fn parse_hex_id(id: &str) -> Result<u16, ParseIntError> {
|
||||
pub struct UsbCommand {
|
||||
/// Hexadecimal vendor id of this USB device
|
||||
#[clap(short = 'v', long = "vendor-id", value_parser=parse_hex_id, value_name="VID")]
|
||||
pub vendor_id: u16,
|
||||
pub vendor_id: Option<u16>,
|
||||
/// Hexadecimal product id of this USB device
|
||||
#[clap(short = 'p', long = "product-id", value_parser=parse_hex_id, value_name="PID")]
|
||||
pub product_id: u16,
|
||||
pub product_id: Option<u16>,
|
||||
/// Path to a custom private key to use for authentication
|
||||
#[clap(short = 'k', long = "private-key")]
|
||||
pub path_to_private_key: Option<PathBuf>,
|
||||
|
||||
@@ -158,11 +158,20 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
Command::Usb(usb) => {
|
||||
let mut device = match usb.path_to_private_key {
|
||||
Some(pk) => {
|
||||
ADBUSBDevice::new_with_custom_private_key(usb.vendor_id, usb.product_id, pk)?
|
||||
let mut device = match (usb.vendor_id, usb.product_id) {
|
||||
(Some(vid), Some(pid)) => match usb.path_to_private_key {
|
||||
Some(pk) => ADBUSBDevice::new_with_custom_private_key(vid, pid, pk)?,
|
||||
None => ADBUSBDevice::new(vid, pid)?,
|
||||
},
|
||||
|
||||
(None, None) => match usb.path_to_private_key {
|
||||
Some(pk) => ADBUSBDevice::autodetect_with_custom_private_key(pk)?,
|
||||
None => ADBUSBDevice::autodetect()?,
|
||||
},
|
||||
|
||||
_ => {
|
||||
anyhow::bail!("please either supply values for both the --vendor-id and --product-id flags or none.");
|
||||
}
|
||||
None => ADBUSBDevice::new(usb.vendor_id, usb.product_id)?,
|
||||
};
|
||||
|
||||
match usb.commands {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[package]
|
||||
authors.workspace = true
|
||||
description = "Rust ADB (Android Debug Bridge) client library"
|
||||
edition.workspace = true
|
||||
keywords.workspace = true
|
||||
license.workspace = true
|
||||
license-file.workspace = true
|
||||
name = "adb_client"
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
|
||||
@@ -22,6 +22,8 @@ pub(crate) enum AdbServerCommand {
|
||||
FrameBuffer,
|
||||
Sync,
|
||||
Reboot(RebootType),
|
||||
Forward(String, String, String),
|
||||
Reverse(String, String),
|
||||
}
|
||||
|
||||
impl Display for AdbServerCommand {
|
||||
@@ -53,6 +55,12 @@ impl Display for AdbServerCommand {
|
||||
write!(f, "host:pair:{code}:{addr}")
|
||||
}
|
||||
AdbServerCommand::FrameBuffer => write!(f, "framebuffer:"),
|
||||
AdbServerCommand::Forward(serial, remote, local) => {
|
||||
write!(f, "host-serial:{serial}:forward:{local};{remote}")
|
||||
}
|
||||
AdbServerCommand::Reverse(remote, local) => {
|
||||
write!(f, "reverse:forward:{remote};{local}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
adb_client/src/server/device_commands/forward.rs
Normal file
14
adb_client/src/server/device_commands/forward.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use crate::{models::AdbServerCommand, ADBServerDevice, Result};
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Forward socket connection
|
||||
pub fn forward(&mut self, remote: String, local: String) -> Result<()> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial.clone()))?;
|
||||
|
||||
self.get_transport_mut()
|
||||
.proxy_connection(AdbServerCommand::Forward(serial, remote, local), false)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
mod forward;
|
||||
mod framebuffer;
|
||||
mod host_features;
|
||||
mod list;
|
||||
mod logcat;
|
||||
mod reboot;
|
||||
mod recv;
|
||||
mod reverse;
|
||||
mod send;
|
||||
mod stat;
|
||||
mod transport;
|
||||
|
||||
14
adb_client/src/server/device_commands/reverse.rs
Normal file
14
adb_client/src/server/device_commands/reverse.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use crate::{models::AdbServerCommand, ADBServerDevice, Result};
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Reverse socket connection
|
||||
pub fn reverse(&mut self, remote: String, local: String) -> Result<()> {
|
||||
let serial = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
self.get_transport_mut()
|
||||
.proxy_connection(AdbServerCommand::Reverse(remote, local), false)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
use byteorder::ReadBytesExt;
|
||||
use rand::Rng;
|
||||
use rusb::Device;
|
||||
use rusb::DeviceDescriptor;
|
||||
use rusb::UsbContext;
|
||||
use std::fs::read_to_string;
|
||||
use std::io::Cursor;
|
||||
use std::io::Read;
|
||||
@@ -37,29 +40,77 @@ fn read_adb_private_key<P: AsRef<Path>>(private_key_path: P) -> Result<Option<AD
|
||||
}
|
||||
})
|
||||
}
|
||||
/// Search for adb devices with known interface class and subclass values
|
||||
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.get(0), 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 {:04x}:{:04x} and {:04x}:{:04x}",
|
||||
vid1, pid1, vid2, pid2
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_adb_device<T: UsbContext>(device: &Device<T>, des: &DeviceDescriptor) -> bool {
|
||||
const ADB_CLASS: u8 = 0xff;
|
||||
|
||||
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 == ADB_CLASS && subcl == ADB_SUBCLASS)
|
||||
|| (class == BULK_CLASS && subcl == BULK_ADB_SUBCLASS))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_default_adb_key_path() -> Result<PathBuf> {
|
||||
homedir::my_home()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|home| home.join(".android").join("adbkey"))
|
||||
.ok_or(RustADBError::NoHomeDirectory)
|
||||
}
|
||||
|
||||
impl ADBUSBDevice {
|
||||
/// Instantiate a new [ADBUSBDevice]
|
||||
pub fn new(vendor_id: u16, product_id: u16) -> Result<Self> {
|
||||
let private_key_path = homedir::my_home()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|home| home.join(".android").join("adbkey"))
|
||||
.ok_or(RustADBError::NoHomeDirectory)?;
|
||||
|
||||
let private_key = match read_adb_private_key(private_key_path)? {
|
||||
Some(pk) => pk,
|
||||
None => ADBRsaKey::random_with_size(2048)?,
|
||||
};
|
||||
|
||||
let mut s = Self {
|
||||
private_key,
|
||||
transport: USBTransport::new(vendor_id, product_id),
|
||||
};
|
||||
|
||||
s.connect()?;
|
||||
|
||||
Ok(s)
|
||||
Self::new_with_custom_private_key(vendor_id, product_id, get_default_adb_key_path()?)
|
||||
}
|
||||
|
||||
/// Instantiate a new [ADBUSBDevice] using a custom private key path
|
||||
@@ -83,6 +134,23 @@ impl ADBUSBDevice {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// autodetect connected ADB devices and establish a connection with the first device found
|
||||
pub fn autodetect() -> Result<Self> {
|
||||
Self::autodetect_with_custom_private_key(get_default_adb_key_path()?)
|
||||
}
|
||||
|
||||
/// autodetect connected ADB devices and establish a connection with the first device found using a custom private key path
|
||||
pub fn autodetect_with_custom_private_key(private_key_path: PathBuf) -> Result<Self> {
|
||||
match search_adb_devices()? {
|
||||
Some((vendor_id, product_id)) => {
|
||||
ADBUSBDevice::new_with_custom_private_key(vendor_id, product_id, private_key_path)
|
||||
}
|
||||
_ => Err(RustADBError::DeviceNotFound(
|
||||
"cannot find USB devices matching the signature of an ADB device".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Send initial connect
|
||||
pub fn connect(&mut self) -> Result<()> {
|
||||
self.transport.connect()?;
|
||||
|
||||
Reference in New Issue
Block a user