├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── adb_monitor.rs ├── byte_buffer.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "autoadb" 5 | version = "0.1.0" 6 | dependencies = [ 7 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "byteorder" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "cfg-if" 18 | version = "0.1.10" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | 21 | [[package]] 22 | name = "log" 23 | version = "0.4.8" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | dependencies = [ 26 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 27 | ] 28 | 29 | [metadata] 30 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 31 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 32 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "autoadb" 3 | version = "0.1.0" 4 | authors = ["Romain Vimont "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | log = "0.4" 11 | byteorder = "1.3" 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AutoAdb 2 | 3 | This command-line tool allows to execute a command whenever a new device is 4 | connected to adb. 5 | 6 | For example, to execute `printf` on device connection: 7 | 8 | ```bash 9 | autoadb printf 'Device connected\n' 10 | ``` 11 | 12 | `{}` replaces the `serial` of the device detected: 13 | 14 | ```bash 15 | autoadb printf 'Device %s connected\n' '{}' 16 | ``` 17 | 18 | It may be used to start [scrcpy]: 19 | 20 | ```bash 21 | autoadb scrcpy -s '{}' 22 | ``` 23 | 24 | [scrcpy]: https://github.com/Genymobile/scrcpy 25 | 26 | 27 | ## Build 28 | 29 | 30 | ``` 31 | cargo build --release 32 | ``` 33 | 34 | It will generate the binary in `target/release/autoadb`. 35 | 36 | 37 | ## Cross-compile from Linux to Windows 38 | 39 | To build `autoadb.exe` from Linux, install the cross-compile toolchain (on 40 | Debian): 41 | 42 | sudo apt install gcc-mingw-w64-x86-64 43 | rustup target add x86_64-pc-windows-gnu 44 | 45 | Add the following lines to `~/.cargo/config`: 46 | 47 | [target.x86_64-pc-windows-gnu] 48 | linker = "x86_64-w64-mingw32-gcc" 49 | ar = "x86_64-w64-mingw32-gcc-ar" 50 | 51 | Then build: 52 | 53 | cargo build --release --target=x86_64-pc-windows-gnu 54 | 55 | It will generate `target/x86_64-pc-windows-gnu/release/autoadb.exe`. 56 | 57 | 58 | ## Licence 59 | 60 | This project reuses the mechanism I implemented in [gnirehtet] and expose it as 61 | a standalone tool, so the licence is the same. 62 | 63 | [gnirehtet]: https://github.com/Genymobile/gnirehtet 64 | 65 | Copyright (C) 2017 Genymobile 66 | Copyright (C) 2019 Romain Vimont 67 | 68 | Licensed under the Apache License, Version 2.0 (the "License"); 69 | you may not use this file except in compliance with the License. 70 | You may obtain a copy of the License at 71 | 72 | http://www.apache.org/licenses/LICENSE-2.0 73 | 74 | Unless required by applicable law or agreed to in writing, software 75 | distributed under the License is distributed on an "AS IS" BASIS, 76 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 77 | See the License for the specific language governing permissions and 78 | limitations under the License. 79 | -------------------------------------------------------------------------------- /src/adb_monitor.rs: -------------------------------------------------------------------------------- 1 | // Copied from gnirehtet/relay-rust/src/adb_monitor.rs and modified 2 | /* 3 | * Copyright (C) 2017 Genymobile 4 | * Copyright (C) 2019 Romain Vimont 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | use crate::byte_buffer::ByteBuffer; 20 | use log::*; 21 | use std::io::{self, Write}; 22 | use std::net::{SocketAddr, TcpStream}; 23 | use std::process; 24 | use std::str; 25 | use std::thread; 26 | use std::time::Duration; 27 | 28 | const TAG: &str = "AdbMonitor"; 29 | 30 | pub trait AdbMonitorCallback { 31 | fn on_new_device_connected(&self, serial: &str); 32 | } 33 | 34 | impl AdbMonitorCallback for F 35 | where 36 | F: Fn(&str), 37 | { 38 | fn on_new_device_connected(&self, serial: &str) { 39 | self(serial); 40 | } 41 | } 42 | pub struct AdbMonitor { 43 | callback: Box, 44 | buf: ByteBuffer, 45 | connected_devices: Vec, 46 | } 47 | 48 | impl AdbMonitor { 49 | const TRACK_DEVICES_REQUEST: &'static [u8] = b"0012host:track-devices"; 50 | const BUFFER_SIZE: usize = 1024; 51 | const RETRY_DELAY_ADB_DAEMON_OK: u64 = 1000; 52 | const RETRY_DELAY_ADB_DAEMON_KO: u64 = 5000; 53 | 54 | pub fn new(callback: Box) -> Self { 55 | Self { 56 | callback, 57 | buf: ByteBuffer::new(Self::BUFFER_SIZE), 58 | connected_devices: Vec::new(), 59 | } 60 | } 61 | 62 | pub fn monitor(&mut self) { 63 | loop { 64 | if let Err(err) = self.track_devices() { 65 | error!(target: TAG, "Failed to monitor adb devices: {}", err); 66 | Self::repair_adb_daemon(); 67 | } 68 | } 69 | } 70 | 71 | fn track_devices(&mut self) -> io::Result<()> { 72 | let adbd_addr = SocketAddr::from(([127, 0, 0, 1], 5037)); 73 | let mut stream = TcpStream::connect(adbd_addr)?; 74 | self.track_devices_on_stream(&mut stream) 75 | } 76 | 77 | fn track_devices_on_stream(&mut self, stream: &mut TcpStream) -> io::Result<()> { 78 | stream.write_all(Self::TRACK_DEVICES_REQUEST)?; 79 | if self.consume_okay(stream)? { 80 | loop { 81 | let packet = self.next_packet(stream)?; 82 | self.handle_packet(packet.as_str()); 83 | } 84 | } 85 | Ok(()) 86 | } 87 | 88 | fn consume_okay(&mut self, stream: &mut TcpStream) -> io::Result { 89 | while self.buf.peek().len() < 4 { 90 | self.buf.read_from(stream)?; 91 | } 92 | let ok = b"OKAY" == &self.buf.peek()[0..4]; 93 | self.buf.consume(4); 94 | Ok(ok) 95 | } 96 | 97 | fn read_packet(buf: &mut ByteBuffer) -> io::Result> { 98 | let packet_length = Self::available_packet_length(buf.peek())?; 99 | if let Some(len) = packet_length { 100 | // retrieve the content and consume the packet 101 | let data = Self::binary_to_string(&buf.peek()[4..len])?; 102 | buf.consume(len); 103 | Ok(Some(data)) 104 | } else { 105 | Ok(None) 106 | } 107 | } 108 | 109 | fn next_packet(&mut self, stream: &mut TcpStream) -> io::Result { 110 | loop { 111 | let packet = Self::read_packet(&mut self.buf)?; 112 | if let Some(packet) = packet { 113 | return Ok(packet); 114 | } else { 115 | self.fill_buffer_from(stream)?; 116 | } 117 | } 118 | } 119 | 120 | fn fill_buffer_from(&mut self, stream: &mut TcpStream) -> io::Result<()> { 121 | match self.buf.read_from(stream) { 122 | Ok(false) => Err(io::Error::new( 123 | io::ErrorKind::UnexpectedEof, 124 | "ADB daemon closed the track-devices connexion", 125 | )), 126 | Ok(_) => Ok(()), 127 | Err(err) => Err(err), 128 | } 129 | } 130 | 131 | fn available_packet_length(input: &[u8]) -> io::Result> { 132 | if input.len() < 4 { 133 | Ok(None) 134 | } else { 135 | // each packet contains 4 bytes representing the String length in hexa, followed by a 136 | // list of device information; 137 | // each line contains: the device serial, `\t', the state, '\n' 138 | // for example: 139 | // "00360123456789abcdef\tdevice\nfedcba9876543210\tunauthorized\n": 140 | // - 0036 indicates that the data is 0x36 (54) bytes length 141 | // - the device with serial 0123456789abcdef is connected 142 | // - the device with serial fedcba9876543210 is unauthorized 143 | let len = Self::parse_length(&input[0..4])?; 144 | if len > Self::BUFFER_SIZE as u32 { 145 | return Err(io::Error::new( 146 | io::ErrorKind::InvalidInput, 147 | format!("Packet size should not be that big: {}", len), 148 | )); 149 | } 150 | if input.len() - 4usize >= len as usize { 151 | Ok(Some(4usize + len as usize)) 152 | } else { 153 | // not enough data 154 | Ok(None) 155 | } 156 | } 157 | } 158 | 159 | fn handle_packet(&mut self, packet: &str) { 160 | let current_connected_devices = self.parse_connected_devices(packet); 161 | for serial in ¤t_connected_devices { 162 | if !self.connected_devices.contains(serial) { 163 | self.callback.on_new_device_connected(serial.as_str()); 164 | } 165 | } 166 | self.connected_devices = current_connected_devices; 167 | } 168 | 169 | fn parse_connected_devices(&self, packet: &str) -> Vec { 170 | packet 171 | .lines() 172 | .filter_map(|line| { 173 | let mut split = line.split_whitespace(); 174 | if let Some(serial) = split.next() { 175 | if let Some(state) = split.next() { 176 | if state == "device" { 177 | return Some(serial.to_string()); 178 | } 179 | } 180 | } 181 | None 182 | }) 183 | .collect() 184 | } 185 | 186 | fn parse_length(data: &[u8]) -> io::Result { 187 | assert!(data.len() == 4, "Invalid length field value"); 188 | let hexa = str::from_utf8(data).map_err(|err| { 189 | io::Error::new( 190 | io::ErrorKind::InvalidInput, 191 | format!("Cannot read hexa length as UTF-8 ({})", err), 192 | ) 193 | })?; 194 | u32::from_str_radix(hexa, 0x10).map_err(|err| { 195 | io::Error::new( 196 | io::ErrorKind::InvalidInput, 197 | format!("Cannot parse hexa length ({})", err), 198 | ) 199 | }) 200 | } 201 | 202 | fn repair_adb_daemon() { 203 | if Self::start_adb_daemon() { 204 | thread::sleep(Duration::from_millis(Self::RETRY_DELAY_ADB_DAEMON_OK)); 205 | } else { 206 | thread::sleep(Duration::from_millis(Self::RETRY_DELAY_ADB_DAEMON_KO)); 207 | } 208 | } 209 | 210 | fn start_adb_daemon() -> bool { 211 | info!(target: TAG, "Restarting adb daemon"); 212 | match process::Command::new("adb") 213 | .args(&["start-server"]) 214 | .status() 215 | { 216 | Ok(exit_status) => { 217 | if exit_status.success() { 218 | true 219 | } else { 220 | error!( 221 | target: TAG, 222 | "Could not restart adb daemon (exited on error)" 223 | ); 224 | false 225 | } 226 | } 227 | Err(err) => { 228 | error!(target: TAG, "Could not restart adb daemon: {}", err); 229 | false 230 | } 231 | } 232 | } 233 | 234 | fn binary_to_string(data: &[u8]) -> io::Result { 235 | let raw_content = data.to_vec(); 236 | let content = String::from_utf8(raw_content); 237 | if let Ok(content) = content { 238 | Ok(content) 239 | } else { 240 | Err(io::Error::new( 241 | io::ErrorKind::InvalidInput, 242 | "Track-devices string is not valid UTF-8", 243 | )) 244 | } 245 | } 246 | } 247 | 248 | #[cfg(test)] 249 | mod tests { 250 | use super::*; 251 | use std::cell::RefCell; 252 | use std::rc::Rc; 253 | 254 | #[test] 255 | fn test_read_valid_packet() { 256 | let mut buf = ByteBuffer::new(64); 257 | let raw = "00180123456789ABCDEF\tdevice\n".as_bytes(); 258 | 259 | let mut cursor = io::Cursor::new(raw); 260 | buf.read_from(&mut cursor).unwrap(); 261 | 262 | let packet = AdbMonitor::read_packet(&mut buf).unwrap().unwrap(); 263 | assert_eq!("0123456789ABCDEF\tdevice\n", packet); 264 | } 265 | 266 | #[test] 267 | fn test_read_valid_packets() { 268 | let mut buf = ByteBuffer::new(64); 269 | let raw = "00300123456789ABCDEF\tdevice\nFEDCBA9876543210\tdevice\n".as_bytes(); 270 | 271 | let mut cursor = io::Cursor::new(raw); 272 | buf.read_from(&mut cursor).unwrap(); 273 | 274 | let packet = AdbMonitor::read_packet(&mut buf).unwrap().unwrap(); 275 | assert_eq!( 276 | "0123456789ABCDEF\tdevice\nFEDCBA9876543210\tdevice\n", 277 | packet 278 | ); 279 | } 280 | 281 | #[test] 282 | fn test_read_valid_packet_with_garbage() { 283 | let mut buf = ByteBuffer::new(64); 284 | let raw = "00180123456789ABCDEF\tdevice\ngarbage".as_bytes(); 285 | 286 | let mut cursor = io::Cursor::new(raw); 287 | buf.read_from(&mut cursor).unwrap(); 288 | 289 | let packet = AdbMonitor::read_packet(&mut buf).unwrap().unwrap(); 290 | assert_eq!("0123456789ABCDEF\tdevice\n", packet); 291 | } 292 | 293 | #[test] 294 | fn test_read_short_packet() { 295 | let mut buf = ByteBuffer::new(64); 296 | let raw = "00180123456789ABCDEF\tdevi".as_bytes(); 297 | 298 | let mut cursor = io::Cursor::new(raw); 299 | buf.read_from(&mut cursor).unwrap(); 300 | 301 | let packet = AdbMonitor::read_packet(&mut buf).unwrap(); 302 | assert!(packet.is_none()); 303 | } 304 | 305 | #[test] 306 | fn test_handle_packet_device() { 307 | let serial = Rc::new(RefCell::new(None)); 308 | let serial_clone = serial.clone(); 309 | 310 | let mut monitor = AdbMonitor::new(Box::new(move |serial: &str| { 311 | serial_clone.replace(Some(serial.to_string())); 312 | })); 313 | monitor.handle_packet("0123456789ABCDEF\tdevice\n"); 314 | 315 | assert_eq!("0123456789ABCDEF", serial.borrow().as_ref().unwrap()); 316 | } 317 | 318 | #[test] 319 | fn test_handle_packet_offline() { 320 | let serial = Rc::new(RefCell::new(None)); 321 | let serial_clone = serial.clone(); 322 | 323 | let mut monitor = AdbMonitor::new(Box::new(move |serial: &str| { 324 | serial_clone.replace(Some(serial.to_string())); 325 | })); 326 | monitor.handle_packet("0123456789ABCDEF\toffline\n"); 327 | 328 | assert!(serial.borrow().is_none()); 329 | } 330 | 331 | #[test] 332 | fn test_multiple_connected_devices() { 333 | let serials = Rc::new(RefCell::new(Vec::new())); 334 | let serials_clone = serials.clone(); 335 | 336 | let mut monitor = AdbMonitor::new(Box::new(move |serial: &str| { 337 | serials_clone.borrow_mut().push(serial.to_string()); 338 | })); 339 | monitor.handle_packet("0123456789ABCDEF\tdevice\nFEDCBA9876543210\tdevice\n"); 340 | 341 | let vec = serials.borrow(); 342 | assert_eq!(2, vec.len()); 343 | assert_eq!("0123456789ABCDEF", vec[0]); 344 | assert_eq!("FEDCBA9876543210", vec[1]); 345 | } 346 | 347 | #[test] 348 | fn test_multiple_connected_devices_with_disconnection() { 349 | let serials = Rc::new(RefCell::new(Vec::new())); 350 | let serials_clone = serials.clone(); 351 | 352 | let mut monitor = AdbMonitor::new(Box::new(move |serial: &str| { 353 | serials_clone.borrow_mut().push(serial.to_string()); 354 | })); 355 | monitor.handle_packet("0123456789ABCDEF\tdevice\nFEDCBA9876543210\tdevice\n"); 356 | monitor.handle_packet("0123456789ABCDEF\tdevice\n"); 357 | monitor.handle_packet("0123456789ABCDEF\tdevice\nFEDCBA9876543210\tdevice\n"); 358 | 359 | let vec = serials.borrow(); 360 | assert_eq!(3, vec.len()); 361 | assert_eq!("0123456789ABCDEF", vec[0]); 362 | assert_eq!("FEDCBA9876543210", vec[1]); 363 | assert_eq!("FEDCBA9876543210", vec[2]); 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /src/byte_buffer.rs: -------------------------------------------------------------------------------- 1 | // Copied from gnirehtet/relay-rust/src/relay/byte_buffer.rs and modified 2 | /* 3 | * Copyright (C) 2017 Genymobile 4 | * Copyright (C) 2019 Romain Vimont 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | use std::io; 20 | use std::ptr; 21 | 22 | pub struct ByteBuffer { 23 | buf: Box<[u8]>, 24 | head: usize, 25 | } 26 | 27 | impl ByteBuffer { 28 | pub fn new(length: usize) -> Self { 29 | Self { 30 | buf: vec![0; length].into_boxed_slice(), 31 | head: 0, 32 | } 33 | } 34 | 35 | pub fn read_from(&mut self, source: &mut R) -> io::Result { 36 | let target_slice = &mut self.buf[self.head..]; 37 | let r = source.read(target_slice)?; 38 | self.head += r; 39 | Ok(r > 0) 40 | } 41 | 42 | pub fn peek(&self) -> &[u8] { 43 | &self.buf[..self.head] 44 | } 45 | 46 | #[allow(unused)] 47 | pub fn peek_mut(&mut self) -> &mut [u8] { 48 | &mut self.buf[..self.head] 49 | } 50 | 51 | pub fn consume(&mut self, length: usize) { 52 | assert!(self.head >= length); 53 | self.head -= length; 54 | if self.head > 0 { 55 | // some data remaining, move them to the front of the buffer 56 | unsafe { 57 | let buf_ptr = self.buf.as_mut_ptr(); 58 | 59 | // Before: 60 | // 61 | // consumed old_head 62 | // | |....................| 63 | // <------> 64 | // length 65 | // 66 | // After: 67 | // 68 | // new_head (= old_head - length) 69 | // |....................| 70 | // <------> 71 | // length 72 | // 73 | // move from [length..old_head] to [0..new_head] 74 | // 75 | // semantically equivalent to memmove() 76 | ptr::copy(buf_ptr.add(length), buf_ptr, self.head); 77 | } 78 | } 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::*; 85 | use std::io; 86 | 87 | #[test] 88 | fn produce_consume_byte_buffer() { 89 | let raw = "hello, world!".as_bytes(); 90 | let mut byte_buffer = ByteBuffer::new(64); 91 | 92 | let mut cursor = io::Cursor::new(raw); 93 | byte_buffer.read_from(&mut cursor).unwrap(); 94 | assert_eq!("hello, world!".as_bytes(), byte_buffer.peek()); 95 | 96 | byte_buffer.consume(7); 97 | assert_eq!("world!".as_bytes(), byte_buffer.peek()); 98 | 99 | let mut cursor = io::Cursor::new(&raw[..5]); 100 | byte_buffer.read_from(&mut cursor).unwrap(); 101 | assert_eq!("world!hello".as_bytes(), byte_buffer.peek()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Romain Vimont 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | mod adb_monitor; 18 | mod byte_buffer; 19 | 20 | use crate::adb_monitor::{AdbMonitor, AdbMonitorCallback}; 21 | use std::env; 22 | use std::process::Command; 23 | 24 | struct AutoAdb { 25 | command: Vec, 26 | } 27 | 28 | impl AdbMonitorCallback for AutoAdb { 29 | fn on_new_device_connected(&self, serial: &str) { 30 | let cmd = self 31 | .command 32 | .iter() 33 | .map(|value| { 34 | // replace any {} parameter by the actual serial 35 | if "{}" == value { 36 | serial.to_string() 37 | } else { 38 | value.to_string() 39 | } 40 | }) 41 | .collect::>(); 42 | println!("Detected device {}", serial); 43 | let process = Command::new(&cmd[0]).args(cmd.iter().skip(1)).spawn(); 44 | if let Err(err) = process { 45 | eprintln!("Could not execute {:?}: {}", cmd, err); 46 | } 47 | } 48 | } 49 | 50 | fn main() { 51 | let command = env::args().skip(1).collect::>(); 52 | if command.is_empty() { 53 | eprintln!("No arguments given"); 54 | return; 55 | } 56 | let auto_adb = AutoAdb { command }; 57 | let mut adb_monitor = AdbMonitor::new(Box::new(auto_adb)); 58 | adb_monitor.monitor(); 59 | } 60 | --------------------------------------------------------------------------------