├── .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