Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
152836fe54 | ||
|
|
507796887b | ||
|
|
2f08e5e16d | ||
|
|
6a2d652f60 | ||
|
|
b7ae0b1155 | ||
|
|
ec0ae681ac | ||
|
|
61ba07ecf0 | ||
|
|
c835f20263 | ||
|
|
b51965f5af | ||
|
|
66b0e4c71c | ||
|
|
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
|
||||
|
||||
4
.github/workflows/rust-quality.yml
vendored
4
.github/workflows/rust-quality.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Rust - Quality
|
||||
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- run: rustup component add clippy
|
||||
- name: Run clippy
|
||||
run : cargo clippy
|
||||
run : cargo clippy --all-features
|
||||
|
||||
fmt:
|
||||
name: "fmt"
|
||||
|
||||
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"
|
||||
repository = "https://github.com/cocool97/adb_client"
|
||||
version = "2.0.0"
|
||||
version = "2.0.5"
|
||||
|
||||
# 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) 2023-2024 Corentin LIAUD
|
||||
|
||||
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.
|
||||
@@ -11,6 +11,9 @@
|
||||
<a href="https://deps.rs/repo/github/cocool97/adb_client">
|
||||
<img alt="dependency status" src="https://deps.rs/repo/github/cocool97/adb_client/status.svg"/>
|
||||
</a>
|
||||
<a href="https://opensource.org/licenses/MIT">
|
||||
<img alt="dependency status" src="https://img.shields.io/badge/License-MIT-yellow.svg"/>
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[package]
|
||||
authors.workspace = true
|
||||
description = "Rust ADB (Android Debug Bridge) CLI"
|
||||
edition.workspace = true
|
||||
keywords.workspace = true
|
||||
@@ -9,7 +10,7 @@ repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
adb_client = { version = "2.0.0" }
|
||||
adb_client = { version = "2.0.5" }
|
||||
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"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::models::RebootTypeCommand;
|
||||
|
||||
@@ -16,6 +17,15 @@ pub enum LocalCommand {
|
||||
Stat { path: String },
|
||||
/// Spawn an interactive shell or run a list of commands on the device
|
||||
Shell { commands: Vec<String> },
|
||||
/// Run an activity on device specified by the intent
|
||||
Run {
|
||||
/// The package whose activity is to be invoked
|
||||
#[clap(short = 'p', long = "package")]
|
||||
package: String,
|
||||
/// The activity to be invoked itself, Usually it is MainActivity
|
||||
#[clap(short = 'a', long = "activity")]
|
||||
activity: String,
|
||||
},
|
||||
/// Reboot the device
|
||||
Reboot {
|
||||
#[clap(subcommand)]
|
||||
@@ -28,4 +38,9 @@ pub enum LocalCommand {
|
||||
/// Path to output file (created if not exists)
|
||||
path: Option<String>,
|
||||
},
|
||||
/// Install an APK on device
|
||||
Install {
|
||||
/// Path to APK file. Extension must be ".apk"
|
||||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
@@ -34,9 +34,23 @@ pub enum UsbCommands {
|
||||
Push { filename: String, path: String },
|
||||
/// Stat a file on device
|
||||
Stat { path: String },
|
||||
/// Run an activity on device specified by the intent
|
||||
Run {
|
||||
/// The package whose activity is to be invoked
|
||||
#[clap(short = 'p', long = "package")]
|
||||
package: String,
|
||||
/// The activity to be invoked itself, Usually it is MainActivity
|
||||
#[clap(short = 'a', long = "activity")]
|
||||
activity: String,
|
||||
},
|
||||
/// Reboot the device
|
||||
Reboot {
|
||||
#[clap(subcommand)]
|
||||
reboot_type: RebootTypeCommand,
|
||||
},
|
||||
/// Install an APK on device
|
||||
Install {
|
||||
/// Path to APK file. Extension must be ".apk"
|
||||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ fn main() -> Result<()> {
|
||||
device.shell_command(commands, std::io::stdout())?;
|
||||
}
|
||||
}
|
||||
LocalCommand::Run { package, activity } => {
|
||||
let output = device.run_activity(&package, &activity)?;
|
||||
std::io::stdout().write_all(&output)?;
|
||||
}
|
||||
LocalCommand::HostFeatures => {
|
||||
let features = device
|
||||
.host_features()?
|
||||
@@ -91,6 +95,10 @@ fn main() -> Result<()> {
|
||||
};
|
||||
device.get_logs(writer)?;
|
||||
}
|
||||
LocalCommand::Install { path } => {
|
||||
log::info!("Starting installation of APK {}...", path.display());
|
||||
device.install(path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Host(host) => {
|
||||
@@ -158,11 +166,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 {
|
||||
@@ -206,6 +223,14 @@ fn main() -> Result<()> {
|
||||
device.push(&mut input, &path)?;
|
||||
log::info!("Uploaded {filename} to {path}");
|
||||
}
|
||||
UsbCommands::Run { package, activity } => {
|
||||
let output = device.run_activity(&package, &activity)?;
|
||||
std::io::stdout().write_all(&output)?;
|
||||
}
|
||||
UsbCommands::Install { path } => {
|
||||
log::info!("Starting installation of APK {}...", path.display());
|
||||
device.install(path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[package]
|
||||
authors.workspace = true
|
||||
description = "Rust ADB (Android Debug Bridge) client library"
|
||||
edition.workspace = true
|
||||
keywords.workspace = true
|
||||
@@ -9,19 +10,21 @@ repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
bincode = "1.3.3"
|
||||
base64 = { version = "0.22.1" }
|
||||
bincode = { version = "1.3.3" }
|
||||
byteorder = { version = "1.5.0" }
|
||||
chrono = { version = "0.4.38" }
|
||||
homedir = { version = "0.3.4" }
|
||||
image = { version = "0.25.4" }
|
||||
lazy_static = { version = "1.5.0" }
|
||||
log = { version = "0.4.22" }
|
||||
num-bigint = { version = "0.6", package = "num-bigint-dig" }
|
||||
rand = { version = "0.7.0" }
|
||||
num-bigint = { version = "0.8.4", package = "num-bigint-dig" }
|
||||
num-traits = { version = "0.2.19" }
|
||||
rand = { version = "0.8.5" }
|
||||
regex = { version = "1.11.0", features = ["perf", "std", "unicode"] }
|
||||
rsa = { version = "0.3.0" }
|
||||
rsa = { version = "0.9.6" }
|
||||
rusb = { version = "0.9.4", features = ["vendored"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_repr = "0.1.19"
|
||||
thiserror = { version = "1.0.64" }
|
||||
serde_repr = { version = "0.1.19" }
|
||||
sha1 = { version = "0.10.6", features = ["oid"] }
|
||||
thiserror = { version = "2.0.1" }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::models::AdbStatResponse;
|
||||
use crate::{RebootType, Result};
|
||||
@@ -28,4 +29,18 @@ pub trait ADBDeviceExt {
|
||||
|
||||
/// Reboots the device using given reboot type
|
||||
fn reboot(&mut self, reboot_type: RebootType) -> Result<()>;
|
||||
|
||||
/// Run `activity` from `package` on device. Return the command output.
|
||||
fn run_activity(&mut self, package: &str, activity: &str) -> Result<Vec<u8>> {
|
||||
let mut output = Vec::new();
|
||||
self.shell_command(
|
||||
["am", "start", &format!("{package}/{package}.{activity}")],
|
||||
&mut output,
|
||||
)?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Install an APK pointed to by `apk_path` on device.
|
||||
fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -87,4 +87,10 @@ pub enum RustADBError {
|
||||
/// Cannot convert given data from slice
|
||||
#[error(transparent)]
|
||||
TryFromSliceError(#[from] std::array::TryFromSliceError),
|
||||
/// Given path does not represent an APK
|
||||
#[error("wrong file extension: {0}")]
|
||||
WrongFileExtension(String),
|
||||
/// An error occurred with PKCS8 data
|
||||
#[error("error with pkcs8: {0}")]
|
||||
RsaPkcs8Error(#[from] rsa::pkcs8::Error),
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ mod utils;
|
||||
|
||||
pub use adb_device_ext::ADBDeviceExt;
|
||||
pub use error::{Result, RustADBError};
|
||||
pub use models::{AdbVersion, DeviceLong, DeviceShort, DeviceState, RebootType};
|
||||
pub use models::{AdbStatResponse, AdbVersion, DeviceLong, DeviceShort, DeviceState, RebootType};
|
||||
pub use server::*;
|
||||
pub use transports::*;
|
||||
pub use usb::ADBUSBDevice;
|
||||
|
||||
@@ -16,12 +16,15 @@ pub(crate) enum AdbServerCommand {
|
||||
Pair(SocketAddrV4, String),
|
||||
TransportAny,
|
||||
TransportSerial(String),
|
||||
Install(u64),
|
||||
// Local commands
|
||||
ShellCommand(String),
|
||||
Shell,
|
||||
FrameBuffer,
|
||||
Sync,
|
||||
Reboot(RebootType),
|
||||
Forward(String, String, String),
|
||||
Reverse(String, String),
|
||||
}
|
||||
|
||||
impl Display for AdbServerCommand {
|
||||
@@ -53,6 +56,13 @@ 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}")
|
||||
}
|
||||
AdbServerCommand::Install(size) => write!(f, "exec:cmd package 'install' -S {size}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@ use std::{
|
||||
use byteorder::LittleEndian;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents a `stat` response
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct AdbStatResponse {
|
||||
/// File permissions
|
||||
pub file_perm: u32,
|
||||
/// File size, in bytes
|
||||
pub file_size: u32,
|
||||
/// File modification time
|
||||
pub mod_time: u32,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::io::{Read, Write};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
constants::BUFFER_SIZE,
|
||||
@@ -121,4 +124,8 @@ impl ADBDeviceExt for ADBServerDevice {
|
||||
fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> {
|
||||
self.push(stream, path)
|
||||
}
|
||||
|
||||
fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()> {
|
||||
self.install(apk_path)
|
||||
}
|
||||
}
|
||||
|
||||
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,7 +1,12 @@
|
||||
use std::{io::Read, iter::Map, path::Path, slice::ChunksExact};
|
||||
use std::{
|
||||
io::{Read, Seek, Write},
|
||||
iter::Map,
|
||||
path::Path,
|
||||
slice::ChunksExact,
|
||||
};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use image::{ImageBuffer, Rgba};
|
||||
use image::{ImageBuffer, ImageFormat, Rgba};
|
||||
|
||||
use crate::{models::AdbServerCommand, utils, ADBServerDevice, Result, RustADBError};
|
||||
|
||||
@@ -98,13 +103,21 @@ impl TryFrom<[u8; std::mem::size_of::<Self>()]> for FrameBufferInfoV2 {
|
||||
}
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Dump framebuffer of this device
|
||||
/// Dump framebuffer of this device into given ['path']
|
||||
/// Big help from source code (https://android.googlesource.com/platform/system/adb/+/refs/heads/main/framebuffer_service.cpp)
|
||||
pub fn framebuffer<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
let img = self.framebuffer_inner()?;
|
||||
Ok(img.save(path.as_ref())?)
|
||||
}
|
||||
|
||||
/// Dump framebuffer of this device and return corresponding bytes.
|
||||
///
|
||||
/// Output data format is currently only `PNG`.
|
||||
pub fn framebuffer_bytes<W: Write + Seek>(&mut self, mut writer: W) -> Result<()> {
|
||||
let img = self.framebuffer_inner()?;
|
||||
Ok(img.write_to(&mut writer, ImageFormat::Png)?)
|
||||
}
|
||||
|
||||
/// Inner method requesting framebuffer from Android device
|
||||
fn framebuffer_inner(&mut self) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>> {
|
||||
let serial: String = self.identifier.clone();
|
||||
|
||||
41
adb_client/src/server/device_commands/install.rs
Normal file
41
adb_client/src/server/device_commands/install.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use std::{fs::File, io::Read, path::Path};
|
||||
|
||||
use crate::{models::AdbServerCommand, utils::check_extension_is_apk, ADBServerDevice, Result};
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Install an APK on device
|
||||
pub fn install<P: AsRef<Path>>(&mut self, apk_path: P) -> Result<()> {
|
||||
let mut apk_file = File::open(&apk_path)?;
|
||||
|
||||
check_extension_is_apk(&apk_path)?;
|
||||
|
||||
let file_size = apk_file.metadata()?.len();
|
||||
|
||||
let serial: String = self.identifier.clone();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
|
||||
self.get_transport_mut()
|
||||
.send_adb_request(AdbServerCommand::Install(file_size))?;
|
||||
|
||||
let mut raw_connection = self.get_transport_mut().get_raw_connection()?;
|
||||
|
||||
std::io::copy(&mut apk_file, &mut raw_connection)?;
|
||||
|
||||
let mut data = [0; 1024];
|
||||
let read_amount = self.get_transport().get_raw_connection()?.read(&mut data)?;
|
||||
|
||||
match &data[0..read_amount] {
|
||||
b"Success\n" => {
|
||||
log::info!(
|
||||
"APK file {} successfully installed",
|
||||
apk_path.as_ref().display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
d => Err(crate::RustADBError::ADBRequestFailed(String::from_utf8(
|
||||
d.to_vec(),
|
||||
)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
mod forward;
|
||||
mod framebuffer;
|
||||
mod host_features;
|
||||
mod install;
|
||||
mod list;
|
||||
mod logcat;
|
||||
mod reboot;
|
||||
mod recv;
|
||||
mod reverse;
|
||||
mod send;
|
||||
mod stat;
|
||||
mod transport;
|
||||
|
||||
@@ -32,7 +32,7 @@ impl<R: Read> Read for ADBRecvCommandReader<R> {
|
||||
match &header[..] {
|
||||
b"DATA" => {
|
||||
let length = self.inner.read_u32::<LittleEndian>()? as usize;
|
||||
let effective_read = self.inner.read(buf)?;
|
||||
let effective_read = self.inner.read(&mut buf[0..length])?;
|
||||
self.remaining_data_bytes_to_read = length - effective_read;
|
||||
|
||||
Ok(effective_read)
|
||||
|
||||
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,93 +1,114 @@
|
||||
use crate::{Result, RustADBError};
|
||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
use byteorder::{LittleEndian, WriteBytesExt};
|
||||
use num_bigint::traits::ModInverse;
|
||||
use num_bigint::BigUint;
|
||||
use num_bigint::{BigUint, ModInverse};
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use num_traits::FromPrimitive;
|
||||
use rand::rngs::OsRng;
|
||||
use rsa::{Hash, PaddingScheme, PublicKeyParts, RSAPrivateKey};
|
||||
use std::convert::TryInto;
|
||||
use rsa::pkcs8::DecodePrivateKey;
|
||||
use rsa::traits::PublicKeyParts;
|
||||
use rsa::{Pkcs1v15Sign, RsaPrivateKey};
|
||||
|
||||
// From project: https://github.com/hajifkd/webadb
|
||||
#[derive(Debug)]
|
||||
pub struct ADBRsaKey {
|
||||
private_key: RSAPrivateKey,
|
||||
const ADB_PRIVATE_KEY_SIZE: usize = 2048;
|
||||
const ANDROID_PUBKEY_MODULUS_SIZE_WORDS: u32 = 64;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
/// Internal ADB representation of a public key
|
||||
struct ADBRsaInternalPublicKey {
|
||||
pub modulus_size_words: u32,
|
||||
pub n0inv: u32,
|
||||
pub modulus: BigUint,
|
||||
pub rr: Vec<u8>,
|
||||
pub exponent: u32,
|
||||
}
|
||||
|
||||
impl ADBRsaKey {
|
||||
pub fn random_with_size(size: usize) -> Result<Self> {
|
||||
let mut rng = OsRng;
|
||||
impl ADBRsaInternalPublicKey {
|
||||
pub fn new(exponent: &BigUint, modulus: &BigUint) -> Result<Self> {
|
||||
Ok(Self {
|
||||
private_key: RSAPrivateKey::new(&mut rng, size)?,
|
||||
modulus_size_words: ANDROID_PUBKEY_MODULUS_SIZE_WORDS,
|
||||
exponent: exponent.to_u32().ok_or(RustADBError::ConversionError)?,
|
||||
modulus: modulus.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_pkcs8(pkcs8_content: &str) -> Result<Self> {
|
||||
let der_encoded = pkcs8_content
|
||||
.lines()
|
||||
.filter(|line| !line.starts_with("-") && !line.is_empty())
|
||||
.fold(String::new(), |mut data, line| {
|
||||
data.push_str(line);
|
||||
data
|
||||
});
|
||||
let der_bytes = STANDARD.decode(&der_encoded)?;
|
||||
let private_key = RSAPrivateKey::from_pkcs8(&der_bytes)?;
|
||||
pub fn into_bytes(mut self) -> Vec<u8> {
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
bytes.append(&mut self.modulus_size_words.to_le_bytes().to_vec());
|
||||
bytes.append(&mut self.n0inv.to_le_bytes().to_vec());
|
||||
bytes.append(&mut self.modulus.to_bytes_le());
|
||||
bytes.append(&mut self.rr);
|
||||
bytes.append(&mut self.exponent.to_le_bytes().to_vec());
|
||||
|
||||
Ok(ADBRsaKey { private_key })
|
||||
}
|
||||
|
||||
pub fn encoded_public_key(&self) -> Result<String> {
|
||||
// see https://android.googlesource.com/platform/system/core/+/android-4.4_r1/adb/adb_auth_host.c
|
||||
// L63 RSA_to_RSAPublicKey
|
||||
const RSANUMBYTES: u32 = 256;
|
||||
const RSANUMWORDS: u32 = 64;
|
||||
let user: String = format!("adb_client@{}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
let mut result = vec![];
|
||||
result.write_u32::<LittleEndian>(RSANUMWORDS)?;
|
||||
let r32 = set_bit(32)?;
|
||||
let n = self.private_key.n();
|
||||
let r = set_bit((32 * RSANUMWORDS) as _)?;
|
||||
// Well, let rr = set_bit((64 * RSANUMWORDS) as _) % n is also fine, since r \sim n.
|
||||
let rr = r.modpow(&BigUint::from(2u32), n);
|
||||
let rem = n % &r32;
|
||||
let n0inv = rem.mod_inverse(&r32);
|
||||
if let Some(n0inv) = n0inv {
|
||||
let n0inv = n0inv.to_biguint().ok_or(RustADBError::ConversionError)?;
|
||||
let n0inv_p: u32 = 1 + !u32::from_le_bytes((&n0inv.to_bytes_le()[..4]).try_into()?);
|
||||
result.write_u32::<LittleEndian>(n0inv_p)?;
|
||||
} else {
|
||||
return Err(RustADBError::ConversionError);
|
||||
}
|
||||
|
||||
write_biguint(&mut result, n, RSANUMBYTES as _)?;
|
||||
write_biguint(&mut result, &rr, RSANUMBYTES as _)?;
|
||||
write_biguint(&mut result, self.private_key.e(), 4)?;
|
||||
|
||||
let mut encoded = STANDARD.encode(&result);
|
||||
encoded.push(' ');
|
||||
encoded.push_str(&user);
|
||||
Ok(encoded)
|
||||
}
|
||||
|
||||
pub fn sign(&self, msg: impl AsRef<[u8]>) -> Result<Vec<u8>> {
|
||||
Ok(self.private_key.sign(
|
||||
PaddingScheme::new_pkcs1v15_sign(Some(Hash::SHA1)),
|
||||
msg.as_ref(),
|
||||
)?)
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
fn write_biguint(writer: &mut Vec<u8>, data: &BigUint, n_bytes: usize) -> Result<()> {
|
||||
for &v in data
|
||||
.to_bytes_le()
|
||||
.iter()
|
||||
.chain(std::iter::repeat(&0))
|
||||
.take(n_bytes)
|
||||
{
|
||||
writer.write_u8(v)?;
|
||||
#[derive(Debug)]
|
||||
pub struct ADBRsaKey {
|
||||
private_key: RsaPrivateKey,
|
||||
}
|
||||
|
||||
impl ADBRsaKey {
|
||||
pub fn new_random() -> Result<Self> {
|
||||
Ok(Self {
|
||||
private_key: RsaPrivateKey::new(&mut OsRng, ADB_PRIVATE_KEY_SIZE)?,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
pub fn new_from_pkcs8(pkcs8_content: &str) -> Result<Self> {
|
||||
Ok(ADBRsaKey {
|
||||
private_key: RsaPrivateKey::from_pkcs8_pem(pkcs8_content)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn android_pubkey_encode(&self) -> Result<String> {
|
||||
// Helped from project: https://github.com/hajifkd/webadb
|
||||
// Source code: https://android.googlesource.com/platform/system/core/+/refs/heads/main/libcrypto_utils/android_pubkey.cpp
|
||||
// Useful function `android_pubkey_encode()`
|
||||
let mut adb_rsa_pubkey =
|
||||
ADBRsaInternalPublicKey::new(self.private_key.e(), self.private_key.n())?;
|
||||
|
||||
// r32 = 2 ^ 32
|
||||
let r32 = BigUint::from_u64(1 << 32).ok_or(RustADBError::ConversionError)?;
|
||||
|
||||
// r = 2 ^ rsa_size = 2 ^ 2048
|
||||
let r = set_bit(ADB_PRIVATE_KEY_SIZE)?;
|
||||
|
||||
// rr = r ^ 2 mod N
|
||||
let rr = r.modpow(&BigUint::from(2u32), &adb_rsa_pubkey.modulus);
|
||||
adb_rsa_pubkey.rr = rr.to_bytes_le();
|
||||
|
||||
// rem = N[0]
|
||||
let rem = &adb_rsa_pubkey.modulus % &r32;
|
||||
|
||||
// n0inv = -1 / rem mod r32
|
||||
let n0inv = rem
|
||||
.mod_inverse(&r32)
|
||||
.and_then(|v| v.to_biguint())
|
||||
.ok_or(RustADBError::ConversionError)?;
|
||||
|
||||
// BN_sub(n0inv, r32, n0inv)
|
||||
adb_rsa_pubkey.n0inv = (r32 - n0inv)
|
||||
.to_u32()
|
||||
.ok_or(RustADBError::ConversionError)?;
|
||||
|
||||
Ok(self.encode_public_key(adb_rsa_pubkey.into_bytes()))
|
||||
}
|
||||
|
||||
fn encode_public_key(&self, pub_key: Vec<u8>) -> String {
|
||||
let mut encoded = STANDARD.encode(pub_key);
|
||||
encoded.push(' ');
|
||||
encoded.push_str(&format!("adb_client@{}", env!("CARGO_PKG_VERSION")));
|
||||
|
||||
encoded
|
||||
}
|
||||
|
||||
pub fn sign(&self, msg: impl AsRef<[u8]>) -> Result<Vec<u8>> {
|
||||
Ok(self
|
||||
.private_key
|
||||
.sign(Pkcs1v15Sign::new::<sha1::Sha1>(), msg.as_ref())?)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_bit(n: usize) -> Result<BigUint> {
|
||||
@@ -133,9 +154,9 @@ CNkECiepaGyquQaffwR1CAi8dH6biJjlTQWQPFcCLA0hvernWo3eaSfiL7fHyym+
|
||||
ile69MHFENUePSpuRSiF3Z02
|
||||
-----END PRIVATE KEY-----";
|
||||
let priv_key =
|
||||
ADBRsaKey::from_pkcs8(DEFAULT_PRIV_KEY).expect("cannot create rsa key from data");
|
||||
ADBRsaKey::new_from_pkcs8(DEFAULT_PRIV_KEY).expect("cannot create rsa key from data");
|
||||
let pub_key = priv_key
|
||||
.encoded_public_key()
|
||||
.android_pubkey_encode()
|
||||
.expect("cannot encode public key");
|
||||
let pub_key_adb = "\
|
||||
QAAAAFH/pU9PVrHRgEjMGnpvOr2QzKYCavSE1fcSwvpS1uPn9GTmuyZr7c9up\
|
||||
|
||||
@@ -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;
|
||||
@@ -29,7 +32,7 @@ pub struct ADBUSBDevice {
|
||||
fn read_adb_private_key<P: AsRef<Path>>(private_key_path: P) -> Result<Option<ADBRsaKey>> {
|
||||
read_to_string(private_key_path.as_ref())
|
||||
.map_err(RustADBError::from)
|
||||
.map(|pk| match ADBRsaKey::from_pkcs8(&pk) {
|
||||
.map(|pk| match ADBRsaKey::new_from_pkcs8(&pk) {
|
||||
Ok(pk) => Some(pk),
|
||||
Err(e) => {
|
||||
log::error!("Error while create RSA private key: {e}");
|
||||
@@ -37,19 +40,88 @@ 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.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 {: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)?;
|
||||
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
|
||||
pub fn new_with_custom_private_key(
|
||||
vendor_id: u16,
|
||||
product_id: u16,
|
||||
private_key_path: PathBuf,
|
||||
) -> Result<Self> {
|
||||
let private_key = match read_adb_private_key(private_key_path)? {
|
||||
Some(pk) => pk,
|
||||
None => ADBRsaKey::random_with_size(2048)?,
|
||||
None => ADBRsaKey::new_random()?,
|
||||
};
|
||||
|
||||
let mut s = Self {
|
||||
@@ -62,25 +134,21 @@ impl ADBUSBDevice {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Instantiate a new [ADBUSBDevice] using a custom private key path
|
||||
pub fn new_with_custom_private_key(
|
||||
vendor_id: u16,
|
||||
product_id: u16,
|
||||
private_key_path: PathBuf,
|
||||
) -> Result<Self> {
|
||||
let private_key = match read_adb_private_key(private_key_path)? {
|
||||
Some(pk) => pk,
|
||||
None => ADBRsaKey::random_with_size(2048)?,
|
||||
};
|
||||
/// 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()?)
|
||||
}
|
||||
|
||||
let mut s = Self {
|
||||
private_key,
|
||||
transport: USBTransport::new(vendor_id, product_id),
|
||||
};
|
||||
|
||||
s.connect()?;
|
||||
|
||||
Ok(s)
|
||||
/// 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
|
||||
@@ -134,7 +202,7 @@ impl ADBUSBDevice {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut pubkey = self.private_key.encoded_public_key()?.into_bytes();
|
||||
let mut pubkey = self.private_key.android_pubkey_encode()?.into_bytes();
|
||||
pubkey.push(b'\0');
|
||||
|
||||
let message = ADBUsbMessage::new(USBCommand::Auth, AUTH_RSAPUBLICKEY, 0, pubkey);
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use crate::{
|
||||
models::AdbStatResponse,
|
||||
usb::{ADBUsbMessage, USBCommand, USBSubcommand},
|
||||
utils::check_extension_is_apk,
|
||||
ADBDeviceExt, ADBUSBDevice, RebootType, Result, RustADBError,
|
||||
};
|
||||
use rand::Rng;
|
||||
use std::io::{Read, Write};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
};
|
||||
|
||||
use super::USBShellWriter;
|
||||
use super::{USBShellWriter, USBWriter};
|
||||
|
||||
impl ADBDeviceExt for ADBUSBDevice {
|
||||
fn shell_command<S: ToString, W: Write>(
|
||||
@@ -194,4 +198,48 @@ impl ADBDeviceExt for ADBUSBDevice {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install<P: AsRef<std::path::Path>>(&mut self, apk_path: P) -> Result<()> {
|
||||
let mut apk_file = File::open(&apk_path)?;
|
||||
|
||||
check_extension_is_apk(&apk_path)?;
|
||||
|
||||
let file_size = apk_file.metadata()?.len();
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let local_id = rng.gen();
|
||||
|
||||
let message = ADBUsbMessage::new(
|
||||
USBCommand::Open,
|
||||
local_id,
|
||||
0,
|
||||
format!("exec:cmd package 'install' -S {}\0", file_size)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
self.transport.write_message(message)?;
|
||||
|
||||
let response = self.transport.read_message()?;
|
||||
let remote_id = response.header().arg0();
|
||||
|
||||
let mut writer = USBWriter::new(self.transport.clone(), local_id, remote_id);
|
||||
|
||||
std::io::copy(&mut apk_file, &mut writer)?;
|
||||
|
||||
let final_status = self.transport.read_message()?;
|
||||
|
||||
match final_status.into_payload().as_slice() {
|
||||
b"Success\n" => {
|
||||
log::info!(
|
||||
"APK file {} successfully installed",
|
||||
apk_path.as_ref().display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
d => Err(crate::RustADBError::ADBRequestFailed(String::from_utf8(
|
||||
d.to_vec(),
|
||||
)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ mod adb_usb_device;
|
||||
mod adb_usb_device_commands;
|
||||
mod adb_usb_message;
|
||||
mod usb_commands;
|
||||
mod usb_shell;
|
||||
mod usb_shell_writer;
|
||||
mod usb_writer;
|
||||
|
||||
pub use adb_rsa_key::ADBRsaKey;
|
||||
pub use adb_usb_device::ADBUSBDevice;
|
||||
pub use adb_usb_message::{ADBUsbMessage, ADBUsbMessageHeader};
|
||||
pub use usb_commands::{USBCommand, USBSubcommand};
|
||||
pub use usb_shell::USBShellWriter;
|
||||
pub use usb_shell_writer::USBShellWriter;
|
||||
pub use usb_writer::USBWriter;
|
||||
|
||||
53
adb_client/src/usb/usb_writer.rs
Normal file
53
adb_client/src/usb/usb_writer.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use std::io::{ErrorKind, Write};
|
||||
|
||||
use crate::USBTransport;
|
||||
|
||||
use super::{ADBUsbMessage, USBCommand};
|
||||
|
||||
/// Wraps a `Writer` to hide underlying ADB protocol write logic.
|
||||
///
|
||||
/// Read received responses to check that message has been received.
|
||||
pub struct USBWriter {
|
||||
transport: USBTransport,
|
||||
local_id: u32,
|
||||
remote_id: u32,
|
||||
}
|
||||
|
||||
impl USBWriter {
|
||||
pub fn new(transport: USBTransport, local_id: u32, remote_id: u32) -> Self {
|
||||
Self {
|
||||
transport,
|
||||
local_id,
|
||||
remote_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for USBWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let message = ADBUsbMessage::new(
|
||||
USBCommand::Write,
|
||||
self.local_id,
|
||||
self.remote_id,
|
||||
buf.to_vec(),
|
||||
);
|
||||
self.transport
|
||||
.write_message(message)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||
|
||||
match self.transport.read_message() {
|
||||
Ok(response) => match response.header().command() {
|
||||
USBCommand::Okay => Ok(buf.len()),
|
||||
c => Err(std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("wrong response received: {c}"),
|
||||
)),
|
||||
},
|
||||
Err(e) => Err(std::io::Error::new(ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::{ffi::OsStr, path::Path};
|
||||
|
||||
use crate::{Result, RustADBError};
|
||||
|
||||
pub fn u32_from_le(value: &[u8]) -> Result<u32> {
|
||||
@@ -7,3 +9,16 @@ pub fn u32_from_le(value: &[u8]) -> Result<u32> {
|
||||
.map_err(|_| RustADBError::ConversionError)?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn check_extension_is_apk<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
if let Some(extension) = path.as_ref().extension() {
|
||||
if ![OsStr::new("apk")].contains(&extension) {
|
||||
return Err(RustADBError::WrongFileExtension(format!(
|
||||
"{} is not an APK file",
|
||||
extension.to_string_lossy()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user