Skip to content

Commit

Permalink
feat: massive internal refactoring, no breaking change
Browse files Browse the repository at this point in the history
  • Loading branch information
Corentin L committed Nov 25, 2024
1 parent 152836f commit ce0fe6a
Show file tree
Hide file tree
Showing 58 changed files with 1,099 additions and 895 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<a href="https://crates.io/crates/adb_client">
<img alt="crates.io" src="https://img.shields.io/crates/v/adb_client.svg"/>
</a>
<a href="https://github.com/cocool97/adb_client/actions">
<img alt="ci status" src="https://github.com/cocool97/adb_client/actions/workflows/rust-build.yml/badge.svg"/>
</a>
<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>
Expand Down
2 changes: 1 addition & 1 deletion adb_client/src/adb_device_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::path::Path;
use crate::models::AdbStatResponse;
use crate::{RebootType, Result};

/// Trait representing all features available on both [`ADBServerDevice`] and [`ADBUSBDevice`]
/// Trait representing all features available on both [`ADBServerDevice`] and [`ADBMessageDevice<T>`] ([`ADBUSBDevice`])
pub trait ADBDeviceExt {
/// Runs 'command' in a shell on the device, and write its output and error streams into [`output`].
fn shell_command<S: ToString, W: Write>(
Expand Down
242 changes: 242 additions & 0 deletions adb_client/src/device/adb_message_device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
use byteorder::{LittleEndian, ReadBytesExt};
use rand::Rng;
use std::io::{Cursor, Read, Seek};

use crate::{constants::BUFFER_SIZE, ADBMessageTransport, AdbStatResponse, Result, RustADBError};

use super::{models::MessageSubcommand, ADBTransportMessage, MessageCommand};

/// Generic structure representing an ADB device reachable over an [`ADBMessageTransport`].
/// Structure is totally agnostic over which transport is truly used.
#[derive(Debug)]
pub struct ADBMessageDevice<T: ADBMessageTransport> {
transport: T,
}

impl<T: ADBMessageTransport> ADBMessageDevice<T> {
/// Instantiate a new [`ADBMessageTransport`]
pub fn new(transport: T) -> Self {
Self { transport }
}

pub(crate) fn get_transport(&mut self) -> &mut T {
&mut self.transport
}

/// Receive a message and acknowledge it by replying with an `OKAY` command
pub(crate) fn recv_and_reply_okay(
&mut self,
local_id: u32,
remote_id: u32,
) -> Result<ADBTransportMessage> {
let message = self.transport.read_message()?;
self.transport.write_message(ADBTransportMessage::new(
MessageCommand::Okay,
local_id,
remote_id,
"".into(),
))?;
Ok(message)
}

/// Expect a message with an `OKAY` command after sending a message.
pub(crate) fn send_and_expect_okay(
&mut self,
message: ADBTransportMessage,
) -> Result<ADBTransportMessage> {
self.transport.write_message(message)?;
let message = self.transport.read_message()?;
let received_command = message.header().command();
if received_command != MessageCommand::Okay {
return Err(RustADBError::ADBRequestFailed(format!(
"expected command OKAY after message, got {}",
received_command
)));
}
Ok(message)
}

pub(crate) fn recv_file<W: std::io::Write>(
&mut self,
local_id: u32,
remote_id: u32,
mut output: W,
) -> std::result::Result<(), RustADBError> {
let mut len: Option<u64> = None;
loop {
let payload = self
.recv_and_reply_okay(local_id, remote_id)?
.into_payload();
let mut rdr = Cursor::new(&payload);
while rdr.position() != payload.len() as u64 {
match len.take() {
Some(0) | None => {
rdr.seek_relative(4)?;
len.replace(rdr.read_u32::<LittleEndian>()? as u64);
}
Some(length) => {
log::debug!("len = {length}");
let remaining_bytes = payload.len() as u64 - rdr.position();
log::debug!(
"payload length {} - reader_position {} = {remaining_bytes}",
payload.len(),
rdr.position()
);
if length < remaining_bytes {
let read = std::io::copy(&mut rdr.by_ref().take(length), &mut output)?;
log::debug!(
"expected to read {length} bytes, actually read {read} bytes"
);
} else {
let read = std::io::copy(&mut rdr.take(remaining_bytes), &mut output)?;
len.replace(length - remaining_bytes);
log::debug!("expected to read {remaining_bytes} bytes, actually read {read} bytes");
// this payload is exhausted
break;
}
}
}
}
if Cursor::new(&payload[(payload.len() - 8)..(payload.len() - 4)])
.read_u32::<LittleEndian>()?
== MessageSubcommand::Done as u32
{
break;
}
}
Ok(())
}

pub(crate) fn push_file<R: std::io::Read>(
&mut self,
local_id: u32,
remote_id: u32,
mut reader: R,
) -> std::result::Result<(), RustADBError> {
let mut buffer = [0; BUFFER_SIZE];
let amount_read = reader.read(&mut buffer)?;
let subcommand_data = MessageSubcommand::Data.with_arg(amount_read as u32);

let mut serialized_message =
bincode::serialize(&subcommand_data).map_err(|_e| RustADBError::ConversionError)?;
serialized_message.append(&mut buffer[..amount_read].to_vec());

let message = ADBTransportMessage::new(
MessageCommand::Write,
local_id,
remote_id,
serialized_message,
);

self.send_and_expect_okay(message)?;

loop {
let mut buffer = [0; BUFFER_SIZE];

match reader.read(&mut buffer) {
Ok(0) => {
// Currently file mtime is not forwarded
let subcommand_data = MessageSubcommand::Done.with_arg(0);

let serialized_message = bincode::serialize(&subcommand_data)
.map_err(|_e| RustADBError::ConversionError)?;

let message = ADBTransportMessage::new(
MessageCommand::Write,
local_id,
remote_id,
serialized_message,
);

self.send_and_expect_okay(message)?;

// Command should end with a Write => Okay
let received = self.transport.read_message()?;
match received.header().command() {
MessageCommand::Write => return Ok(()),
c => {
return Err(RustADBError::ADBRequestFailed(format!(
"Wrong command received {}",
c
)))
}
}
}
Ok(size) => {
let subcommand_data = MessageSubcommand::Data.with_arg(size as u32);

let mut serialized_message = bincode::serialize(&subcommand_data)
.map_err(|_e| RustADBError::ConversionError)?;
serialized_message.append(&mut buffer[..size].to_vec());

let message = ADBTransportMessage::new(
MessageCommand::Write,
local_id,
remote_id,
serialized_message,
);

self.send_and_expect_okay(message)?;
}
Err(e) => {
return Err(RustADBError::IOError(e));
}
}
}
}

pub(crate) fn begin_synchronization(&mut self) -> Result<(u32, u32)> {
let sync_directive = "sync:\0";

let mut rng = rand::thread_rng();
let message = ADBTransportMessage::new(
MessageCommand::Open,
rng.gen(), /* Our 'local-id' */
0,
sync_directive.into(),
);
let message = self.send_and_expect_okay(message)?;
let local_id = message.header().arg1();
let remote_id = message.header().arg0();
Ok((local_id, remote_id))
}

pub(crate) fn stat_with_explicit_ids(
&mut self,
remote_path: &str,
local_id: u32,
remote_id: u32,
) -> Result<AdbStatResponse> {
let stat_buffer = MessageSubcommand::Stat.with_arg(remote_path.len() as u32);
let message = ADBTransportMessage::new(
MessageCommand::Write,
local_id,
remote_id,
bincode::serialize(&stat_buffer).map_err(|_e| RustADBError::ConversionError)?,
);
self.send_and_expect_okay(message)?;
self.send_and_expect_okay(ADBTransportMessage::new(
MessageCommand::Write,
local_id,
remote_id,
remote_path.into(),
))?;
let response = self.transport.read_message()?;
// Skip first 4 bytes as this is the literal "STAT".
// Interesting part starts right after
bincode::deserialize(&response.into_payload()[4..])
.map_err(|_e| RustADBError::ConversionError)
}

pub(crate) fn end_transaction(&mut self, local_id: u32, remote_id: u32) -> Result<()> {
let quit_buffer = MessageSubcommand::Quit.with_arg(0u32);
self.send_and_expect_okay(ADBTransportMessage::new(
MessageCommand::Write,
local_id,
remote_id,
bincode::serialize(&quit_buffer).map_err(|_e| RustADBError::ConversionError)?,
))?;
let _discard_close = self.transport.read_message()?;
Ok(())
}
}
38 changes: 38 additions & 0 deletions adb_client/src/device/adb_message_device_commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::{models::AdbStatResponse, ADBDeviceExt, ADBMessageTransport, RebootType, Result};
use std::io::{Read, Write};

use super::ADBMessageDevice;

impl<T: ADBMessageTransport> ADBDeviceExt for ADBMessageDevice<T> {
fn shell_command<S: ToString, W: Write>(
&mut self,
command: impl IntoIterator<Item = S>,
output: W,
) -> Result<()> {
self.shell_command(command, output)
}

fn shell<R: Read, W: Write + Send + 'static>(&mut self, reader: R, writer: W) -> Result<()> {
self.shell(reader, writer)
}

fn stat(&mut self, remote_path: &str) -> Result<AdbStatResponse> {
self.stat(remote_path)
}

fn pull<A: AsRef<str>, W: Write>(&mut self, source: A, output: W) -> Result<()> {
self.pull(source, output)
}

fn push<R: Read, A: AsRef<str>>(&mut self, stream: R, path: A) -> Result<()> {
self.push(stream, path)
}

fn reboot(&mut self, reboot_type: RebootType) -> Result<()> {
self.reboot(reboot_type)
}

fn install<P: AsRef<std::path::Path>>(&mut self, apk_path: P) -> Result<()> {
self.install(apk_path)
}
}
Loading

0 comments on commit ce0fe6a

Please sign in to comment.