├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── LICENSE ├── README.md ├── adbclient.iml ├── build_deb.sh ├── releases └── adbr_1.0.0-1.deb ├── src ├── adb │ ├── app_installation │ │ ├── install.rs │ │ ├── mod.rs │ │ └── uninstall.rs │ ├── client.rs │ ├── debugging │ │ ├── debugging.rs │ │ └── mod.rs │ ├── file_transfer │ │ ├── mod.rs │ │ ├── pull.rs │ │ └── push.rs │ ├── io │ │ ├── io.rs │ │ └── mod.rs │ ├── mod.rs │ ├── network │ │ ├── forward.rs │ │ ├── mod.rs │ │ └── reverse.rs │ ├── protocol │ │ ├── mod.rs │ │ └── protocol.rs │ ├── scripting │ │ ├── mod.rs │ │ └── scripting.rs │ ├── security │ │ ├── mod.rs │ │ └── security.rs │ └── shell │ │ ├── mod.rs │ │ └── shell.rs ├── constants.rs ├── enums │ ├── device_transport.rs │ ├── mod.rs │ ├── pull_result.rs │ └── push_result.rs ├── lib.rs ├── main.rs ├── models │ ├── mod.rs │ ├── remote_dir_entry.rs │ ├── remote_metadata.rs │ └── stat_data.rs └── utils.rs └── tests ├── client_tests.rs └── resources └── test_app.apk /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adbr" 7 | version = "1.0.0" 8 | dependencies = [ 9 | "atty", 10 | "chrono", 11 | "ctor", 12 | "dirs", 13 | "filetime", 14 | "indicatif", 15 | "nix", 16 | "openssl", 17 | "rand", 18 | "termios", 19 | "tokio", 20 | "walkdir", 21 | ] 22 | 23 | [[package]] 24 | name = "addr2line" 25 | version = "0.21.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 28 | dependencies = [ 29 | "gimli", 30 | ] 31 | 32 | [[package]] 33 | name = "adler" 34 | version = "1.0.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 37 | 38 | [[package]] 39 | name = "android-tzdata" 40 | version = "0.1.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 43 | 44 | [[package]] 45 | name = "android_system_properties" 46 | version = "0.1.5" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 49 | dependencies = [ 50 | "libc", 51 | ] 52 | 53 | [[package]] 54 | name = "atty" 55 | version = "0.2.14" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 58 | dependencies = [ 59 | "hermit-abi 0.1.19", 60 | "libc", 61 | "winapi", 62 | ] 63 | 64 | [[package]] 65 | name = "autocfg" 66 | version = "1.3.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 69 | 70 | [[package]] 71 | name = "backtrace" 72 | version = "0.3.71" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 75 | dependencies = [ 76 | "addr2line", 77 | "cc", 78 | "cfg-if", 79 | "libc", 80 | "miniz_oxide", 81 | "object", 82 | "rustc-demangle", 83 | ] 84 | 85 | [[package]] 86 | name = "bitflags" 87 | version = "2.5.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 90 | 91 | [[package]] 92 | name = "bumpalo" 93 | version = "3.16.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 96 | 97 | [[package]] 98 | name = "byteorder" 99 | version = "1.5.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 102 | 103 | [[package]] 104 | name = "bytes" 105 | version = "1.6.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 108 | 109 | [[package]] 110 | name = "cc" 111 | version = "1.0.98" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" 114 | 115 | [[package]] 116 | name = "cfg-if" 117 | version = "1.0.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 120 | 121 | [[package]] 122 | name = "cfg_aliases" 123 | version = "0.2.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 126 | 127 | [[package]] 128 | name = "chrono" 129 | version = "0.4.38" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 132 | dependencies = [ 133 | "android-tzdata", 134 | "iana-time-zone", 135 | "js-sys", 136 | "num-traits", 137 | "wasm-bindgen", 138 | "windows-targets 0.52.6", 139 | ] 140 | 141 | [[package]] 142 | name = "console" 143 | version = "0.15.8" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 146 | dependencies = [ 147 | "encode_unicode", 148 | "lazy_static", 149 | "libc", 150 | "unicode-width", 151 | "windows-sys 0.52.0", 152 | ] 153 | 154 | [[package]] 155 | name = "core-foundation-sys" 156 | version = "0.8.7" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 159 | 160 | [[package]] 161 | name = "ctor" 162 | version = "0.2.8" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" 165 | dependencies = [ 166 | "quote", 167 | "syn", 168 | ] 169 | 170 | [[package]] 171 | name = "dirs" 172 | version = "5.0.1" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 175 | dependencies = [ 176 | "dirs-sys", 177 | ] 178 | 179 | [[package]] 180 | name = "dirs-sys" 181 | version = "0.4.1" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 184 | dependencies = [ 185 | "libc", 186 | "option-ext", 187 | "redox_users", 188 | "windows-sys 0.48.0", 189 | ] 190 | 191 | [[package]] 192 | name = "encode_unicode" 193 | version = "0.3.6" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 196 | 197 | [[package]] 198 | name = "filetime" 199 | version = "0.2.25" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" 202 | dependencies = [ 203 | "cfg-if", 204 | "libc", 205 | "libredox", 206 | "windows-sys 0.59.0", 207 | ] 208 | 209 | [[package]] 210 | name = "foreign-types" 211 | version = "0.3.2" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 214 | dependencies = [ 215 | "foreign-types-shared", 216 | ] 217 | 218 | [[package]] 219 | name = "foreign-types-shared" 220 | version = "0.1.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 223 | 224 | [[package]] 225 | name = "getrandom" 226 | version = "0.2.15" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 229 | dependencies = [ 230 | "cfg-if", 231 | "libc", 232 | "wasi", 233 | ] 234 | 235 | [[package]] 236 | name = "gimli" 237 | version = "0.28.1" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 240 | 241 | [[package]] 242 | name = "hermit-abi" 243 | version = "0.1.19" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 246 | dependencies = [ 247 | "libc", 248 | ] 249 | 250 | [[package]] 251 | name = "hermit-abi" 252 | version = "0.3.9" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 255 | 256 | [[package]] 257 | name = "iana-time-zone" 258 | version = "0.1.60" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" 261 | dependencies = [ 262 | "android_system_properties", 263 | "core-foundation-sys", 264 | "iana-time-zone-haiku", 265 | "js-sys", 266 | "wasm-bindgen", 267 | "windows-core", 268 | ] 269 | 270 | [[package]] 271 | name = "iana-time-zone-haiku" 272 | version = "0.1.2" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 275 | dependencies = [ 276 | "cc", 277 | ] 278 | 279 | [[package]] 280 | name = "indicatif" 281 | version = "0.17.8" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" 284 | dependencies = [ 285 | "console", 286 | "instant", 287 | "number_prefix", 288 | "portable-atomic", 289 | "unicode-width", 290 | ] 291 | 292 | [[package]] 293 | name = "instant" 294 | version = "0.1.13" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 297 | dependencies = [ 298 | "cfg-if", 299 | ] 300 | 301 | [[package]] 302 | name = "js-sys" 303 | version = "0.3.70" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 306 | dependencies = [ 307 | "wasm-bindgen", 308 | ] 309 | 310 | [[package]] 311 | name = "lazy_static" 312 | version = "1.5.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 315 | 316 | [[package]] 317 | name = "libc" 318 | version = "0.2.155" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 321 | 322 | [[package]] 323 | name = "libredox" 324 | version = "0.1.3" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 327 | dependencies = [ 328 | "bitflags", 329 | "libc", 330 | "redox_syscall", 331 | ] 332 | 333 | [[package]] 334 | name = "lock_api" 335 | version = "0.4.12" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 338 | dependencies = [ 339 | "autocfg", 340 | "scopeguard", 341 | ] 342 | 343 | [[package]] 344 | name = "log" 345 | version = "0.4.22" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 348 | 349 | [[package]] 350 | name = "memchr" 351 | version = "2.7.2" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 354 | 355 | [[package]] 356 | name = "miniz_oxide" 357 | version = "0.7.3" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" 360 | dependencies = [ 361 | "adler", 362 | ] 363 | 364 | [[package]] 365 | name = "mio" 366 | version = "0.8.11" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 369 | dependencies = [ 370 | "libc", 371 | "wasi", 372 | "windows-sys 0.48.0", 373 | ] 374 | 375 | [[package]] 376 | name = "nix" 377 | version = "0.29.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 380 | dependencies = [ 381 | "bitflags", 382 | "cfg-if", 383 | "cfg_aliases", 384 | "libc", 385 | ] 386 | 387 | [[package]] 388 | name = "num-traits" 389 | version = "0.2.19" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 392 | dependencies = [ 393 | "autocfg", 394 | ] 395 | 396 | [[package]] 397 | name = "num_cpus" 398 | version = "1.16.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 401 | dependencies = [ 402 | "hermit-abi 0.3.9", 403 | "libc", 404 | ] 405 | 406 | [[package]] 407 | name = "number_prefix" 408 | version = "0.4.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 411 | 412 | [[package]] 413 | name = "object" 414 | version = "0.32.2" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 417 | dependencies = [ 418 | "memchr", 419 | ] 420 | 421 | [[package]] 422 | name = "once_cell" 423 | version = "1.19.0" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 426 | 427 | [[package]] 428 | name = "openssl" 429 | version = "0.10.68" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" 432 | dependencies = [ 433 | "bitflags", 434 | "cfg-if", 435 | "foreign-types", 436 | "libc", 437 | "once_cell", 438 | "openssl-macros", 439 | "openssl-sys", 440 | ] 441 | 442 | [[package]] 443 | name = "openssl-macros" 444 | version = "0.1.1" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 447 | dependencies = [ 448 | "proc-macro2", 449 | "quote", 450 | "syn", 451 | ] 452 | 453 | [[package]] 454 | name = "openssl-src" 455 | version = "300.4.1+3.4.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" 458 | dependencies = [ 459 | "cc", 460 | ] 461 | 462 | [[package]] 463 | name = "openssl-sys" 464 | version = "0.9.104" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" 467 | dependencies = [ 468 | "cc", 469 | "libc", 470 | "openssl-src", 471 | "pkg-config", 472 | "vcpkg", 473 | ] 474 | 475 | [[package]] 476 | name = "option-ext" 477 | version = "0.2.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 480 | 481 | [[package]] 482 | name = "parking_lot" 483 | version = "0.12.2" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" 486 | dependencies = [ 487 | "lock_api", 488 | "parking_lot_core", 489 | ] 490 | 491 | [[package]] 492 | name = "parking_lot_core" 493 | version = "0.9.10" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 496 | dependencies = [ 497 | "cfg-if", 498 | "libc", 499 | "redox_syscall", 500 | "smallvec", 501 | "windows-targets 0.52.6", 502 | ] 503 | 504 | [[package]] 505 | name = "pin-project-lite" 506 | version = "0.2.14" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 509 | 510 | [[package]] 511 | name = "pkg-config" 512 | version = "0.3.31" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 515 | 516 | [[package]] 517 | name = "portable-atomic" 518 | version = "1.9.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" 521 | 522 | [[package]] 523 | name = "ppv-lite86" 524 | version = "0.2.20" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 527 | dependencies = [ 528 | "zerocopy", 529 | ] 530 | 531 | [[package]] 532 | name = "proc-macro2" 533 | version = "1.0.83" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" 536 | dependencies = [ 537 | "unicode-ident", 538 | ] 539 | 540 | [[package]] 541 | name = "quote" 542 | version = "1.0.36" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 545 | dependencies = [ 546 | "proc-macro2", 547 | ] 548 | 549 | [[package]] 550 | name = "rand" 551 | version = "0.9.0-alpha.2" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "c3e256ff62cee3e03def855c4d4260106d2bb1696fdc01af03e9935b993720a5" 554 | dependencies = [ 555 | "rand_chacha", 556 | "rand_core", 557 | "zerocopy", 558 | ] 559 | 560 | [[package]] 561 | name = "rand_chacha" 562 | version = "0.9.0-alpha.2" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "d299e9db34f6623b2a9e86c015d6e173d5f46d64d4b9b8cc46ae8a982a50b04c" 565 | dependencies = [ 566 | "ppv-lite86", 567 | "rand_core", 568 | ] 569 | 570 | [[package]] 571 | name = "rand_core" 572 | version = "0.9.0-alpha.2" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "f4e93f5a5e3c528cda9acb0928c31b2ba868c551cc46e67b778075e34aab9906" 575 | dependencies = [ 576 | "getrandom", 577 | "zerocopy", 578 | ] 579 | 580 | [[package]] 581 | name = "redox_syscall" 582 | version = "0.5.1" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 585 | dependencies = [ 586 | "bitflags", 587 | ] 588 | 589 | [[package]] 590 | name = "redox_users" 591 | version = "0.4.6" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 594 | dependencies = [ 595 | "getrandom", 596 | "libredox", 597 | "thiserror", 598 | ] 599 | 600 | [[package]] 601 | name = "rustc-demangle" 602 | version = "0.1.24" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 605 | 606 | [[package]] 607 | name = "same-file" 608 | version = "1.0.6" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 611 | dependencies = [ 612 | "winapi-util", 613 | ] 614 | 615 | [[package]] 616 | name = "scopeguard" 617 | version = "1.2.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 620 | 621 | [[package]] 622 | name = "signal-hook-registry" 623 | version = "1.4.2" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 626 | dependencies = [ 627 | "libc", 628 | ] 629 | 630 | [[package]] 631 | name = "smallvec" 632 | version = "1.13.2" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 635 | 636 | [[package]] 637 | name = "socket2" 638 | version = "0.5.7" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 641 | dependencies = [ 642 | "libc", 643 | "windows-sys 0.52.0", 644 | ] 645 | 646 | [[package]] 647 | name = "syn" 648 | version = "2.0.65" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" 651 | dependencies = [ 652 | "proc-macro2", 653 | "quote", 654 | "unicode-ident", 655 | ] 656 | 657 | [[package]] 658 | name = "termios" 659 | version = "0.3.3" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" 662 | dependencies = [ 663 | "libc", 664 | ] 665 | 666 | [[package]] 667 | name = "thiserror" 668 | version = "1.0.64" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 671 | dependencies = [ 672 | "thiserror-impl", 673 | ] 674 | 675 | [[package]] 676 | name = "thiserror-impl" 677 | version = "1.0.64" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 680 | dependencies = [ 681 | "proc-macro2", 682 | "quote", 683 | "syn", 684 | ] 685 | 686 | [[package]] 687 | name = "tokio" 688 | version = "1.37.0" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" 691 | dependencies = [ 692 | "backtrace", 693 | "bytes", 694 | "libc", 695 | "mio", 696 | "num_cpus", 697 | "parking_lot", 698 | "pin-project-lite", 699 | "signal-hook-registry", 700 | "socket2", 701 | "tokio-macros", 702 | "windows-sys 0.48.0", 703 | ] 704 | 705 | [[package]] 706 | name = "tokio-macros" 707 | version = "2.2.0" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 710 | dependencies = [ 711 | "proc-macro2", 712 | "quote", 713 | "syn", 714 | ] 715 | 716 | [[package]] 717 | name = "unicode-ident" 718 | version = "1.0.12" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 721 | 722 | [[package]] 723 | name = "unicode-width" 724 | version = "0.1.14" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 727 | 728 | [[package]] 729 | name = "vcpkg" 730 | version = "0.2.15" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 733 | 734 | [[package]] 735 | name = "walkdir" 736 | version = "2.5.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 739 | dependencies = [ 740 | "same-file", 741 | "winapi-util", 742 | ] 743 | 744 | [[package]] 745 | name = "wasi" 746 | version = "0.11.0+wasi-snapshot-preview1" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 749 | 750 | [[package]] 751 | name = "wasm-bindgen" 752 | version = "0.2.93" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 755 | dependencies = [ 756 | "cfg-if", 757 | "once_cell", 758 | "wasm-bindgen-macro", 759 | ] 760 | 761 | [[package]] 762 | name = "wasm-bindgen-backend" 763 | version = "0.2.93" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 766 | dependencies = [ 767 | "bumpalo", 768 | "log", 769 | "once_cell", 770 | "proc-macro2", 771 | "quote", 772 | "syn", 773 | "wasm-bindgen-shared", 774 | ] 775 | 776 | [[package]] 777 | name = "wasm-bindgen-macro" 778 | version = "0.2.93" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 781 | dependencies = [ 782 | "quote", 783 | "wasm-bindgen-macro-support", 784 | ] 785 | 786 | [[package]] 787 | name = "wasm-bindgen-macro-support" 788 | version = "0.2.93" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 791 | dependencies = [ 792 | "proc-macro2", 793 | "quote", 794 | "syn", 795 | "wasm-bindgen-backend", 796 | "wasm-bindgen-shared", 797 | ] 798 | 799 | [[package]] 800 | name = "wasm-bindgen-shared" 801 | version = "0.2.93" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 804 | 805 | [[package]] 806 | name = "winapi" 807 | version = "0.3.9" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 810 | dependencies = [ 811 | "winapi-i686-pc-windows-gnu", 812 | "winapi-x86_64-pc-windows-gnu", 813 | ] 814 | 815 | [[package]] 816 | name = "winapi-i686-pc-windows-gnu" 817 | version = "0.4.0" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 820 | 821 | [[package]] 822 | name = "winapi-util" 823 | version = "0.1.9" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 826 | dependencies = [ 827 | "windows-sys 0.59.0", 828 | ] 829 | 830 | [[package]] 831 | name = "winapi-x86_64-pc-windows-gnu" 832 | version = "0.4.0" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 835 | 836 | [[package]] 837 | name = "windows-core" 838 | version = "0.52.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 841 | dependencies = [ 842 | "windows-targets 0.52.6", 843 | ] 844 | 845 | [[package]] 846 | name = "windows-sys" 847 | version = "0.48.0" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 850 | dependencies = [ 851 | "windows-targets 0.48.5", 852 | ] 853 | 854 | [[package]] 855 | name = "windows-sys" 856 | version = "0.52.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 859 | dependencies = [ 860 | "windows-targets 0.52.6", 861 | ] 862 | 863 | [[package]] 864 | name = "windows-sys" 865 | version = "0.59.0" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 868 | dependencies = [ 869 | "windows-targets 0.52.6", 870 | ] 871 | 872 | [[package]] 873 | name = "windows-targets" 874 | version = "0.48.5" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 877 | dependencies = [ 878 | "windows_aarch64_gnullvm 0.48.5", 879 | "windows_aarch64_msvc 0.48.5", 880 | "windows_i686_gnu 0.48.5", 881 | "windows_i686_msvc 0.48.5", 882 | "windows_x86_64_gnu 0.48.5", 883 | "windows_x86_64_gnullvm 0.48.5", 884 | "windows_x86_64_msvc 0.48.5", 885 | ] 886 | 887 | [[package]] 888 | name = "windows-targets" 889 | version = "0.52.6" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 892 | dependencies = [ 893 | "windows_aarch64_gnullvm 0.52.6", 894 | "windows_aarch64_msvc 0.52.6", 895 | "windows_i686_gnu 0.52.6", 896 | "windows_i686_gnullvm", 897 | "windows_i686_msvc 0.52.6", 898 | "windows_x86_64_gnu 0.52.6", 899 | "windows_x86_64_gnullvm 0.52.6", 900 | "windows_x86_64_msvc 0.52.6", 901 | ] 902 | 903 | [[package]] 904 | name = "windows_aarch64_gnullvm" 905 | version = "0.48.5" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 908 | 909 | [[package]] 910 | name = "windows_aarch64_gnullvm" 911 | version = "0.52.6" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 914 | 915 | [[package]] 916 | name = "windows_aarch64_msvc" 917 | version = "0.48.5" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 920 | 921 | [[package]] 922 | name = "windows_aarch64_msvc" 923 | version = "0.52.6" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 926 | 927 | [[package]] 928 | name = "windows_i686_gnu" 929 | version = "0.48.5" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 932 | 933 | [[package]] 934 | name = "windows_i686_gnu" 935 | version = "0.52.6" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 938 | 939 | [[package]] 940 | name = "windows_i686_gnullvm" 941 | version = "0.52.6" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 944 | 945 | [[package]] 946 | name = "windows_i686_msvc" 947 | version = "0.48.5" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 950 | 951 | [[package]] 952 | name = "windows_i686_msvc" 953 | version = "0.52.6" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 956 | 957 | [[package]] 958 | name = "windows_x86_64_gnu" 959 | version = "0.48.5" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 962 | 963 | [[package]] 964 | name = "windows_x86_64_gnu" 965 | version = "0.52.6" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 968 | 969 | [[package]] 970 | name = "windows_x86_64_gnullvm" 971 | version = "0.48.5" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 974 | 975 | [[package]] 976 | name = "windows_x86_64_gnullvm" 977 | version = "0.52.6" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 980 | 981 | [[package]] 982 | name = "windows_x86_64_msvc" 983 | version = "0.48.5" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 986 | 987 | [[package]] 988 | name = "windows_x86_64_msvc" 989 | version = "0.52.6" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 992 | 993 | [[package]] 994 | name = "zerocopy" 995 | version = "0.7.35" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 998 | dependencies = [ 999 | "byteorder", 1000 | "zerocopy-derive", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "zerocopy-derive" 1005 | version = "0.7.35" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1008 | dependencies = [ 1009 | "proc-macro2", 1010 | "quote", 1011 | "syn", 1012 | ] 1013 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "adbr" 3 | version = "1.0.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "adbr" 8 | path = "src/lib.rs" 9 | 10 | [[bin]] 11 | name = "adbr" 12 | path = "src/main.rs" 13 | 14 | [profile.release] 15 | lto = true 16 | 17 | [dependencies] 18 | tokio = { version = "1", features = ["full"] } 19 | termios = "0.3" 20 | nix = { version = "0.29.0", features = ["poll"] } 21 | atty = "0.2" 22 | chrono = "0.4.38" 23 | walkdir = "2.3.2" 24 | filetime = "0.2.25" 25 | indicatif = "0.17.8" 26 | openssl = { version = "0.10.66", features = ["vendored"] } 27 | dirs = "5.0.1" 28 | rand = "0.9.0-alpha.2" 29 | ctor = "0.2.8" 30 | 31 | [dev-dependencies] 32 | tokio = { version = "1", features = ["full", "test-util"] } -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-gnu] 2 | image = "rustembedded/cross:x86_64-pc-windows-gnu" 3 | 4 | [target.i686-pc-windows-gnu] 5 | image = "rustembedded/cross:i686-pc-windows-gnu" 6 | 7 | [target.x86_64-apple-darwin] 8 | image = "ghcr.io/cross-rs/x86_64-apple-darwin:main" 9 | 10 | [target.aarch64-apple-darwin] 11 | image = "ghcr.io/cross-rs/aarch64-apple-darwin:main" 12 | 13 | [target.x86_64-unknown-linux-gnu] 14 | image = "rustembedded/cross:x86_64-unknown-linux-gnu" 15 | 16 | [target.i686-unknown-linux-gnu] 17 | image = "rustembedded/cross:i686-unknown-linux-gnu" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ADBR 2 | 3 | A modern implementation of the Android Debug Bridge (ADB) client written in Rust, focusing on code maintainability and better error handling. Currently optimized for Ubuntu systems. 4 | 5 | ## Why ADBR? 6 | 7 | - 📝 Clean, modern Rust implementation compared to AOSP's C-based ADB 8 | - ✨ Improved error handling and user feedback 9 | - 🚀 Well-structured and maintainable codebase 10 | - 🔍 Easy to understand and modify 11 | 12 | Key improvements over traditional ADB: 13 | - Clear separation of client/server communication 14 | - Modern error handling patterns 15 | - Well-structured command processing 16 | 17 | Coming Soon: A full Rust implementation of the ADB server! 18 | 19 | ## Requirements 20 | 21 | - ADB server (running on default port 5037 or custom) 22 | - Android device with USB debugging enabled 23 | - Ubuntu 20.04 or newer 24 | 25 | ## Installation 26 | 27 | ### Option 1: Install from DEB Package (Recommended) 28 | ```bash 29 | # Download and install 30 | wget https://raw.githubusercontent.com/xDvir/ADBRClient/main/releases/adbr_1.0.0-1.deb 31 | sudo dpkg -i adbr_1.0.0-1.deb 32 | 33 | # If needed, resolve dependencies 34 | sudo apt-get install -f 35 | ``` 36 | 37 | ### Option 2: Build from Source 38 | ```bash 39 | # Install build dependencies 40 | sudo apt-get update 41 | sudo apt-get install -y build-essential pkg-config libssl-dev musl-tools 42 | 43 | # Install Rust (if not already installed) 44 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 45 | 46 | # Add musl target 47 | rustup target add x86_64-unknown-linux-musl 48 | 49 | # Clone and build 50 | git clone https://github.com/xDvir/ADBRClient.git 51 | cd ADBRClient 52 | ./build_deb.sh 53 | ``` 54 | 55 | ## Basic Usage 56 | 57 | ```bash 58 | # List devices 59 | adbr devices 60 | 61 | # File operations 62 | adbr push local_file.txt /sdcard/ 63 | adbr pull /sdcard/remote_file.txt ./ 64 | 65 | # Shell commands 66 | adbr shell "ls -l" 67 | 68 | # App management 69 | adbr install app.apk 70 | adbr uninstall package.name 71 | 72 | # Network 73 | adbr forward tcp:8000 tcp:8001 74 | ``` 75 | 76 | ## Configuration 77 | 78 | Set custom ADB server: 79 | ```bash 80 | # Environment variable 81 | export ADB_ADDRESS=192.168.1.100:5037 82 | 83 | # Or command-line 84 | adbr -H 192.168.1.100 -P 5037 devices 85 | ``` 86 | 87 | ## Available Commands 88 | 89 | ### Device Management 90 | ```bash 91 | adbr devices # List connected devices 92 | adbr devices -w # Monitor devices continuously 93 | adbr wait-for-device # Wait for device to connect 94 | adbr get-state # Get device state 95 | ``` 96 | 97 | ### File Operations 98 | ```bash 99 | adbr push SOURCE TARGET # Copy to device 100 | adbr pull SOURCE TARGET # Copy from device 101 | ``` 102 | 103 | ### App Management 104 | ```bash 105 | adbr install APP.apk # Install an app 106 | adbr uninstall PACKAGE # Remove an app 107 | ``` 108 | 109 | ### Network 110 | ```bash 111 | adbr forward LOCAL REMOTE # Forward ports 112 | adbr reverse REMOTE LOCAL # Reverse forward ports 113 | ``` 114 | 115 | ### System 116 | ```bash 117 | adbr root # Restart ADB with root 118 | adbr unroot # Restart ADB without root 119 | adbr reboot # Reboot device 120 | adbr shell # Start shell session 121 | ``` 122 | 123 | ## Notes 124 | 125 | - Compatible with Ubuntu 20.04 and newer 126 | - Works with all Android devices that support ADB 127 | - Requires an ADB server to be running 128 | - USB debugging must be enabled on Android devices 129 | - Binary location: `/usr/local/bin/adbr` 130 | 131 | ## Contributing 132 | 133 | Found a bug or want to contribute? Open an issue or submit a pull request! 134 | 135 | ## License 136 | 137 | Licensed under the Apache License, Version 2.0 (the "License"); 138 | you may not use this file except in compliance with the License. 139 | You may obtain a copy of the License at 140 | 141 | http://www.apache.org/licenses/LICENSE-2.0 142 | 143 | Unless required by applicable law or agreed to in writing, software 144 | distributed under the License is distributed on an "AS IS" BASIS, 145 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 146 | See the License for the specific language governing permissions and 147 | limitations under the License. 148 | 149 | ## Version History 150 | 151 | 1.0.0-1: Initial release 152 | - Complete ADB command set implementation 153 | - Ubuntu package support 154 | - Command-line interface parity with ADB 155 | 156 | ## Related Publications 157 | 158 | - [adbDocumentation](https://github.com/cstyan/adbDocumentation) 159 | - [python-adb](https://github.com/google/python-adb) 160 | - [paramiko-shell](https://github.com/sirosen/paramiko-shell/blob/master/interactive_shell.py) -------------------------------------------------------------------------------- /adbclient.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /build_deb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Version configuration - change this in one place 6 | VERSION="1.0.0-1" 7 | 8 | # Ensure we're in the project root 9 | cd "$(dirname "$0")" 10 | 11 | # Create releases directory if it doesn't exist 12 | mkdir -p releases 13 | 14 | # Build the project 15 | cargo build --release --target x86_64-unknown-linux-musl 16 | 17 | # Define package name and temporary directory 18 | PKG_NAME="adbr_${VERSION}" 19 | TMP_DIR="${PKG_NAME}" 20 | 21 | # Create package directories 22 | mkdir -p ${TMP_DIR}/DEBIAN 23 | mkdir -p ${TMP_DIR}/usr/local/bin 24 | 25 | # Create control file 26 | cat > ${TMP_DIR}/DEBIAN/control << EOL 27 | Package: adbr 28 | Version: ${VERSION} 29 | Section: utils 30 | Priority: optional 31 | Architecture: amd64 32 | Depends: 33 | Maintainer: Your Name 34 | Description: ADB Rust Implementation 35 | A Rust implementation of the Android Debug Bridge (ADB) client. 36 | This package provides the adbr command line tool. 37 | EOL 38 | 39 | # Create postinst script 40 | cat > ${TMP_DIR}/DEBIAN/postinst << EOL 41 | #!/bin/sh 42 | set -e 43 | 44 | # Set correct permissions 45 | chmod 755 /usr/local/bin/adbr 46 | 47 | # Add /usr/local/bin to PATH if not already there 48 | if ! echo \$PATH | grep -q "/usr/local/bin"; then 49 | echo 'export PATH="/usr/local/bin:\$PATH"' >> /etc/profile 50 | echo "Added /usr/local/bin to PATH. Please restart your shell or run 'source /etc/profile' to apply changes." 51 | fi 52 | 53 | echo "adbr has been installed. You can now use it by running 'adbr' from anywhere in your system." 54 | EOL 55 | 56 | chmod 755 ${TMP_DIR}/DEBIAN/postinst 57 | 58 | # Copy binary 59 | cp target/x86_64-unknown-linux-musl/release/adbr ${TMP_DIR}/usr/local/bin/ 60 | 61 | # Build the package 62 | dpkg-deb --build ${TMP_DIR} 63 | 64 | # Move the .deb file to releases directory 65 | mv ${TMP_DIR}.deb releases/ 66 | 67 | # Clean up temporary directory 68 | rm -rf ${TMP_DIR} 69 | 70 | echo "Debian package created: releases/${PKG_NAME}.deb" 71 | 72 | # Install the package 73 | echo "Installing the package..." 74 | if [ "$EUID" -ne 0 ]; then 75 | echo "This script needs root privileges to install the package." 76 | sudo dpkg -i releases/${PKG_NAME}.deb 77 | if [ $? -ne 0 ]; then 78 | echo "Installation failed. Attempting to resolve dependencies..." 79 | sudo apt-get install -f 80 | sudo dpkg -i releases/${PKG_NAME}.deb 81 | fi 82 | else 83 | dpkg -i releases/${PKG_NAME}.deb 84 | if [ $? -ne 0 ]; then 85 | echo "Installation failed. Attempting to resolve dependencies..." 86 | apt-get install -f 87 | dpkg -i releases/${PKG_NAME}.deb 88 | fi 89 | fi 90 | 91 | if [ $? -eq 0 ]; then 92 | echo "Installation completed successfully!" 93 | echo "You may need to restart your shell or run 'source /etc/profile' to update your PATH." 94 | else 95 | echo "Installation failed. Please check the error messages above." 96 | fi -------------------------------------------------------------------------------- /releases/adbr_1.0.0-1.deb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xDvir/ADBRClient/baf5e48b15e2f90d15df7adce7d99aa06ad0ad9c/releases/adbr_1.0.0-1.deb -------------------------------------------------------------------------------- /src/adb/app_installation/install.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::Path; 3 | use std::error::Error; 4 | use crate::adb::client::Client; 5 | use crate::enums::device_transport::DeviceTransport; 6 | use crate::constants::{ 7 | PM_INSTALL, 8 | INSTALL_FLAG_SDCARD, INSTALL_FLAG_INTERNAL, 9 | INSTALL_FLAG_DOWNGRADE, INSTALL_FLAG_REPLACE, 10 | DEVICE_TEMP_DIRECTORY, 11 | }; 12 | 13 | impl Client { 14 | pub async fn adb_install(&mut self, device_transport: DeviceTransport, local_apk_path: &str, flags: &[String]) -> Result> { 15 | if !fs::metadata(local_apk_path).is_ok() { 16 | return Err(format!("APK file not found at path: {}", local_apk_path).into()); 17 | } 18 | 19 | if flags.contains(&INSTALL_FLAG_SDCARD.to_string()) && flags.contains(&INSTALL_FLAG_INTERNAL.to_string()) { 20 | return Err(format!("{} and {} flags are mutually exclusive", INSTALL_FLAG_SDCARD, INSTALL_FLAG_INTERNAL).into()); 21 | } 22 | 23 | if flags.contains(&INSTALL_FLAG_DOWNGRADE.to_string()) && flags.contains(&INSTALL_FLAG_REPLACE.to_string()) { 24 | println!("Warning: {} and {} flags may not work together on some Android versions", INSTALL_FLAG_DOWNGRADE, INSTALL_FLAG_REPLACE); 25 | } 26 | 27 | let apk_filename = Path::new(local_apk_path) 28 | .file_name() 29 | .ok_or("Invalid APK path")? 30 | .to_str() 31 | .ok_or("APK filename is not valid UTF-8")?; 32 | 33 | let remote_path = format!("{}{}", DEVICE_TEMP_DIRECTORY, apk_filename); 34 | 35 | self.adb_push(device_transport.clone(), &[local_apk_path.to_string()], &remote_path, false).await?; 36 | 37 | self.reconnect().await?; 38 | 39 | let mut pm_command = String::from(PM_INSTALL); 40 | for flag in flags { 41 | pm_command.push_str(&format!(" {}", flag)); 42 | } 43 | pm_command.push_str(&format!(" {}", remote_path)); 44 | 45 | self.adb_shell(device_transport, &pm_command).await 46 | } 47 | } -------------------------------------------------------------------------------- /src/adb/app_installation/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod install; 2 | pub mod uninstall; 3 | 4 | -------------------------------------------------------------------------------- /src/adb/app_installation/uninstall.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use crate::adb::client::Client; 3 | use crate::constants::PM_UNINSTALL; 4 | use crate::enums::device_transport::DeviceTransport; 5 | 6 | 7 | 8 | impl Client { 9 | pub async fn adb_uninstall(&mut self, device_transport: DeviceTransport, package_name: &str, flags: &[String], ) -> Result> { 10 | if package_name.is_empty() { 11 | return Err("Package name is required".into()); 12 | } 13 | 14 | let mut pm_command = String::from(PM_UNINSTALL); 15 | for flag in flags { 16 | pm_command.push_str(&format!(" {}", flag)); 17 | } 18 | 19 | pm_command.push_str(&format!(" {}", package_name)); 20 | 21 | self.adb_shell(device_transport, &pm_command).await 22 | } 23 | } -------------------------------------------------------------------------------- /src/adb/client.rs: -------------------------------------------------------------------------------- 1 | use std::{env}; 2 | use std::error::Error; 3 | use tokio::net::TcpStream; 4 | use tokio::time::{timeout, Duration}; 5 | use std::io::{ErrorKind}; 6 | use tokio::io::{AsyncWriteExt}; 7 | use crate::constants::{DEFAULT_ADB_SERVER_IP, DEFAULT_ADB_SERVER_PORT, ADB_ADDRESS_ENV, ADB_SERVER_CONNECT_TIMEOUT_SECONDS_DURATION}; 8 | 9 | pub struct Client { 10 | pub adb_stream: TcpStream, 11 | server_address: Option, 12 | server_port: Option, 13 | } 14 | 15 | impl Client { 16 | pub async fn new(server_address: Option, server_port: Option) -> Result> { 17 | let (adb_stream, _adb_server_addr) = Self::connect(server_address.clone(), server_port).await?; 18 | 19 | Ok(Client { 20 | adb_stream, 21 | server_address, 22 | server_port, 23 | }) 24 | } 25 | 26 | async fn connect(server_address: Option, server_port: Option) -> Result<(TcpStream, String), Box> { 27 | let adb_server_addr = match (server_address, server_port) { 28 | (Some(addr), Some(port)) => format!("{}:{}", addr, port), 29 | (Some(addr), None) => format!("{}:{}", addr, DEFAULT_ADB_SERVER_PORT), 30 | (None, Some(port)) => format!("{}:{}", DEFAULT_ADB_SERVER_IP, port), 31 | (None, None) => env::var(ADB_ADDRESS_ENV).unwrap_or_else(|_| { 32 | format!("{}:{}", DEFAULT_ADB_SERVER_IP, DEFAULT_ADB_SERVER_PORT) 33 | }), 34 | }; 35 | 36 | let change_env_message = format!( 37 | "You can change the ADB server address by setting the {} environment variable (e.g., export {}=127.0.0.1:5037)", 38 | ADB_ADDRESS_ENV, ADB_ADDRESS_ENV 39 | ); 40 | 41 | let adb_stream = match timeout( 42 | Duration::from_secs(ADB_SERVER_CONNECT_TIMEOUT_SECONDS_DURATION), 43 | TcpStream::connect(&adb_server_addr), 44 | ).await { 45 | Ok(Ok(stream)) => stream, 46 | Ok(Err(e)) => return Err(format!( 47 | "Failed to connect to ADB server at address {}: {}. {}", 48 | adb_server_addr, e, change_env_message 49 | ).into()), 50 | Err(_) => return Err(format!( 51 | "Connection attempt to ADB server at address {} timed out. {}", 52 | adb_server_addr, change_env_message 53 | ).into()), 54 | }; 55 | 56 | Ok((adb_stream, adb_server_addr)) 57 | } 58 | 59 | pub async fn reconnect(&mut self) -> Result<(), Box> { 60 | self.close().await; 61 | let (new_stream, _) = Self::connect(self.server_address.clone(), self.server_port).await?; 62 | self.adb_stream = new_stream; 63 | Ok(()) 64 | } 65 | 66 | pub async fn close(&mut self) { 67 | match self.adb_stream.shutdown().await { 68 | Ok(_) => {} 69 | Err(e) if e.kind() == ErrorKind::NotConnected => {} 70 | Err(e) => { 71 | eprintln!("Error shutting down ADB stream: {}", e); 72 | } 73 | } 74 | } 75 | pub async fn is_connected(&self) -> bool { 76 | let mut buf = [0; 1]; 77 | match self.adb_stream.try_read(&mut buf) { 78 | Ok(0) => false, 79 | Ok(_) => true, 80 | Err(ref e) if e.kind() == ErrorKind::WouldBlock => true, 81 | Err(_) => false, 82 | } 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /src/adb/debugging/debugging.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use crate::adb::client::Client; 3 | use crate::enums::device_transport::DeviceTransport; 4 | use crate::constants::{OKAY}; 5 | use std::path::Path; 6 | use tokio::io::{AsyncBufReadExt, BufReader}; 7 | use chrono::Local; 8 | use indicatif::{ProgressBar, ProgressStyle}; 9 | 10 | const LOGCAT_COMMAND_FORMAT: &str = "export ANDROID_LOG_TAGS=\"''\"; exec logcat {}"; 11 | const BUGREPORT_COMMAND: &str = "shell:bugreportz -p"; 12 | const DEFAULT_BUGREPORT_FILENAME: &str = "bugreport.zip"; 13 | const BUGREPORT_PREFIX: &str = "bugreport"; 14 | const PROGRESS_PREFIX: &str = "PROGRESS:"; 15 | const OK_PREFIX: &str = "OK:"; 16 | const XOK_PREFIX: &str = "XOK:"; 17 | const INFO_PREFIX: &str = "INFO:"; 18 | const DATE_FORMAT: &str = "%Y-%m-%d-%H-%M-%S"; 19 | const PROGRESS_BAR_LENGTH: u64 = 5000; 20 | 21 | impl Client { 22 | pub async fn adb_bugreport(&mut self, device_transport: DeviceTransport, path: Option<&str>) -> Result<(), Box> { 23 | self.send_transport(device_transport.clone()).await?; 24 | self.send_adb_command(BUGREPORT_COMMAND.as_ref()).await?; 25 | 26 | let response = self.read_first_four_bytes_response().await?; 27 | if response != OKAY { 28 | let error_msg = self.read_adb_full_response().await?; 29 | return Err(format!("Failed to initiate bugreport: {}", error_msg).into()); 30 | } 31 | 32 | let output_path = path.unwrap_or(DEFAULT_BUGREPORT_FILENAME); 33 | let path = Path::new(output_path); 34 | 35 | let final_path = if path.is_dir() { 36 | let filename = format!("{}-{}.zip", BUGREPORT_PREFIX, Local::now().format(DATE_FORMAT)); 37 | path.join(filename) 38 | } else { 39 | path.to_path_buf() 40 | }; 41 | self.save_bugreportz_to_file(device_transport.clone(), &final_path).await 42 | } 43 | 44 | async fn save_bugreportz_to_file(&mut self, device: DeviceTransport, path: &Path) -> Result<(), Box> { 45 | println!("Generating bugreport. This may take a while..."); 46 | 47 | let pb = self.create_progress_bar(); 48 | 49 | let mut reader = BufReader::new(&mut self.adb_stream); 50 | let mut line = String::new(); 51 | let mut zip_file = String::new(); 52 | 53 | loop { 54 | line.clear(); 55 | let bytes_read = reader.read_line(&mut line).await?; 56 | 57 | if bytes_read == 0 { 58 | break; 59 | } 60 | 61 | let trimmed_line = line.trim(); 62 | 63 | if let Some(progress_str) = Self::extract_progress(trimmed_line) { 64 | if let Ok(progress) = progress_str.parse::() { 65 | pb.set_position(progress); 66 | } 67 | } else if let Some(path_str) = Self::extract_zip_path(trimmed_line) { 68 | zip_file = path_str.to_string(); 69 | break; 70 | } else if !trimmed_line.is_empty() && !trimmed_line.starts_with(INFO_PREFIX) { 71 | pb.suspend(|| println!("Info: {}", trimmed_line)); 72 | } 73 | } 74 | 75 | pb.finish_with_message("Bugreport generated"); 76 | 77 | if zip_file.is_empty() { 78 | return Err("Failed to generate bugreport: No zip file path received".into()); 79 | } 80 | 81 | println!("\nPulling bugreport file..."); 82 | 83 | let mut pull_client = Client::new(None, None).await?; 84 | pull_client.send_transport(device.clone()).await?; 85 | 86 | pull_client.adb_pull(device.clone(), &[zip_file], path.to_str().unwrap(), false).await?; 87 | 88 | println!("Bugreport saved to: {}", path.display()); 89 | Ok(()) 90 | } 91 | 92 | fn create_progress_bar(&self) -> ProgressBar { 93 | let pb = ProgressBar::new(PROGRESS_BAR_LENGTH); 94 | pb.set_style( 95 | ProgressStyle::default_bar() 96 | .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})") 97 | .unwrap_or_else(|_| ProgressStyle::default_bar()) 98 | .progress_chars("#>-") 99 | ); 100 | pb 101 | } 102 | 103 | fn extract_progress(line: &str) -> Option<&str> { 104 | if let Some(idx) = line.find(PROGRESS_PREFIX) { 105 | let progress_part = &line[idx + PROGRESS_PREFIX.len()..]; 106 | progress_part.split('/').next() 107 | } else { 108 | None 109 | } 110 | } 111 | 112 | fn extract_zip_path(line: &str) -> Option<&str> { 113 | if let Some(idx) = line.find(OK_PREFIX) { 114 | Some(line[idx + OK_PREFIX.len()..].trim()) 115 | } else if let Some(idx) = line.find(XOK_PREFIX) { 116 | Some(line[idx + XOK_PREFIX.len()..].trim()) 117 | } else { 118 | None 119 | } 120 | } 121 | 122 | pub async fn adb_logcat(&mut self, device: DeviceTransport, args: &str) -> Result> { 123 | let logcat_command = format!("{} {}", LOGCAT_COMMAND_FORMAT, args); 124 | self.adb_shell(device, &logcat_command).await 125 | } 126 | } -------------------------------------------------------------------------------- /src/adb/debugging/mod.rs: -------------------------------------------------------------------------------- 1 | mod debugging; 2 | -------------------------------------------------------------------------------- /src/adb/file_transfer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod push; 2 | pub mod pull; 3 | 4 | -------------------------------------------------------------------------------- /src/adb/file_transfer/pull.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::error::Error; 3 | use std::fs; 4 | use std::fs::set_permissions; 5 | use std::os::unix::fs::PermissionsExt; 6 | use std::path::{Path}; 7 | use std::time::{Instant, UNIX_EPOCH}; 8 | use filetime::{FileTime, set_file_times}; 9 | use crate::adb::client::Client; 10 | use crate::enums::pull_result::PullResult; 11 | use crate::constants::{RECV_COMMAND, DATA_COMMAND, DONE_COMMAND, FAIL, OKAY, S_IFDIR, SYNC_COMMAND, QUIT_COMMAND, LIST_COMMAND, DENT_COMMAND}; 12 | use crate::enums::device_transport::DeviceTransport; 13 | use tokio::fs::File; 14 | use tokio::io::{AsyncWriteExt}; 15 | use crate::models::remote_dir_entry::RemoteDirEntry; 16 | 17 | 18 | impl Client { 19 | pub async fn adb_pull(&mut self, device_transport: DeviceTransport, remote_paths: &[String], local_path: &str, preserve: bool) -> Result>)>, Box> { 20 | self.send_transport(device_transport).await?; 21 | self.send_adb_command(SYNC_COMMAND).await?; 22 | let response = self.read_first_four_bytes_response().await?; 23 | 24 | 25 | if response != OKAY { 26 | return Err(format!("adb pull failed: Unexpected response: {}", response).into()); 27 | } 28 | 29 | let local_path = Path::new(local_path); 30 | let should_be_directory = remote_paths.len() > 1 || local_path.is_dir(); 31 | 32 | if should_be_directory && !local_path.exists() { 33 | tokio::fs::create_dir_all(local_path).await?; 34 | } 35 | 36 | let mut results = Vec::new(); 37 | for remote_path in remote_paths { 38 | let result = self.pull_single_file(remote_path, local_path, should_be_directory, preserve).await; 39 | 40 | match &result { 41 | Ok(PullResult::FailedAllPull(err)) => { 42 | return Err(err.clone().into()); 43 | } 44 | Err(err) => { 45 | return Err(err.to_string().into()); 46 | } 47 | _ => results.push((remote_path.clone(), result)), 48 | } 49 | } 50 | 51 | self.send_command(QUIT_COMMAND.as_bytes()).await?; 52 | 53 | Ok(results) 54 | } 55 | 56 | async fn pull_single_file(&mut self, remote_path: &str, local_path: &Path, should_be_directory: bool, preserve: bool) -> Result> { 57 | let is_directory = self.check_remote_path_is_directory(remote_path).await?; 58 | 59 | if is_directory { 60 | self.pull_directory(remote_path, local_path, preserve).await 61 | } else { 62 | let dest_path = if should_be_directory { 63 | if !local_path.is_dir() { 64 | tokio::fs::create_dir_all(local_path).await?; 65 | } 66 | local_path.join(Path::new(remote_path).file_name().ok_or("Invalid remote filename")?) 67 | } else { 68 | local_path.to_path_buf() 69 | }; 70 | self.pull_file(remote_path, &dest_path, preserve).await 71 | } 72 | } 73 | 74 | async fn pull_file(&mut self, remote_path: &str, local_path: &Path, preserve: bool) -> Result> { 75 | let pull_start_time = Instant::now(); 76 | 77 | self.send_command(RECV_COMMAND.as_bytes()).await?; 78 | self.send_command(&(remote_path.len() as u32).to_le_bytes()).await?; 79 | self.send_command(remote_path.as_bytes()).await?; 80 | 81 | let header = self.get_exact_bytes(8).await?; 82 | let cmd = std::str::from_utf8(&header[..4])?; 83 | let size = u32::from_le_bytes(header[4..8].try_into()?); 84 | 85 | if cmd == FAIL { 86 | let error_msg = self.get_exact_bytes(size as usize).await?; 87 | return Err(format!( 88 | "adb: error: failed to copy '{}' to '{}': {}", 89 | remote_path, 90 | local_path.display(), 91 | String::from_utf8_lossy(&error_msg) 92 | ).into()); 93 | } 94 | 95 | if cmd != DATA_COMMAND { 96 | return Err(format!("adb: error: unexpected response: {}", cmd).into()); 97 | } 98 | 99 | let mut file = File::create(local_path).await?; 100 | let mut total_bytes = 0u64; 101 | 102 | let data = self.get_exact_bytes(size as usize).await?; 103 | file.write_all(&data).await?; 104 | total_bytes += size as u64; 105 | 106 | loop { 107 | let header = self.get_exact_bytes(8).await?; 108 | let cmd = std::str::from_utf8(&header[..4])?; 109 | let size = u32::from_le_bytes(header[4..8].try_into()?); 110 | 111 | match cmd { 112 | DATA_COMMAND => { 113 | let data = self.get_exact_bytes(size as usize).await?; 114 | file.write_all(&data).await?; 115 | total_bytes += size as u64; 116 | } 117 | DONE_COMMAND => { 118 | break; 119 | } 120 | FAIL => { 121 | let error_msg = self.get_exact_bytes(size as usize).await?; 122 | return Err(format!( 123 | "adb: error: failed to copy '{}' to '{}': {}", 124 | remote_path, 125 | local_path.display(), 126 | String::from_utf8_lossy(&error_msg) 127 | ).into()); 128 | } 129 | _ => { 130 | continue; 131 | } 132 | } 133 | } 134 | 135 | if preserve { 136 | match self.get_remote_metadata(remote_path).await { 137 | Ok(metadata) => { 138 | set_permissions(local_path, fs::Permissions::from_mode(metadata.mode))?; 139 | let mtime = UNIX_EPOCH + std::time::Duration::from_secs(metadata.mtime as u64); 140 | let system_time = mtime; 141 | set_file_times(local_path, FileTime::from_system_time(system_time), FileTime::from_system_time(system_time))?; 142 | } 143 | Err(e) => { 144 | eprintln!("Warning: Failed to preserve metadata for '{}': {}", remote_path, e); 145 | } 146 | } 147 | } 148 | let duration = pull_start_time.elapsed(); 149 | let transfer_rate = total_bytes as f64 / duration.as_secs_f64() / 1_000_000.0; 150 | 151 | Ok(PullResult::Success(transfer_rate, total_bytes, duration, 1)) 152 | } 153 | 154 | async fn pull_directory(&mut self, remote_path: &str, local_path: &Path, preserve: bool) -> Result> { 155 | let mut total_files = 0; 156 | let mut total_bytes = 0u64; 157 | let start_time = Instant::now(); 158 | 159 | tokio::fs::create_dir_all(local_path).await?; 160 | 161 | let mut dirs_to_process = VecDeque::new(); 162 | dirs_to_process.push_back((remote_path.to_string(), local_path.to_path_buf())); 163 | 164 | while let Some((current_remote_dir, current_local_dir)) = dirs_to_process.pop_front() { 165 | tokio::fs::create_dir_all(¤t_local_dir).await?; 166 | 167 | let entries = self.list_remote_directory(¤t_remote_dir).await?; 168 | 169 | for entry in entries { 170 | let remote_file_path = format!("{}/{}", current_remote_dir.trim_end_matches('/'), entry.name); 171 | let local_file_path = current_local_dir.join(&entry.name); 172 | 173 | if entry.mode & S_IFDIR != 0 { 174 | dirs_to_process.push_back((remote_file_path, local_file_path)); 175 | } else { 176 | match self.pull_file(&remote_file_path, &local_file_path, preserve).await { 177 | Ok(PullResult::Success(_, bytes, _, _)) => { 178 | total_files += 1; 179 | total_bytes += bytes; 180 | } 181 | Ok(PullResult::FailedAllPull(err)) => { 182 | return Ok(PullResult::FailedAllPull(err)); 183 | } 184 | Err(e) => { 185 | return Err(e); 186 | } 187 | _ => {} 188 | } 189 | } 190 | } 191 | } 192 | 193 | let duration = start_time.elapsed(); 194 | let transfer_rate = if duration.as_secs_f64() > 0.0 { 195 | total_bytes as f64 / duration.as_secs_f64() / 1_000_000.0 196 | } else { 197 | 0.0 198 | }; 199 | 200 | Ok(PullResult::SuccessDirectory(transfer_rate, total_bytes, duration, total_files)) 201 | } 202 | 203 | async fn list_remote_directory(&mut self, remote_path: &str) -> Result, Box> { 204 | self.send_command(LIST_COMMAND.as_bytes()).await?; 205 | self.send_command(&(remote_path.len() as u32).to_le_bytes()).await?; 206 | self.send_command(remote_path.as_bytes()).await?; 207 | 208 | let mut entries = Vec::new(); 209 | 210 | loop { 211 | let header = self.get_exact_bytes(4).await?; 212 | let cmd = std::str::from_utf8(&header)?; 213 | 214 | 215 | if cmd == DONE_COMMAND { 216 | break; 217 | } else if cmd == FAIL { 218 | let size_bytes = self.get_exact_bytes(4).await?; 219 | let size = u32::from_le_bytes(size_bytes.as_slice().try_into()?); 220 | let error_msg = self.get_exact_bytes(size as usize).await?; 221 | return Err(format!( 222 | "adb: error: failed to list directory '{}': {}", 223 | remote_path, 224 | String::from_utf8_lossy(&error_msg) 225 | ).into()); 226 | } else if cmd == DENT_COMMAND { 227 | let data = self.get_exact_bytes(16).await?; 228 | let mode = u32::from_le_bytes(data[0..4].try_into()?); 229 | let size = u32::from_le_bytes(data[4..8].try_into()?); 230 | let mtime = u32::from_le_bytes(data[8..12].try_into()?); 231 | let namelen = u32::from_le_bytes(data[12..16].try_into()?); 232 | 233 | let name_bytes = self.get_exact_bytes(namelen as usize).await?; 234 | let name = String::from_utf8_lossy(&name_bytes).to_string(); 235 | 236 | entries.push(RemoteDirEntry { 237 | name, 238 | mode, 239 | size, 240 | mtime, 241 | }); 242 | } else { 243 | return Err(format!("adb: error: unexpected response during list: {}", cmd).into()); 244 | } 245 | } 246 | 247 | Ok(entries) 248 | } 249 | } -------------------------------------------------------------------------------- /src/adb/file_transfer/push.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::os::unix::fs::PermissionsExt; 3 | use std::path::{Path, PathBuf}; 4 | use std::time::Instant; 5 | use walkdir::WalkDir; 6 | use crate::adb::client::Client; 7 | use tokio::io::{AsyncReadExt}; 8 | use crate::enums::push_result::PushResult; 9 | use tokio::fs::File; 10 | use crate::constants::{DATA_COMMAND, DONE_COMMAND, FAIL, OKAY, SEND_COMMAND, SYNC_COMMAND, QUIT_COMMAND, DEFAULT_PUSH_MODE}; 11 | use crate::enums::device_transport::DeviceTransport; 12 | use crate::models::stat_data::StatData; 13 | 14 | impl Client { 15 | pub async fn adb_push(&mut self, device_transport: DeviceTransport, local_paths: &[String], remote_path: &str, sync: bool) -> Result>)>, Box> { 16 | self.send_transport(device_transport).await?; 17 | self.send_adb_command(SYNC_COMMAND).await?; 18 | let response = self.read_first_four_bytes_response().await?; 19 | 20 | if response != OKAY { 21 | println!("{}", self.read_adb_full_response().await?); 22 | return Err(format!("adbr push failed: Unexpected response: {}", response).into()); 23 | } 24 | let mut full_remote_path = remote_path.to_string(); 25 | let should_be_directory = local_paths.len() > 1 || full_remote_path.ends_with('/') || full_remote_path.ends_with('\\'); 26 | if should_be_directory || !full_remote_path.ends_with('/') && !full_remote_path.ends_with('\\') { 27 | match self.check_remote_path_is_directory(&full_remote_path).await? { 28 | true => { 29 | if !full_remote_path.ends_with('/') && !full_remote_path.ends_with('\\') { 30 | full_remote_path.push('/'); 31 | } 32 | } 33 | false => { 34 | if should_be_directory { 35 | return Err(format!("adbr: error: target '{}' is not a directory", remote_path).into()); 36 | } 37 | } 38 | } 39 | } 40 | 41 | let mut results = Vec::new(); 42 | for local_path in local_paths { 43 | let mut current_remote_path = full_remote_path.clone(); 44 | if current_remote_path.ends_with('/') || current_remote_path.ends_with('\\') { 45 | if let Some(file_name) = Path::new(local_path).file_name() { 46 | if let Some(file_name_str) = file_name.to_str() { 47 | current_remote_path.push_str(file_name_str); 48 | } else { 49 | return Err("Invalid UTF-8 in filename".into()); 50 | } 51 | } else { 52 | return Err("Invalid local filename".into()); 53 | } 54 | } 55 | let result = self.push_single_file(local_path, ¤t_remote_path, sync).await; 56 | 57 | match &result { 58 | Ok(PushResult::FailedAllPush(_)) | Err(_) => { 59 | results.push((local_path.clone(), result)); 60 | return Ok(results); 61 | } 62 | _ => results.push((local_path.clone(), result)), 63 | } 64 | } 65 | 66 | self.send_command(QUIT_COMMAND.as_ref()).await?; 67 | 68 | Ok(results) 69 | } 70 | 71 | async fn push_single_file(&mut self, local_path: &str, remote_path: &str, sync: bool) -> Result> { 72 | let local_path = Path::new(local_path); 73 | if local_path.is_dir() { 74 | self.push_directory(local_path, remote_path, sync).await 75 | } else { 76 | self.push_file(local_path, remote_path, sync).await 77 | } 78 | } 79 | 80 | async fn push_directory(&mut self, local_dir: &Path, remote_dir: &str, sync: bool) -> Result> { 81 | let mut total_files = 0; 82 | let mut total_bytes = 0; 83 | let start_time = Instant::now(); 84 | 85 | for entry in WalkDir::new(local_dir) { 86 | let entry = entry?; 87 | if entry.file_type().is_file() { 88 | let relative_path = entry.path().strip_prefix(local_dir)?; 89 | let remote_path = Path::new(remote_dir).join(relative_path); 90 | match self.push_file(entry.path(), &remote_path.to_string_lossy(), sync).await { 91 | Ok(PushResult::Success(_, bytes, _, _)) => { 92 | total_files += 1; 93 | total_bytes += bytes; 94 | } 95 | Ok(PushResult::Skip) => {} 96 | Ok(PushResult::FailedAllPush(err)) => return Ok(PushResult::FailedAllPush(err)), 97 | Err(e) => return Err(e), 98 | _ => {} 99 | } 100 | } 101 | } 102 | 103 | let duration = start_time.elapsed(); 104 | let transfer_rate = total_bytes as f64 / duration.as_secs_f64() / 1_000_000.0; 105 | 106 | Ok(PushResult::SuccessDirectory(transfer_rate, total_bytes, duration, total_files)) 107 | } 108 | 109 | async fn push_file(&mut self, local_path: &Path, remote_path: &str, sync: bool) -> Result> { 110 | let push_start_time = Instant::now(); 111 | 112 | if !local_path.exists() { 113 | return Err(format!("adb: error: cannot stat '{}': No such file or directory", local_path.display()).into()); 114 | } 115 | 116 | let full_remote_path = PathBuf::from(remote_path); 117 | 118 | if sync { 119 | let should_push = match self.get_remote_stat(&full_remote_path).await { 120 | Ok(remote_stat) => self.should_push_file(local_path, &remote_stat).await?, 121 | Err(_) => true, 122 | }; 123 | 124 | if !should_push { 125 | return Ok(PushResult::Skip); 126 | } 127 | } 128 | 129 | self.send_command(SEND_COMMAND.as_ref()).await?; 130 | 131 | let mode = if local_path.metadata()?.permissions().mode() & 0o111 != 0 { 132 | 0o755 // rwxr-xr-x 133 | } else { 134 | DEFAULT_PUSH_MODE 135 | }; 136 | 137 | let remote_path_with_mode = format!("{},{}", full_remote_path.to_string_lossy(), mode); 138 | 139 | self.send_command(&(remote_path_with_mode.len() as u32).to_le_bytes()).await?; 140 | self.send_command(remote_path_with_mode.as_bytes()).await?; 141 | 142 | let mut file = File::open(local_path).await?; 143 | let bytes_transferred = self.send_file_contents(&mut file).await?; 144 | 145 | self.send_last_modified_time(local_path).await?; 146 | 147 | let response = self.read_first_four_bytes_response().await?; 148 | if response == FAIL { 149 | let adb_full_response = self.read_adb_full_response().await?; 150 | return Ok(PushResult::FailedAllPush(format!("adbr: error: failed to copy '{}' to '{}': remote {}", local_path.display(), full_remote_path.display(), adb_full_response))); 151 | } 152 | 153 | let duration = push_start_time.elapsed(); 154 | let transfer_rate = bytes_transferred as f64 / duration.as_secs_f64() / 1_000_000.0; 155 | 156 | Ok(PushResult::Success(transfer_rate, bytes_transferred, duration, 1)) 157 | } 158 | 159 | 160 | async fn should_push_file(&mut self, local_path: &Path, remote_stat: &StatData) -> Result> { 161 | let local_metadata = tokio::fs::metadata(local_path).await?; 162 | let local_mtime = local_metadata.modified()? 163 | .duration_since(std::time::UNIX_EPOCH)? 164 | .as_secs() as i64; 165 | 166 | Ok(local_mtime > remote_stat.mtime()) 167 | } 168 | 169 | async fn send_file_contents(&mut self, file: &mut File) -> Result> { 170 | let mut buffer = vec![0u8; 64 * 1024]; 171 | let mut total_sent = 0u64; 172 | loop { 173 | let bytes_read = file.read(&mut buffer).await?; 174 | if bytes_read == 0 { 175 | break; 176 | } 177 | self.send_command(DATA_COMMAND.as_ref()).await?; 178 | self.send_command(&(bytes_read as u32).to_le_bytes()).await?; 179 | self.send_command(&buffer[..bytes_read]).await?; 180 | total_sent += bytes_read as u64; 181 | } 182 | Ok(total_sent) 183 | } 184 | 185 | async fn send_last_modified_time(&mut self, local_path: &Path) -> Result<(), Box> { 186 | let metadata = tokio::fs::metadata(local_path).await?; 187 | let mtime = metadata.modified()? 188 | .duration_since(std::time::UNIX_EPOCH)? 189 | .as_secs() as u32; 190 | 191 | self.send_command(DONE_COMMAND.as_ref()).await?; 192 | self.send_command(&mtime.to_le_bytes()).await?; 193 | 194 | Ok(()) 195 | } 196 | } -------------------------------------------------------------------------------- /src/adb/io/io.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::io::ErrorKind; 3 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 4 | use crate::adb::client::Client; 5 | use std::io::{Write}; 6 | 7 | impl Client { 8 | pub async fn send_command(&mut self, data: &[u8]) -> Result<(), Box> { 9 | self.adb_stream.write_all(data).await?; 10 | Ok(()) 11 | } 12 | 13 | pub async fn read_variable_length_response(&mut self, prefix: String) -> Result> { 14 | let mut data = prefix.into_bytes(); 15 | let mut temp_buffer = [0u8; 1024]; 16 | 17 | loop { 18 | match self.adb_stream.read(&mut temp_buffer).await { 19 | Ok(0) => break, 20 | Ok(n) => data.extend_from_slice(&temp_buffer[..n]), 21 | Err(err) if err.kind() == ErrorKind::Interrupted => continue, 22 | Err(err) => return Err(err.into()), 23 | } 24 | } 25 | 26 | Ok(String::from_utf8(data)?) 27 | } 28 | 29 | pub async fn read_first_four_bytes_response(&mut self) -> Result> { 30 | self.read_exact_string(4).await 31 | } 32 | 33 | 34 | pub async fn read_exact_string(&mut self, length: usize) -> Result> { 35 | if length == 0 { 36 | return Ok(String::new()); 37 | } 38 | 39 | let mut data = vec![0u8; length]; 40 | match self.adb_stream.read_exact(&mut data).await { 41 | Ok(_) => Ok(String::from_utf8(data)?), 42 | Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(String::new()), 43 | Err(e) => Err(Box::new(e)) 44 | } 45 | } 46 | 47 | pub async fn get_exact_bytes(&mut self, num_bytes: usize) -> Result, Box> { 48 | let mut buffer = vec![0u8; num_bytes]; 49 | self.adb_stream.read_exact(&mut buffer).await.map_err(|e| Box::new(e) as Box)?; 50 | Ok(buffer) 51 | } 52 | 53 | pub async fn read_all_data(&mut self) -> Result> { 54 | let mut buffer = Vec::new(); 55 | let mut temp_buffer = [0u8; 1024]; 56 | 57 | loop { 58 | match self.adb_stream.read(&mut temp_buffer).await { 59 | Ok(0) => break, 60 | Ok(n) => buffer.extend_from_slice(&temp_buffer[..n]), 61 | Err(e) if e.kind() == ErrorKind::WouldBlock => break, 62 | Err(e) if e.kind() == ErrorKind::Interrupted => continue, 63 | Err(e) => return Err(e.into()), 64 | } 65 | } 66 | 67 | String::from_utf8(buffer).map_err(|e| e.into()) 68 | } 69 | 70 | 71 | pub async fn read_and_print_data(&mut self) -> Result<(), Box> { 72 | let mut temp_buffer = [0u8; 1024]; 73 | 74 | loop { 75 | match self.adb_stream.read(&mut temp_buffer).await { 76 | Ok(0) => break, 77 | Ok(n) => { 78 | let chunk = String::from_utf8_lossy(&temp_buffer[..n]); 79 | print!("{}", chunk); 80 | std::io::stdout().flush()?; 81 | }, 82 | Err(e) if e.kind() == ErrorKind::WouldBlock => break, 83 | Err(e) if e.kind() == ErrorKind::Interrupted => continue, 84 | Err(e) => return Err(e.into()), 85 | } 86 | } 87 | 88 | Ok(()) 89 | } 90 | 91 | pub async fn read_print_and_collect_output(&mut self) -> Result> { 92 | let mut temp_buffer = [0u8; 1024]; 93 | let mut full_output = String::new(); 94 | 95 | loop { 96 | match self.adb_stream.read(&mut temp_buffer).await { 97 | Ok(0) => break, 98 | Ok(n) => { 99 | let chunk = String::from_utf8_lossy(&temp_buffer[..n]); 100 | print!("{}", chunk); 101 | std::io::stdout().flush()?; 102 | full_output.push_str(&chunk); 103 | }, 104 | Err(e) if e.kind() == ErrorKind::WouldBlock => break, 105 | Err(e) if e.kind() == ErrorKind::Interrupted => continue, 106 | Err(e) => return Err(e.into()), 107 | } 108 | } 109 | 110 | Ok(full_output) 111 | } 112 | 113 | pub async fn has_more_data(&mut self) -> Result> { 114 | let mut buf = [0u8; 1]; 115 | let bytes_read = self.adb_stream.peek(&mut buf).await?; 116 | Ok(bytes_read > 0) 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/adb/io/mod.rs: -------------------------------------------------------------------------------- 1 | mod io; 2 | -------------------------------------------------------------------------------- /src/adb/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod file_transfer; 3 | pub mod protocol; 4 | pub mod shell; 5 | pub mod io; 6 | pub mod debugging; 7 | pub mod security; 8 | pub mod network; 9 | pub mod scripting; 10 | pub mod app_installation; 11 | -------------------------------------------------------------------------------- /src/adb/network/forward.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use crate::adb::client::Client; 3 | use crate::constants::{FAIL, HOST_FORWARD_COMMAND, HOST_FORWARD_KILL_ALL_COMMAND, HOST_FORWARD_KILL_COMMAND, HOST_FORWARD_LIST_COMMAND, NO_REBIND_OPTION, OKAY}; 4 | use crate::enums::device_transport::DeviceTransport; 5 | 6 | 7 | impl Client { 8 | pub async fn send_forward_command_set(&mut self, device_transport: DeviceTransport, local: &str, remote: &str, no_rebind: bool) -> Result<(), Box> { 9 | self.send_transport(device_transport).await?; 10 | 11 | let forward_message = if no_rebind { 12 | format!("{}:{}:{};{}", HOST_FORWARD_COMMAND, NO_REBIND_OPTION, local, remote) 13 | } else { 14 | format!("{}:{};{}", HOST_FORWARD_COMMAND, local, remote) 15 | }; 16 | self.send_adb_command_and_check_if_fail(&forward_message, &forward_message).await 17 | } 18 | 19 | pub async fn send_forward_command_remove(&mut self, device_transport: DeviceTransport, local: &str) -> Result<(), Box> { 20 | self.send_transport(device_transport).await?; 21 | 22 | let forward_message = format!("{}:{}", HOST_FORWARD_KILL_COMMAND, local); 23 | self.send_adb_command_and_check_if_fail(&forward_message, &forward_message).await 24 | } 25 | 26 | pub async fn send_forward_command_remove_all(&mut self, device_transport: DeviceTransport) -> Result<(), Box> { 27 | self.send_transport(device_transport).await?; 28 | 29 | let forward_message = format!("{}", HOST_FORWARD_KILL_ALL_COMMAND); 30 | self.send_adb_command_and_check_if_fail(&forward_message, &forward_message).await 31 | } 32 | 33 | 34 | pub async fn send_forward_command_list(&mut self, device_transport: DeviceTransport) -> Result> { 35 | self.send_transport(device_transport).await?; 36 | 37 | let forward_message = format!("{}", HOST_FORWARD_LIST_COMMAND); 38 | self.send_adb_command(&forward_message).await?; 39 | 40 | let response = self.read_first_four_bytes_response().await?; 41 | if response == OKAY { 42 | let list = self.read_adb_full_response().await?; 43 | Ok(list) 44 | } else if response == FAIL { 45 | let error_msg_str = self.read_adb_full_response().await?; 46 | Err(format!("Failed to list forward connections: {}", error_msg_str).into()) 47 | } else { 48 | Err(format!("Unexpected response: {}", response).into()) 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/adb/network/mod.rs: -------------------------------------------------------------------------------- 1 | mod forward; 2 | mod reverse; 3 | -------------------------------------------------------------------------------- /src/adb/network/reverse.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use crate::adb::client::Client; 3 | use crate::constants::{HOST_REVERSE_COMMAND, HOST_REVERSE_LIST_COMMAND, HOST_REVERSE_REMOVE_COMMAND, HOST_REVERSE_REMOVE_ALL_COMMAND, NO_REBIND_OPTION}; 4 | use crate::enums::device_transport::DeviceTransport; 5 | 6 | impl Client { 7 | pub async fn send_reverse_command_set(&mut self, device_transport: DeviceTransport, remote: &str, local: &str, no_rebind: bool, ) -> Result> { 8 | self.send_transport(device_transport).await?; 9 | 10 | let reverse_message = if no_rebind { 11 | format!("{}:{}:{};{}", HOST_REVERSE_COMMAND, NO_REBIND_OPTION, remote, local) 12 | } else { 13 | format!("{}:{};{}", HOST_REVERSE_COMMAND, remote, local) 14 | }; 15 | 16 | self.send_adb_command_with_extended_response(&reverse_message, &reverse_message).await 17 | } 18 | 19 | 20 | pub async fn send_reverse_command_remove(&mut self, device_transport: DeviceTransport, remote: &str, ) -> Result> { 21 | self.send_transport(device_transport).await?; 22 | 23 | let reverse_message = format!("{}:{}", HOST_REVERSE_REMOVE_COMMAND, remote); 24 | self.send_adb_command_with_extended_response(&reverse_message, &reverse_message).await 25 | } 26 | 27 | pub async fn send_reverse_command_remove_all(&mut self, device_transport: DeviceTransport, ) -> Result> { 28 | self.send_transport(device_transport).await?; 29 | 30 | let reverse_message = format!("{}", HOST_REVERSE_REMOVE_ALL_COMMAND); 31 | self.send_adb_command_with_extended_response(&reverse_message, &reverse_message).await 32 | } 33 | 34 | pub async fn send_reverse_command_list(&mut self, device_transport: DeviceTransport, ) -> Result> { 35 | self.send_transport(device_transport).await?; 36 | let reverse_message = format!("{}", HOST_REVERSE_LIST_COMMAND); 37 | self.send_adb_and_return_response(&reverse_message,&reverse_message).await 38 | } 39 | 40 | 41 | } -------------------------------------------------------------------------------- /src/adb/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | mod protocol; 2 | -------------------------------------------------------------------------------- /src/adb/protocol/protocol.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::path::Path; 3 | use crate::adb::client::Client; 4 | use crate::constants::{ADB_DEVICES_COMMAND, FAIL, OKAY, S_IFDIR, STAT_COMMAND, STAT_DATA_SIZE, USER_TRANSPORT_COMMAND}; 5 | use crate::enums::device_transport::DeviceTransport; 6 | use crate::models::remote_metadata::RemoteMetadata; 7 | use crate::models::stat_data::StatData; 8 | use crate::utils::strip_adb_prefix; 9 | 10 | 11 | impl Client { 12 | pub async fn send_transport(&mut self, device_transport: DeviceTransport) -> Result<(), Box> { 13 | let transport_command = device_transport.get_device_transport(); 14 | self.send_adb_command_and_check_if_fail(&transport_command, USER_TRANSPORT_COMMAND).await 15 | } 16 | 17 | pub async fn adb_devices(&mut self) -> Result> { 18 | self.send_adb_command(ADB_DEVICES_COMMAND).await?; 19 | let response = self.read_first_four_bytes_response().await?; 20 | if response == OKAY { 21 | let device_list_str = self.read_adb_full_response().await?; 22 | Ok(format!("List of devices attached\n{}", device_list_str)) 23 | } else { 24 | let error_msg_str = self.read_adb_full_response().await?; 25 | println!("Received: {:?} Error message: {}", response, error_msg_str); 26 | Err("Failed to get devices list".into()) 27 | } 28 | } 29 | 30 | pub async fn send_adb_command(&mut self, command: &str) -> Result<(), Box> { 31 | let msg_len = format!("{:04x}", command.len()); 32 | let adb_message = format!("{}{}", msg_len, command); 33 | self.send_command(adb_message.as_bytes()).await?; 34 | Ok(()) 35 | } 36 | 37 | pub(crate) async fn get_remote_stat(&mut self, remote_path: &Path) -> Result> { 38 | let remote_path_str = remote_path.to_string_lossy(); 39 | self.send_command(STAT_COMMAND.as_ref()).await?; 40 | self.send_command(&(remote_path_str.len() as u32).to_le_bytes()).await?; 41 | self.send_command(remote_path_str.as_bytes()).await?; 42 | 43 | let response = self.read_first_four_bytes_response().await?; 44 | 45 | if response == STAT_COMMAND { 46 | let stat_data = self.get_exact_bytes(STAT_DATA_SIZE).await?; 47 | let stat = StatData::from_bytes(&stat_data)?; 48 | Ok(stat) 49 | } else { 50 | Err(format!("ADB STAT error: Unexpected response: {}", response).into()) 51 | } 52 | } 53 | 54 | pub async fn read_adb_full_response(&mut self) -> Result> { 55 | let prefix = self.read_exact_string(4).await?; 56 | let response = if let Ok(length) = usize::from_str_radix(&prefix, 16) { 57 | self.read_exact_string(length).await? 58 | } else { 59 | self.read_variable_length_response(prefix).await? 60 | }; 61 | 62 | Ok(strip_adb_prefix(response)) 63 | } 64 | 65 | pub async fn check_remote_path_is_directory(&mut self, remote_path: &str) -> Result> { 66 | self.send_command(STAT_COMMAND.as_bytes()).await?; 67 | self.send_command(&((remote_path.len() + 1) as u32).to_le_bytes()).await?; // +1 for null terminator 68 | let remote_path_with_null = format!("{}\0", remote_path); 69 | self.send_command(remote_path_with_null.as_bytes()).await?; 70 | 71 | let response = self.read_first_four_bytes_response().await?; 72 | 73 | if response == STAT_COMMAND { 74 | let stat_data = self.get_exact_bytes(STAT_DATA_SIZE).await?; 75 | let stat = StatData::from_bytes(&stat_data)?; 76 | Ok((stat.mode() & S_IFDIR) != 0) 77 | } else { 78 | Ok(false) 79 | } 80 | } 81 | pub async fn get_remote_metadata(&mut self, remote_path: &str) -> Result> { 82 | self.send_command(STAT_COMMAND.as_bytes()).await?; 83 | 84 | let path_with_null = format!("{}\0", remote_path); 85 | let path_len = (path_with_null.len()) as u32; 86 | self.send_command(&path_len.to_le_bytes()).await?; 87 | 88 | self.send_command(path_with_null.as_bytes()).await?; 89 | 90 | let response = self.get_exact_bytes(4).await?; 91 | let response_str = std::str::from_utf8(&response)?; 92 | 93 | if response_str != STAT_COMMAND { 94 | return Err(format!("Unexpected response for STAT_COMMAND: {}", response_str).into()); 95 | } 96 | 97 | let stat_data_bytes = self.get_exact_bytes(12).await?; 98 | let stat = StatData::from_bytes(&stat_data_bytes)?; 99 | 100 | Ok(RemoteMetadata { 101 | mode: stat.mode(), 102 | mtime: stat.mtime(), 103 | }) 104 | } 105 | pub async fn send_adb_command_and_check_if_fail(&mut self, command: &str, debug_command: &str) -> Result<(), Box> { 106 | self.send_adb_command(&command).await?; 107 | let response = self.read_first_four_bytes_response().await?; 108 | match response.as_str() { 109 | OKAY => { 110 | Ok(()) 111 | } 112 | FAIL => { 113 | let error_msg_str = self.read_adb_full_response().await?; 114 | return Err(format!("{}", error_msg_str).into()); 115 | } 116 | _ => { 117 | let error_msg_str = self.read_adb_full_response().await?; 118 | return Err(format!("Failed send {} command : {}", debug_command, error_msg_str).into()); 119 | } 120 | } 121 | } 122 | pub async fn send_adb_and_return_response(&mut self, command: &str, debug_command: &str) -> Result> { 123 | self.send_adb_command(&command).await?; 124 | 125 | let response = self.read_first_four_bytes_response().await?; 126 | if response == OKAY { 127 | let response = self.read_adb_full_response().await?; 128 | Ok(response) 129 | } else if response == FAIL { 130 | let error_msg_str = self.read_adb_full_response().await?; 131 | Err(format!("Failed to send {} command: {}", debug_command, error_msg_str).into()) 132 | } else { 133 | Err(format!("Unexpected response: {}", response).into()) 134 | } 135 | } 136 | 137 | pub async fn send_adb_command_with_extended_response(&mut self, command: &str, debug_command: &str) -> Result> { 138 | self.send_adb_command(&command).await?; 139 | let response = self.read_first_four_bytes_response().await?; 140 | if response == OKAY { 141 | if self.has_more_data().await? { 142 | let next_response = self.read_first_four_bytes_response().await?; 143 | match next_response.as_str() { 144 | OKAY => { 145 | let mut response_data = String::new(); 146 | if self.has_more_data().await? { 147 | response_data = self.read_adb_full_response().await?; 148 | } 149 | Ok(response_data) 150 | } 151 | FAIL => { 152 | let error_msg_str = self.read_adb_full_response().await?; 153 | Err(format!("{}", error_msg_str).into()) 154 | } 155 | _ => { 156 | let error_msg_str = self.read_adb_full_response().await?; 157 | Err(format!("Failed to send {} command: {}", debug_command, error_msg_str).into()) 158 | } 159 | } 160 | } else { 161 | Ok(String::new()) 162 | } 163 | } else if response == FAIL { 164 | let error_msg_str = self.read_adb_full_response().await?; 165 | Err(format!("{}", error_msg_str).into()) 166 | } else { 167 | let error_msg_str = self.read_adb_full_response().await?; 168 | Err(format!("Failed to send {} command: {}", debug_command, error_msg_str).into()) 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/adb/scripting/mod.rs: -------------------------------------------------------------------------------- 1 | mod scripting; 2 | -------------------------------------------------------------------------------- /src/adb/scripting/scripting.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use crate::adb::client::Client; 3 | use crate::constants::{HOST_GET_DEVPATH_COMMAND, REBOOT_BOOTLOADER, HOST_SERIALNO_COMMAND, REBOOT_RECOVERY, REBOOT_SIDELOAD, REBOOT_SIDELOAD_AUTO_REBOOT, OKAY, ADB_REBOOT_BOOTLOADER_COMMAND, ADB_REBOOT_RECOVERY_COMMAND, ADB_REBOOT_SIDELOAD_COMMAND, ADB_REBOOT_SIDELOAD_AUTO_REBOOT_COMMAND, ADB_REBOOT_COMMAND, ADB_ROOT_COMMAND, ADB_UNROOT_COMMAND, ADB_REMOUNT_COMMAND, ADB_USB_COMMAND, ADB_TCPIP_COMMAND, ADB_GET_STATE_COMMAND}; 4 | use crate::enums::device_transport::DeviceTransport; 5 | use tokio::time::{Duration, Instant, sleep}; 6 | 7 | const WAIT_FOR_STATE_POLL_INTERVAL_SEC: u64 = 1; 8 | 9 | impl Client { 10 | pub async fn adb_wait_for(&mut self, device_transport: DeviceTransport, desired_state: &str, timeout_duration: Option) -> Result<(), Box> { 11 | let start_time = Instant::now(); 12 | 13 | loop { 14 | if let Some(timeout) = timeout_duration { 15 | if Instant::now().duration_since(start_time) >= timeout { 16 | return Err(format!( 17 | "Timeout while waiting for device to reach '{}' state", 18 | desired_state 19 | ).into()); 20 | } 21 | } 22 | 23 | match self.adb_get_state(device_transport.clone()).await { 24 | Ok(current_state) => { 25 | if current_state == desired_state { 26 | return Ok(()); 27 | } else { 28 | sleep(Duration::from_secs(WAIT_FOR_STATE_POLL_INTERVAL_SEC)).await; 29 | } 30 | } 31 | Err(_) => { 32 | sleep(Duration::from_secs(WAIT_FOR_STATE_POLL_INTERVAL_SEC)).await; 33 | } 34 | } 35 | } 36 | } 37 | 38 | 39 | pub async fn adb_get_state(&mut self, device_transport: DeviceTransport) -> Result> { 40 | self.send_transport(device_transport.clone()).await?; 41 | self.send_adb_command(ADB_GET_STATE_COMMAND).await?; 42 | let response = self.read_first_four_bytes_response().await?; 43 | 44 | if response != OKAY { 45 | let error_msg_str = self.read_adb_full_response().await?; 46 | return Err(format!("Failed to get device state: {}", error_msg_str).into()); 47 | } 48 | 49 | self.read_adb_full_response().await 50 | } 51 | 52 | 53 | pub async fn adb_reboot(&mut self, device_transport: DeviceTransport, reboot_target: Option) -> Result<(), Box> { 54 | self.send_transport(device_transport.clone()).await?; 55 | let command = match reboot_target.as_deref() { 56 | Some(REBOOT_BOOTLOADER) => ADB_REBOOT_BOOTLOADER_COMMAND, 57 | Some(REBOOT_RECOVERY) => ADB_REBOOT_RECOVERY_COMMAND, 58 | Some(REBOOT_SIDELOAD) => ADB_REBOOT_SIDELOAD_COMMAND, 59 | Some(REBOOT_SIDELOAD_AUTO_REBOOT) => ADB_REBOOT_SIDELOAD_AUTO_REBOOT_COMMAND, 60 | Some(target) => { 61 | return Err(format!("Invalid reboot target: {}", target).into()); 62 | } 63 | None => ADB_REBOOT_COMMAND, 64 | }; 65 | self.send_adb_command(command).await?; 66 | let response = self.read_first_four_bytes_response().await?; 67 | 68 | if response != OKAY { 69 | let error_msg_str = self.read_adb_full_response().await?; 70 | return Err(format!("Failed to reboot the device: {}", error_msg_str).into()); 71 | } 72 | Ok(()) 73 | } 74 | pub async fn adb_serialno(&mut self, device_transport: DeviceTransport) -> Result> { 75 | self.send_transport(device_transport.clone()).await?; 76 | self.send_adb_command(HOST_SERIALNO_COMMAND).await?; 77 | 78 | let response = self.read_first_four_bytes_response().await?; 79 | 80 | if response != OKAY { 81 | let error_msg_str = self.read_adb_full_response().await?; 82 | return Err(format!("Failed to get device serial: {}", error_msg_str).into()); 83 | } 84 | 85 | self.read_adb_full_response().await 86 | } 87 | 88 | pub async fn adb_remount(&mut self, device_transport: DeviceTransport) -> Result> { 89 | self.send_transport(device_transport.clone()).await?; 90 | self.send_adb_command(ADB_REMOUNT_COMMAND).await?; 91 | let response = self.read_first_four_bytes_response().await?; 92 | if response != OKAY { 93 | let error_msg_str = self.read_adb_full_response().await?; 94 | return Err(format!("Failed to remount the device: {}", error_msg_str).into()); 95 | } 96 | self.read_all_data().await 97 | } 98 | 99 | pub async fn adb_root(&mut self, device_transport: DeviceTransport) -> Result> { 100 | self.send_transport(device_transport.clone()).await?; 101 | self.send_adb_command(ADB_ROOT_COMMAND).await?; 102 | let response = self.read_first_four_bytes_response().await?; 103 | 104 | if response != OKAY { 105 | let error_msg_str = self.read_adb_full_response().await?; 106 | return Err(format!("Failed to root the device: {}", error_msg_str).into()); 107 | } 108 | 109 | self.read_all_data().await 110 | } 111 | 112 | pub async fn adb_unroot(&mut self, device_transport: DeviceTransport) -> Result> { 113 | self.send_transport(device_transport.clone()).await?; 114 | self.send_adb_command(ADB_UNROOT_COMMAND).await?; 115 | let response = self.read_first_four_bytes_response().await?; 116 | 117 | if response != OKAY { 118 | let error_msg_str = self.read_adb_full_response().await?; 119 | return Err(format!("Failed to unroot the device: {}", error_msg_str).into()); 120 | } 121 | 122 | self.read_all_data().await 123 | } 124 | 125 | pub async fn adb_get_devpath(&mut self, device_transport: DeviceTransport) -> Result> { 126 | self.send_transport(device_transport.clone()).await?; 127 | self.send_adb_command(HOST_GET_DEVPATH_COMMAND).await?; 128 | 129 | let response = self.read_first_four_bytes_response().await?; 130 | 131 | if response != OKAY { 132 | let error_msg_str = self.read_adb_full_response().await?; 133 | return Err(format!("Failed to get device dev path: {}", error_msg_str).into()); 134 | } 135 | 136 | self.read_adb_full_response().await 137 | } 138 | 139 | pub async fn adb_usb(&mut self, device_transport: DeviceTransport) -> Result> { 140 | self.send_transport(device_transport.clone()).await?; 141 | self.send_adb_command(ADB_USB_COMMAND).await?; 142 | let response = self.read_first_four_bytes_response().await?; 143 | 144 | if response != OKAY { 145 | let error_msg_str = self.read_adb_full_response().await?; 146 | return Err(format!("Failed to switch to USB mode: {}", error_msg_str).into()); 147 | } 148 | 149 | self.read_adb_full_response().await 150 | } 151 | 152 | pub async fn adb_tcpip(&mut self, device_transport: DeviceTransport, port: u16) -> Result> { 153 | self.send_transport(device_transport.clone()).await?; 154 | let command = format!("{}{}", ADB_TCPIP_COMMAND, port); 155 | self.send_adb_command(&command).await?; 156 | let response = self.read_first_four_bytes_response().await?; 157 | 158 | if response != OKAY { 159 | let error_msg_str = self.read_adb_full_response().await?; 160 | return Err(format!("Failed to switch to TCP mode on port {}: {}", port, error_msg_str).into()); 161 | } 162 | 163 | self.read_adb_full_response().await 164 | } 165 | } -------------------------------------------------------------------------------- /src/adb/security/mod.rs: -------------------------------------------------------------------------------- 1 | mod security; 2 | -------------------------------------------------------------------------------- /src/adb/security/security.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, create_dir_all}; 2 | use std::error::Error; 3 | use std::path::PathBuf; 4 | use crate::adb::client::Client; 5 | use crate::constants::OKAY; 6 | use crate::enums::device_transport::DeviceTransport; 7 | use std::io::Write; 8 | use openssl::rsa::Rsa; 9 | use openssl::pkey::PKey; 10 | use dirs::home_dir; 11 | 12 | pub const ADB_DISABLE_VERITY_COMMAND: &str = "disable-verity:"; 13 | pub const ADB_ENABLE_VERITY_COMMAND: &str = "enable-verity:"; 14 | pub const ADB_FOLDER_NAME: &str = ".android"; 15 | pub const ADB_KEY_FILENAME: &str = "adbkey"; 16 | 17 | impl Client { 18 | pub async fn adb_disable_verity(&mut self, device_transport: DeviceTransport) -> Result> { 19 | self.send_transport(device_transport.clone()).await?; 20 | self.send_adb_command(ADB_DISABLE_VERITY_COMMAND).await?; 21 | let response = self.read_first_four_bytes_response().await?; 22 | if response != OKAY { 23 | let error_msg_str = self.read_adb_full_response().await?; 24 | return Err(format!("Failed to disable verity: {}", error_msg_str).into()); 25 | } 26 | 27 | let disable_verity_response = self.read_all_data().await?; 28 | Ok(disable_verity_response) 29 | } 30 | pub async fn adb_enable_verity(&mut self, device_transport: DeviceTransport) -> Result> { 31 | self.send_transport(device_transport.clone()).await?; 32 | self.send_adb_command(ADB_ENABLE_VERITY_COMMAND).await?; 33 | let response = self.read_first_four_bytes_response().await?; 34 | if response != OKAY { 35 | let error_msg_str = self.read_adb_full_response().await?; 36 | return Err(format!("Failed to enable verity: {}", error_msg_str).into()); 37 | } 38 | 39 | let enable_verity_response = self.read_all_data().await?; 40 | Ok(enable_verity_response) 41 | } 42 | 43 | pub fn adb_keygen(&self, file_path: Option<&str>) -> Result> { 44 | let file_path = if let Some(path) = file_path { 45 | let path = PathBuf::from(path); 46 | if path.is_dir() { 47 | path.join(ADB_KEY_FILENAME) 48 | } else { 49 | path 50 | } 51 | } else { 52 | let mut path = home_dir().ok_or("Unable to determine home directory")?; 53 | path.push(ADB_FOLDER_NAME); 54 | path.push(ADB_KEY_FILENAME); 55 | path 56 | }; 57 | 58 | if let Some(parent) = file_path.parent() { 59 | create_dir_all(parent)?; 60 | } 61 | 62 | let rsa = Rsa::generate(2048)?; 63 | let pkey = PKey::from_rsa(rsa)?; 64 | 65 | let private_key = pkey.private_key_to_pem_pkcs8()?; 66 | let public_key = pkey.public_key_to_pem()?; 67 | 68 | let mut private_key_file = File::create(&file_path)?; 69 | private_key_file.write_all(&private_key)?; 70 | 71 | let public_key_path = file_path.with_extension("pub"); 72 | let mut public_key_file = File::create(&public_key_path)?; 73 | public_key_file.write_all(&public_key)?; 74 | 75 | Ok(format!("ADB key pair generated successfully.\nPrivate key saved to: {}\nPublic key saved to: {}", 76 | file_path.display(), public_key_path.display())) 77 | } 78 | } -------------------------------------------------------------------------------- /src/adb/shell/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod shell; 2 | -------------------------------------------------------------------------------- /src/adb/shell/shell.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::io; 3 | 4 | use tokio::time::{Duration}; 5 | use std::io::{Write}; 6 | use std::os::fd::BorrowedFd; 7 | use std::os::unix::io::{AsRawFd}; 8 | use termios::{Termios, TCSAFLUSH, ICANON, ECHO}; 9 | use nix::sys::select::{select, FdSet}; 10 | use nix::sys::time::TimeVal; 11 | use nix::unistd::read; 12 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 13 | use crate::adb::client::Client; 14 | use crate::constants::{ADB_SHELL_COMMAND, FAIL, OKAY, SELECT_TIMEOUT_USEC, USER_EXIT_COMMAND}; 15 | use crate::enums::device_transport::DeviceTransport; 16 | 17 | const WAIT_FOR_FIRST_CONNECTION_DURATION_MS: u64 = 300; 18 | 19 | impl Client { 20 | pub async fn adb_shell(&mut self, device_transport: DeviceTransport, shell_command: &str) -> Result> { 21 | self.send_transport(device_transport.clone()).await?; 22 | let adb_shell_command = format!("{}{}", ADB_SHELL_COMMAND, shell_command); 23 | self.send_adb_command(&adb_shell_command).await?; 24 | 25 | tokio::time::sleep(Duration::from_millis(WAIT_FOR_FIRST_CONNECTION_DURATION_MS)).await; 26 | 27 | match self.read_first_four_bytes_response().await?.as_str() { 28 | OKAY => { 29 | if shell_command.is_empty() { 30 | self.interactive_shell().await?; 31 | Ok(String::new()) 32 | } else { 33 | let output = self.read_print_and_collect_output().await?; 34 | io::stdout().flush()?; 35 | Ok(output) 36 | } 37 | } 38 | FAIL => { 39 | let fail_response = self.read_adb_full_response().await?; 40 | Ok(fail_response) 41 | } 42 | _ => { 43 | let error_message = self.read_adb_full_response().await?; 44 | Err(format!( 45 | "Failed to send shell command : {}", 46 | error_message 47 | ) 48 | .into()) 49 | } 50 | } 51 | } 52 | 53 | pub async fn interactive_shell(&mut self) -> Result<(), Box> { 54 | let stdin = io::stdin(); 55 | let stdin_fd = stdin.as_raw_fd(); 56 | let mut oldtty_attrs = None; 57 | 58 | if atty::is(atty::Stream::Stdin) { 59 | oldtty_attrs = Some(Termios::from_fd(stdin_fd)?); 60 | let mut new_termios = oldtty_attrs.unwrap(); 61 | new_termios.c_lflag &= !(ICANON | ECHO); 62 | termios::tcsetattr(stdin_fd, TCSAFLUSH, &new_termios)?; 63 | } 64 | 65 | let mut is_alive = true; 66 | let mut input_buffer = String::new(); 67 | 68 | while is_alive { 69 | let mut read_fds = FdSet::new(); 70 | read_fds.insert(unsafe { BorrowedFd::borrow_raw(self.adb_stream.as_raw_fd()) }); 71 | read_fds.insert(unsafe { BorrowedFd::borrow_raw(stdin_fd) }); 72 | 73 | match select(None, &mut read_fds, None, None, Some(&mut TimeVal::new(0, SELECT_TIMEOUT_USEC))) { 74 | Ok(ready) if ready > 0 => { 75 | if read_fds.contains(unsafe { BorrowedFd::borrow_raw(self.adb_stream.as_raw_fd()) }) { 76 | let mut buf = [0; 8000]; 77 | match self.adb_stream.read(&mut buf).await { 78 | Ok(0) => is_alive = false, 79 | Ok(n) => { 80 | let decoded_out = String::from_utf8_lossy(&buf[..n]); 81 | print!("{}", decoded_out); 82 | io::stdout().flush()?; 83 | } 84 | Err(_) => is_alive = false, 85 | } 86 | } 87 | 88 | if read_fds.contains(unsafe { BorrowedFd::borrow_raw(stdin_fd) }) && is_alive { 89 | let mut char = [0; 1]; 90 | match read(stdin_fd, &mut char) { 91 | Ok(0) => is_alive = false, 92 | Ok(_) => { 93 | self.adb_stream.write_all(&char).await?; 94 | input_buffer.push(char[0] as char); 95 | 96 | if input_buffer.ends_with(USER_EXIT_COMMAND) { 97 | print!("\n"); 98 | is_alive = false; 99 | } 100 | } 101 | Err(_) => is_alive = false, 102 | } 103 | } 104 | } 105 | Ok(_) => {} 106 | Err(e) => return Err(Box::new(e)), 107 | } 108 | } 109 | 110 | if let Some(attrs) = oldtty_attrs { 111 | termios::tcsetattr(stdin_fd, TCSAFLUSH, &attrs)?; 112 | } 113 | 114 | Ok(()) 115 | } 116 | } -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const VERSION: &str = "1.0.0"; 2 | pub const PROGRAM_NAME: &str = "adbr"; 3 | 4 | pub const DEFAULT_ADB_SERVER_IP: &str = "127.0.0.1"; 5 | pub const DEFAULT_ADB_SERVER_PORT: u16 = 5037; 6 | 7 | pub const ADB_ADDRESS_ENV: &str = "ADB_ADDRESS"; 8 | pub const ADB_SERVER_CONNECT_TIMEOUT_SECONDS_DURATION: u64 = 5; 9 | 10 | pub const FLAG_HELP_SHORT: &str = "-h"; 11 | pub const FLAG_HELP_LONG: &str = "--help"; 12 | pub const FLAG_VERSION: &str = "--version"; 13 | pub const FLAG_SERVER_ADDRESS: &str = "-H"; 14 | pub const FLAG_SERVER_PORT: &str = "-P"; 15 | pub const FLAG_SERIAL: &str = "-s"; 16 | pub const FLAG_USB: &str = "-d"; 17 | pub const FLAG_EMULATOR: &str = "-e"; 18 | pub const FLAG_WATCH_DEVICES: &str = "-w"; 19 | pub const FLAG_TIMEOUT: &str = "-t"; 20 | 21 | pub const ADB_SHELL_COMMAND: &str = "shell:"; 22 | pub const ADB_DEVICES_COMMAND: &str = "host:devices"; 23 | pub const HOST_FORWARD_COMMAND: &str = "host:forward"; 24 | pub const HOST_FORWARD_KILL_COMMAND: &str = "host:killforward"; 25 | pub const HOST_FORWARD_KILL_ALL_COMMAND: &str = "host:killforward-all"; 26 | pub const HOST_FORWARD_LIST_COMMAND: &str = "host:list-forward"; 27 | 28 | pub const HOST_REVERSE_COMMAND: &str = "reverse:forward"; 29 | pub const HOST_REVERSE_REMOVE_COMMAND: &str = "reverse:killforward"; 30 | pub const HOST_REVERSE_REMOVE_ALL_COMMAND: &str = "reverse:killforward-all"; 31 | pub const HOST_REVERSE_LIST_COMMAND: &str = "reverse:list-forward"; 32 | 33 | 34 | pub const HOST_SERIALNO_COMMAND: &str = "host:get-serialno"; 35 | pub const HOST_GET_DEVPATH_COMMAND: &str = "host:get-devpath"; 36 | 37 | pub const USER_ENABLE_VERITY_COMMAND: &str = "enable-verity"; 38 | pub const USER_KEYGEN_COMMAND: &str = "keygen"; 39 | pub const USER_DISABLE_VERITY_COMMAND: &str = "disable-verity"; 40 | pub const USER_BUGREPORT_COMMAND: &str = "bugreport"; 41 | pub const USER_LOGCAT_COMMAND: &str = "logcat"; 42 | pub const USER_TRANSPORT_COMMAND: &str = "transport"; 43 | pub const USER_REBOOT_COMMAND: &str = "reboot"; 44 | pub const USER_SERIALNO_COMMAND: &str = "get-serialno"; 45 | pub const USER_REMOUNT_COMMAND: &str = "remount"; 46 | pub const USER_SHELL_COMMAND: &str = "shell"; 47 | pub const USER_DEVICES_COMMAND: &str = "devices"; 48 | pub const USER_CONNECT_COMMAND: &str = "connect"; 49 | pub const USER_FORWARD_COMMAND: &str = "forward"; 50 | pub const USER_REVERSE_COMMAND: &str = "reverse"; 51 | pub const USER_PUSH_COMMAND: &str = "push"; 52 | pub const USER_PULL_COMMAND: &str = "pull"; 53 | pub const USER_EXIT_COMMAND: &str = "exit\n"; 54 | pub const USER_USB_COMMAND: &str = "usb"; 55 | pub const USER_TCPIP_COMMAND: &str = "tcpip"; 56 | pub const USER_WAIT_FOR_COMMAND: &str = "wait-for"; 57 | pub const USER_GET_STATE_COMMAND: &str = "get-state"; 58 | 59 | pub const OPTION_NO_REBIND: &str = "--no-rebind"; 60 | pub const OPTION_REMOVE: &str = "--remove"; 61 | pub const OPTION_REMOVE_ALL: &str = "--remove-all"; 62 | pub const OPTION_LIST: &str = "--list"; 63 | 64 | pub const USER_GET_DEVPATH_COMMAND: &str = "get-devpath"; 65 | pub const NO_REBIND_OPTION: &str = "norebind"; 66 | pub const SYNC_COMMAND: &str = "sync:"; 67 | 68 | pub const REBOOT_BOOTLOADER: &str = "bootloader"; 69 | pub const REBOOT_RECOVERY: &str = "recovery"; 70 | pub const REBOOT_SIDELOAD: &str = "sideload"; 71 | pub const REBOOT_SIDELOAD_AUTO_REBOOT: &str = "sideload-auto-reboot"; 72 | 73 | pub const ADB_REBOOT_COMMAND: &str = "reboot:"; 74 | pub const ADB_REMOUNT_COMMAND: &str = "remount:"; 75 | pub const ADB_ROOT_COMMAND: &str = "root:"; 76 | pub const ADB_UNROOT_COMMAND: &str = "unroot:"; 77 | pub const ADB_USB_COMMAND: &str = "usb:"; 78 | pub const ADB_TCPIP_COMMAND: &str = "tcpip:"; 79 | pub const ADB_REBOOT_BOOTLOADER_COMMAND: &str = "reboot:bootloader"; 80 | pub const ADB_REBOOT_RECOVERY_COMMAND: &str = "reboot:recovery"; 81 | pub const ADB_REBOOT_SIDELOAD_COMMAND: &str = "reboot:sideload"; 82 | pub const ADB_REBOOT_SIDELOAD_AUTO_REBOOT_COMMAND: &str = "reboot:sideload-auto-reboot"; 83 | pub const ADB_GET_STATE_COMMAND: &str = "host:get-state"; 84 | pub const SEND_COMMAND: &str = "SEND"; 85 | pub const STAT_COMMAND: &str = "STAT"; 86 | pub const DATA_COMMAND: &str = "DATA"; 87 | pub const DONE_COMMAND: &str = "DONE"; 88 | pub const QUIT_COMMAND: &str = "QUIT"; 89 | pub const RECV_COMMAND: &str = "RECV"; 90 | pub const LIST_COMMAND: &str = "LIST"; 91 | pub const DENT_COMMAND: &str = "DENT"; 92 | 93 | pub const USER_INSTALL_COMMAND: &str = "install"; 94 | pub const PM_INSTALL: &str = "pm install"; 95 | pub const PM_UNINSTALL: &str = "pm uninstall"; 96 | pub const USER_UNINSTALL_COMMAND: &str = "uninstall"; 97 | 98 | 99 | pub const UNINSTALL_FLAG_KEEP_DATA: &str = "-k"; 100 | pub const INSTALL_FLAG_REPLACE: &str = "-r"; 101 | pub const INSTALL_FLAG_DOWNGRADE: &str = "-d"; 102 | pub const INSTALL_FLAG_GRANT_PERMISSIONS: &str = "-g"; 103 | pub const INSTALL_FLAG_TEST: &str = "-t"; 104 | pub const INSTALL_FLAG_SDCARD: &str = "-s"; 105 | pub const INSTALL_FLAG_INTERNAL: &str = "-f"; 106 | pub const INSTALL_FLAG_FORWARD_LOCK: &str = "-l"; 107 | 108 | pub const USER_ROOT_COMMAND: &str = "root"; 109 | pub const USER_UNROOT_COMMAND: &str = "unroot"; 110 | 111 | pub const DEFAULT_WAIT_STATE: &str = "device"; 112 | 113 | pub const S_IFDIR: u32 = 0x4000; 114 | pub const DEFAULT_PUSH_MODE: u32 = 0o644; // r 115 | pub const STAT_DATA_SIZE: usize = 12; 116 | 117 | pub const OKAY: &str = "OKAY"; 118 | pub const FAIL: &str = "FAIL"; 119 | 120 | pub const DEVICE_TEMP_DIRECTORY: &str = "/data/local/tmp/"; 121 | 122 | pub const REFRESH_INTERVAL_SECS: u64 = 1; 123 | 124 | pub const SELECT_TIMEOUT_USEC: i64 = 100_000; -------------------------------------------------------------------------------- /src/enums/device_transport.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub enum DeviceTransport { 3 | Any(String), 4 | EmulatorAny(String), 5 | UsbAny(String), 6 | Usb(String), 7 | } 8 | 9 | impl DeviceTransport { 10 | pub fn default() -> Self { 11 | DeviceTransport::Any(String::from("host:transport-any")) 12 | } 13 | 14 | pub fn default_usb() -> Self { 15 | DeviceTransport::UsbAny(String::from("host:transport-usb")) 16 | } 17 | 18 | pub fn default_emulator() -> Self { 19 | DeviceTransport::EmulatorAny(String::from("host:transport-local")) 20 | } 21 | 22 | pub fn usb(serial: String) -> Self { 23 | DeviceTransport::Usb(format!("host:transport:{}", serial)) 24 | } 25 | 26 | pub fn get_device_transport(&self) -> &str { 27 | match self { 28 | DeviceTransport::Any(s) => s, 29 | DeviceTransport::EmulatorAny(s) => s, 30 | DeviceTransport::UsbAny(s) => s, 31 | DeviceTransport::Usb(s) => s, 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/enums/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod device_transport; 2 | pub mod push_result; 3 | pub mod pull_result; -------------------------------------------------------------------------------- /src/enums/pull_result.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | 4 | #[allow(dead_code)] 5 | #[derive(Debug)] 6 | pub enum PullResult { 7 | Success(f64, u64, Duration, u32), 8 | SuccessDirectory(f64, u64, Duration, u32), 9 | FailedAllPull(String), 10 | } -------------------------------------------------------------------------------- /src/enums/push_result.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum PushResult { 5 | Skip, 6 | Success(f64, u64, Duration, usize), // transfer_rate, bytes, duration, file_count 7 | SuccessDirectory(f64, u64, Duration, usize), 8 | FailedAllPush(String), 9 | } 10 | 11 | impl std::fmt::Display for PushResult { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | match self { 14 | PushResult::Skip => write!(f, "0 files pushed. 1 file skipped."), 15 | PushResult::Success(transfer_rate, bytes_transferred, duration, _) => write!( 16 | f, 17 | "1 file pushed. {:.1} MB/s ({} bytes in {:.3}s)", 18 | transfer_rate, 19 | bytes_transferred, 20 | duration.as_secs_f64() 21 | ), 22 | PushResult::SuccessDirectory(transfer_rate, bytes_transferred, duration, file_count) => write!( 23 | f, 24 | "{} files pushed. {:.1} MB/s ({} bytes in {:.3}s)", 25 | file_count, 26 | transfer_rate, 27 | bytes_transferred, 28 | duration.as_secs_f64() 29 | ), 30 | PushResult::FailedAllPush(err_msg) => write!(f, "{}", err_msg) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // src/lib.rs 2 | 3 | pub mod adb; 4 | pub mod constants; 5 | pub mod enums; 6 | pub mod utils; 7 | pub mod models; 8 | 9 | pub use self::adb::client::Client; 10 | pub use self::enums::device_transport::DeviceTransport; 11 | pub use self::enums::pull_result::PullResult; 12 | pub use self::enums::push_result::PushResult; 13 | 14 | pub use self::adb::app_installation; 15 | pub use self::adb::debugging; 16 | pub use self::adb::file_transfer; 17 | pub use self::adb::io; 18 | pub use self::adb::network; 19 | pub use self::adb::protocol; 20 | pub use self::adb::scripting; 21 | pub use self::adb::security; 22 | pub use self::adb::shell; 23 | 24 | pub use self::adb::app_installation::{install, uninstall}; 25 | pub use self::adb::file_transfer::{push, pull}; 26 | 27 | pub use self::utils::strip_adb_prefix; 28 | 29 | pub use self::models::remote_dir_entry::RemoteDirEntry; 30 | pub use self::models::remote_metadata::RemoteMetadata; 31 | pub use self::models::stat_data::StatData; 32 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env::args; 2 | use std::io; 3 | use std::io::Write; 4 | use std::time::Duration; 5 | 6 | use adbr::Client; 7 | use adbr::DeviceTransport; 8 | use adbr::constants::{FLAG_SERVER_ADDRESS, FLAG_VERSION, FLAG_EMULATOR, FLAG_SERVER_PORT, UNINSTALL_FLAG_KEEP_DATA, FLAG_SERIAL, FLAG_USB, OPTION_LIST, OPTION_NO_REBIND, OPTION_REMOVE, OPTION_REMOVE_ALL, USER_CONNECT_COMMAND, USER_DEVICES_COMMAND, USER_SHELL_COMMAND, USER_FORWARD_COMMAND, USER_REBOOT_COMMAND, USER_PUSH_COMMAND, USER_PULL_COMMAND, INSTALL_FLAG_SDCARD, INSTALL_FLAG_INTERNAL, USER_INSTALL_COMMAND, INSTALL_FLAG_DOWNGRADE, INSTALL_FLAG_REPLACE, INSTALL_FLAG_GRANT_PERMISSIONS, INSTALL_FLAG_TEST, INSTALL_FLAG_FORWARD_LOCK, USER_SERIALNO_COMMAND, USER_GET_DEVPATH_COMMAND, USER_ROOT_COMMAND, USER_UNROOT_COMMAND, USER_REMOUNT_COMMAND, FLAG_HELP_SHORT, VERSION, PROGRAM_NAME, FLAG_HELP_LONG, REFRESH_INTERVAL_SECS, FLAG_WATCH_DEVICES, USER_DISABLE_VERITY_COMMAND, USER_LOGCAT_COMMAND, USER_BUGREPORT_COMMAND, USER_TCPIP_COMMAND, USER_USB_COMMAND, USER_ENABLE_VERITY_COMMAND, USER_KEYGEN_COMMAND, USER_REVERSE_COMMAND, USER_GET_STATE_COMMAND, DEFAULT_WAIT_STATE, USER_WAIT_FOR_COMMAND, FLAG_TIMEOUT, USER_UNINSTALL_COMMAND}; 9 | use adbr::PushResult; 10 | use adbr::PullResult; 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | let args: Vec = args().collect(); 15 | 16 | if args.len() > 1 && (args[1] == FLAG_HELP_SHORT || args[1] == FLAG_HELP_LONG) { 17 | print_usage(); 18 | return; 19 | } else if args.len() > 1 && args[1] == FLAG_VERSION { 20 | println!("{} version {}", PROGRAM_NAME, VERSION); 21 | return; 22 | } else if args.len() < 2 { 23 | print_usage(); 24 | return; 25 | } 26 | 27 | handle_commands(args).await; 28 | } 29 | 30 | fn print_usage() { 31 | println!("Usage: {} [options] [command args]", PROGRAM_NAME); 32 | println!(); 33 | println!("global options:"); 34 | println!(" -s Use device with given serial number"); 35 | println!(" -d Use USB device (error if multiple devices connected)"); 36 | println!(" -e Use TCP/IP device (error if multiple TCP/IP devices available)"); 37 | println!(" -H Name of adb server host [default=localhost]"); 38 | println!(" -P Port of adb server [default=5037]"); 39 | println!(); 40 | println!("general commands:"); 41 | println!(" devices [-w] List connected devices"); 42 | println!(" -w: continuously monitors devices, refreshing every {} seconds", REFRESH_INTERVAL_SECS); 43 | println!(" --version Print the version of the adbr client"); 44 | 45 | println!(); 46 | println!("networking:"); 47 | println!(" forward Forward socket connections"); 48 | println!(" forward --list"); 49 | println!(" List all forward socket connections"); 50 | println!(" forward [--no-rebind] "); 51 | println!(" Forward socket connection"); 52 | println!(" --no-rebind: Fail if local specification is already used"); 53 | println!(" forward --remove "); 54 | println!(" Remove specific forward socket connection"); 55 | println!(" forward --remove-all"); 56 | println!(" Remove all forward socket connections"); 57 | println!(" reverse Reverse socket connections"); 58 | println!(" reverse --list"); 59 | println!(" List all reverse socket connections"); 60 | println!(" reverse [--no-rebind] "); 61 | println!(" Reverse socket connection"); 62 | println!(" --no-rebind: Fail if remote specification is already used"); 63 | println!(" reverse --remove "); 64 | println!(" Remove specific reverse socket connection"); 65 | println!(" reverse --remove-all"); 66 | println!(" Remove all reverse socket connections"); 67 | println!(""); 68 | println!("file transfer:"); 69 | println!(" push [--sync] LOCAL... REMOTE"); 70 | println!(" Copy local files/directories to device"); 71 | println!(" --sync: only push files that are newer on the host than the device"); 72 | println!(" pull [-a] REMOTE... LOCAL"); 73 | println!(" Copy remote files/directories to host"); 74 | println!(" -a: preserve file timestamp and mode"); 75 | println!(); 76 | println!("shell:"); 77 | println!(" shell [] Run remote shell command (interactive shell if no command given)"); 78 | println!(); 79 | println!("app installation:"); 80 | println!(" install [] "); 81 | println!(" Install package from the given file"); 82 | println!(" flags:"); 83 | println!(" -r: Replace existing application"); 84 | println!(" -d: Allow version code downgrade"); 85 | println!(" -g: Grant all runtime permissions"); 86 | println!(" -t: Allow test packages"); 87 | println!(" -s: Install package on the shared mass storage (SD card)"); 88 | println!(" -f: Install package on the internal system memory"); 89 | println!(" -l: Forward lock application"); 90 | println!(" uninstall [-k] PACKAGE"); 91 | println!(" remove this app package from the device"); 92 | println!(" '-k': keep the data and cache directories"); 93 | println!(); 94 | println!(" Note: -s and -f flags are mutually exclusive"); 95 | println!(" -d and -r flags may not work together on some Android versions"); 96 | println!(); 97 | println!("debugging:"); 98 | println!(" logcat [] []"); 99 | println!(" View device log"); 100 | println!(" options:"); 101 | println!(" -c Clear (flush) the entire log and exit"); 102 | println!(" -f Log to file instead of stdout"); 103 | println!(" -v Sets the output format for log messages"); 104 | println!(" Formats: brief, process, tag, thread, raw, time, threadtime, long"); 105 | println!(" -b Request alternate ring buffer"); 106 | println!(" Buffers: main, system, radio, events, crash, default"); 107 | println!(" -d Dump the log and then exit (don't block)"); 108 | println!(" -t Print only the most recent lines (implies -d)"); 109 | println!(" -T