adbproxy 0.1.0__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. adbproxy-1.0.0/PKG-INFO +357 -0
  2. adbproxy-1.0.0/README.md +336 -0
  3. adbproxy-1.0.0/pyproject.toml +101 -0
  4. adbproxy-1.0.0/src/adbproxy/cli/device.py +17 -0
  5. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/cli/main.py +0 -1
  6. adbproxy-1.0.0/src/adbproxy/cli/server.py +17 -0
  7. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/config.py +22 -8
  8. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/device/app.py +76 -179
  9. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/device/backend.py +15 -0
  10. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/device/connection.py +110 -87
  11. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/device/session.py +11 -2
  12. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/device/stream_table.py +1 -5
  13. adbproxy-1.0.0/src/adbproxy/observers/decompress.py +121 -0
  14. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/observers/install.py +10 -3
  15. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/observers/shell.py +126 -81
  16. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/observers/stream.py +16 -8
  17. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/observers/sync.py +84 -159
  18. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/adb.py +16 -2
  19. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/payload/install.py +1 -1
  20. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/payload/shell_v2.py +1 -1
  21. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/smart_socket/classifier.py +22 -4
  22. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/smart_socket/conversation.py +23 -7
  23. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/transport/__init__.py +0 -6
  24. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/transport/codec.py +1 -3
  25. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/transport/constants.py +2 -3
  26. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/runtime/adb_process.py +12 -0
  27. adbproxy-1.0.0/src/adbproxy/runtime/app.py +231 -0
  28. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/runtime/capture.py +0 -1
  29. adbproxy-1.0.0/src/adbproxy/runtime/idle.py +109 -0
  30. adbproxy-1.0.0/src/adbproxy/runtime/logging.py +278 -0
  31. adbproxy-1.0.0/src/adbproxy/runtime/timeouts.py +22 -0
  32. adbproxy-1.0.0/src/adbproxy/server/app.py +189 -0
  33. adbproxy-1.0.0/src/adbproxy/server/connection.py +391 -0
  34. adbproxy-1.0.0/src/adbproxy.egg-info/PKG-INFO +357 -0
  35. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy.egg-info/SOURCES.txt +9 -1
  36. adbproxy-1.0.0/tests/test_app_lifecycle.py +1008 -0
  37. adbproxy-1.0.0/tests/test_device_transport.py +625 -0
  38. {adbproxy-0.1.0 → adbproxy-1.0.0}/tests/test_dynamic_commands.py +336 -63
  39. {adbproxy-0.1.0 → adbproxy-1.0.0}/tests/test_imports.py +4 -2
  40. adbproxy-1.0.0/tests/test_observers.py +606 -0
  41. adbproxy-1.0.0/tests/test_protocol_codecs.py +479 -0
  42. adbproxy-1.0.0/tests/test_server_connection.py +348 -0
  43. adbproxy-0.1.0/PKG-INFO +0 -254
  44. adbproxy-0.1.0/README.md +0 -233
  45. adbproxy-0.1.0/pyproject.toml +0 -37
  46. adbproxy-0.1.0/src/adbproxy/cli/device.py +0 -13
  47. adbproxy-0.1.0/src/adbproxy/cli/server.py +0 -13
  48. adbproxy-0.1.0/src/adbproxy/runtime/logging.py +0 -113
  49. adbproxy-0.1.0/src/adbproxy/server/app.py +0 -221
  50. adbproxy-0.1.0/src/adbproxy/server/connection.py +0 -378
  51. adbproxy-0.1.0/src/adbproxy.egg-info/PKG-INFO +0 -254
  52. adbproxy-0.1.0/tests/test_protocols.py +0 -1522
  53. {adbproxy-0.1.0 → adbproxy-1.0.0}/LICENSE +0 -0
  54. {adbproxy-0.1.0 → adbproxy-1.0.0}/setup.cfg +0 -0
  55. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/__init__.py +0 -0
  56. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/__main__.py +0 -0
  57. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/cli/__init__.py +0 -0
  58. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/device/__init__.py +0 -0
  59. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/device/banner.py +0 -0
  60. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/observers/__init__.py +0 -0
  61. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/observers/events.py +0 -0
  62. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/__init__.py +0 -0
  63. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/payload/__init__.py +0 -0
  64. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/payload/sync.py +2 -2
  65. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/smart_socket/__init__.py +0 -0
  66. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/smart_socket/handshake.py +0 -0
  67. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/smart_socket/wire.py +0 -0
  68. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/protocols/transport/errors.py +0 -0
  69. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/runtime/__init__.py +0 -0
  70. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/runtime/sockets.py +0 -0
  71. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy/server/__init__.py +0 -0
  72. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy.egg-info/dependency_links.txt +0 -0
  73. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy.egg-info/entry_points.txt +0 -0
  74. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy.egg-info/requires.txt +0 -0
  75. {adbproxy-0.1.0 → adbproxy-1.0.0}/src/adbproxy.egg-info/top_level.txt +0 -0
  76. {adbproxy-0.1.0 → adbproxy-1.0.0}/tests/test_cli_main.py +0 -0
