adbproxy 0.1.0__py3-none-any.whl
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.
- adbproxy/__init__.py +2 -0
- adbproxy/__main__.py +5 -0
- adbproxy/cli/__init__.py +1 -0
- adbproxy/cli/device.py +13 -0
- adbproxy/cli/main.py +60 -0
- adbproxy/cli/server.py +13 -0
- adbproxy/config.py +39 -0
- adbproxy/device/__init__.py +1 -0
- adbproxy/device/app.py +532 -0
- adbproxy/device/backend.py +242 -0
- adbproxy/device/banner.py +33 -0
- adbproxy/device/connection.py +505 -0
- adbproxy/device/session.py +304 -0
- adbproxy/device/stream_table.py +125 -0
- adbproxy/observers/__init__.py +1 -0
- adbproxy/observers/events.py +19 -0
- adbproxy/observers/install.py +126 -0
- adbproxy/observers/shell.py +371 -0
- adbproxy/observers/stream.py +94 -0
- adbproxy/observers/sync.py +550 -0
- adbproxy/protocols/__init__.py +1 -0
- adbproxy/protocols/adb.py +66 -0
- adbproxy/protocols/payload/__init__.py +17 -0
- adbproxy/protocols/payload/install.py +44 -0
- adbproxy/protocols/payload/shell_v2.py +28 -0
- adbproxy/protocols/payload/sync.py +180 -0
- adbproxy/protocols/smart_socket/__init__.py +33 -0
- adbproxy/protocols/smart_socket/classifier.py +78 -0
- adbproxy/protocols/smart_socket/conversation.py +335 -0
- adbproxy/protocols/smart_socket/handshake.py +75 -0
- adbproxy/protocols/smart_socket/wire.py +82 -0
- adbproxy/protocols/transport/__init__.py +52 -0
- adbproxy/protocols/transport/codec.py +90 -0
- adbproxy/protocols/transport/constants.py +21 -0
- adbproxy/protocols/transport/errors.py +5 -0
- adbproxy/runtime/__init__.py +1 -0
- adbproxy/runtime/adb_process.py +195 -0
- adbproxy/runtime/capture.py +41 -0
- adbproxy/runtime/logging.py +113 -0
- adbproxy/runtime/sockets.py +14 -0
- adbproxy/server/__init__.py +1 -0
- adbproxy/server/app.py +221 -0
- adbproxy/server/connection.py +378 -0
- adbproxy-0.1.0.dist-info/METADATA +254 -0
- adbproxy-0.1.0.dist-info/RECORD +49 -0
- adbproxy-0.1.0.dist-info/WHEEL +5 -0
- adbproxy-0.1.0.dist-info/entry_points.txt +4 -0
- adbproxy-0.1.0.dist-info/licenses/LICENSE +21 -0
- adbproxy-0.1.0.dist-info/top_level.txt +1 -0
adbproxy/__init__.py
ADDED
adbproxy/__main__.py
ADDED
adbproxy/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""代理程序命令行入口(统一 adbproxy 与兼容旧脚本)。"""
|
adbproxy/cli/device.py
ADDED
adbproxy/cli/main.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""统一 CLI:adbproxy server | device。
|
|
2
|
+
|
|
3
|
+
不修改现有 cli/server.py、cli/device.py;通过临时改写 sys.argv
|
|
4
|
+
再调用旧入口,保证子命令参数与直接运行旧脚本一致。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_TOP_HELP = """\
|
|
13
|
+
usage: adbproxy {server,device} ...
|
|
14
|
+
|
|
15
|
+
子命令:
|
|
16
|
+
server client↔adb server smart-socket MITM(原 adb-server-proxy)
|
|
17
|
+
device 模拟网络 adbd / transport 转发(原 adb-device-proxy)
|
|
18
|
+
|
|
19
|
+
示例:
|
|
20
|
+
adbproxy server --help
|
|
21
|
+
adbproxy device --listen 5566 --target usb
|
|
22
|
+
python -m adbproxy server --debug
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main(argv=None):
|
|
27
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
28
|
+
|
|
29
|
+
if not argv:
|
|
30
|
+
print(_TOP_HELP, file=sys.stderr)
|
|
31
|
+
return 2
|
|
32
|
+
|
|
33
|
+
if argv[0] in ("-h", "--help"):
|
|
34
|
+
print(_TOP_HELP)
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
cmd, rest = argv[0], argv[1:]
|
|
38
|
+
if cmd == "server":
|
|
39
|
+
from adbproxy.cli import server as sub
|
|
40
|
+
elif cmd == "device":
|
|
41
|
+
from adbproxy.cli import device as sub
|
|
42
|
+
else:
|
|
43
|
+
print(f"unknown command: {cmd}", file=sys.stderr)
|
|
44
|
+
print(_TOP_HELP, file=sys.stderr)
|
|
45
|
+
return 2
|
|
46
|
+
|
|
47
|
+
old_argv = sys.argv
|
|
48
|
+
try:
|
|
49
|
+
# 旧入口固定读 sys.argv[1:];剥掉子命令名后再调用,避免把
|
|
50
|
+
# "server"/"device" 传给 AdbProxy / ClientMock 的 argparse。
|
|
51
|
+
sys.argv = [old_argv[0], *rest]
|
|
52
|
+
result = sub.main()
|
|
53
|
+
finally:
|
|
54
|
+
sys.argv = old_argv
|
|
55
|
+
|
|
56
|
+
return 0 if result is None else result
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
raise SystemExit(main())
|
adbproxy/cli/server.py
ADDED
adbproxy/config.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import struct
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
PROXY_PORT = 5037
|
|
6
|
+
REAL_ADB_PORT = 5038
|
|
7
|
+
# 源码布局:本文件在 src/adbproxy/config.py,项目根为 src 的上两级。
|
|
8
|
+
# 运行产物统一落到 <project>/out/{logs,files},可用 ADB_PROXY_OUT_DIR 覆盖 out 根目录。
|
|
9
|
+
_SRC_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
10
|
+
_PROJECT_ROOT = os.path.dirname(os.path.dirname(_SRC_DIR))
|
|
11
|
+
_OUT_ROOT = os.environ.get(
|
|
12
|
+
"ADB_PROXY_OUT_DIR", os.path.join(_PROJECT_ROOT, "out")
|
|
13
|
+
)
|
|
14
|
+
LOG_DIR = os.path.join(_OUT_ROOT, "logs")
|
|
15
|
+
FILES_DIR = os.path.join(_OUT_ROOT, "files")
|
|
16
|
+
IDLE_TIMEOUT = 0.15
|
|
17
|
+
MAX_V2_BUF = 1 << 20 # 1 MiB,v2 半包重组缓冲上限
|
|
18
|
+
MAX_CMD = 4096 # 单条命令累积上限
|
|
19
|
+
SYNC_DATA_MAX = (
|
|
20
|
+
64 * 1024
|
|
21
|
+
) # 与 AOSP file_sync_protocol.h:146 一致,DATA 单块上限(校验用)
|
|
22
|
+
MAX_SYNC_FILE = 256 * 1024 * 1024 # 单文件落盘上限 256 MiB,防磁盘失控
|
|
23
|
+
# sync v2 定长 struct 大小(核对 file_sync_protocol.h,packed little-endian)
|
|
24
|
+
# sync_stat_v2: id,error,dev,ino,mode,nlink,uid,gid,size,atime,mtime,ctime
|
|
25
|
+
_STAT_V2_FMT = "<IIQQIIIIQqqq"
|
|
26
|
+
_STAT_V2_SIZE = struct.calcsize(_STAT_V2_FMT) # 72
|
|
27
|
+
# sync_dent_v2: stat_v2 + namelen
|
|
28
|
+
_DENT_V2_FMT = _STAT_V2_FMT + "I"
|
|
29
|
+
_DENT_V2_SIZE = struct.calcsize(_DENT_V2_FMT) # 76,后随 namelen 字节 name
|
|
30
|
+
# sync_stat_v1: id,mode,size,mtime
|
|
31
|
+
_STAT_V1_SIZE = struct.calcsize("<IIII") # 16
|
|
32
|
+
# sync_dent_v1: id,mode,size,mtime,namelen
|
|
33
|
+
_DENT_V1_SIZE = struct.calcsize("<IIIII") # 20,后随 namelen 字节 name
|
|
34
|
+
_SEND_V2_SIZE = struct.calcsize("<III") # sync_send_v2: id,mode,flags = 12
|
|
35
|
+
_RECV_V2_SIZE = struct.calcsize("<II") # sync_recv_v2: id,flags = 8
|
|
36
|
+
|
|
37
|
+
DEFAULT_LISTEN_PORT = 5566
|
|
38
|
+
DEFAULT_ADB_SERVER_PORT = 5037
|
|
39
|
+
DEFAULT_BANNER_FEATURES = "shell_v2,sendrecv_v2"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""ADB server 到 daemon 的传输代理。"""
|
adbproxy/device/app.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import datetime
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
5
|
+
import socket
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import threading
|
|
9
|
+
|
|
10
|
+
from adbproxy.config import (
|
|
11
|
+
DEFAULT_ADB_SERVER_PORT,
|
|
12
|
+
DEFAULT_BANNER_FEATURES,
|
|
13
|
+
DEFAULT_LISTEN_PORT,
|
|
14
|
+
FILES_DIR,
|
|
15
|
+
LOG_DIR,
|
|
16
|
+
)
|
|
17
|
+
from adbproxy.protocols.adb import AdbProtocol
|
|
18
|
+
from adbproxy.runtime.adb_process import AdbServerLease
|
|
19
|
+
from adbproxy.runtime.logging import DebugHexLogger, FileLogger
|
|
20
|
+
|
|
21
|
+
from .connection import TransportConnection, _next_conn_id
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ClientMock:
|
|
25
|
+
"""主流程:监听 --listen、accept 循环、可选自起 adb server / net 目标 connect、信号清理。
|
|
26
|
+
|
|
27
|
+
CLI 参数解析、运行生命周期与参考脚本保持一致。
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, argv):
|
|
31
|
+
parser = argparse.ArgumentParser(
|
|
32
|
+
description="client-mock:模拟网络 adbd,MITM 转发到真实设备(传输协议↔5037 文本协议)"
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--listen",
|
|
36
|
+
type=int,
|
|
37
|
+
default=DEFAULT_LISTEN_PORT,
|
|
38
|
+
help="监听端口(默认 5566)",
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--target",
|
|
42
|
+
default="usb",
|
|
43
|
+
help="目标设备:serial:<s> | net:<ip>:<port> | usb(默认 usb 自动取首个 USB 设备)",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--adb-server-port",
|
|
47
|
+
type=int,
|
|
48
|
+
default=DEFAULT_ADB_SERVER_PORT,
|
|
49
|
+
help="后端 adb server 端口(默认 5037 复用系统;非 5037 自起并退出清理)",
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"--debug",
|
|
53
|
+
action="store_true",
|
|
54
|
+
help="每 recv 原始字节 hex 落盘 logs/debug-*.log",
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--no-capture", action="store_true", help="禁用 sync/install 文件落盘"
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"--banner-type",
|
|
61
|
+
default="device",
|
|
62
|
+
help="CNXN banner type 前缀(效仿 adbd -b,默认 device)",
|
|
63
|
+
)
|
|
64
|
+
args = parser.parse_args(argv)
|
|
65
|
+
self.listen_port = args.listen
|
|
66
|
+
self.target = args.target
|
|
67
|
+
self.adb_server_port = args.adb_server_port
|
|
68
|
+
self.banner_type = (
|
|
69
|
+
args.banner_type
|
|
70
|
+
) # 效仿 adbd adb_device_banner (adb_trace.cpp:34 + main.cpp:350)
|
|
71
|
+
self.banner_product = None # ro.product.name,None=回退
|
|
72
|
+
self.banner_model = None # ro.product.model
|
|
73
|
+
self.banner_device = None # ro.product.device
|
|
74
|
+
self.banner_features = DEFAULT_BANNER_FEATURES # 回退初值,serve() 中 _resolve_banner_features 覆盖
|
|
75
|
+
self.capture_enabled = not args.no_capture
|
|
76
|
+
self.debug_enabled = args.debug
|
|
77
|
+
# 日志目录/文件
|
|
78
|
+
try:
|
|
79
|
+
os.makedirs(LOG_DIR, exist_ok=True)
|
|
80
|
+
except OSError:
|
|
81
|
+
pass
|
|
82
|
+
if self.capture_enabled:
|
|
83
|
+
try:
|
|
84
|
+
os.makedirs(FILES_DIR, exist_ok=True)
|
|
85
|
+
except OSError:
|
|
86
|
+
self.capture_enabled = False
|
|
87
|
+
now = datetime.datetime.now()
|
|
88
|
+
stamp = now.strftime("%Y-%m-%d-%H:%M:%S-") + f"{now.microsecond // 1000:03d}"
|
|
89
|
+
self.log_path = os.path.join(LOG_DIR, stamp + ".log")
|
|
90
|
+
self.file_logger = FileLogger(self.log_path)
|
|
91
|
+
if self.debug_enabled:
|
|
92
|
+
self.debug_path = os.path.join(LOG_DIR, stamp + "-debug.log")
|
|
93
|
+
self.debug_logger = DebugHexLogger(self.debug_path)
|
|
94
|
+
else:
|
|
95
|
+
self.debug_path = None
|
|
96
|
+
self.debug_logger = None
|
|
97
|
+
self._conn_counter = 0
|
|
98
|
+
self._counter_lock = threading.Lock()
|
|
99
|
+
self._connections_lock = threading.Lock()
|
|
100
|
+
self._connections = {}
|
|
101
|
+
self._cleanup_lock = threading.Lock()
|
|
102
|
+
self._cleaned = False
|
|
103
|
+
self.listen_sock = None
|
|
104
|
+
self._stop = False
|
|
105
|
+
self.adb_server_lease = AdbServerLease(
|
|
106
|
+
managed_port=self.adb_server_port,
|
|
107
|
+
log=self._glog,
|
|
108
|
+
)
|
|
109
|
+
self.serial = None # serve() 解析 --target 后填
|
|
110
|
+
|
|
111
|
+
def _glog(self, msg):
|
|
112
|
+
try:
|
|
113
|
+
print(msg)
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
self.file_logger.write(msg)
|
|
117
|
+
|
|
118
|
+
# ---------- 目标解析 ----------
|
|
119
|
+
def _resolve_target(self):
|
|
120
|
+
t = self.target
|
|
121
|
+
if t.startswith("serial:"):
|
|
122
|
+
return t[len("serial:") :]
|
|
123
|
+
if t.startswith("net:"):
|
|
124
|
+
addr = t[len("net:") :]
|
|
125
|
+
self._glog(f"[*] adb connect {addr} ...")
|
|
126
|
+
try:
|
|
127
|
+
result = subprocess.run(
|
|
128
|
+
["adb", "-P", str(self.adb_server_port), "connect", addr],
|
|
129
|
+
stdout=subprocess.DEVNULL,
|
|
130
|
+
stderr=subprocess.DEVNULL,
|
|
131
|
+
timeout=10,
|
|
132
|
+
)
|
|
133
|
+
except subprocess.TimeoutExpired:
|
|
134
|
+
self._glog("[!] adb 调用超时: connect")
|
|
135
|
+
return None
|
|
136
|
+
except FileNotFoundError:
|
|
137
|
+
self._glog("[!] adb 未找到: connect")
|
|
138
|
+
return None
|
|
139
|
+
if result.returncode != 0:
|
|
140
|
+
self._glog(
|
|
141
|
+
f"[!] adb connect {addr} 失败,returncode={result.returncode}"
|
|
142
|
+
)
|
|
143
|
+
return None
|
|
144
|
+
return addr
|
|
145
|
+
if t == "usb" or not t:
|
|
146
|
+
return self._first_usb_serial()
|
|
147
|
+
return t # 当作裸 serial
|
|
148
|
+
|
|
149
|
+
def _first_usb_serial(self):
|
|
150
|
+
try:
|
|
151
|
+
r = subprocess.run(
|
|
152
|
+
["adb", "-P", str(self.adb_server_port), "devices", "-l"],
|
|
153
|
+
capture_output=True,
|
|
154
|
+
text=True,
|
|
155
|
+
timeout=5,
|
|
156
|
+
)
|
|
157
|
+
except subprocess.TimeoutExpired:
|
|
158
|
+
self._glog("[!] adb 调用超时: devices")
|
|
159
|
+
return None
|
|
160
|
+
except FileNotFoundError:
|
|
161
|
+
self._glog("[!] adb 未找到: devices")
|
|
162
|
+
return None
|
|
163
|
+
if r.returncode != 0:
|
|
164
|
+
self._glog(f"[!] adb devices -l 失败,returncode={r.returncode}")
|
|
165
|
+
return None
|
|
166
|
+
for line in r.stdout.splitlines()[1:]:
|
|
167
|
+
line = line.strip()
|
|
168
|
+
if not line:
|
|
169
|
+
continue
|
|
170
|
+
parts = line.split()
|
|
171
|
+
if len(parts) < 3:
|
|
172
|
+
continue
|
|
173
|
+
ser, st, *metadata = parts
|
|
174
|
+
if st != "device":
|
|
175
|
+
continue
|
|
176
|
+
if not any(field.startswith("usb:") for field in metadata):
|
|
177
|
+
continue
|
|
178
|
+
return ser
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
# ---------- 查询后端真机 banner features ----------
|
|
182
|
+
def _resolve_banner_features(self):
|
|
183
|
+
"""启动时经 5037 单帧 host-serial:<serial>:features 查后端真机 CNXN banner
|
|
184
|
+
features,原样 advertised 给前端,仅硬剥 delayed_ack(front v1 停等流控不支持,
|
|
185
|
+
banner 含 delayed_ack 触发 adb.cpp:507 mismatch → send_close)。压缩子 feature
|
|
186
|
+
(sendrecv_v2_brotli/lz4/zstd/dry_run_send)一律保留,靠 raw 透传。失败返回 None,
|
|
187
|
+
调用方用 DEFAULT_BANNER_FEATURES 回退不崩。
|
|
188
|
+
|
|
189
|
+
帧序:单帧 host-serial:<serial>:features(对齐真 adb client
|
|
190
|
+
format_host_command("features"),adb_client.cpp:415-422)。非 pin-transport 双握手:
|
|
191
|
+
host:transport 返回 SwitchedTransport 后 smart_socket_data.clear()(sockets.cpp:865),
|
|
192
|
+
下一帧裸 features 无 host-* 前缀会被误路由到传输流路径向设备发 A_OPEN service=features
|
|
193
|
+
→ 设备无此流服务 → CLSE → 静默失败。"""
|
|
194
|
+
try:
|
|
195
|
+
sock = socket.create_connection(
|
|
196
|
+
("127.0.0.1", self.adb_server_port), timeout=5
|
|
197
|
+
)
|
|
198
|
+
except Exception as e:
|
|
199
|
+
self._glog(f"[!] 查询 banner features 连接 5037 失败: {e}")
|
|
200
|
+
return None
|
|
201
|
+
try:
|
|
202
|
+
sock.settimeout(5)
|
|
203
|
+
body = f"host-serial:{self.serial}:features"
|
|
204
|
+
sock.sendall(AdbProtocol.format_service_frame(body))
|
|
205
|
+
# 4B status(recv 凑齐半包)
|
|
206
|
+
st = self._recv_exact_sock(sock, 4)
|
|
207
|
+
if st is None:
|
|
208
|
+
self._glog("[!] 查询 banner features 读 status EOF")
|
|
209
|
+
return None
|
|
210
|
+
s = st.decode("ascii", errors="ignore")
|
|
211
|
+
if s == "OKAY":
|
|
212
|
+
hb = self._recv_exact_sock(sock, 4)
|
|
213
|
+
if hb is None:
|
|
214
|
+
self._glog("[!] 查询 banner features 读 hex4 EOF")
|
|
215
|
+
return None
|
|
216
|
+
try:
|
|
217
|
+
plen = int(hb.decode("ascii", errors="ignore"), 16)
|
|
218
|
+
except ValueError:
|
|
219
|
+
self._glog(f"[!] 查询 banner features hex4 非法: {hb!r}")
|
|
220
|
+
return None
|
|
221
|
+
payload = self._recv_exact_sock(sock, plen) if plen else b""
|
|
222
|
+
if payload is None:
|
|
223
|
+
self._glog("[!] 查询 banner features 读 payload EOF")
|
|
224
|
+
return None
|
|
225
|
+
feats = payload.decode("utf-8", errors="replace")
|
|
226
|
+
elif s == "FAIL":
|
|
227
|
+
hb = self._recv_exact_sock(sock, 4)
|
|
228
|
+
plen = 0
|
|
229
|
+
if hb:
|
|
230
|
+
try:
|
|
231
|
+
plen = int(hb.decode("ascii", errors="ignore"), 16)
|
|
232
|
+
except ValueError:
|
|
233
|
+
plen = 0
|
|
234
|
+
fbody = self._recv_exact_sock(sock, plen) if plen else b""
|
|
235
|
+
self._glog(
|
|
236
|
+
f"[!] 查询 banner features FAIL: {fbody.decode('utf-8', errors='replace')!r}"
|
|
237
|
+
)
|
|
238
|
+
return None
|
|
239
|
+
else:
|
|
240
|
+
self._glog(f"[!] 查询 banner features status 异常: {s!r}")
|
|
241
|
+
return None
|
|
242
|
+
except Exception as e:
|
|
243
|
+
self._glog(f"[!] 查询 banner features 异常: {e}")
|
|
244
|
+
return None
|
|
245
|
+
finally:
|
|
246
|
+
try:
|
|
247
|
+
sock.close()
|
|
248
|
+
except Exception:
|
|
249
|
+
pass
|
|
250
|
+
# 硬剥 delayed_ack(front v1 流控硬约束);压缩子 feature 一律保留
|
|
251
|
+
feats_list = [f for f in feats.split(",") if f and f != "delayed_ack"]
|
|
252
|
+
return ",".join(feats_list)
|
|
253
|
+
|
|
254
|
+
# ---------- 查询后端真机 banner ro.product.*(效仿 adbd GetProperty 循环)----------
|
|
255
|
+
def _resolve_banner_fields(self):
|
|
256
|
+
"""效仿 adbd get_connection_string (adb.cpp:294-316) 的 GetProperty(prop) 循环:
|
|
257
|
+
逐属性经 5037 pin+shell 两步读后端 raw 系统属性(= __system_property_get 远端等价)。
|
|
258
|
+
shell 须先 host:transport:<serial> pin(adb.cpp:1339-1347 SwitchedTransport)再同 socket
|
|
259
|
+
发 shell:,adb server 经 connect_to_remote(sockets.cpp:925-934)A_OPEN 到设备
|
|
260
|
+
ShellService(daemon/services.cpp:354 ConsumePrefix("shell")),输出 raw 流至 EOF
|
|
261
|
+
(ReadOrderlyShutdown,adb_client.cpp:234)。成功返回 (product,model,device),
|
|
262
|
+
失败 None。解析只读旁路,启动期查询 socket 与 per-conn 转发隔离,不影响 sendall 透传。"""
|
|
263
|
+
_BANNER_PROPS = ("ro.product.name", "ro.product.model", "ro.product.device")
|
|
264
|
+
values = []
|
|
265
|
+
for prop in _BANNER_PROPS:
|
|
266
|
+
val = self._query_getprop(prop)
|
|
267
|
+
if val is None:
|
|
268
|
+
return None # 任一属性失败→整体回退(效仿 adbd 不容部分缺失)
|
|
269
|
+
values.append(val)
|
|
270
|
+
return tuple(values)
|
|
271
|
+
|
|
272
|
+
def _query_getprop(self, prop):
|
|
273
|
+
"""单次 shell:getprop 查询(pin+shell 两步同 socket,architect round2 修订)。
|
|
274
|
+
返回 strip 后属性值,失败 None。逐属性独立 socket:shell EOF 后 adb server 关
|
|
275
|
+
socket 不可复用,故 3 socket/3 RTT。"""
|
|
276
|
+
try:
|
|
277
|
+
sock = socket.create_connection(
|
|
278
|
+
("127.0.0.1", self.adb_server_port), timeout=5
|
|
279
|
+
)
|
|
280
|
+
except Exception as e:
|
|
281
|
+
self._glog(f"[!] 查询 {prop} 连接 5037 失败: {e}")
|
|
282
|
+
return None
|
|
283
|
+
try:
|
|
284
|
+
sock.settimeout(5)
|
|
285
|
+
# ① pin transport: host:transport:<serial>(legacy,无 transport_id 回写)
|
|
286
|
+
# 对齐 problem3 AdbClientBackend.start() 既有 pin 模式(adb.cpp:1339-1347)
|
|
287
|
+
tport = f"host:transport:{self.serial}"
|
|
288
|
+
sock.sendall(AdbProtocol.format_service_frame(tport))
|
|
289
|
+
ts = self._recv_exact_sock(sock, 4)
|
|
290
|
+
if ts is None:
|
|
291
|
+
self._glog(f"[!] 查询 {prop} 读 tport status EOF")
|
|
292
|
+
return None
|
|
293
|
+
ts = ts.decode("ascii", errors="ignore")
|
|
294
|
+
if ts == "FAIL": # tport FAIL:读 hex4+error(device 未找到等)
|
|
295
|
+
self._read_fail_body(sock, f"tport {prop}")
|
|
296
|
+
return None
|
|
297
|
+
if ts != "OKAY":
|
|
298
|
+
self._glog(f"[!] 查询 {prop} tport status 异常: {ts!r}")
|
|
299
|
+
return None
|
|
300
|
+
# ② 同 socket 发 shell:getprop <prop>(无 host 前缀,走 connect_to_remote)
|
|
301
|
+
svc = f"shell:getprop {prop}"
|
|
302
|
+
sock.sendall(AdbProtocol.format_service_frame(svc))
|
|
303
|
+
st = self._recv_exact_sock(sock, 4)
|
|
304
|
+
if st is None:
|
|
305
|
+
self._glog(f"[!] 查询 {prop} 读 svc status EOF")
|
|
306
|
+
return None
|
|
307
|
+
s = st.decode("ascii", errors="ignore")
|
|
308
|
+
if s == "OKAY":
|
|
309
|
+
# shell 流式:recv 循环至 EOF(ReadOrderlyShutdown 语义)
|
|
310
|
+
stream = bytearray()
|
|
311
|
+
while True:
|
|
312
|
+
try:
|
|
313
|
+
chunk = sock.recv(4096)
|
|
314
|
+
except OSError:
|
|
315
|
+
break # 超时/异常→EOF
|
|
316
|
+
if not chunk:
|
|
317
|
+
break # adb server 命令结束关 socket
|
|
318
|
+
stream.extend(chunk)
|
|
319
|
+
return stream.decode("utf-8", errors="replace").strip()
|
|
320
|
+
elif s == "FAIL":
|
|
321
|
+
self._read_fail_body(sock, f"shell {prop}")
|
|
322
|
+
return None
|
|
323
|
+
else:
|
|
324
|
+
self._glog(f"[!] 查询 {prop} svc status 异常: {s!r}")
|
|
325
|
+
return None
|
|
326
|
+
except Exception as e:
|
|
327
|
+
self._glog(f"[!] 查询 {prop} 异常: {e}")
|
|
328
|
+
return None
|
|
329
|
+
finally:
|
|
330
|
+
try:
|
|
331
|
+
sock.close()
|
|
332
|
+
except Exception:
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
def _read_fail_body(self, sock, label):
|
|
336
|
+
"""读 FAIL 后的 hex4+error body 并打日志(复用 features 分支模式,tport/shell 共用)。"""
|
|
337
|
+
hb = self._recv_exact_sock(sock, 4)
|
|
338
|
+
plen = 0
|
|
339
|
+
if hb:
|
|
340
|
+
try:
|
|
341
|
+
plen = int(hb.decode("ascii", errors="ignore"), 16)
|
|
342
|
+
except ValueError:
|
|
343
|
+
plen = 0
|
|
344
|
+
fbody = self._recv_exact_sock(sock, plen) if plen else b""
|
|
345
|
+
self._glog(
|
|
346
|
+
f"[!] 查询 {label} FAIL: {fbody.decode('utf-8', errors='replace')!r}"
|
|
347
|
+
if fbody
|
|
348
|
+
else f"[!] 查询 {label} FAIL"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
@staticmethod
|
|
352
|
+
def _recv_exact_sock(sock, n):
|
|
353
|
+
"""精确读 n 字节(循环拼半包)。EOF/异常返回 None。"""
|
|
354
|
+
buf = bytearray()
|
|
355
|
+
while len(buf) < n:
|
|
356
|
+
try:
|
|
357
|
+
chunk = sock.recv(n - len(buf))
|
|
358
|
+
except OSError:
|
|
359
|
+
return None
|
|
360
|
+
if not chunk:
|
|
361
|
+
return None
|
|
362
|
+
buf.extend(chunk)
|
|
363
|
+
return bytes(buf)
|
|
364
|
+
|
|
365
|
+
# ---------- 自起后端 adb server ----------
|
|
366
|
+
def _ensure_adb_server(self):
|
|
367
|
+
if self.adb_server_port == DEFAULT_ADB_SERVER_PORT:
|
|
368
|
+
self.adb_server_lease.initial_state = "reused"
|
|
369
|
+
return True # 复用系统 5037
|
|
370
|
+
self._glog(f"[*] 自起 adb server @ 端口 {self.adb_server_port} ...")
|
|
371
|
+
return self.adb_server_lease.acquire_owned()
|
|
372
|
+
|
|
373
|
+
# ---------- 信号 ----------
|
|
374
|
+
def _signal_handler(self, signum, frame):
|
|
375
|
+
self._stop = True
|
|
376
|
+
self._glog(f"\n[*] 接收到信号 {signum},正在清理...")
|
|
377
|
+
try:
|
|
378
|
+
if self.listen_sock:
|
|
379
|
+
self.listen_sock.close()
|
|
380
|
+
except Exception:
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
# ---------- serve ----------
|
|
384
|
+
def serve(self):
|
|
385
|
+
if not self._ensure_adb_server():
|
|
386
|
+
self.cleanup()
|
|
387
|
+
sys.exit(1)
|
|
388
|
+
self.serial = self._resolve_target()
|
|
389
|
+
if not self.serial:
|
|
390
|
+
self._glog(f"[!] 无法解析 --target {self.target!r}:未取到可用设备 serial")
|
|
391
|
+
self.cleanup()
|
|
392
|
+
sys.exit(1)
|
|
393
|
+
self._glog(f"[*] target serial={self.serial}")
|
|
394
|
+
resolved = self._resolve_banner_features()
|
|
395
|
+
if resolved is not None:
|
|
396
|
+
self.banner_features = resolved
|
|
397
|
+
self._glog(
|
|
398
|
+
f"[*] resolved banner features (from back :{self.adb_server_port}): {resolved}"
|
|
399
|
+
)
|
|
400
|
+
else:
|
|
401
|
+
self._glog(
|
|
402
|
+
f"[!] banner features 查询失败,回退默认: {DEFAULT_BANNER_FEATURES}"
|
|
403
|
+
)
|
|
404
|
+
fields = self._resolve_banner_fields()
|
|
405
|
+
if fields is not None:
|
|
406
|
+
self.banner_product, self.banner_model, self.banner_device = fields
|
|
407
|
+
self._glog(
|
|
408
|
+
f"[*] resolved banner fields (效仿 adbd GetProperty, from back :{self.adb_server_port}): "
|
|
409
|
+
f"name={self.banner_product} model={self.banner_model} device={self.banner_device}"
|
|
410
|
+
)
|
|
411
|
+
else:
|
|
412
|
+
self._glog(
|
|
413
|
+
"[!] banner fields 查询失败,banner 回退修复前形态(type=device, ro.product.name=client-mock)"
|
|
414
|
+
)
|
|
415
|
+
# bind listen
|
|
416
|
+
self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
417
|
+
self.listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
418
|
+
try:
|
|
419
|
+
self.listen_sock.bind(("127.0.0.1", self.listen_port))
|
|
420
|
+
self.listen_sock.listen(5)
|
|
421
|
+
except OSError as e:
|
|
422
|
+
self._glog(f"[!] 监听端口 {self.listen_port} 绑定失败: {e}")
|
|
423
|
+
self.cleanup()
|
|
424
|
+
sys.exit(1)
|
|
425
|
+
self._glog(
|
|
426
|
+
f"[*] client-mock 运行中,监听 {self.listen_port},后端 adb server :{self.adb_server_port},target={self.serial}"
|
|
427
|
+
)
|
|
428
|
+
self._glog(f"[*] 日志文件: {self.log_path}")
|
|
429
|
+
if self.debug_logger:
|
|
430
|
+
self._glog(f"[*] debug hex 日志: {self.debug_path}")
|
|
431
|
+
else:
|
|
432
|
+
self._glog("[*] debug hex 日志: <off>(--debug 可开启)")
|
|
433
|
+
if self.capture_enabled:
|
|
434
|
+
self._glog(
|
|
435
|
+
f"[*] 文件传输捕获已开启,落盘目录: {FILES_DIR}(--no-capture 可禁用)"
|
|
436
|
+
)
|
|
437
|
+
else:
|
|
438
|
+
self._glog(
|
|
439
|
+
"[*] 文件传输捕获已禁用(--no-capture),仅打印 sync/install 摘要"
|
|
440
|
+
)
|
|
441
|
+
self._glog(
|
|
442
|
+
"[*] 用法:adb connect 127.0.0.1:%d,然后 adb -s 127.0.0.1:%d <cmd>"
|
|
443
|
+
% (self.listen_port, self.listen_port)
|
|
444
|
+
)
|
|
445
|
+
signal.signal(signal.SIGINT, self._signal_handler)
|
|
446
|
+
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
447
|
+
self.listen_sock.settimeout(0.5)
|
|
448
|
+
try:
|
|
449
|
+
while not self._stop:
|
|
450
|
+
try:
|
|
451
|
+
client, _ = self.listen_sock.accept()
|
|
452
|
+
except socket.timeout:
|
|
453
|
+
continue
|
|
454
|
+
except OSError:
|
|
455
|
+
break
|
|
456
|
+
conn_id = _next_conn_id() # 与 per-stream sb 共用计数器,保证全局唯一
|
|
457
|
+
conn = TransportConnection(
|
|
458
|
+
conn_id,
|
|
459
|
+
client,
|
|
460
|
+
self.file_logger,
|
|
461
|
+
self.capture_enabled,
|
|
462
|
+
self.debug_logger,
|
|
463
|
+
self.serial,
|
|
464
|
+
self.adb_server_port,
|
|
465
|
+
self.banner_features,
|
|
466
|
+
self.banner_type,
|
|
467
|
+
self.banner_product,
|
|
468
|
+
self.banner_model,
|
|
469
|
+
self.banner_device,
|
|
470
|
+
)
|
|
471
|
+
thread = threading.Thread(
|
|
472
|
+
target=self._connection_worker, args=(conn,), daemon=True
|
|
473
|
+
)
|
|
474
|
+
with self._connections_lock:
|
|
475
|
+
self._connections[conn] = thread
|
|
476
|
+
thread.start()
|
|
477
|
+
except KeyboardInterrupt:
|
|
478
|
+
self._glog("\n[*] 接收到中断信号,正在清理环境并退出...")
|
|
479
|
+
finally:
|
|
480
|
+
self.cleanup()
|
|
481
|
+
|
|
482
|
+
def _connection_worker(self, conn):
|
|
483
|
+
try:
|
|
484
|
+
conn.run()
|
|
485
|
+
conn.wait()
|
|
486
|
+
finally:
|
|
487
|
+
with self._connections_lock:
|
|
488
|
+
self._connections.pop(conn, None)
|
|
489
|
+
|
|
490
|
+
def _drain_connections(self):
|
|
491
|
+
with self._connections_lock:
|
|
492
|
+
connections = list(self._connections.items())
|
|
493
|
+
for conn, _thread in connections:
|
|
494
|
+
conn.stop()
|
|
495
|
+
current = threading.current_thread()
|
|
496
|
+
for conn, thread in connections:
|
|
497
|
+
conn.wait(timeout=5)
|
|
498
|
+
if thread is not current:
|
|
499
|
+
thread.join(timeout=5)
|
|
500
|
+
|
|
501
|
+
def cleanup(self):
|
|
502
|
+
cleanup_lock = getattr(self, "_cleanup_lock", None)
|
|
503
|
+
if cleanup_lock is None:
|
|
504
|
+
cleanup_lock = threading.Lock()
|
|
505
|
+
self._cleanup_lock = cleanup_lock
|
|
506
|
+
with cleanup_lock:
|
|
507
|
+
if getattr(self, "_cleaned", False):
|
|
508
|
+
return
|
|
509
|
+
self._cleaned = True
|
|
510
|
+
# 1. 关 listen socket 释放端口,打断 accept
|
|
511
|
+
try:
|
|
512
|
+
if self.listen_sock:
|
|
513
|
+
self.listen_sock.close()
|
|
514
|
+
except Exception:
|
|
515
|
+
pass
|
|
516
|
+
# 2. 先停活动连接并等待 transport/backend/observer 收尾。
|
|
517
|
+
self._drain_connections()
|
|
518
|
+
# 3. 只清理 lease 明确拥有的 adb server;端口存在不构成 ownership。
|
|
519
|
+
lease = getattr(self, "adb_server_lease", None)
|
|
520
|
+
lease_complete = True
|
|
521
|
+
if lease is not None:
|
|
522
|
+
if lease.cleanup() is False:
|
|
523
|
+
lease_complete = False
|
|
524
|
+
self._glog("[!] adb server 清理未完成;已保留 lease 状态供后续重试")
|
|
525
|
+
# net 目标不主动 disconnect(让 adb server 自然回收)
|
|
526
|
+
self.file_logger.close()
|
|
527
|
+
if self.debug_logger:
|
|
528
|
+
self.debug_logger.close()
|
|
529
|
+
if not lease_complete:
|
|
530
|
+
with cleanup_lock:
|
|
531
|
+
self._cleaned = False
|
|
532
|
+
return lease_complete
|