diff --git a/adb_cli/src/handlers/local_commands.rs b/adb_cli/src/handlers/local_commands.rs index 6ff5d91..38160ee 100644 --- a/adb_cli/src/handlers/local_commands.rs +++ b/adb_cli/src/handlers/local_commands.rs @@ -21,25 +21,6 @@ pub fn handle_local_commands( Ok(()) } - LocalDeviceCommand::List { path } => { - let dirs = device.list(path)?; - for dir in dirs { - let list_item_type = match dir.item_type { - ADBListItemType::File => "File".to_string(), - ADBListItemType::Directory => "Dir".to_string(), - ADBListItemType::Symlink => "Symlink".to_string(), - }; - log::info!( - "type: {}, name: {}, time: {}, size: {}, permissions: {:#o}", - list_item_type, - dir.name, - dir.time, - dir.size, - dir.permissions - ); - } - Ok(()) - } LocalDeviceCommand::Logcat { path } => { let writer: Box = if let Some(path) = path { let f = File::create(path)?; diff --git a/adb_cli/src/models/local.rs b/adb_cli/src/models/local.rs index 188b610..c04014c 100644 --- a/adb_cli/src/models/local.rs +++ b/adb_cli/src/models/local.rs @@ -14,8 +14,6 @@ pub enum LocalCommand { pub enum LocalDeviceCommand { /// List available server features. HostFeatures, - /// List a directory on device - List { path: String }, /// Get logs of device Logcat { /// Path to output file (created if not exists) diff --git a/adb_client/src/device/commands/list.rs b/adb_client/src/device/commands/list.rs index 5713437..ba93718 100644 --- a/adb_client/src/device/commands/list.rs +++ b/adb_client/src/device/commands/list.rs @@ -1,174 +1,174 @@ -use crate::{ - device::{ - adb_message_device::ADBMessageDevice, ADBTransportMessage, MessageCommand, - MessageSubcommand, - }, - ADBListItem, ADBListItemType, ADBMessageTransport, Result, RustADBError, -}; -use byteorder::ByteOrder; -use byteorder::LittleEndian; -use std::str; - -impl ADBMessageDevice { - /// List the entries in the given directory on the device. - /// note: path uses internal file paths, so Documents is at /storage/emulated/0/Documents - pub(crate) fn list(&mut self, path: &str) -> Result> { - self.begin_synchronization()?; - - let output = self.handle_list(path); - - self.end_transaction()?; - output - } - - /// Request amount of bytes from transport, potentially across payloads - /// - /// This automatically request a new payload by sending back "Okay" and waiting for the next payload - /// It reads the request bytes across the existing payload, and if there is not enough bytes left, - /// reads the rest from the next payload - /// - /// Current index - /// ┼───────────────┼ Requested - /// ┌─────────────┐ - /// ┌───────────────┼───────┐ │ - /// └───────────────────────┘ - /// Current └─────┘ - /// payload Wanted in - /// Next payload - fn read_bytes_from_transport( - requested_bytes: &usize, - current_index: &mut usize, - transport: &mut T, - payload: &mut Vec, - local_id: &u32, - remote_id: &u32, - ) -> Result> { - if *current_index + requested_bytes <= payload.len() { - // if there is enough bytes in this payload - // Copy from existing payload - let slice = &payload[*current_index..*current_index + requested_bytes]; - *current_index += requested_bytes; - Ok(slice.to_vec()) - } else { - // Read the rest of the existing payload, then continue with the next message - let mut slice = Vec::new(); - let bytes_read_from_existing_payload = payload.len() - *current_index; - slice.extend_from_slice( - &payload[*current_index..*current_index + bytes_read_from_existing_payload], - ); - - // Request the next message - let send_message = - ADBTransportMessage::new(MessageCommand::Okay, *local_id, *remote_id, &[]); - transport.write_message(send_message)?; - // Read the new message - *payload = transport.read_message()?.into_payload(); - let bytes_read_from_new_payload = requested_bytes - bytes_read_from_existing_payload; - slice.extend_from_slice(&payload[..bytes_read_from_new_payload]); - *current_index = bytes_read_from_new_payload; - Ok(slice) - } - } - - fn handle_list(&mut self, path: &str) -> Result> { - // TODO: use LIS2 to support files over 2.14 GB in size. - // SEE: https://github.com/cstyan/adbDocumentation?tab=readme-ov-file#adb-list - let local_id = self.get_local_id()?; - let remote_id = self.get_remote_id()?; - { - let mut len_buf = Vec::from([0_u8; 4]); - LittleEndian::write_u32(&mut len_buf, path.len() as u32); - - let subcommand_data = MessageSubcommand::List; //.with_arg(path.len() as u32); - - let mut serialized_message = - bincode::serialize(&subcommand_data).map_err(|_e| RustADBError::ConversionError)?; - - serialized_message.append(&mut len_buf); - let mut path_bytes: Vec = Vec::from(path.as_bytes()); - serialized_message.append(&mut path_bytes); - - let message = ADBTransportMessage::new( - MessageCommand::Write, - local_id, - remote_id, - &serialized_message, - ); - self.send_and_expect_okay(message)?; - } - - let mut list_items = Vec::new(); - - let transport = self.get_transport_mut(); - let mut payload = transport.read_message()?.into_payload(); - let mut current_index = 0; - loop { - // Loop though the response for all the entries - const STATUS_CODE_LENGTH_IN_BYTES: usize = 4; - let status_code = Self::read_bytes_from_transport( - &STATUS_CODE_LENGTH_IN_BYTES, - &mut current_index, - transport, - &mut payload, - &local_id, - &remote_id, - )?; - match str::from_utf8(&status_code)? { - "DENT" => { - // Read the file mode, size, mod time and name length in one go, since all their sizes are predictable - const U32_SIZE_IN_BYTES: usize = 4; - const SIZE_OF_METADATA: usize = U32_SIZE_IN_BYTES * 4; - let metadata = Self::read_bytes_from_transport( - &SIZE_OF_METADATA, - &mut current_index, - transport, - &mut payload, - &local_id, - &remote_id, - )?; - let mode = metadata[..U32_SIZE_IN_BYTES].to_vec(); - let size = metadata[U32_SIZE_IN_BYTES..2 * U32_SIZE_IN_BYTES].to_vec(); - let time = metadata[2 * U32_SIZE_IN_BYTES..3 * U32_SIZE_IN_BYTES].to_vec(); - let name_len = metadata[3 * U32_SIZE_IN_BYTES..4 * U32_SIZE_IN_BYTES].to_vec(); - - let mode = LittleEndian::read_u32(&mode); - let size = LittleEndian::read_u32(&size); - let time = LittleEndian::read_u32(&time); - let name_len = LittleEndian::read_u32(&name_len) as usize; - // Read the file name, since it requires the length from the name_len - let name_buf = Self::read_bytes_from_transport( - &name_len, - &mut current_index, - transport, - &mut payload, - &local_id, - &remote_id, - )?; - let name = String::from_utf8(name_buf)?; - - // First 9 bits are the file permissions - let permissions = mode & 0b111111111; - // Bits 14 to 16 are the file type - let item_type = match (mode >> 13) & 0b111 { - 0b010 => ADBListItemType::Directory, - 0b100 => ADBListItemType::File, - 0b101 => ADBListItemType::Symlink, - type_code => return Err(RustADBError::UnknownFileMode(type_code)), - }; - let entry = ADBListItem { - item_type, - name, - time, - size, - permissions, - }; - list_items.push(entry); - } - "DONE" => { - return Ok(list_items); - } - x => log::error!("Got an unknown response {}", x), - } - } - } -} +use crate::{ + device::{ + adb_message_device::ADBMessageDevice, ADBTransportMessage, MessageCommand, + MessageSubcommand, + }, + ADBListItem, ADBListItemType, ADBMessageTransport, Result, RustADBError, +}; +use byteorder::ByteOrder; +use byteorder::LittleEndian; +use std::str; + +impl ADBMessageDevice { + /// List the entries in the given directory on the device. + /// note: path uses internal file paths, so Documents is at /storage/emulated/0/Documents + pub(crate) fn list(&mut self, path: &str) -> Result> { + self.begin_synchronization()?; + + let output = self.handle_list(path); + + self.end_transaction()?; + output + } + + /// Request amount of bytes from transport, potentially across payloads + /// + /// This automatically request a new payload by sending back "Okay" and waiting for the next payload + /// It reads the request bytes across the existing payload, and if there is not enough bytes left, + /// reads the rest from the next payload + /// + /// Current index + /// ┼───────────────┼ Requested + /// ┌─────────────┐ + /// ┌───────────────┼───────┐ │ + /// └───────────────────────┘ + /// Current └─────┘ + /// payload Wanted in + /// Next payload + fn read_bytes_from_transport( + requested_bytes: &usize, + current_index: &mut usize, + transport: &mut T, + payload: &mut Vec, + local_id: &u32, + remote_id: &u32, + ) -> Result> { + if *current_index + requested_bytes <= payload.len() { + // if there is enough bytes in this payload + // Copy from existing payload + let slice = &payload[*current_index..*current_index + requested_bytes]; + *current_index += requested_bytes; + Ok(slice.to_vec()) + } else { + // Read the rest of the existing payload, then continue with the next message + let mut slice = Vec::new(); + let bytes_read_from_existing_payload = payload.len() - *current_index; + slice.extend_from_slice( + &payload[*current_index..*current_index + bytes_read_from_existing_payload], + ); + + // Request the next message + let send_message = + ADBTransportMessage::new(MessageCommand::Okay, *local_id, *remote_id, &[]); + transport.write_message(send_message)?; + // Read the new message + *payload = transport.read_message()?.into_payload(); + let bytes_read_from_new_payload = requested_bytes - bytes_read_from_existing_payload; + slice.extend_from_slice(&payload[..bytes_read_from_new_payload]); + *current_index = bytes_read_from_new_payload; + Ok(slice) + } + } + + fn handle_list(&mut self, path: &str) -> Result> { + // TODO: use LIS2 to support files over 2.14 GB in size. + // SEE: https://github.com/cstyan/adbDocumentation?tab=readme-ov-file#adb-list + let local_id = self.get_local_id()?; + let remote_id = self.get_remote_id()?; + { + let mut len_buf = Vec::from([0_u8; 4]); + LittleEndian::write_u32(&mut len_buf, path.len() as u32); + + let subcommand_data = MessageSubcommand::List; //.with_arg(path.len() as u32); + + let mut serialized_message = + bincode::serialize(&subcommand_data).map_err(|_e| RustADBError::ConversionError)?; + + serialized_message.append(&mut len_buf); + let mut path_bytes: Vec = Vec::from(path.as_bytes()); + serialized_message.append(&mut path_bytes); + + let message = ADBTransportMessage::new( + MessageCommand::Write, + local_id, + remote_id, + &serialized_message, + ); + self.send_and_expect_okay(message)?; + } + + let mut list_items = Vec::new(); + + let transport = self.get_transport_mut(); + let mut payload = transport.read_message()?.into_payload(); + let mut current_index = 0; + loop { + // Loop though the response for all the entries + const STATUS_CODE_LENGTH_IN_BYTES: usize = 4; + let status_code = Self::read_bytes_from_transport( + &STATUS_CODE_LENGTH_IN_BYTES, + &mut current_index, + transport, + &mut payload, + &local_id, + &remote_id, + )?; + match str::from_utf8(&status_code)? { + "DENT" => { + // Read the file mode, size, mod time and name length in one go, since all their sizes are predictable + const U32_SIZE_IN_BYTES: usize = 4; + const SIZE_OF_METADATA: usize = U32_SIZE_IN_BYTES * 4; + let metadata = Self::read_bytes_from_transport( + &SIZE_OF_METADATA, + &mut current_index, + transport, + &mut payload, + &local_id, + &remote_id, + )?; + let mode = metadata[..U32_SIZE_IN_BYTES].to_vec(); + let size = metadata[U32_SIZE_IN_BYTES..2 * U32_SIZE_IN_BYTES].to_vec(); + let time = metadata[2 * U32_SIZE_IN_BYTES..3 * U32_SIZE_IN_BYTES].to_vec(); + let name_len = metadata[3 * U32_SIZE_IN_BYTES..4 * U32_SIZE_IN_BYTES].to_vec(); + + let mode = LittleEndian::read_u32(&mode); + let size = LittleEndian::read_u32(&size); + let time = LittleEndian::read_u32(&time); + let name_len = LittleEndian::read_u32(&name_len) as usize; + // Read the file name, since it requires the length from the name_len + let name_buf = Self::read_bytes_from_transport( + &name_len, + &mut current_index, + transport, + &mut payload, + &local_id, + &remote_id, + )?; + let name = String::from_utf8(name_buf)?; + + // First 9 bits are the file permissions + let permissions = mode & 0b111111111; + // Bits 14 to 16 are the file type + let item_type = match (mode >> 13) & 0b111 { + 0b010 => ADBListItemType::Directory, + 0b100 => ADBListItemType::File, + 0b101 => ADBListItemType::Symlink, + type_code => return Err(RustADBError::UnknownFileMode(type_code)), + }; + let entry = ADBListItem { + item_type, + name, + time, + size, + permissions, + }; + list_items.push(entry); + } + "DONE" => { + return Ok(list_items); + } + x => log::error!("Got an unknown response {}", x), + } + } + } +}