Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f39c13355 | ||
|
|
488af2b9dd |
11
Cargo.toml
11
Cargo.toml
@@ -6,8 +6,15 @@ resolver = "2"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/cocool97/adb_client"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
|
||||
# To build locally when working on a new version
|
||||
[patch.crates-io]
|
||||
adb_client = { path = "./adb_client" }
|
||||
adb_client = { path = "./adb_client" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug-assertions = false
|
||||
lto = "thin"
|
||||
opt-level = 'z'
|
||||
strip = true
|
||||
@@ -10,7 +10,7 @@ version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
adb_client = { version = "1.0.6" }
|
||||
anyhow = { version = "1.0.86" }
|
||||
clap = { version = "4.5.17", features = ["derive"] }
|
||||
anyhow = { version = "1.0.89" }
|
||||
clap = { version = "4.5.18", features = ["derive"] }
|
||||
env_logger = { version = "0.11.5" }
|
||||
log = "0.4.22"
|
||||
|
||||
@@ -18,4 +18,4 @@ log = "0.4.22"
|
||||
mio = { version = "1.0.2", features = ["os-ext", "os-poll"] }
|
||||
regex = { version = "1.10.6", features = ["perf", "std", "unicode"] }
|
||||
termios = { version = "0.3.3" }
|
||||
thiserror = { version = "1.0.63" }
|
||||
thiserror = { version = "1.0.64" }
|
||||
|
||||
1
adb_client/src/constants.rs
Normal file
1
adb_client/src/constants.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub const BUFFER_SIZE: usize = 65536;
|
||||
@@ -5,6 +5,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
mod adb_termios;
|
||||
mod constants;
|
||||
mod emulator;
|
||||
mod error;
|
||||
mod models;
|
||||
|
||||
@@ -1,9 +1,72 @@
|
||||
use crate::{
|
||||
constants,
|
||||
models::{AdbServerCommand, SyncCommand},
|
||||
ADBServerDevice, Result, RustADBError,
|
||||
ADBServerDevice, Result,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::io::{Read, Write};
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use std::io::{BufReader, BufWriter, Read, Write};
|
||||
|
||||
/// Internal structure wrapping a [std::io::Read] and hiding underlying protocol logic.
|
||||
struct ADBRecvCommandReader<R: Read> {
|
||||
inner: R,
|
||||
remaining_data_bytes_to_read: usize,
|
||||
}
|
||||
|
||||
impl<R: Read> ADBRecvCommandReader<R> {
|
||||
pub fn new(inner: R) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
remaining_data_bytes_to_read: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for ADBRecvCommandReader<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
// In case of a "DATA" header, we may not have enough space in `buf` to fill it with "length" bytes coming from device.
|
||||
// `remaining_data_bytes_to_read` represents how many bytes are still left to read before receiving another header.
|
||||
if self.remaining_data_bytes_to_read == 0 {
|
||||
let mut header = [0_u8; 4];
|
||||
self.inner.read_exact(&mut header)?;
|
||||
|
||||
match &header[..] {
|
||||
b"DATA" => {
|
||||
let length = self.inner.read_u32::<LittleEndian>()? as usize;
|
||||
let effective_read = self.inner.read(buf)?;
|
||||
self.remaining_data_bytes_to_read = length - effective_read;
|
||||
|
||||
Ok(effective_read)
|
||||
}
|
||||
b"DONE" => Ok(0),
|
||||
b"FAIL" => {
|
||||
let length = self.inner.read_u32::<LittleEndian>()? as usize;
|
||||
let mut error_msg = vec![0; length];
|
||||
self.inner.read_exact(&mut error_msg)?;
|
||||
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"ADB request failed: {}",
|
||||
String::from_utf8_lossy(&error_msg)
|
||||
),
|
||||
))
|
||||
}
|
||||
_ => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("Unknown response from device {:#?}", header),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
// Computing minimum to ensure to stop reading before next header...
|
||||
let data_to_read = std::cmp::min(self.remaining_data_bytes_to_read, buf.len());
|
||||
self.inner.read_exact(&mut buf[..data_to_read])?;
|
||||
|
||||
self.remaining_data_bytes_to_read -= self.remaining_data_bytes_to_read;
|
||||
|
||||
Ok(data_to_read)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Receives [path] to [stream] from the device.
|
||||
@@ -28,58 +91,21 @@ impl ADBServerDevice {
|
||||
from: S,
|
||||
output: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
// First send 8 byte common header
|
||||
let mut len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut len_buf, from.as_ref().len() as u32);
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(from.as_ref().as_bytes())?;
|
||||
let mut raw_connection = self.get_transport().get_raw_connection()?;
|
||||
|
||||
// Then we receive the byte data in chunks of up to 64k
|
||||
// Chunk looks like 'DATA' <length> <data>
|
||||
let mut buffer = [0_u8; 64 * 1024]; // Should this be Boxed?
|
||||
let mut data_header = [0_u8; 4]; // DATA
|
||||
loop {
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut data_header)?;
|
||||
// Check if data_header is DATA or DONE or FAIL
|
||||
match &data_header {
|
||||
b"DATA" => {
|
||||
// Handle received data
|
||||
let length: usize = self
|
||||
.get_transport_mut()
|
||||
.get_body_length()?
|
||||
.try_into()
|
||||
.unwrap();
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut buffer[..length])?;
|
||||
output.write_all(&buffer[..length])?;
|
||||
}
|
||||
b"DONE" => break, // We're done here
|
||||
b"FAIL" => {
|
||||
// Handle fail
|
||||
let length: usize = self
|
||||
.get_transport_mut()
|
||||
.get_body_length()?
|
||||
.try_into()
|
||||
.unwrap();
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut buffer[..length])?;
|
||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(
|
||||
buffer[..length].to_vec(),
|
||||
)?))?;
|
||||
}
|
||||
_ => panic!("Unknown response from device {:#?}", data_header),
|
||||
}
|
||||
}
|
||||
let from_as_bytes = from.as_ref().as_bytes();
|
||||
let mut buffer = Vec::with_capacity(4 + from_as_bytes.len());
|
||||
buffer.extend_from_slice(&(from.as_ref().len() as u32).to_le_bytes());
|
||||
buffer.extend_from_slice(from_as_bytes);
|
||||
raw_connection.write_all(&buffer)?;
|
||||
|
||||
// Connection should've left SYNC by now
|
||||
let reader = ADBRecvCommandReader::new(raw_connection);
|
||||
std::io::copy(
|
||||
&mut BufReader::with_capacity(constants::BUFFER_SIZE, reader),
|
||||
&mut BufWriter::with_capacity(constants::BUFFER_SIZE, output),
|
||||
)?;
|
||||
|
||||
// Connection should've been left in SYNC mode by now
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,50 @@
|
||||
use crate::{
|
||||
constants,
|
||||
models::{AdbRequestStatus, AdbServerCommand, SyncCommand},
|
||||
ADBServerDevice, Result, RustADBError,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
io::{Read, Write},
|
||||
io::{BufReader, BufWriter, Read, Write},
|
||||
str::{self, FromStr},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
/// Internal structure wrapping a [std::io::Write] and hiding underlying protocol logic.
|
||||
struct ADBSendCommandWriter<W: Write> {
|
||||
inner: W,
|
||||
}
|
||||
|
||||
impl<W: Write> ADBSendCommandWriter<W> {
|
||||
pub fn new(inner: W) -> Self {
|
||||
ADBSendCommandWriter { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Write for ADBSendCommandWriter<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let chunk_len = buf.len() as u32;
|
||||
|
||||
// 8 = "DATA".len() + sizeof(u32)
|
||||
let mut buffer = Vec::with_capacity(8 + buf.len());
|
||||
buffer.extend_from_slice(b"DATA");
|
||||
buffer.extend_from_slice(&chunk_len.to_le_bytes());
|
||||
buffer.extend_from_slice(buf);
|
||||
|
||||
self.inner.write_all(&buffer)?;
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.inner.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl ADBServerDevice {
|
||||
/// Send [stream] to [path] on the device.
|
||||
pub fn send<A: AsRef<str>>(&mut self, stream: &mut dyn Read, path: A) -> Result<()> {
|
||||
pub fn send<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();
|
||||
self.connect()?
|
||||
.send_adb_request(AdbServerCommand::TransportSerial(serial))?;
|
||||
@@ -28,64 +60,43 @@ impl ADBServerDevice {
|
||||
self.handle_send_command(stream, path)
|
||||
}
|
||||
|
||||
fn handle_send_command<S: AsRef<str>>(&mut self, input: &mut dyn Read, to: S) -> Result<()> {
|
||||
fn handle_send_command<R: Read, S: AsRef<str>>(&mut self, input: R, to: S) -> Result<()> {
|
||||
// Append the permission flags to the filename
|
||||
let to = to.as_ref().to_string() + ",0777";
|
||||
|
||||
// The name of command is already sent by get_transport()?.send_sync_request
|
||||
let mut len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut len_buf, to.len() as u32);
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
let mut raw_connection = self.get_transport_mut().get_raw_connection()?;
|
||||
|
||||
// Send appends the filemode to the string sent
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(to.as_bytes())?;
|
||||
// The name of the command is already sent by get_transport()?.send_sync_request
|
||||
let to_as_bytes = to.as_bytes();
|
||||
let mut buffer = Vec::with_capacity(4 + to_as_bytes.len());
|
||||
buffer.extend_from_slice(&(to.len() as u32).to_le_bytes());
|
||||
buffer.extend_from_slice(to_as_bytes);
|
||||
raw_connection.write_all(&buffer)?;
|
||||
|
||||
// Then we send the byte data in chunks of up to 64k
|
||||
// Chunk looks like 'DATA' <length> <data>
|
||||
let mut buffer = [0_u8; 64 * 1024];
|
||||
loop {
|
||||
let bytes_read = input.read(&mut buffer)?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
let mut chunk_len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut chunk_len_buf, bytes_read as u32);
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(b"DATA")?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&chunk_len_buf)?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&buffer[..bytes_read])?;
|
||||
}
|
||||
let writer = ADBSendCommandWriter::new(raw_connection);
|
||||
|
||||
// When we are done sending, we send 'DONE' <last modified time>
|
||||
// Re-use len_buf to send the last modified time
|
||||
std::io::copy(
|
||||
&mut BufReader::with_capacity(constants::BUFFER_SIZE, input),
|
||||
&mut BufWriter::with_capacity(constants::BUFFER_SIZE, writer),
|
||||
)?;
|
||||
|
||||
// Copy is finished, we can now notify as finished
|
||||
// Have to send DONE + file mtime
|
||||
let last_modified = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
|
||||
Ok(n) => n,
|
||||
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
||||
};
|
||||
LittleEndian::write_u32(&mut len_buf, last_modified.as_secs() as u32);
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(b"DONE")?;
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.write_all(&len_buf)?;
|
||||
|
||||
let mut done_buffer = Vec::with_capacity(8);
|
||||
done_buffer.extend_from_slice(b"DONE");
|
||||
done_buffer.extend_from_slice(&last_modified.as_secs().to_le_bytes());
|
||||
raw_connection.write_all(&done_buffer)?;
|
||||
|
||||
// We expect 'OKAY' response from this
|
||||
let mut request_status = [0; 4];
|
||||
self.get_transport_mut()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut request_status)?;
|
||||
raw_connection.read_exact(&mut request_status)?;
|
||||
|
||||
match AdbRequestStatus::from_str(str::from_utf8(request_status.as_ref())?)? {
|
||||
match AdbRequestStatus::from_str(str::from_utf8(&request_status)?)? {
|
||||
AdbRequestStatus::Fail => {
|
||||
// We can keep reading to get further details
|
||||
let length = self.get_transport_mut().get_body_length()?;
|
||||
@@ -97,7 +108,7 @@ impl ADBServerDevice {
|
||||
.map_err(|_| RustADBError::ConversionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
self.get_transport_mut()
|
||||
self.get_transport()
|
||||
.get_raw_connection()?
|
||||
.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
@@ -78,8 +78,6 @@ impl ADBServerDevice {
|
||||
let mut adb_termios = ADBTermios::new(std::io::stdin())?;
|
||||
adb_termios.set_adb_termios()?;
|
||||
|
||||
self.connect()?.get_raw_connection()?.set_nodelay(true)?;
|
||||
|
||||
// TODO: FORWARD CTRL+C !!
|
||||
|
||||
let supported_features = self.host_features()?;
|
||||
|
||||
@@ -154,7 +154,9 @@ impl ADBTransport for TCPServerTransport {
|
||||
// Ignoring underlying error, we will recreate a new connection
|
||||
let _ = previous.shutdown(std::net::Shutdown::Both);
|
||||
}
|
||||
self.tcp_stream = Some(TcpStream::connect(self.socket_addr)?);
|
||||
let tcp_stream = TcpStream::connect(self.socket_addr)?;
|
||||
tcp_stream.set_nodelay(true)?;
|
||||
self.tcp_stream = Some(tcp_stream);
|
||||
log::trace!("Successfully connected to {}", self.socket_addr);
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user