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,242 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
|
|
3
|
+
from adbproxy.protocols.smart_socket.handshake import SmartSocketHandshake
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AdbClientBackend:
|
|
7
|
+
"""per-stream 5037 后端:经真实 adb server 转发到 USB/网络设备。
|
|
8
|
+
文本协议:host:transport:<serial>(OKAY 无 body)+ <hex4><service>(OKAY/FAIL+流)。
|
|
9
|
+
握手由 SmartSocketHandshake 构建帧/解释 status;I/O 仍在本类阻塞 sequencer。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, conn, entry, serial, adb_server_port, logger):
|
|
13
|
+
self.conn = conn
|
|
14
|
+
self.entry = entry
|
|
15
|
+
self.serial = serial
|
|
16
|
+
self.adb_server_port = adb_server_port
|
|
17
|
+
self.logger = logger
|
|
18
|
+
self.sock = None
|
|
19
|
+
self.host_phase = "status"
|
|
20
|
+
self.stage = "tport"
|
|
21
|
+
self.host_status_buf = bytearray()
|
|
22
|
+
self.host_hex_buf = bytearray()
|
|
23
|
+
self.host_payload_remaining = 0
|
|
24
|
+
self._fail_msg_buf = bytearray()
|
|
25
|
+
self._last_host_svc = None
|
|
26
|
+
self._stopped = False
|
|
27
|
+
self.handshake = SmartSocketHandshake()
|
|
28
|
+
|
|
29
|
+
def start(self):
|
|
30
|
+
"""建 TCP + 发 host:transport + 读 tport OKAY + 发 <hex4><service>。
|
|
31
|
+
|
|
32
|
+
关键:host:transport 与 service 两帧必须“发→读 OKAY→发”,不能背靠背连发。
|
|
33
|
+
adb server smart_socket_enqueue 在同一 enqueue 内合并两帧时,处理完首帧
|
|
34
|
+
(SwitchedTransport)会 smart_socket_data.clear() 丢弃第二帧(sockets.cpp:867),
|
|
35
|
+
导致 svc 永不响应、设备流无法回传。真实 adb client 也是先读 tport OKAY 再发
|
|
36
|
+
service(adb_client.cpp 双握手)。
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
self.sock = socket.create_connection(
|
|
40
|
+
("127.0.0.1", self.adb_server_port), timeout=5
|
|
41
|
+
)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
self.logger.event(f"[!] back 连接 adb server 失败: {e}")
|
|
44
|
+
return False
|
|
45
|
+
self.logger.event(
|
|
46
|
+
f"[back] 已连接 adb server :{self.adb_server_port},target serial={self.serial}"
|
|
47
|
+
)
|
|
48
|
+
if not self._send_frame(SmartSocketHandshake.build_transport_frame(self.serial)):
|
|
49
|
+
return False
|
|
50
|
+
tport_st = self._recv_exact(4)
|
|
51
|
+
if tport_st is None:
|
|
52
|
+
self.logger.event("[!] back 读 tport status EOF/异常,关流 CLSE")
|
|
53
|
+
return False
|
|
54
|
+
st = SmartSocketHandshake.interpret_status(tport_st)
|
|
55
|
+
if st.ok:
|
|
56
|
+
self.logger.event("📤 [tport Status]: OKAY")
|
|
57
|
+
elif st.fail:
|
|
58
|
+
self.logger.event("📤 [tport Status]: FAIL")
|
|
59
|
+
self._consume_fail_body(prefix="tport")
|
|
60
|
+
return False
|
|
61
|
+
else:
|
|
62
|
+
self.logger.event(f"[!] back tport status 异常: {st.word!r},关流 CLSE")
|
|
63
|
+
return False
|
|
64
|
+
svc = self.entry.service or ""
|
|
65
|
+
if not self._send_service_frame(svc):
|
|
66
|
+
return False
|
|
67
|
+
svc_st = self._recv_exact(4)
|
|
68
|
+
if svc_st is None:
|
|
69
|
+
self.logger.event("[!] back 读 service status EOF/超时,关流 CLSE")
|
|
70
|
+
return False
|
|
71
|
+
svc_status = SmartSocketHandshake.interpret_status(svc_st)
|
|
72
|
+
if svc_status.ok:
|
|
73
|
+
self.logger.event("📤 [Status]: OKAY")
|
|
74
|
+
elif svc_status.fail:
|
|
75
|
+
self.logger.event("📤 [Status]: FAIL")
|
|
76
|
+
self._consume_fail_body(prefix="svc")
|
|
77
|
+
return False
|
|
78
|
+
else:
|
|
79
|
+
self.logger.event(f"[!] back service status 异常: {svc_status.word!r}")
|
|
80
|
+
return False
|
|
81
|
+
try:
|
|
82
|
+
self.sock.settimeout(None)
|
|
83
|
+
except OSError:
|
|
84
|
+
return False
|
|
85
|
+
# service OKAY 已在握手 worker 中精确消费;后续 recv 从第一字节起就是服务流。
|
|
86
|
+
self.host_phase = "stream"
|
|
87
|
+
self.stage = "stream"
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
def _consume_fail_body(self, prefix: str):
|
|
91
|
+
"""Read FAIL protocol-string body after a FAIL status word (blocking)."""
|
|
92
|
+
hb = self._recv_exact(4)
|
|
93
|
+
plen = 0
|
|
94
|
+
if hb:
|
|
95
|
+
decoded = SmartSocketHandshake.decode_fail_length(hb)
|
|
96
|
+
plen = decoded if decoded is not None else 0
|
|
97
|
+
fbody = self._recv_exact(plen) if plen else b""
|
|
98
|
+
if fbody:
|
|
99
|
+
label = "tport FAIL body" if prefix == "tport" else "FAIL"
|
|
100
|
+
self.logger.event(
|
|
101
|
+
f"📤 [{label}]: {fbody.decode('utf-8', errors='replace')!r}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def _recv_exact(self, n):
|
|
105
|
+
"""精确读 n 字节(循环拼半包)。EOF/异常返回 None。"""
|
|
106
|
+
buf = bytearray()
|
|
107
|
+
while len(buf) < n:
|
|
108
|
+
try:
|
|
109
|
+
chunk = self.sock.recv(n - len(buf))
|
|
110
|
+
except OSError:
|
|
111
|
+
return None
|
|
112
|
+
if not chunk:
|
|
113
|
+
return None
|
|
114
|
+
buf.extend(chunk)
|
|
115
|
+
return bytes(buf)
|
|
116
|
+
|
|
117
|
+
def _send_frame(self, frame: bytes) -> bool:
|
|
118
|
+
try:
|
|
119
|
+
self.sock.sendall(frame)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
self.logger.event(f"[!] back sendall 失败: {e}")
|
|
122
|
+
return False
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
def _send_service_frame(self, svc):
|
|
126
|
+
try:
|
|
127
|
+
frame = SmartSocketHandshake.build_service_frame(svc)
|
|
128
|
+
except (TypeError, UnicodeError, ValueError) as e:
|
|
129
|
+
self.logger.event(f"[!] back service frame 非法: {e}")
|
|
130
|
+
return False
|
|
131
|
+
return self._send_frame(frame)
|
|
132
|
+
|
|
133
|
+
def run(self):
|
|
134
|
+
try:
|
|
135
|
+
while not self._stopped:
|
|
136
|
+
try:
|
|
137
|
+
data = self.sock.recv(65536)
|
|
138
|
+
except OSError:
|
|
139
|
+
break
|
|
140
|
+
if not data:
|
|
141
|
+
break
|
|
142
|
+
self._on_back_data(data)
|
|
143
|
+
finally:
|
|
144
|
+
self.conn._back_eof(self.entry)
|
|
145
|
+
|
|
146
|
+
def stop(self):
|
|
147
|
+
self._stopped = True
|
|
148
|
+
try:
|
|
149
|
+
self.sock.shutdown(socket.SHUT_RDWR)
|
|
150
|
+
except Exception:
|
|
151
|
+
pass
|
|
152
|
+
try:
|
|
153
|
+
self.sock.close()
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
def _on_back_data(self, data):
|
|
158
|
+
pos = 0
|
|
159
|
+
n = len(data)
|
|
160
|
+
out = bytearray()
|
|
161
|
+
while pos < n:
|
|
162
|
+
if self.host_phase == "status":
|
|
163
|
+
need = 4 - len(self.host_status_buf)
|
|
164
|
+
take = min(need, n - pos)
|
|
165
|
+
self.host_status_buf.extend(data[pos : pos + take])
|
|
166
|
+
pos += take
|
|
167
|
+
if len(self.host_status_buf) < 4:
|
|
168
|
+
break
|
|
169
|
+
st = bytes(self.host_status_buf).decode("ascii", errors="ignore")
|
|
170
|
+
self.host_status_buf = bytearray()
|
|
171
|
+
if st == "OKAY":
|
|
172
|
+
if self.stage == "tport":
|
|
173
|
+
self.logger.event("📤 [tport Status]: OKAY")
|
|
174
|
+
self.stage = "svc"
|
|
175
|
+
self.host_phase = "status"
|
|
176
|
+
else:
|
|
177
|
+
self.logger.event("📤 [Status]: OKAY")
|
|
178
|
+
self.stage = "stream"
|
|
179
|
+
self.host_phase = "stream"
|
|
180
|
+
elif st == "FAIL":
|
|
181
|
+
self.host_phase = "hex"
|
|
182
|
+
else:
|
|
183
|
+
out.extend(st.encode("ascii", errors="ignore"))
|
|
184
|
+
self.host_phase = "stream"
|
|
185
|
+
self.stage = "stream"
|
|
186
|
+
elif self.host_phase == "hex":
|
|
187
|
+
need = 4 - len(self.host_hex_buf)
|
|
188
|
+
take = min(need, n - pos)
|
|
189
|
+
self.host_hex_buf.extend(data[pos : pos + take])
|
|
190
|
+
pos += take
|
|
191
|
+
if len(self.host_hex_buf) < 4:
|
|
192
|
+
break
|
|
193
|
+
hx = bytes(self.host_hex_buf).decode("ascii", errors="ignore")
|
|
194
|
+
self.host_hex_buf = bytearray()
|
|
195
|
+
plen = SmartSocketHandshake.decode_fail_length(
|
|
196
|
+
hx.encode("ascii") if len(hx) == 4 else b""
|
|
197
|
+
)
|
|
198
|
+
if plen is None:
|
|
199
|
+
# invalid hex — original used int(hx,16) ValueError path
|
|
200
|
+
try:
|
|
201
|
+
plen = int(hx, 16)
|
|
202
|
+
except ValueError:
|
|
203
|
+
out.extend(hx.encode("ascii", errors="ignore"))
|
|
204
|
+
self.host_phase = "stream"
|
|
205
|
+
self.stage = "stream"
|
|
206
|
+
continue
|
|
207
|
+
self.host_payload_remaining = plen
|
|
208
|
+
if self.host_payload_remaining == 0:
|
|
209
|
+
self._on_fail_msg(b"")
|
|
210
|
+
self._stopped = True
|
|
211
|
+
break
|
|
212
|
+
else:
|
|
213
|
+
self.host_phase = "payload"
|
|
214
|
+
elif self.host_phase == "payload":
|
|
215
|
+
take = min(self.host_payload_remaining, n - pos)
|
|
216
|
+
chunk = data[pos : pos + take]
|
|
217
|
+
pos += take
|
|
218
|
+
self.host_payload_remaining -= take
|
|
219
|
+
self._fail_msg_buf.extend(chunk)
|
|
220
|
+
if self.host_payload_remaining == 0:
|
|
221
|
+
self._on_fail_msg(bytes(self._fail_msg_buf))
|
|
222
|
+
self._fail_msg_buf = bytearray()
|
|
223
|
+
self._stopped = True
|
|
224
|
+
break
|
|
225
|
+
else:
|
|
226
|
+
out.extend(data[pos:])
|
|
227
|
+
pos = n
|
|
228
|
+
if out and not self._stopped:
|
|
229
|
+
self.conn._front_wrte_from_back(self.entry, bytes(out))
|
|
230
|
+
|
|
231
|
+
def _on_fail_msg(self, msg):
|
|
232
|
+
"""FAIL body 处理:打日志 +(非 shell/sync 激活时)喂 entry.sb.add_server + front CLSE。"""
|
|
233
|
+
self.logger.event("📤 [Status]: FAIL")
|
|
234
|
+
msg_text = msg.decode("utf-8", errors="replace")
|
|
235
|
+
self.logger.event(f"📤 [FAIL body]: {msg_text!r}")
|
|
236
|
+
sb = self.entry.sb
|
|
237
|
+
if sb is not None and not (sb.shell.active or sb.sync.active):
|
|
238
|
+
try:
|
|
239
|
+
sb.add_server(msg)
|
|
240
|
+
except Exception:
|
|
241
|
+
pass
|
|
242
|
+
self.conn._front_clse(self.entry)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Pure CNXN banner helpers (no socket I/O).
|
|
2
|
+
|
|
3
|
+
App retains adb-server queries for features/product fields; session only
|
|
4
|
+
assembles the reply string via ``build_cnxn_banner``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_cnxn_banner(
|
|
11
|
+
*,
|
|
12
|
+
banner_type: str,
|
|
13
|
+
features: str,
|
|
14
|
+
product=None,
|
|
15
|
+
model=None,
|
|
16
|
+
device=None,
|
|
17
|
+
) -> str:
|
|
18
|
+
"""Build the local CNXN banner body (type::props;features=...).
|
|
19
|
+
|
|
20
|
+
When product/model/device are all present, emit full ro.product.* fields.
|
|
21
|
+
Otherwise fall back to features-only + ro.product.name=client-mock.
|
|
22
|
+
"""
|
|
23
|
+
if product is not None and model is not None and device is not None:
|
|
24
|
+
return (
|
|
25
|
+
f"{banner_type}::ro.product.name={product}"
|
|
26
|
+
f";ro.product.model={model}"
|
|
27
|
+
f";ro.product.device={device}"
|
|
28
|
+
f";features={features}"
|
|
29
|
+
)
|
|
30
|
+
return (
|
|
31
|
+
f"{banner_type}::features={features}"
|
|
32
|
+
f";ro.product.name=client-mock"
|
|
33
|
+
)
|