@@ -0,0 +1,357 @@
1
+ Metadata-Version: 2.4
2
+ Name: adbproxy
3
+ Version: 1.0.0
4
+ Summary: ADB smart-socket and device transport proxies
5
+ Author: Forgo7ten
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Forgo7ten/adbproxy
8
+ Project-URL: Repository, https://github.com/Forgo7ten/adbproxy
9
+ Project-URL: Issues, https://github.com/Forgo7ten/adbproxy/issues
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: zstandard>=0.25.0
14
+ Provides-Extra: lz4
15
+ Requires-Dist: lz4>=4.3.0; extra == "lz4"
16
+ Provides-Extra: brotli
17
+ Requires-Dist: brotli>=1.1.0; extra == "brotli"
18
+ Provides-Extra: decompress
19
+ Requires-Dist: adbproxy[brotli,lz4]; extra == "decompress"
20
+ Dynamic: license-file
21
+
22
+ # adbproxy
23
+
24
+ 在 ADB client 与 server / 设备之间插入透明代理,解析并记录 ADB 协议流量,同时保持原始字节原样转发。
25
+
26
+ 适用于调试 adb 命令、观察 shell / sync / install 等协议交互、抓取 push/pull 文件内容。
27
+
28
+ ## 功能
29
+
30
+ - **server-proxy**:MITM 本机 `adb client ↔ adb server` 的 smart-socket 链路(默认监听 `5037`,真实 server 挪到 `5038`)
31
+ - **device-proxy**:模拟网络 adbd,接受 transport 协议连接,再经系统 adb server 转发到真实设备
32
+ - 解析并记录 shell、sync、install、stream 等协议事件
33
+ - 可选落盘 sync / install 文件内容,以及 `--debug` 原始 hex
34
+ - 观察 / 解码 / 抓包失败时 fail-open,不中断透明转发
35
+
36
+ ## 架构
37
+
38
+ ```mermaid
39
+ flowchart LR
40
+ Client[adb client]
41
+ SP[server-proxy :5037]
42
+ Server[adb server :5038]
43
+ Dev[真实设备]
44
+
45
+ Client -->|smart socket| SP
46
+ SP -->|smart socket| Server
47
+ Server --> Dev
48
+
49
+ Client2[adb client]
50
+ ServerFront[adb server :5037]
51
+ DP[device-proxy :5566]
52
+ ServerBack[adb server :5037]
53
+ Dev2[真实设备]
54
+
55
+ Client2 -->|smart socket| ServerFront
56
+ ServerFront -->|transport| DP
57
+ DP -->|smart socket| ServerBack
58
+ ServerBack --> Dev2
59
+ ```
60
+
61
+ ## 环境要求
62
+
63
+ - Python ≥ 3.10
64
+ - [uv](https://docs.astral.sh/uv/)
65
+ - 系统 `adb`(Android platform-tools)
66
+
67
+ ## 安装
68
+
69
+ ```bash
70
+ git clone <repo-url>
71
+ cd adbproxy # 或你的本地目录名
72
+ uv sync
73
+ ```
74
+
75
+ 也可从 PyPI:
76
+
77
+ ```bash
78
+ pip install adbproxy
79
+ # 可选:sync 解压依赖
80
+ pip install "adbproxy[decompress]"
81
+
82
+ # 临时运行
83
+ uvx adbproxy server --help
84
+ uvx adbproxy device --help
85
+ ```
86
+
87
+ `uv sync` 会安装依赖,并根据 `pyproject.toml` 的 `[project.scripts]` 注册命令行入口:
88
+
89
+ | 命令 | 对应模块 | 作用 |
90
+ |------|----------|------|
91
+ | `adbproxy` | `adbproxy.cli.main:main` | **推荐**统一入口:`server` / `device` 子命令 |
92
+ | `adb-server-proxy` | `adbproxy.cli.server:main` | 兼容旧入口(等同 `adbproxy server`) |
93
+ | `adb-device-proxy` | `adbproxy.cli.device:main` | 兼容旧入口(等同 `adbproxy device`) |
94
+
95
+ 推荐启动方式:
96
+
97
+ ```bash
98
+ # 统一入口(推荐)
99
+ uv run adbproxy server --help
100
+ uv run adbproxy device --help
101
+ uv run python -m adbproxy server --help
102
+ uv run python -m adbproxy device --help
103
+
104
+ # 兼容旧 console script / 模块路径
105
+ uv run adb-server-proxy --help
106
+ uv run adb-device-proxy --help
107
+ uv run python -m adbproxy.cli.server --help
108
+ uv run python -m adbproxy.cli.device --help
109
+ ```
110
+
111
+ 下文示例统一使用 `adbproxy server` / `adbproxy device`。
112
+
113
+ ## 使用
114
+
115
+ ### 1. server-proxy(MITM 本机 5037)
116
+
117
+ 启动前确认本机 `5038` 未被占用。代理会接管默认 `5037`,把真实 adb server 挪到 `5038`;退出时按所有权恢复。
118
+
119
+ ```bash
120
+ uv run adbproxy server
121
+ # 等价:
122
+ # uv run python -m adbproxy server
123
+ # uv run adb-server-proxy
124
+
125
+ # 可选参数:
126
+ # --debug 将每段 recv 的原始 hex 落盘
127
+ # --no-capture 不落盘 sync/install 文件,仅打印摘要
128
+ # --log-format json 事件日志写成 JSONL(默认 text)
129
+ # --listen PORT 代理监听端口,即 adb client 连的那个(默认 5037)
130
+ # --adb-server-port PORT 转发到的真实 adb server 端口(默认 5038)
131
+ # --reuse-adb-server 复用已在运行的后端 server,不接管也不启停任何 server
132
+ ```
133
+
134
+ 两个端口成对使用:`--listen` 是要从真 adb server 手里接管的端口,`--adb-server-port`
135
+ 是真 server 被挪去的地方。改这两个值可以同时跑多个实例,或在默认端口被别的工具占用时改道:
136
+
137
+ ```bash
138
+ # 接管 6037,把真实 server 放到 6038
139
+ uv run adbproxy server --listen 6037 --adb-server-port 6038
140
+ adb -P 6037 devices -l
141
+ ```
142
+
143
+ #### 复用模式(不接管任何 server)
144
+
145
+ `--reuse-adb-server` 让代理只做转发:不 kill 监听端口上的实例、不起新的后端、退出时也
146
+ 没有要恢复的东西。适合本机还有别的 adb 客户端(IDE 之类)时挂一层观察——它们继续用
147
+ 默认 5037,你用代理端口:
148
+
149
+ ```bash
150
+ # 监听 6037,转发到已在运行的 5037
151
+ uv run adbproxy server --listen 6037 --adb-server-port 5037 --reuse-adb-server
152
+ adb -P 6037 shell echo hello # 经过代理
153
+ adb devices -l # 不经过代理,照常工作
154
+ ```
155
+
156
+ 两种模式的区别:
157
+
158
+ | | 默认(接管) | `--reuse-adb-server` |
159
+ |---|---|---|
160
+ | 监听端口上的真 server | 先 kill,退出时恢复 | 不碰(该端口必须空闲,否则 bind 失败) |
161
+ | 后端 server | 由代理启动并持有所有权 | 必须已在运行;代理不启不停 |
162
+ | 后端意外死掉 | 下一条连接自动重建 | 不重建(没有所有权) |
163
+ | 设备可见性 | 后端要重新独占 USB | 设备始终在原 server 上 |
164
+
165
+ 另开终端正常使用 adb(默认走 5037,即经过代理):
166
+
167
+ ```bash
168
+ adb devices -l
169
+ adb shell echo hello
170
+ adb push local.apk /data/local/tmp/
171
+ adb install local.apk
172
+ ```
173
+
174
+ ### 2. device-proxy(模拟网络 adbd)
175
+
176
+ 默认监听 `5566`,后端复用系统 `5037` adb server,把请求转发到指定真实设备。
177
+
178
+ ```bash
179
+ uv run adbproxy device \
180
+ --listen 5566 \
181
+ --target serial:<设备序列号>
182
+
183
+ # 等价:
184
+ # uv run python -m adbproxy device \
185
+ # --listen 5566 \
186
+ # --target serial:<设备序列号>
187
+ # uv run adb-device-proxy \
188
+ # --listen 5566 \
189
+ # --target serial:<设备序列号>
190
+ ```
191
+
192
+ `--target` 支持:
193
+
194
+ | 值 | 含义 |
195
+ |----|------|
196
+ | `usb` | 自动选择首个 USB 且状态为 `device` 的设备(默认) |
197
+ | `serial:<s>` | 指定序列号 |
198
+ | `net:<ip>:<port>` | 先 `adb connect` 到该网络设备 |
199
+
200
+ 常用参数:
201
+
202
+ ```text
203
+ --listen PORT 监听端口,默认 5566
204
+ --adb-server-port PORT 后端 adb server 端口,默认 5037
205
+ --debug 原始 hex 落盘
206
+ --no-capture 禁用 sync/install 文件落盘
207
+ --banner-type TYPE CNXN banner 前缀,默认 device
208
+ --log-format {text,json} 事件日志格式,默认 text
209
+ ```
210
+
211
+ 另开终端连接并操作:
212
+
213
+ ```bash
214
+ adb connect 127.0.0.1:5566
215
+ adb -s 127.0.0.1:5566 get-state
216
+ adb -s 127.0.0.1:5566 shell echo hello
217
+ adb -s 127.0.0.1:5566 push local.apk /data/local/tmp/
218
+ adb disconnect 127.0.0.1:5566
219
+ ```
220
+
221
+ ## 输出目录
222
+
223
+ | 路径 | 内容 |
224
+ |------|------|
225
+ | `out/logs/*.log` | 事件日志(默认 text 格式);加 `--debug` 时另有 debug hex 日志 |
226
+ | `out/logs/*.jsonl` | `--log-format json` 时的事件日志,每行一条 JSON |
227
+ | `out/logs/*.1` | 日志超过大小上限后转存的上一代文件(只保留一代) |
228
+ | `out/files/` | sync push/pull、install 流捕获(未加 `--no-capture` 时) |
229
+
230
+ 单个日志文件超过 64 MiB 会转存为 `.1` 并重开,磁盘占用因此稳定在 2× 上限以内;
231
+ `--debug` 对每块 recv 落一整段 hexdump,长跑时增长很快,这个上限尤其有用。
232
+ 用 `ADB_PROXY_MAX_LOG_BYTES` 调整。
233
+
234
+ JSONL 每行含 `ts` / `conn` / `level` / `msg` 四个字段(进程级消息没有 `conn`):
235
+
236
+ ```bash
237
+ jq -r 'select(.level=="event") | "\(.conn)\t\(.msg)"' out/logs/*.jsonl
238
+ ```
239
+
240
+ `out/` 的位置:从源码树运行时落在项目根;通过 `pip install` / `uvx` 安装后落在当前工作目录。
241
+ 两种情况都可用 `ADB_PROXY_OUT_DIR` 覆盖。
242
+
243
+ ```bash
244
+ export ADB_PROXY_OUT_DIR=/tmp/adbproxy-out
245
+ # 写入 /tmp/adbproxy-out/logs 与 /tmp/adbproxy-out/files
246
+ ```
247
+
248
+ ## 测试
249
+
250
+ ### 准备
251
+
252
+ 真机动态矩阵(push / pull / install)依赖测试 APK。仓库不附带该文件,运行前请自行放置:
253
+
254
+ ```bash
255
+ # 任意可安装的 APK 即可
256
+ cp /path/to/your.apk tests/app.apk
257
+ ```
258
+
259
+ `tests/app.apk` 已在 `tests/.gitignore` 中忽略,不会被提交。
260
+
261
+ 无设备的单元 / 静态测试不需要该文件。
262
+
263
+ ### 环境变量
264
+
265
+ | 变量 | 说明 |
266
+ |------|------|
267
+ | `ADB_PROXY_REAL_DEVICE=1` | 启用真机动态用例;未设置时相关用例自动 skip |
268
+ | `ADB_PROXY_EXCLUSIVE_ADB=1` | 额外启用接管默认端口的用例;要求本机没有其它 adb 客户端 |
269
+ | `ADB_SERIAL` | 指定真机序列号;省略时取首个 USB 且状态为 `device` 的设备 |
270
+ | `ADB_PROXY_OUT_DIR` | 覆盖运行产物根目录(日志 / 抓包),默认见「输出目录」一节 |
271
+ | `ADB_PROXY_MAX_LOG_BYTES` | 单个日志文件的大小上限,超过即轮转,默认 64 MiB |
272
+
273
+ ### 全部测试
274
+
275
+ ```bash
276
+ # 无设备:单元 + 静态;动态用例自动 skip
277
+ uv run python -B -m unittest discover -s tests -v
278
+
279
+ # 含真实设备动态矩阵(需先放置 tests/app.apk)
280
+ export ADB_PROXY_REAL_DEVICE=1
281
+ export ADB_SERIAL=<可选,默认取首个 USB device>
282
+ uv run python -B -m unittest discover -s tests -v
283
+ ```
284
+
285
+ ### 按文件运行
286
+
287
+ | 文件 | 覆盖范围 |
288
+ |------|----------|
289
+ | `tests/test_protocol_codecs.py` | smart-socket wire / classifier / conversation、payload、transport codec |
290
+ | `tests/test_server_connection.py` | server-proxy 转发与旁路观察、fail-open 不变量 |
291
+ | `tests/test_device_transport.py` | AdbdSession 决策、StreamTable、后端握手、front 传输行为 |
292
+ | `tests/test_observers.py` | StreamBuffer 路由、sync 分帧/压缩/落盘、install 抓包命名 |
293
+ | `tests/test_app_lifecycle.py` | 输出目录、cleanup 顺序、adb server lease、目标解析 |
294
+ | `tests/test_imports.py` | 包布局与导入 |
295
+ | `tests/test_cli_main.py` | 统一 CLI 分发 |
296
+ | `tests/test_dynamic_commands.py` | 真机动态矩阵(需 `tests/app.apk`) |
297
+
298
+ server-proxy 的动态矩阵跑在「监听空闲端口 + 复用现有 5037 作后端」的模式下,因此本机开着
299
+ Android Studio 之类的 adb 客户端也不影响它。接管默认端口那条路径是单独的用例,需要
300
+ `ADB_PROXY_EXCLUSIVE_ADB=1` 才会跑。
301
+
302
+ ```bash
303
+ uv run python -B -m unittest tests.test_protocol_codecs -v
304
+
305
+ # 真机动态矩阵(server-proxy + device-proxy;需 tests/app.apk)
306
+ export ADB_PROXY_REAL_DEVICE=1
307
+ export ADB_SERIAL=<可选>
308
+ uv run python -B -m unittest tests.test_dynamic_commands -v
309
+ ```
310
+
311
+ ### 代码检查
312
+
313
+ ```bash
314
+ uv sync --group dev
315
+
316
+ uv run ruff check . # lint
317
+ uv run mypy # 类型检查(协议层开启 check_untyped_defs)
318
+ uv run coverage run -m unittest discover -s tests && uv run coverage report
319
+ ```
320
+
321
+ CI(`.github/workflows/ci.yml`)在 Python 3.10–3.13 上跑同一组命令,覆盖率低于 72% 会失败。
322
+
323
+ ## 说明
324
+
325
+ - 代理是透明的:解析失败不会改写或阻断转发字节。观察器出错只降级、不影响转发,
326
+ 被吞掉的异常在 `--debug` 下会连 traceback 写进 debug 日志,否则记一条 debug 级提示。
327
+ - 退出码:正常收尾为 `0`;若退出时没能把 adb server 恢复原状(例如 `adb start-server`
328
+ 失败),返回 `1` 并在日志里给出需要手动执行的命令。
329
+
330
+ ### adb kill-server 与后端自愈
331
+
332
+ `host:kill` 和别的请求一样原样转发,代理不改写这条语义:客户端拿到的是真实 server 的
333
+ 应答,`adb kill-server` 的输出与退出码都和没有代理时一致(AOSP 服务端的处理是 `SendOkay`
334
+ 后 `exit(0)`,因此客户端读到 OKAY 再看到连接关闭)。
335
+
336
+ 后端就此退出。只要它的所有权在代理手上(默认模式下由代理启动的那个),下一条需要它的
337
+ 连接会把它重建出来——`adb start-server`、`adb devices`、任何命令都算,这也正是 adb 自己
338
+ 的语义:kill 之后任何需要 server 的命令都会重新唤起它。重建遵循和 lease 一样的规矩:
339
+ 只在 ledger 能证明该端口上的 server 是本进程起的时候才动手,端口上换成了别人的实例就不碰。
340
+
341
+ 自愈不只针对 kill-server:后端崩溃、被 `pkill`、被 OOM 杀掉都走同一条路径(下一条连接
342
+ 连不上后端 → 重建 → 重试一次)。
343
+
344
+ `--reuse-adb-server` 下后端是别人的 server,代理不会替它重建;转发 kill 时日志会提醒
345
+ 一句,之后需要自己 `adb start-server`。
346
+
347
+ 顺带一提,`adb start-server` 没有对应的代理逻辑:客户端在发出 `host:start-server`
348
+ **之前**就 return 了(`adb_client.cpp`),线上真正到达 server 的只有 `host:version`。
349
+
350
+ ### 已知限制
351
+
352
+ | 限制 | 说明 |
353
+ |------|------|
354
+ | 不支持 `A_AUTH` | device-proxy 收到 AUTH 包会终止该连接。前端需已授权、或用无需配对的连接方式 |
355
+ | 不支持 `A_STLS` | 同上,收到 STLS 即终止;不支持 TLS 化的 adb 连接 |
356
+ | v1 停等流控 | device-proxy 的 CNXN banner 会硬剥 `delayed_ack`,后端真机广告了也不透传 |
357
+ | 单文件捕获上限 | sync/install 落盘单文件超过 256 MiB 即停止写入并记日志 |
@@ -0,0 +1,336 @@
1
+ # adbproxy
2
+
3
+ 在 ADB client 与 server / 设备之间插入透明代理,解析并记录 ADB 协议流量,同时保持原始字节原样转发。
4
+
5
+ 适用于调试 adb 命令、观察 shell / sync / install 等协议交互、抓取 push/pull 文件内容。
6
+
7
+ ## 功能
8
+
9
+ - **server-proxy**:MITM 本机 `adb client ↔ adb server` 的 smart-socket 链路(默认监听 `5037`,真实 server 挪到 `5038`)
10
+ - **device-proxy**:模拟网络 adbd,接受 transport 协议连接,再经系统 adb server 转发到真实设备
11
+ - 解析并记录 shell、sync、install、stream 等协议事件
12
+ - 可选落盘 sync / install 文件内容,以及 `--debug` 原始 hex
13
+ - 观察 / 解码 / 抓包失败时 fail-open,不中断透明转发
14
+
15
+ ## 架构
16
+
17
+ ```mermaid
18
+ flowchart LR
19
+ Client[adb client]
20
+ SP[server-proxy :5037]
21
+ Server[adb server :5038]
22
+ Dev[真实设备]
23
+
24
+ Client -->|smart socket| SP
25
+ SP -->|smart socket| Server
26
+ Server --> Dev
27
+
28
+ Client2[adb client]
29
+ ServerFront[adb server :5037]
30
+ DP[device-proxy :5566]
31
+ ServerBack[adb server :5037]
32
+ Dev2[真实设备]
33
+
34
+ Client2 -->|smart socket| ServerFront
35
+ ServerFront -->|transport| DP
36
+ DP -->|smart socket| ServerBack
37
+ ServerBack --> Dev2
38
+ ```
39
+
40
+ ## 环境要求
41
+
42
+ - Python ≥ 3.10
43
+ - [uv](https://docs.astral.sh/uv/)
44
+ - 系统 `adb`(Android platform-tools)
45
+
46
+ ## 安装
47
+
48
+ ```bash
49
+ git clone <repo-url>
50
+ cd adbproxy # 或你的本地目录名
51
+ uv sync
52
+ ```
53
+
54
+ 也可从 PyPI:
55
+
56
+ ```bash
57
+ pip install adbproxy
58
+ # 可选:sync 解压依赖
59
+ pip install "adbproxy[decompress]"
60
+
61
+ # 临时运行
62
+ uvx adbproxy server --help
63
+ uvx adbproxy device --help
64
+ ```
65
+
66
+ `uv sync` 会安装依赖,并根据 `pyproject.toml` 的 `[project.scripts]` 注册命令行入口:
67
+
68
+ | 命令 | 对应模块 | 作用 |
69
+ |------|----------|------|
70
+ | `adbproxy` | `adbproxy.cli.main:main` | **推荐**统一入口:`server` / `device` 子命令 |
71
+ | `adb-server-proxy` | `adbproxy.cli.server:main` | 兼容旧入口(等同 `adbproxy server`) |
72
+ | `adb-device-proxy` | `adbproxy.cli.device:main` | 兼容旧入口(等同 `adbproxy device`) |
73
+
74
+ 推荐启动方式:
75
+
76
+ ```bash
77
+ # 统一入口(推荐)
78
+ uv run adbproxy server --help
79
+ uv run adbproxy device --help
80
+ uv run python -m adbproxy server --help
81
+ uv run python -m adbproxy device --help
82
+
83
+ # 兼容旧 console script / 模块路径
84
+ uv run adb-server-proxy --help
85
+ uv run adb-device-proxy --help
86
+ uv run python -m adbproxy.cli.server --help
87
+ uv run python -m adbproxy.cli.device --help
88
+ ```
89
+
90
+ 下文示例统一使用 `adbproxy server` / `adbproxy device`。
91
+
92
+ ## 使用
93
+
94
+ ### 1. server-proxy(MITM 本机 5037)
95
+
96
+ 启动前确认本机 `5038` 未被占用。代理会接管默认 `5037`,把真实 adb server 挪到 `5038`;退出时按所有权恢复。
97
+
98
+ ```bash
99
+ uv run adbproxy server
100
+ # 等价:
101
+ # uv run python -m adbproxy server
102
+ # uv run adb-server-proxy
103
+
104
+ # 可选参数:
105
+ # --debug 将每段 recv 的原始 hex 落盘
106
+ # --no-capture 不落盘 sync/install 文件,仅打印摘要
107
+ # --log-format json 事件日志写成 JSONL(默认 text)
108
+ # --listen PORT 代理监听端口,即 adb client 连的那个(默认 5037)
109
+ # --adb-server-port PORT 转发到的真实 adb server 端口(默认 5038)
110
+ # --reuse-adb-server 复用已在运行的后端 server,不接管也不启停任何 server
111
+ ```
112
+
113
+ 两个端口成对使用:`--listen` 是要从真 adb server 手里接管的端口,`--adb-server-port`
114
+ 是真 server 被挪去的地方。改这两个值可以同时跑多个实例,或在默认端口被别的工具占用时改道:
115
+
116
+ ```bash
117
+ # 接管 6037,把真实 server 放到 6038
118
+ uv run adbproxy server --listen 6037 --adb-server-port 6038
119
+ adb -P 6037 devices -l
120
+ ```
121
+
122
+ #### 复用模式(不接管任何 server)
123
+
124
+ `--reuse-adb-server` 让代理只做转发:不 kill 监听端口上的实例、不起新的后端、退出时也
125
+ 没有要恢复的东西。适合本机还有别的 adb 客户端(IDE 之类)时挂一层观察——它们继续用
126
+ 默认 5037,你用代理端口:
127
+
128
+ ```bash
129
+ # 监听 6037,转发到已在运行的 5037
130
+ uv run adbproxy server --listen 6037 --adb-server-port 5037 --reuse-adb-server
131
+ adb -P 6037 shell echo hello # 经过代理
132
+ adb devices -l # 不经过代理,照常工作
133
+ ```
134
+
135
+ 两种模式的区别:
136
+
137
+ | | 默认(接管) | `--reuse-adb-server` |
138
+ |---|---|---|
139
+ | 监听端口上的真 server | 先 kill,退出时恢复 | 不碰(该端口必须空闲,否则 bind 失败) |
140
+ | 后端 server | 由代理启动并持有所有权 | 必须已在运行;代理不启不停 |
141
+ | 后端意外死掉 | 下一条连接自动重建 | 不重建(没有所有权) |
142
+ | 设备可见性 | 后端要重新独占 USB | 设备始终在原 server 上 |
143
+
144
+ 另开终端正常使用 adb(默认走 5037,即经过代理):
145
+
146
+ ```bash
147
+ adb devices -l
148
+ adb shell echo hello
149
+ adb push local.apk /data/local/tmp/
150
+ adb install local.apk
151
+ ```
152
+
153
+ ### 2. device-proxy(模拟网络 adbd)
154
+
155
+ 默认监听 `5566`,后端复用系统 `5037` adb server,把请求转发到指定真实设备。
156
+
157
+ ```bash
158
+ uv run adbproxy device \
159
+ --listen 5566 \
160
+ --target serial:<设备序列号>
161
+
162
+ # 等价:
163
+ # uv run python -m adbproxy device \
164
+ # --listen 5566 \
165
+ # --target serial:<设备序列号>
166
+ # uv run adb-device-proxy \
167
+ # --listen 5566 \
168
+ # --target serial:<设备序列号>
169
+ ```
170
+
171
+ `--target` 支持:
172
+
173
+ | 值 | 含义 |
174
+ |----|------|
175
+ | `usb` | 自动选择首个 USB 且状态为 `device` 的设备(默认) |
176
+ | `serial:<s>` | 指定序列号 |
177
+ | `net:<ip>:<port>` | 先 `adb connect` 到该网络设备 |
178
+
179
+ 常用参数:
180
+
181
+ ```text
182
+ --listen PORT 监听端口,默认 5566
183
+ --adb-server-port PORT 后端 adb server 端口,默认 5037
184
+ --debug 原始 hex 落盘
185
+ --no-capture 禁用 sync/install 文件落盘
186
+ --banner-type TYPE CNXN banner 前缀,默认 device
187
+ --log-format {text,json} 事件日志格式,默认 text
188
+ ```
189
+
190
+ 另开终端连接并操作:
191
+
192
+ ```bash
193
+ adb connect 127.0.0.1:5566
194
+ adb -s 127.0.0.1:5566 get-state
195
+ adb -s 127.0.0.1:5566 shell echo hello
196
+ adb -s 127.0.0.1:5566 push local.apk /data/local/tmp/
197
+ adb disconnect 127.0.0.1:5566
198
+ ```
199
+
200
+ ## 输出目录
201
+
202
+ | 路径 | 内容 |
203
+ |------|------|
204
+ | `out/logs/*.log` | 事件日志(默认 text 格式);加 `--debug` 时另有 debug hex 日志 |
205
+ | `out/logs/*.jsonl` | `--log-format json` 时的事件日志,每行一条 JSON |
206
+ | `out/logs/*.1` | 日志超过大小上限后转存的上一代文件(只保留一代) |
207
+ | `out/files/` | sync push/pull、install 流捕获(未加 `--no-capture` 时) |
208
+
209
+ 单个日志文件超过 64 MiB 会转存为 `.1` 并重开,磁盘占用因此稳定在 2× 上限以内;
210
+ `--debug` 对每块 recv 落一整段 hexdump,长跑时增长很快,这个上限尤其有用。
211
+ 用 `ADB_PROXY_MAX_LOG_BYTES` 调整。
212
+
213
+ JSONL 每行含 `ts` / `conn` / `level` / `msg` 四个字段(进程级消息没有 `conn`):
214
+
215
+ ```bash
216
+ jq -r 'select(.level=="event") | "\(.conn)\t\(.msg)"' out/logs/*.jsonl
217
+ ```
218
+
219
+ `out/` 的位置:从源码树运行时落在项目根;通过 `pip install` / `uvx` 安装后落在当前工作目录。
220
+ 两种情况都可用 `ADB_PROXY_OUT_DIR` 覆盖。
221
+
222
+ ```bash
223
+ export ADB_PROXY_OUT_DIR=/tmp/adbproxy-out
224
+ # 写入 /tmp/adbproxy-out/logs 与 /tmp/adbproxy-out/files
225
+ ```
226
+
227
+ ## 测试
228
+
229
+ ### 准备
230
+
231
+ 真机动态矩阵(push / pull / install)依赖测试 APK。仓库不附带该文件,运行前请自行放置:
232
+
233
+ ```bash
234
+ # 任意可安装的 APK 即可
235
+ cp /path/to/your.apk tests/app.apk
236
+ ```
237
+
238
+ `tests/app.apk` 已在 `tests/.gitignore` 中忽略,不会被提交。
239
+
240
+ 无设备的单元 / 静态测试不需要该文件。
241
+
242
+ ### 环境变量
243
+
244
+ | 变量 | 说明 |
245
+ |------|------|
246
+ | `ADB_PROXY_REAL_DEVICE=1` | 启用真机动态用例;未设置时相关用例自动 skip |
247
+ | `ADB_PROXY_EXCLUSIVE_ADB=1` | 额外启用接管默认端口的用例;要求本机没有其它 adb 客户端 |
248
+ | `ADB_SERIAL` | 指定真机序列号;省略时取首个 USB 且状态为 `device` 的设备 |
249
+ | `ADB_PROXY_OUT_DIR` | 覆盖运行产物根目录(日志 / 抓包),默认见「输出目录」一节 |
250
+ | `ADB_PROXY_MAX_LOG_BYTES` | 单个日志文件的大小上限,超过即轮转,默认 64 MiB |
251
+
252
+ ### 全部测试
253
+
254
+ ```bash
255
+ # 无设备:单元 + 静态;动态用例自动 skip
256
+ uv run python -B -m unittest discover -s tests -v
257
+
258
+ # 含真实设备动态矩阵(需先放置 tests/app.apk)
259
+ export ADB_PROXY_REAL_DEVICE=1
260
+ export ADB_SERIAL=<可选,默认取首个 USB device>
261
+ uv run python -B -m unittest discover -s tests -v
262
+ ```
263
+
264
+ ### 按文件运行
265
+
266
+ | 文件 | 覆盖范围 |
267
+ |------|----------|
268
+ | `tests/test_protocol_codecs.py` | smart-socket wire / classifier / conversation、payload、transport codec |
269
+ | `tests/test_server_connection.py` | server-proxy 转发与旁路观察、fail-open 不变量 |
270
+ | `tests/test_device_transport.py` | AdbdSession 决策、StreamTable、后端握手、front 传输行为 |
271
+ | `tests/test_observers.py` | StreamBuffer 路由、sync 分帧/压缩/落盘、install 抓包命名 |
272
+ | `tests/test_app_lifecycle.py` | 输出目录、cleanup 顺序、adb server lease、目标解析 |
273
+ | `tests/test_imports.py` | 包布局与导入 |
274
+ | `tests/test_cli_main.py` | 统一 CLI 分发 |
275
+ | `tests/test_dynamic_commands.py` | 真机动态矩阵(需 `tests/app.apk`) |
276
+
277
+ server-proxy 的动态矩阵跑在「监听空闲端口 + 复用现有 5037 作后端」的模式下,因此本机开着
278
+ Android Studio 之类的 adb 客户端也不影响它。接管默认端口那条路径是单独的用例,需要
279
+ `ADB_PROXY_EXCLUSIVE_ADB=1` 才会跑。
280
+
281
+ ```bash
282
+ uv run python -B -m unittest tests.test_protocol_codecs -v
283
+
284
+ # 真机动态矩阵(server-proxy + device-proxy;需 tests/app.apk)
285
+ export ADB_PROXY_REAL_DEVICE=1
286
+ export ADB_SERIAL=<可选>
287
+ uv run python -B -m unittest tests.test_dynamic_commands -v
288
+ ```
289
+
290
+ ### 代码检查
291
+
292
+ ```bash
293
+ uv sync --group dev
294
+
295
+ uv run ruff check . # lint
296
+ uv run mypy # 类型检查(协议层开启 check_untyped_defs)
297
+ uv run coverage run -m unittest discover -s tests && uv run coverage report
298
+ ```
299
+
300
+ CI(`.github/workflows/ci.yml`)在 Python 3.10–3.13 上跑同一组命令,覆盖率低于 72% 会失败。
301
+
302
+ ## 说明
303
+
304
+ - 代理是透明的:解析失败不会改写或阻断转发字节。观察器出错只降级、不影响转发,
305
+ 被吞掉的异常在 `--debug` 下会连 traceback 写进 debug 日志,否则记一条 debug 级提示。
306
+ - 退出码:正常收尾为 `0`;若退出时没能把 adb server 恢复原状(例如 `adb start-server`
307
+ 失败),返回 `1` 并在日志里给出需要手动执行的命令。
308
+
309
+ ### adb kill-server 与后端自愈
310
+
311
+ `host:kill` 和别的请求一样原样转发,代理不改写这条语义:客户端拿到的是真实 server 的
312
+ 应答,`adb kill-server` 的输出与退出码都和没有代理时一致(AOSP 服务端的处理是 `SendOkay`
313
+ 后 `exit(0)`,因此客户端读到 OKAY 再看到连接关闭)。
314
+
315
+ 后端就此退出。只要它的所有权在代理手上(默认模式下由代理启动的那个),下一条需要它的
316
+ 连接会把它重建出来——`adb start-server`、`adb devices`、任何命令都算,这也正是 adb 自己
317
+ 的语义:kill 之后任何需要 server 的命令都会重新唤起它。重建遵循和 lease 一样的规矩:
318
+ 只在 ledger 能证明该端口上的 server 是本进程起的时候才动手,端口上换成了别人的实例就不碰。
319
+
320
+ 自愈不只针对 kill-server:后端崩溃、被 `pkill`、被 OOM 杀掉都走同一条路径(下一条连接
321
+ 连不上后端 → 重建 → 重试一次)。
322
+
323
+ `--reuse-adb-server` 下后端是别人的 server,代理不会替它重建;转发 kill 时日志会提醒
324
+ 一句,之后需要自己 `adb start-server`。
325
+
326
+ 顺带一提,`adb start-server` 没有对应的代理逻辑:客户端在发出 `host:start-server`
327
+ **之前**就 return 了(`adb_client.cpp`),线上真正到达 server 的只有 `host:version`。
328
+
329
+ ### 已知限制
330
+
331
+ | 限制 | 说明 |
332
+ |------|------|
333
+ | 不支持 `A_AUTH` | device-proxy 收到 AUTH 包会终止该连接。前端需已授权、或用无需配对的连接方式 |
334
+ | 不支持 `A_STLS` | 同上,收到 STLS 即终止;不支持 TLS 化的 adb 连接 |
335
+ | v1 停等流控 | device-proxy 的 CNXN banner 会硬剥 `delayed_ack`,后端真机广告了也不透传 |
336
+ | 单文件捕获上限 | sync/install 落盘单文件超过 256 MiB 即停止写入并记日志 |