/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pubmod adb; pubmod shell;
#[cfg(test)] pubmod test;
use log::{debug, info, trace, warn}; use once_cell::sync::Lazy; use regex::Regex; use std::collections::BTreeMap; use std::fs::File; use std::io::{self, Read, Write}; use std::net::TcpStream; use std::num::{ParseIntError, TryFromIntError}; use std::path::{Component, Path}; use std::str::{FromStr, Utf8Error}; use std::time::{Duration, SystemTime}; use thiserror::Error; pubuse unix_path::{Path as UnixPath, PathBuf as UnixPathBuf}; use uuid::Uuid; use walkdir::WalkDir;
if !bytes.starts_with(SyncCommand::Okay.code()) { let n = bytes.len().min(read_length(stream)?);
stream.read_exact(&mut bytes[0..n])?;
let message = std::str::from_utf8(&bytes[0..n]).map(|s| format!("adb error: {}", s))?;
return Err(DeviceError::Adb(message));
}
letmut response = Vec::new();
if has_output {
stream.read_to_end(&mut response)?;
if response.starts_with(SyncCommand::Okay.code()) { // Sometimes the server produces OKAYOKAY. Sometimes there is a transport OKAY and // then the underlying command OKAY. This is straight from `chromedriver`.
response = response.split_off(4);
}
if response.starts_with(SyncCommand::Fail.code()) { // The server may even produce OKAYFAIL, which means the underlying // command failed. First split-off the `FAIL` and length of the message.
response = response.split_off(8);
let message = std::str::from_utf8(&response).map(|s| format!("adb error: {}", s))?;
return Err(DeviceError::Adb(message));
}
if has_length { if response.len() >= 4 { let message = response.split_off(4); let slice: &mut &[u8] = &mut &*response;
let n = read_length(slice)?; if n != message.len() {
warn!("adb server response contained hexstring len {} but remaining message length is {}", n, message.len());
}
trace!( "adb server response was {:?}",
std::str::from_utf8(&message)?
);
return Ok(message);
} else { return Err(DeviceError::Adb(format!( "adb server response did not contain expected hexstring length: {:?}",
std::str::from_utf8(&response)?
)));
}
}
}
Ok(response)
}
/// Detailed information about an ADB device. #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] pubstruct DeviceInfo { pub serial: DeviceSerial, pub info: BTreeMap<String, String>,
}
/// Represents a connection to an ADB host, which multiplexes the connections to /// individual devices. #[derive(Debug)] pubstruct Host { /// The TCP host to connect to. Defaults to `"localhost"`. pub host: Option<String>, /// The TCP port to connect to. Defaults to `5037`. pub port: Option<u16>, /// Optional TCP read timeout duration. Defaults to 2s. pub read_timeout: Option<Duration>, /// Optional TCP write timeout duration. Defaults to 2s. pub write_timeout: Option<Duration>,
}
impl Host { /// Searches for available devices, and selects the one as specified by `device_serial`. /// /// If multiple devices are online, and no device has been specified, /// the `ANDROID_SERIAL` environment variable can be used to select one. pubfn device_or_default<T: AsRef<str>>( self,
device_serial: Option<&T>,
storage: AndroidStorageInput,
) -> Result<Device> { let serials: Vec<String> = self
.devices::<Vec<_>>()?
.into_iter()
.map(|d| d.serial)
.collect();
stream.write_all(encode_message(command)?.as_bytes())?; let bytes = read_response(&mut stream, has_output, has_length)?; // TODO: should we assert no bytes were read?
let switch_command = format!("host:transport:{}", self.serial);
trace!("execute_host_command: >> {:?}", &switch_command);
stream.write_all(encode_message(&switch_command)?.as_bytes())?; let _bytes = read_response(&mut stream, false, false)?;
trace!("execute_host_command: << {:?}", _bytes); // TODO: should we assert no bytes were read?
trace!("execute_host_command: >> {:?}", &command);
stream.write_all(encode_message(command)?.as_bytes())?; let bytes = read_response(&mut stream, has_output, has_length)?; let response = std::str::from_utf8(&bytes)?;
trace!("execute_host_command: << {:?}", response);
// Unify new lines by removing possible carriage returns
Ok(response.replace("\r\n", "\n"))
}
fn list_dir_flat(
&self,
src: &UnixPath,
depth: usize,
prefix: String,
) -> Result<Vec<RemoteDirEntry>> { // Implement the ADB protocol to list a directory from the device. letmut stream = self.host.connect()?;
// Send "host:transport" command with device serial let message = encode_message(&format!("host:transport:{}", self.serial))?;
stream.write_all(message.as_bytes())?; let _bytes = read_response(&mut stream, false, true)?;
// Send "sync:" command to initialize file transfer let message = encode_message("sync:")?;
stream.write_all(message.as_bytes())?; let _bytes = read_response(&mut stream, false, true)?;
// Send "LIST" command with name of the directory
stream.write_all(SyncCommand::List.code())?; let args_ = format!("{}", src.display()); let args = args_.as_bytes();
write_length_little_endian(&mut stream, args.len())?;
stream.write_all(args)?;
// Use the maximum 64KB buffer to transfer the file contents. letmut buf = [0; 64 * 1024];
letmut listings = Vec::new();
// Read "DENT" command one or more times for the directory entries loop {
stream.read_exact(&mut buf[0..4])?;
if &buf[0..4] == SyncCommand::Dent.code() { // From https://github.com/cstyan/adbDocumentation/blob/6d025b3e4af41be6f93d37f516a8ac7913688623/README.md: // // A four-byte integer representing file mode - first 9 bits of this mode represent // the file permissions, as with chmod mode. Bits 14 to 16 seem to represent the // file type, one of 0b100 (file), 0b010 (directory), 0b101 (symlink) // A four-byte integer representing file size. // A four-byte integer representing last modified time in seconds since Unix Epoch. // A four-byte integer representing file name length. // A utf-8 string representing the file name. let mode = read_length_little_endian(&mut stream)?; let size = read_length_little_endian(&mut stream)?; let _time = read_length_little_endian(&mut stream)?; let name_length = read_length_little_endian(&mut stream)?;
stream.read_exact(&mut buf[0..name_length])?;
letmut name = std::str::from_utf8(&buf[0..name_length])?.to_owned();
if name == "." || name == ".." { continue;
}
if !prefix.is_empty() {
name = format!("{}/{}", prefix, &name);
}
pubfn path_exists(&self, path: &UnixPath, enable_run_as: bool) -> Result<bool> { self.execute_host_shell_command_as(format!("ls {}", path.display()).as_str(), enable_run_as)
.map(|path| !path.contains("No such file or directory"))
}
pubfn pull(&self, src: &UnixPath, buffer: &mutdyn Write) -> Result<()> { // Implement the ADB protocol to receive a file from the device. letmut stream = self.host.connect()?;
// Send "host:transport" command with device serial let message = encode_message(&format!("host:transport:{}", self.serial))?;
stream.write_all(message.as_bytes())?; let _bytes = read_response(&mut stream, false, true)?;
// Send "sync:" command to initialize file transfer let message = encode_message("sync:")?;
stream.write_all(message.as_bytes())?; let _bytes = read_response(&mut stream, false, true)?;
// Send "RECV" command with name of the file
stream.write_all(SyncCommand::Recv.code())?; let args_string = format!("{}", src.display()); let args = args_string.as_bytes();
write_length_little_endian(&mut stream, args.len())?;
stream.write_all(args)?;
// Use the maximum 64KB buffer to transfer the file contents. letmut buf = [0; 64 * 1024];
// Read "DATA" command one or more times for the file content loop {
stream.read_exact(&mut buf[0..4])?;
if &buf[0..4] == SyncCommand::Data.code() { let len = read_length_little_endian(&mut stream)?;
stream.read_exact(&mut buf[0..len])?;
buffer.write_all(&buf[0..len])?;
} elseif &buf[0..4] == SyncCommand::Done.code() { // "DONE" command indicates end of file transfer break;
} elseif &buf[0..4] == SyncCommand::Fail.code() { let n = buf.len().min(read_length_little_endian(&mut stream)?);
stream.read_exact(&mut buf[0..n])?;
let message = std::str::from_utf8(&buf[0..n])
.map(|s| format!("adb error: {}", s))
.unwrap_or_else(|_| "adb error was not utf-8".into());
pubfn pull_dir(&self, src: &UnixPath, dest_dir: &Path) -> Result<()> { let src = src.to_path_buf(); let dest_dir = dest_dir.to_path_buf();
for entry inself.list_dir(&src)? { match entry.metadata {
RemoteMetadata::RemoteSymlink => {} // Ignored.
RemoteMetadata::RemoteDir => { letmut d = dest_dir.clone();
d.push(&entry.name);
std::fs::create_dir_all(&d)?;
}
RemoteMetadata::RemoteFile(_) => { letmut s = src.clone();
s.push(&entry.name); letmut d = dest_dir.clone();
d.push(&entry.name);
self.pull(&s, &mut File::create(d)?)?;
}
}
}
Ok(())
}
pubfn push(&self, buffer: &mutdyn Read, dest: &UnixPath, mode: u32) -> Result<()> { // Implement the ADB protocol to send a file to the device. // The protocol consists of the following steps: // * Send "host:transport" command with device serial // * Send "sync:" command to initialize file transfer // * Send "SEND" command with name and mode of the file // * Send "DATA" command one or more times for the file content // * Send "DONE" command to indicate end of file transfer
let enable_run_as = self.enable_run_as_for_path(&dest.to_path_buf()); let dest1 = match enable_run_as { true => self.tempfile.as_path(), false => UnixPath::new(dest),
};
// If the destination directory does not exist, adb will // create it and any necessary ancestors however it will not // set the directory permissions to 0o777. In addition, // Android 9 (P) has a bug in its push implementation which // will cause a push which creates directories to fail with // the error `secure_mkdirs failed: Operation not // permitted`. We can work around this by creating the // destination directories prior to the push. Collect the // ancestors of the destination directory which do not yet // exist so we can create them and adjust their permissions // prior to performing the push. letmut current = dest.parent(); letmut leaf: Option<&UnixPath> = None; letmut root: Option<&UnixPath> = None;
whilelet Some(path) = current { ifself.path_exists(path, enable_run_as)? { break;
} if leaf.is_none() {
leaf = Some(path);
}
root = Some(path);
current = path.parent();
}
// https://android.googlesource.com/platform/system/core/+/master/adb/SYNC.TXT#66 // // When the file is transferred a sync request "DONE" is sent, where length is set // to the last modified time for the file. The server responds to this last // request (but not to chunk requests) with an "OKAY" sync response (length can // be ignored). let time: u32 = ((SystemTime::now().duration_since(SystemTime::UNIX_EPOCH))
.unwrap()
.as_secs()
& 0xFFFF_FFFF) as u32;
stream.write_all(SyncCommand::Done.code())?;
write_length_little_endian(&mut stream, time as usize)?;
// Status.
stream.read_exact(&mut buf[0..4])?;
if buf.starts_with(SyncCommand::Okay.code()) { if enable_run_as { // Use cp -a to preserve the permissions set by push. let result = self.execute_host_shell_command_as(
format!("cp -aR {} {}", dest1.display(), dest.display()).as_str(),
enable_run_as,
); ifself.remove(dest1).is_err() {
warn!("Failed to remove {}", dest1.display());
}
result?;
}
Ok(())
} elseif buf.starts_with(SyncCommand::Fail.code()) { if enable_run_as && self.remove(dest1).is_err() {
warn!("Failed to remove {}", dest1.display());
} let n = buf.len().min(read_length_little_endian(&mut stream)?);
stream.read_exact(&mut buf[0..n])?;
let message = std::str::from_utf8(&buf[0..n])
.map(|s| format!("adb error: {}", s))
.unwrap_or_else(|_| "adb error was not utf-8".into());
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.