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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""ADB client 到 server 的透明代理。"""
|
adbproxy/server/app.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""ADB smart-socket 代理应用生命周期。"""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import datetime
|
|
5
|
+
import os
|
|
6
|
+
import signal
|
|
7
|
+
import socket
|
|
8
|
+
import sys
|
|
9
|
+
import threading
|
|
10
|
+
|
|
11
|
+
from adbproxy.config import FILES_DIR, LOG_DIR, PROXY_PORT, REAL_ADB_PORT
|
|
12
|
+
from adbproxy.runtime.adb_process import AdbServerLease
|
|
13
|
+
from adbproxy.runtime.logging import DebugHexLogger, FileLogger
|
|
14
|
+
from adbproxy.server.connection import Connection
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AdbProxy:
|
|
18
|
+
def __init__(self, argv):
|
|
19
|
+
parser = argparse.ArgumentParser(
|
|
20
|
+
description="ADB MITM 代理:转发并解析 adb 协议数据包"
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--no-capture",
|
|
24
|
+
action="store_true",
|
|
25
|
+
help="禁用 sync 文件传输落盘到 files/,仅打印摘要",
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"--debug",
|
|
29
|
+
action="store_true",
|
|
30
|
+
help="每 recv 块原始 hex 落盘到 logs/debug-{date}.log,旁路不改转发",
|
|
31
|
+
)
|
|
32
|
+
args = parser.parse_args(argv)
|
|
33
|
+
self.capture_enabled = not args.no_capture
|
|
34
|
+
try:
|
|
35
|
+
os.makedirs(LOG_DIR, exist_ok=True)
|
|
36
|
+
except OSError:
|
|
37
|
+
pass
|
|
38
|
+
if self.capture_enabled:
|
|
39
|
+
try:
|
|
40
|
+
os.makedirs(FILES_DIR, exist_ok=True)
|
|
41
|
+
except OSError:
|
|
42
|
+
self.capture_enabled = False
|
|
43
|
+
now = datetime.datetime.now()
|
|
44
|
+
fname = (
|
|
45
|
+
now.strftime("%Y-%m-%d-%H:%M:%S-") + f"{now.microsecond // 1000:03d}.log"
|
|
46
|
+
)
|
|
47
|
+
self.log_path = os.path.join(LOG_DIR, fname)
|
|
48
|
+
self.file_logger = FileLogger(self.log_path)
|
|
49
|
+
# P0: --debug 开启时每 recv 一块写一行 hex 到独立 debug 文件,旁路不改转发字节
|
|
50
|
+
self.debug_enabled = args.debug
|
|
51
|
+
if self.debug_enabled:
|
|
52
|
+
debug_fname = (
|
|
53
|
+
now.strftime("%Y-%m-%d-%H:%M:%S-")
|
|
54
|
+
+ f"{now.microsecond // 1000:03d}-debug.log"
|
|
55
|
+
)
|
|
56
|
+
self.debug_path = os.path.join(LOG_DIR, debug_fname)
|
|
57
|
+
self.debug_logger = DebugHexLogger(self.debug_path)
|
|
58
|
+
else:
|
|
59
|
+
self.debug_path = None
|
|
60
|
+
self.debug_logger = None
|
|
61
|
+
self._conn_counter = 0
|
|
62
|
+
self._counter_lock = threading.Lock()
|
|
63
|
+
self._connections_lock = threading.Lock()
|
|
64
|
+
self._connections = {}
|
|
65
|
+
self._cleanup_lock = threading.Lock()
|
|
66
|
+
self._cleaned = False
|
|
67
|
+
self.proxy_sock = None
|
|
68
|
+
self._stop = False # 信号处理器置位,serve() 超时轮询据此退出 accept 循环
|
|
69
|
+
self.adb_server_lease = AdbServerLease(
|
|
70
|
+
managed_port=REAL_ADB_PORT,
|
|
71
|
+
restore_port=PROXY_PORT,
|
|
72
|
+
log=self._glog,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def _glog(self, msg):
|
|
76
|
+
try:
|
|
77
|
+
print(msg)
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
self.file_logger.write(msg)
|
|
81
|
+
|
|
82
|
+
def _signal_handler(self, signum, frame):
|
|
83
|
+
# 置标志位 + 日志;close proxy_sock 唤醒 accept 抛 OSError 立即跳出(#5)
|
|
84
|
+
self._stop = True
|
|
85
|
+
self._glog(f"\n[*] 接收到信号 {signum},正在清理...")
|
|
86
|
+
try:
|
|
87
|
+
if self.proxy_sock:
|
|
88
|
+
self.proxy_sock.close()
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
def _kill_default_server(self):
|
|
93
|
+
"""先检查 5038 未被占用,再按 lease 证据停止需恢复的 5037。"""
|
|
94
|
+
return self.adb_server_lease.prepare_takeover()
|
|
95
|
+
|
|
96
|
+
def init_adb_server(self):
|
|
97
|
+
"""启动并验证 5038;只有 start 成功且 post-probe 就绪才获得所有权。"""
|
|
98
|
+
return self.adb_server_lease.start_owned()
|
|
99
|
+
|
|
100
|
+
def serve(self):
|
|
101
|
+
# #4+#7+#顺序修正: kill 默认 5037 → bind 5037 → 起 5038。
|
|
102
|
+
# 必须先 kill:此时 5037 是真 adb server,adb kill-server 连真 server 不会连到 proxy 自身 backlog
|
|
103
|
+
# 被队列吞并转发 host:kill 去杀 5038。bind 前移根治竞态,bind 失败先 cleanup 再退出不留孤儿 5038。
|
|
104
|
+
if not self._kill_default_server():
|
|
105
|
+
self._glog("[!] adb server lease 预检失败,未修改现有实例")
|
|
106
|
+
self.cleanup()
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
self.proxy_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
109
|
+
self.proxy_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
110
|
+
try:
|
|
111
|
+
self.proxy_sock.bind(("127.0.0.1", PROXY_PORT))
|
|
112
|
+
self.proxy_sock.listen(5)
|
|
113
|
+
except OSError as e:
|
|
114
|
+
self._glog(f"[!] 端口绑定失败: {e}")
|
|
115
|
+
self.cleanup()
|
|
116
|
+
sys.exit(1)
|
|
117
|
+
# bind 成功后起 5038 真实 server
|
|
118
|
+
if not self.init_adb_server():
|
|
119
|
+
self._glog("[!] 5038 未就绪,清理退出")
|
|
120
|
+
self.cleanup()
|
|
121
|
+
sys.exit(1)
|
|
122
|
+
self._glog(
|
|
123
|
+
f"[*] ADB Proxy 运行中。正在监听 {PROXY_PORT},转发至 {REAL_ADB_PORT}..."
|
|
124
|
+
)
|
|
125
|
+
self._glog(f"[*] 日志文件: {self.log_path}")
|
|
126
|
+
if self.debug_logger:
|
|
127
|
+
self._glog(f"[*] debug hex 日志: {self.debug_path}")
|
|
128
|
+
else:
|
|
129
|
+
self._glog("[*] debug hex 日志: <off>(--debug 可开启)")
|
|
130
|
+
# Step 6: 落盘 banner
|
|
131
|
+
if self.capture_enabled:
|
|
132
|
+
self._glog(
|
|
133
|
+
f"[*] 文件传输捕获已开启,落盘目录: {FILES_DIR} (--no-capture 可禁用)"
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
self._glog("[*] 文件传输捕获已禁用(--no-capture),仅打印 sync 摘要不落盘")
|
|
137
|
+
# 装信号处理器 + accept 超时轮询,让 SIGINT/SIGTERM 可靠触发 cleanup。
|
|
138
|
+
# 原因:主线程阻塞在 C 级 accept(),Python SIGINT 只在字节码间检查、不打断阻塞;
|
|
139
|
+
# 超时轮询让标志位 _stop 在每次 accept 超时后被检查,从而可靠退出。
|
|
140
|
+
signal.signal(signal.SIGINT, self._signal_handler)
|
|
141
|
+
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
142
|
+
self.proxy_sock.settimeout(0.5)
|
|
143
|
+
try:
|
|
144
|
+
while not self._stop:
|
|
145
|
+
try:
|
|
146
|
+
client, _ = self.proxy_sock.accept()
|
|
147
|
+
except socket.timeout:
|
|
148
|
+
continue
|
|
149
|
+
except OSError:
|
|
150
|
+
break
|
|
151
|
+
with self._counter_lock:
|
|
152
|
+
self._conn_counter += 1
|
|
153
|
+
conn_id = self._conn_counter
|
|
154
|
+
conn = Connection(
|
|
155
|
+
conn_id,
|
|
156
|
+
client,
|
|
157
|
+
self.file_logger,
|
|
158
|
+
self.capture_enabled,
|
|
159
|
+
self.debug_logger,
|
|
160
|
+
)
|
|
161
|
+
thread = threading.Thread(
|
|
162
|
+
target=self._connection_worker, args=(conn,), daemon=True
|
|
163
|
+
)
|
|
164
|
+
with self._connections_lock:
|
|
165
|
+
self._connections[conn] = thread
|
|
166
|
+
thread.start()
|
|
167
|
+
except KeyboardInterrupt:
|
|
168
|
+
self._glog("\n[*] 接收到中断信号,正在清理环境并退出...")
|
|
169
|
+
finally:
|
|
170
|
+
self.cleanup()
|
|
171
|
+
|
|
172
|
+
def _connection_worker(self, conn):
|
|
173
|
+
try:
|
|
174
|
+
conn.run()
|
|
175
|
+
conn.wait()
|
|
176
|
+
finally:
|
|
177
|
+
with self._connections_lock:
|
|
178
|
+
self._connections.pop(conn, None)
|
|
179
|
+
|
|
180
|
+
def _drain_connections(self):
|
|
181
|
+
with self._connections_lock:
|
|
182
|
+
connections = list(self._connections.items())
|
|
183
|
+
for conn, _thread in connections:
|
|
184
|
+
conn.stop()
|
|
185
|
+
current = threading.current_thread()
|
|
186
|
+
for conn, thread in connections:
|
|
187
|
+
conn.wait(timeout=5)
|
|
188
|
+
if thread is not current:
|
|
189
|
+
thread.join(timeout=5)
|
|
190
|
+
|
|
191
|
+
def cleanup(self):
|
|
192
|
+
cleanup_lock = getattr(self, "_cleanup_lock", None)
|
|
193
|
+
if cleanup_lock is None:
|
|
194
|
+
cleanup_lock = threading.Lock()
|
|
195
|
+
self._cleanup_lock = cleanup_lock
|
|
196
|
+
with cleanup_lock:
|
|
197
|
+
if getattr(self, "_cleaned", False):
|
|
198
|
+
return
|
|
199
|
+
self._cleaned = True
|
|
200
|
+
# 1. 关闭 Proxy Socket,释放 5037,打断 accept
|
|
201
|
+
try:
|
|
202
|
+
if self.proxy_sock:
|
|
203
|
+
self.proxy_sock.close()
|
|
204
|
+
except Exception:
|
|
205
|
+
pass
|
|
206
|
+
# 2. 先停活动连接并等待 relay/observer 收尾,再处理共享进程和日志。
|
|
207
|
+
self._drain_connections()
|
|
208
|
+
# 3. 仅撤销 lease action ledger 中由本进程完成的 stop/start。
|
|
209
|
+
lease = getattr(self, "adb_server_lease", None)
|
|
210
|
+
lease_complete = True
|
|
211
|
+
if lease is not None:
|
|
212
|
+
if lease.cleanup() is False:
|
|
213
|
+
lease_complete = False
|
|
214
|
+
self._glog("[!] adb server 清理未完成;已保留 lease 状态供后续重试")
|
|
215
|
+
self.file_logger.close()
|
|
216
|
+
if self.debug_logger:
|
|
217
|
+
self.debug_logger.close()
|
|
218
|
+
if not lease_complete:
|
|
219
|
+
with cleanup_lock:
|
|
220
|
+
self._cleaned = False
|
|
221
|
+
return lease_complete
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""ADB 客户端到服务端的代理连接。"""
|
|
2
|
+
|
|
3
|
+
import socket
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
from adbproxy.config import REAL_ADB_PORT
|
|
7
|
+
from adbproxy.observers.stream import StreamBuffer
|
|
8
|
+
from adbproxy.protocols.adb import AdbProtocol
|
|
9
|
+
from adbproxy.protocols.smart_socket.conversation import SmartSocketConversation
|
|
10
|
+
from adbproxy.runtime.sockets import close_sockets
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Connection:
|
|
14
|
+
def __init__(
|
|
15
|
+
self, conn_id, client_sock, file_logger, capture_enabled=True, debug_logger=None
|
|
16
|
+
):
|
|
17
|
+
self.conn_id = conn_id
|
|
18
|
+
self.client = client_sock
|
|
19
|
+
self.file_logger = file_logger
|
|
20
|
+
self.debug_logger = (
|
|
21
|
+
debug_logger # P0: None 或 DebugHexLogger 实例,sendall 后旁路落盘 hex
|
|
22
|
+
)
|
|
23
|
+
self.sb = StreamBuffer(conn_id, file_logger, capture_enabled)
|
|
24
|
+
self.server = None
|
|
25
|
+
self.svc = None # client 首帧 service 串,供 server 侧 host-length-gating 查
|
|
26
|
+
self.shell_okay_consumed = (
|
|
27
|
+
False # shell 服务的 OKAY/FAIL 是否已剥除(仅 server 线程读写)
|
|
28
|
+
)
|
|
29
|
+
self.sync_okay_consumed = (
|
|
30
|
+
False # sync 服务的 OKAY/FAIL 是否已剥除(仅 server 线程读写)
|
|
31
|
+
)
|
|
32
|
+
# pure smart-socket conversation (request/response policy); no I/O
|
|
33
|
+
self.conversation = SmartSocketConversation()
|
|
34
|
+
# 兼容测试与内部状态:暴露 conversation 缓冲/相位字段
|
|
35
|
+
self._service_request_buf = self.conversation._service_request_buf
|
|
36
|
+
self._shell_first_response = self.conversation._shell_first_response
|
|
37
|
+
self._sync_first_response = self.conversation._sync_first_response
|
|
38
|
+
self.host_phase = self.conversation.host_phase
|
|
39
|
+
self.host_status_buf = self.conversation.host_status_buf
|
|
40
|
+
self.host_hex_buf = self.conversation.host_hex_buf
|
|
41
|
+
self.host_payload_remaining = self.conversation.host_payload_remaining
|
|
42
|
+
self.host_fixed_remaining = self.conversation.host_fixed_remaining
|
|
43
|
+
self.host_track_active = self.conversation.host_track_active
|
|
44
|
+
self._last_host_svc = self.conversation._last_host_svc
|
|
45
|
+
self._finished = threading.Event()
|
|
46
|
+
self._finish_lock = threading.Lock()
|
|
47
|
+
self._relay_threads = []
|
|
48
|
+
# 不变量:self.svc 在 client sendall 之前写入,server recv 在收到该 sendall 之后
|
|
49
|
+
# —— TCP 顺序天然 happens-before,无需额外锁
|
|
50
|
+
|
|
51
|
+
def _sync_host_view(self):
|
|
52
|
+
"""Mirror conversation host SM fields onto Connection for tests/debug."""
|
|
53
|
+
c = self.conversation
|
|
54
|
+
self.host_phase = c.host_phase
|
|
55
|
+
self.host_status_buf = c.host_status_buf
|
|
56
|
+
self.host_hex_buf = c.host_hex_buf
|
|
57
|
+
self.host_payload_remaining = c.host_payload_remaining
|
|
58
|
+
self.host_fixed_remaining = c.host_fixed_remaining
|
|
59
|
+
self.host_track_active = c.host_track_active
|
|
60
|
+
self._last_host_svc = c._last_host_svc
|
|
61
|
+
self._service_request_buf = c._service_request_buf
|
|
62
|
+
self._shell_first_response = c._shell_first_response
|
|
63
|
+
self._sync_first_response = c._sync_first_response
|
|
64
|
+
|
|
65
|
+
def run(self):
|
|
66
|
+
try:
|
|
67
|
+
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
68
|
+
self.server.connect(("127.0.0.1", REAL_ADB_PORT))
|
|
69
|
+
except Exception as e:
|
|
70
|
+
self.sb.logger.event(f"[!] 连接真实 ADB Server 失败: {e}")
|
|
71
|
+
try:
|
|
72
|
+
self.client.close()
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
self._finish() # #9: 早退路径过收尾,保证 sb.close 幂等、timer 不残留
|
|
76
|
+
return
|
|
77
|
+
t_c = threading.Thread(target=self._client_to_server, daemon=True)
|
|
78
|
+
t_s = threading.Thread(target=self._server_to_client, daemon=True)
|
|
79
|
+
self._relay_threads = [t_c, t_s]
|
|
80
|
+
t_c.start()
|
|
81
|
+
t_s.start()
|
|
82
|
+
# 不 join:任一方 EOF 即在 _finish 里 close_sockets 双向,立即解阻塞对端 recv。
|
|
83
|
+
# 否则 server EOF 后 run 卡在 t_c.join 等 client 线程,而 client 卡在 recv 等
|
|
84
|
+
# 代理关闭发 EOF —— 死锁。
|
|
85
|
+
|
|
86
|
+
def _finish(self):
|
|
87
|
+
"""任一方结束时关闭双向 socket 解阻塞对端;once-guard 保证 sb.close 只调一次。"""
|
|
88
|
+
with self._finish_lock:
|
|
89
|
+
if self._finished.is_set():
|
|
90
|
+
return
|
|
91
|
+
close_sockets(self.client, self.server)
|
|
92
|
+
try:
|
|
93
|
+
self.sb.close()
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
96
|
+
self._finished.set()
|
|
97
|
+
|
|
98
|
+
def stop(self):
|
|
99
|
+
self._finish()
|
|
100
|
+
|
|
101
|
+
def wait(self, timeout=None):
|
|
102
|
+
done = self._finished.wait(timeout)
|
|
103
|
+
if not done:
|
|
104
|
+
return False
|
|
105
|
+
current = threading.current_thread()
|
|
106
|
+
for thread in list(self._relay_threads):
|
|
107
|
+
if thread is not current:
|
|
108
|
+
thread.join(timeout)
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
def _client_to_server(self):
|
|
112
|
+
# 用"shell/sync/install 是否已激活"驱动解析,而非首帧布尔。
|
|
113
|
+
# adb -s <serial> shell ls /data 在同一 TCP 连接发两帧:
|
|
114
|
+
# 帧1 host:tport:serial:<serial>(host 服务 → shell_mode=None)
|
|
115
|
+
# 帧2 0029shell,v2,...:ls /data\x04(真正的 shell 服务)
|
|
116
|
+
# 故只要 shell/sync/install 未激活,每个 recv 都尝试解析 service 帧。
|
|
117
|
+
# A2: 仅未激活时 feed_request(守卫);解析单源 conversation;activate 副作用留 connection。
|
|
118
|
+
try:
|
|
119
|
+
while True:
|
|
120
|
+
data = self.client.recv(65536) # #11: 64KB recv
|
|
121
|
+
if not data:
|
|
122
|
+
break
|
|
123
|
+
# Phase A — pre-sendall classify / set_mode / activate (I2, S1–S4)
|
|
124
|
+
leftover = b""
|
|
125
|
+
parsed = None
|
|
126
|
+
try:
|
|
127
|
+
if (
|
|
128
|
+
not self.sb.shell.active
|
|
129
|
+
and not self.sb.sync.active
|
|
130
|
+
and not self.sb.install.active
|
|
131
|
+
):
|
|
132
|
+
for ev in self.conversation.feed_request(data):
|
|
133
|
+
if ev.kind != "service_request" or not ev.svc:
|
|
134
|
+
continue
|
|
135
|
+
svc = ev.svc
|
|
136
|
+
leftover = ev.leftover or b""
|
|
137
|
+
parsed = (svc, leftover)
|
|
138
|
+
self.svc = svc
|
|
139
|
+
self.conversation.svc = svc
|
|
140
|
+
self.sb.logger.event("========")
|
|
141
|
+
self.sb.logger.event(f"📥 [User Command]: {svc}")
|
|
142
|
+
self.sb.shell.set_mode_from_service(svc)
|
|
143
|
+
if AdbProtocol.is_sync_service(svc):
|
|
144
|
+
self.sb.sync.activate()
|
|
145
|
+
if AdbProtocol.is_install_stream_service(svc):
|
|
146
|
+
size = AdbProtocol.parse_install_size(svc)
|
|
147
|
+
if size is not None:
|
|
148
|
+
self.sb.install.activate(size, svc)
|
|
149
|
+
cap = (
|
|
150
|
+
self.sb.install.capture_path
|
|
151
|
+
if (
|
|
152
|
+
self.sb.install.capture_enabled
|
|
153
|
+
and self.sb.install.capture_path
|
|
154
|
+
)
|
|
155
|
+
else "<disabled>"
|
|
156
|
+
)
|
|
157
|
+
self.sb.logger.event(
|
|
158
|
+
f"📤 [install push]: remote={svc} size={size} local_capture={cap}"
|
|
159
|
+
)
|
|
160
|
+
self._sync_host_view()
|
|
161
|
+
except Exception:
|
|
162
|
+
parsed = None
|
|
163
|
+
leftover = b""
|
|
164
|
+
# Phase B — 原样透传 (I1);观察失败不得跳过
|
|
165
|
+
self.server.sendall(data)
|
|
166
|
+
if self.debug_logger is not None:
|
|
167
|
+
try:
|
|
168
|
+
ln = self.debug_logger.write_hex(self.conn_id, "c", data)
|
|
169
|
+
self.sb.logger.event(
|
|
170
|
+
f"📥 [debug]: written to {self.debug_logger.file.name} L{ln} ({len(data)}B, client→server)"
|
|
171
|
+
)
|
|
172
|
+
except Exception:
|
|
173
|
+
pass
|
|
174
|
+
# Phase C — post-sendall payload observe (I3 fail-open)
|
|
175
|
+
try:
|
|
176
|
+
if self.sb.shell.active:
|
|
177
|
+
if parsed is not None:
|
|
178
|
+
if leftover:
|
|
179
|
+
self.sb.add_client(leftover)
|
|
180
|
+
else:
|
|
181
|
+
self.sb.add_client(data)
|
|
182
|
+
elif self.sb.sync.active:
|
|
183
|
+
if parsed is not None:
|
|
184
|
+
if leftover:
|
|
185
|
+
self.sb.add_client(leftover)
|
|
186
|
+
else:
|
|
187
|
+
self.sb.add_client(data)
|
|
188
|
+
else:
|
|
189
|
+
if leftover:
|
|
190
|
+
self.sb.add_client(leftover)
|
|
191
|
+
elif parsed is None:
|
|
192
|
+
self.sb.add_client(data)
|
|
193
|
+
except Exception:
|
|
194
|
+
pass
|
|
195
|
+
except OSError:
|
|
196
|
+
pass
|
|
197
|
+
finally:
|
|
198
|
+
self._finish()
|
|
199
|
+
|
|
200
|
+
def _try_parse_service_frame(self, data):
|
|
201
|
+
"""兼容包装:委托 conversation.feed_request / try_parse 单源。"""
|
|
202
|
+
result = self.conversation.try_parse_service_frame(data)
|
|
203
|
+
self._sync_host_view()
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
def _apply_response_events(self, events, complete):
|
|
207
|
+
"""Log status/fail, drain host _status_events, update okay flags, feed payloads."""
|
|
208
|
+
for ev in events:
|
|
209
|
+
if ev.kind == "status" and ev.status is not None:
|
|
210
|
+
self.sb.logger.event(f"📤 [Status]: {ev.status}")
|
|
211
|
+
elif ev.kind == "fail_reason" and ev.reason is not None:
|
|
212
|
+
self.sb.logger.event(
|
|
213
|
+
f"📤 [FAIL]: {ev.reason.decode('utf-8', errors='replace')}"
|
|
214
|
+
)
|
|
215
|
+
# S5: drain side-channel even if already mirrored into events
|
|
216
|
+
for st in self.conversation._status_events:
|
|
217
|
+
# avoid double-log when feed_response already emitted status events
|
|
218
|
+
if not any(
|
|
219
|
+
ev.kind == "status" and ev.status == st for ev in events
|
|
220
|
+
):
|
|
221
|
+
self.sb.logger.event(f"📤 [Status]: {st}")
|
|
222
|
+
self.conversation._status_events = []
|
|
223
|
+
|
|
224
|
+
if self.sb.shell.active and not self.shell_okay_consumed and complete:
|
|
225
|
+
self.shell_okay_consumed = True
|
|
226
|
+
if self.sb.sync.active and not self.sync_okay_consumed and complete:
|
|
227
|
+
self.sync_okay_consumed = True
|
|
228
|
+
|
|
229
|
+
for ev in events:
|
|
230
|
+
if ev.kind == "payload" and ev.data:
|
|
231
|
+
self.sb.add_server(ev.data)
|
|
232
|
+
self._sync_host_view()
|
|
233
|
+
|
|
234
|
+
def _server_to_client(self):
|
|
235
|
+
# sendall 先于 feed_response(I1/I3);okay_consumed 仅 server 线程写。
|
|
236
|
+
try:
|
|
237
|
+
while True:
|
|
238
|
+
data = self.server.recv(65536)
|
|
239
|
+
if not data:
|
|
240
|
+
break
|
|
241
|
+
self.client.sendall(data)
|
|
242
|
+
if self.debug_logger is not None:
|
|
243
|
+
try:
|
|
244
|
+
ln = self.debug_logger.write_hex(self.conn_id, "s", data)
|
|
245
|
+
self.sb.logger.event(
|
|
246
|
+
f"📤 [debug]: written to {self.debug_logger.file.name} L{ln} ({len(data)}B, server→client)"
|
|
247
|
+
)
|
|
248
|
+
except Exception:
|
|
249
|
+
pass
|
|
250
|
+
try:
|
|
251
|
+
events, complete = self.conversation.feed_response(
|
|
252
|
+
data,
|
|
253
|
+
shell_active=self.sb.shell.active,
|
|
254
|
+
shell_okay_consumed=self.shell_okay_consumed,
|
|
255
|
+
sync_active=self.sb.sync.active,
|
|
256
|
+
sync_okay_consumed=self.sync_okay_consumed,
|
|
257
|
+
svc=self.svc,
|
|
258
|
+
)
|
|
259
|
+
self._apply_response_events(events, complete)
|
|
260
|
+
except Exception:
|
|
261
|
+
pass
|
|
262
|
+
except OSError:
|
|
263
|
+
pass
|
|
264
|
+
finally:
|
|
265
|
+
self._finish()
|
|
266
|
+
|
|
267
|
+
def _parse_shell_status(self, data):
|
|
268
|
+
"""增量消费 shell 的首个 OKAY/FAIL,返回 (完成, payload)。"""
|
|
269
|
+
events, complete = self.conversation.feed_response(
|
|
270
|
+
data,
|
|
271
|
+
shell_active=True,
|
|
272
|
+
shell_okay_consumed=False,
|
|
273
|
+
sync_active=False,
|
|
274
|
+
sync_okay_consumed=True,
|
|
275
|
+
svc=self.svc,
|
|
276
|
+
)
|
|
277
|
+
leftover = b""
|
|
278
|
+
for ev in events:
|
|
279
|
+
if ev.kind == "status" and ev.status is not None:
|
|
280
|
+
self.sb.logger.event(f"📤 [Status]: {ev.status}")
|
|
281
|
+
elif ev.kind == "fail_reason" and ev.reason is not None:
|
|
282
|
+
self.sb.logger.event(
|
|
283
|
+
f"📤 [FAIL]: {ev.reason.decode('utf-8', errors='replace')}"
|
|
284
|
+
)
|
|
285
|
+
elif ev.kind == "payload" and ev.data:
|
|
286
|
+
leftover = ev.data
|
|
287
|
+
self._sync_host_view()
|
|
288
|
+
return complete, leftover
|
|
289
|
+
|
|
290
|
+
def _parse_sync_okay(self, data):
|
|
291
|
+
"""增量消费 sync 的首个 OKAY/FAIL,返回 (完成, payload)。"""
|
|
292
|
+
events, complete = self.conversation.feed_response(
|
|
293
|
+
data,
|
|
294
|
+
shell_active=False,
|
|
295
|
+
shell_okay_consumed=True,
|
|
296
|
+
sync_active=True,
|
|
297
|
+
sync_okay_consumed=False,
|
|
298
|
+
svc=self.svc,
|
|
299
|
+
)
|
|
300
|
+
leftover = b""
|
|
301
|
+
for ev in events:
|
|
302
|
+
if ev.kind == "status" and ev.status is not None:
|
|
303
|
+
self.sb.logger.event(f"📤 [Status]: {ev.status}")
|
|
304
|
+
elif ev.kind == "fail_reason" and ev.reason is not None:
|
|
305
|
+
self.sb.logger.event(
|
|
306
|
+
f"📤 [FAIL]: {ev.reason.decode('utf-8', errors='replace')}"
|
|
307
|
+
)
|
|
308
|
+
elif ev.kind == "payload" and ev.data:
|
|
309
|
+
leftover = ev.data
|
|
310
|
+
self._sync_host_view()
|
|
311
|
+
return complete, leftover
|
|
312
|
+
|
|
313
|
+
@staticmethod
|
|
314
|
+
def _new_first_response_state():
|
|
315
|
+
"""创建仅供 server 线程使用的首响应重组状态。"""
|
|
316
|
+
return SmartSocketConversation._new_first_response_state()
|
|
317
|
+
|
|
318
|
+
def _consume_first_response(self, data, state):
|
|
319
|
+
"""兼容包装:委托 conversation.consume_first_response。"""
|
|
320
|
+
events = []
|
|
321
|
+
complete, leftover = self.conversation.consume_first_response(
|
|
322
|
+
data, state, emit=events
|
|
323
|
+
)
|
|
324
|
+
for ev in events:
|
|
325
|
+
if ev.kind == "status" and ev.status is not None:
|
|
326
|
+
self.sb.logger.event(f"📤 [Status]: {ev.status}")
|
|
327
|
+
elif ev.kind == "fail_reason" and ev.reason is not None:
|
|
328
|
+
self.sb.logger.event(
|
|
329
|
+
f"📤 [FAIL]: {ev.reason.decode('utf-8', errors='replace')}"
|
|
330
|
+
)
|
|
331
|
+
self._sync_host_view()
|
|
332
|
+
return complete, leftover
|
|
333
|
+
|
|
334
|
+
def _parse_server_first(self, data):
|
|
335
|
+
"""非 host 服务的状态剥除(stateless 兜底);经 feed_response 单源。"""
|
|
336
|
+
events, _complete = self.conversation.feed_response(
|
|
337
|
+
data,
|
|
338
|
+
shell_active=False,
|
|
339
|
+
shell_okay_consumed=True,
|
|
340
|
+
sync_active=False,
|
|
341
|
+
sync_okay_consumed=True,
|
|
342
|
+
svc=self.svc,
|
|
343
|
+
)
|
|
344
|
+
leftover = b""
|
|
345
|
+
for ev in events:
|
|
346
|
+
if ev.kind == "status" and ev.status is not None:
|
|
347
|
+
self.sb.logger.event(f"📤 [Status]: {ev.status}")
|
|
348
|
+
elif ev.kind == "payload" and ev.data:
|
|
349
|
+
leftover = ev.data
|
|
350
|
+
self._sync_host_view()
|
|
351
|
+
return leftover
|
|
352
|
+
|
|
353
|
+
def _parse_host_response(self, data):
|
|
354
|
+
"""按 service wire shape 增量消费 host 响应,返回纯 payload bytes。"""
|
|
355
|
+
events, _complete = self.conversation.feed_response(
|
|
356
|
+
data,
|
|
357
|
+
shell_active=False,
|
|
358
|
+
shell_okay_consumed=True,
|
|
359
|
+
sync_active=False,
|
|
360
|
+
sync_okay_consumed=True,
|
|
361
|
+
svc=self.svc,
|
|
362
|
+
)
|
|
363
|
+
leftover = b""
|
|
364
|
+
seen_status = set()
|
|
365
|
+
for ev in events:
|
|
366
|
+
if ev.kind == "status" and ev.status is not None:
|
|
367
|
+
self.sb.logger.event(f"📤 [Status]: {ev.status}")
|
|
368
|
+
seen_status.add(ev.status)
|
|
369
|
+
elif ev.kind == "payload" and ev.data:
|
|
370
|
+
leftover = (
|
|
371
|
+
leftover + ev.data if leftover else ev.data
|
|
372
|
+
)
|
|
373
|
+
for st in self.conversation._status_events:
|
|
374
|
+
if st not in seen_status:
|
|
375
|
+
self.sb.logger.event(f"📤 [Status]: {st}")
|
|
376
|
+
self.conversation._status_events = []
|
|
377
|
+
self._sync_host_view()
|
|
378
|
+
return leftover
|