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.
Files changed (49) hide show
  1. adbproxy/__init__.py +2 -0
  2. adbproxy/__main__.py +5 -0
  3. adbproxy/cli/__init__.py +1 -0
  4. adbproxy/cli/device.py +13 -0
  5. adbproxy/cli/main.py +60 -0
  6. adbproxy/cli/server.py +13 -0
  7. adbproxy/config.py +39 -0
  8. adbproxy/device/__init__.py +1 -0
  9. adbproxy/device/app.py +532 -0
  10. adbproxy/device/backend.py +242 -0
  11. adbproxy/device/banner.py +33 -0
  12. adbproxy/device/connection.py +505 -0
  13. adbproxy/device/session.py +304 -0
  14. adbproxy/device/stream_table.py +125 -0
  15. adbproxy/observers/__init__.py +1 -0
  16. adbproxy/observers/events.py +19 -0
  17. adbproxy/observers/install.py +126 -0
  18. adbproxy/observers/shell.py +371 -0
  19. adbproxy/observers/stream.py +94 -0
  20. adbproxy/observers/sync.py +550 -0
  21. adbproxy/protocols/__init__.py +1 -0
  22. adbproxy/protocols/adb.py +66 -0
  23. adbproxy/protocols/payload/__init__.py +17 -0
  24. adbproxy/protocols/payload/install.py +44 -0
  25. adbproxy/protocols/payload/shell_v2.py +28 -0
  26. adbproxy/protocols/payload/sync.py +180 -0
  27. adbproxy/protocols/smart_socket/__init__.py +33 -0
  28. adbproxy/protocols/smart_socket/classifier.py +78 -0
  29. adbproxy/protocols/smart_socket/conversation.py +335 -0
  30. adbproxy/protocols/smart_socket/handshake.py +75 -0
  31. adbproxy/protocols/smart_socket/wire.py +82 -0
  32. adbproxy/protocols/transport/__init__.py +52 -0
  33. adbproxy/protocols/transport/codec.py +90 -0
  34. adbproxy/protocols/transport/constants.py +21 -0
  35. adbproxy/protocols/transport/errors.py +5 -0
  36. adbproxy/runtime/__init__.py +1 -0
  37. adbproxy/runtime/adb_process.py +195 -0
  38. adbproxy/runtime/capture.py +41 -0
  39. adbproxy/runtime/logging.py +113 -0
  40. adbproxy/runtime/sockets.py +14 -0
  41. adbproxy/server/__init__.py +1 -0
  42. adbproxy/server/app.py +221 -0
  43. adbproxy/server/connection.py +378 -0
  44. adbproxy-0.1.0.dist-info/METADATA +254 -0
  45. adbproxy-0.1.0.dist-info/RECORD +49 -0
  46. adbproxy-0.1.0.dist-info/WHEEL +5 -0
  47. adbproxy-0.1.0.dist-info/entry_points.txt +4 -0
  48. adbproxy-0.1.0.dist-info/licenses/LICENSE +21 -0
  49. 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
+ )