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,505 @@
1
+ import socket
2
+ import threading
3
+
4
+ from adbproxy.observers.stream import StreamBuffer
5
+ from adbproxy.protocols.adb import AdbProtocol
6
+ from adbproxy.protocols.transport import (
7
+ A_AUTH,
8
+ A_CLSE,
9
+ A_CNXN,
10
+ A_OKAY,
11
+ A_OPEN,
12
+ A_STLS,
13
+ A_WRTE,
14
+ MAX_SEND_QUEUE,
15
+ MAX_PAYLOAD_V1,
16
+ TransportCodec,
17
+ TransportProtocolError,
18
+ )
19
+ from adbproxy.runtime.logging import Logger
20
+
21
+ from .backend import AdbClientBackend
22
+ from .session import (
23
+ AcceptOpen,
24
+ AttachObserver,
25
+ BackendFailed,
26
+ BackendReady,
27
+ CloseStream,
28
+ ForwardToBackend,
29
+ Ignore,
30
+ LogEvent,
31
+ MarkTransportTerminal,
32
+ NegotiateCodec,
33
+ OpenBackend,
34
+ QueueFromBackend,
35
+ RejectOpen,
36
+ ResetStreamsForCnxn,
37
+ SendPacket,
38
+ SetCanSend,
39
+ AdbdSession,
40
+ )
41
+ from .stream_table import StreamEntry, StreamTable, _StreamEntry
42
+
43
+ # re-export for tests: from adbproxy.device.connection import _StreamEntry
44
+ __all__ = ["TransportConnection", "_StreamEntry", "StreamEntry"]
45
+
46
+
47
+ _CONN_ID_LOCK = threading.Lock()
48
+ _CONN_ID_SEQ = 0
49
+
50
+
51
+ def _next_conn_id():
52
+ global _CONN_ID_SEQ
53
+ with _CONN_ID_LOCK:
54
+ _CONN_ID_SEQ += 1
55
+ return _CONN_ID_SEQ
56
+
57
+
58
+ class TransportConnection:
59
+ """per-conn front 传输状态机:CNXN 握手 + OPEN/OKAY/WRTE/CLSE 流表 + id 重映射
60
+ + v1 停等流控 + CLSE 幂等 + service 派发。
61
+
62
+ Decision layer: ``AdbdSession`` (Track 2b). This class executes actions and
63
+ owns I/O / threads / ``_try_flush`` (algorithm unchanged).
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ conn_id,
69
+ front_sock,
70
+ file_logger,
71
+ capture_enabled,
72
+ debug_logger,
73
+ serial,
74
+ adb_server_port,
75
+ banner_features,
76
+ banner_type,
77
+ banner_product,
78
+ banner_model,
79
+ banner_device,
80
+ ):
81
+ self.conn_id = conn_id
82
+ self.front = front_sock
83
+ self.logger = Logger(conn_id, file_logger)
84
+ self.file_logger = file_logger
85
+ self.capture_enabled = capture_enabled
86
+ self.codec = TransportCodec()
87
+ self.debug_logger = debug_logger
88
+ self.serial = serial
89
+ self.adb_server_port = adb_server_port
90
+ self.banner_features = banner_features
91
+ self.banner_type = banner_type
92
+ self.banner_product = banner_product
93
+ self.banner_model = banner_model
94
+ self.banner_device = banner_device
95
+ self.stream_table = StreamTable()
96
+ # Compat views used by tests / existing methods:
97
+ self.streams = self.stream_table.streams
98
+ self._streams_lock = self.stream_table.lock
99
+ self.session = AdbdSession(
100
+ self.stream_table,
101
+ banner_features=banner_features,
102
+ banner_type=banner_type,
103
+ banner_product=banner_product,
104
+ banner_model=banner_model,
105
+ banner_device=banner_device,
106
+ peer_maxdata=MAX_PAYLOAD_V1,
107
+ )
108
+ self._front_send_lock = threading.Lock()
109
+ self.peer_maxdata = MAX_PAYLOAD_V1
110
+ self._finished = threading.Event()
111
+ self._finish_lock = threading.Lock()
112
+ self._backend_threads = set()
113
+ self._backend_threads_lock = threading.Lock()
114
+ self._stop = False
115
+
116
+ @property
117
+ def _cnxn_done(self):
118
+ return self.session.cnxn_done
119
+
120
+ @_cnxn_done.setter
121
+ def _cnxn_done(self, value):
122
+ self.session._cnxn_done = bool(value)
123
+
124
+ def run(self):
125
+ try:
126
+ while not self._stop:
127
+ try:
128
+ data = self.front.recv(65536)
129
+ except OSError:
130
+ break
131
+ if not data:
132
+ break
133
+ if self.debug_logger is not None:
134
+ try:
135
+ self.debug_logger.write_hex(self.conn_id, "c", data)
136
+ except Exception:
137
+ pass
138
+ for pkt in self.codec.feed(data):
139
+ self._dispatch(pkt)
140
+ except TransportProtocolError as exc:
141
+ self.logger.event(f"[!] transport 协议错误,终止连接: {exc}")
142
+ except OSError:
143
+ pass
144
+ finally:
145
+ self._finish()
146
+
147
+ def _dispatch(self, apacket):
148
+ actions = self.session.receive(apacket)
149
+ self._execute_actions(actions)
150
+
151
+ def _execute_actions(self, actions):
152
+ for action in actions:
153
+ self._execute_action(action)
154
+
155
+ def _execute_action(self, action):
156
+ if isinstance(action, Ignore):
157
+ return
158
+ if isinstance(action, LogEvent):
159
+ self.logger.event(action.message)
160
+ return
161
+ if isinstance(action, MarkTransportTerminal):
162
+ raise TransportProtocolError(action.reason)
163
+ if isinstance(action, ResetStreamsForCnxn):
164
+ for entry in self.stream_table.snapshot():
165
+ self._close_entry(entry, send_clse=False)
166
+ return
167
+ if isinstance(action, NegotiateCodec):
168
+ self.peer_maxdata = action.peer_maxdata
169
+ self.session.peer_maxdata = action.peer_maxdata
170
+ self.codec.negotiate(action.version, action.peer_maxdata)
171
+ return
172
+ if isinstance(action, SendPacket):
173
+ try:
174
+ self._front_send(
175
+ self.codec.encode(
176
+ action.cmd, action.arg0, action.arg1, action.payload
177
+ )
178
+ )
179
+ except Exception:
180
+ if action.cmd == A_OKAY:
181
+ # backend ready path: fail closed without front OKAY already sent
182
+ pass
183
+ raise
184
+ return
185
+ if isinstance(action, RejectOpen):
186
+ self._front_send(self.codec.encode(A_CLSE, 0, action.peer_local))
187
+ return
188
+ if isinstance(action, AcceptOpen):
189
+ # allocation already done in session; nothing extra
190
+ return
191
+ if isinstance(action, AttachObserver):
192
+ entry = action.entry
193
+ service = action.service
194
+ try:
195
+ entry.sb = StreamBuffer(
196
+ _next_conn_id(), self.file_logger, self.capture_enabled
197
+ )
198
+ entry.sb.logger.event(f"📥 [User Command]: {service}")
199
+ entry.sb.shell.set_mode_from_service(service)
200
+ if AdbProtocol.is_sync_service(service):
201
+ entry.sb.sync.activate()
202
+ if AdbProtocol.is_install_stream_service(service):
203
+ size = AdbProtocol.parse_install_size(service)
204
+ if size is not None:
205
+ entry.sb.install.activate(size, service)
206
+ cap = (
207
+ entry.sb.install.capture_path
208
+ if (
209
+ entry.sb.install.capture_enabled
210
+ and entry.sb.install.capture_path
211
+ )
212
+ else "<disabled>"
213
+ )
214
+ entry.sb.logger.event(
215
+ f"📤 [install push]: remote={service} size={size} local_capture={cap}"
216
+ )
217
+ except Exception:
218
+ if entry.sb is not None:
219
+ try:
220
+ entry.sb.close()
221
+ except Exception:
222
+ pass
223
+ entry.sb = None
224
+ return
225
+ if isinstance(action, OpenBackend):
226
+ entry = action.entry
227
+ observer_logger = entry.sb.logger if entry.sb is not None else self.logger
228
+ back = AdbClientBackend(
229
+ self, entry, self.serial, self.adb_server_port, observer_logger
230
+ )
231
+ entry.back = back
232
+ worker = threading.Thread(
233
+ target=self._backend_worker,
234
+ args=(entry, back, action.generation),
235
+ daemon=True,
236
+ )
237
+ with self._backend_threads_lock:
238
+ self._backend_threads.add(worker)
239
+ worker.start()
240
+ return
241
+ if isinstance(action, SetCanSend):
242
+ entry = action.entry
243
+ with entry.lock:
244
+ entry.can_send = action.value
245
+ if action.flush:
246
+ self._try_flush(entry)
247
+ return
248
+ if isinstance(action, ForwardToBackend):
249
+ entry = action.entry
250
+ if action.also_okay:
251
+ self._front_send(
252
+ self.codec.encode(A_OKAY, entry.local_id, entry.remote_id)
253
+ )
254
+ if entry.sb is not None:
255
+ try:
256
+ entry.sb.add_client(action.payload)
257
+ except Exception:
258
+ pass
259
+ if entry.back is not None and entry.back.sock is not None:
260
+ try:
261
+ entry.back.sock.sendall(action.payload)
262
+ except Exception as e:
263
+ self.logger.event(f"[!] back sendall(wrte) 失败: {e}")
264
+ self._front_clse(entry)
265
+ return
266
+ if isinstance(action, CloseStream):
267
+ self._close_entry(
268
+ action.entry,
269
+ send_clse=action.send_clse,
270
+ close_arg0=action.close_arg0,
271
+ close_arg1=action.close_arg1,
272
+ )
273
+ return
274
+ if isinstance(action, QueueFromBackend):
275
+ self._execute_queue_from_backend(action.entry, action.data)
276
+ return
277
+ self.logger.event(f"[!] unknown TransportAction {type(action)!r}")
278
+
279
+ def _backend_worker(self, entry, back, generation):
280
+ """在独立 worker 中完成后端握手,front dispatch 不等待网络 I/O。"""
281
+ try:
282
+ if not back.start():
283
+ self._backend_failed(entry, generation)
284
+ return
285
+ if not self._backend_ready(entry, generation):
286
+ back.stop()
287
+ return
288
+ back.run()
289
+ finally:
290
+ current = threading.current_thread()
291
+ with self._backend_threads_lock:
292
+ self._backend_threads.discard(current)
293
+
294
+ def _backend_ready(self, entry, generation):
295
+ actions = self.session.on_session_event(BackendReady(entry, generation))
296
+ # Special-case: if only Ignore, return False (stale generation)
297
+ if len(actions) == 1 and isinstance(actions[0], Ignore):
298
+ return False
299
+ try:
300
+ # Execute without double SetCanSend flush before OKAY send order:
301
+ # plan: SendPacket(OKAY) then flush. Session emits SendPacket + SetCanSend.
302
+ for action in actions:
303
+ if isinstance(action, SendPacket):
304
+ try:
305
+ self._front_send(
306
+ self.codec.encode(
307
+ action.cmd, action.arg0, action.arg1, action.payload
308
+ )
309
+ )
310
+ except Exception:
311
+ self._close_entry(entry, send_clse=False)
312
+ return False
313
+ elif isinstance(action, SetCanSend):
314
+ # can_send already set under lock in session; just flush
315
+ if action.flush:
316
+ self._try_flush(action.entry)
317
+ elif isinstance(action, Ignore):
318
+ continue
319
+ else:
320
+ self._execute_action(action)
321
+ except Exception:
322
+ self._close_entry(entry, send_clse=False)
323
+ return False
324
+ return True
325
+
326
+ def _backend_failed(self, entry, generation):
327
+ actions = self.session.on_session_event(BackendFailed(entry, generation))
328
+ self._execute_actions(actions)
329
+
330
+ # Compat wrappers for tests that call handlers directly
331
+ def _handle_cnxn(self, arg0, arg1, payload):
332
+ self._execute_actions(self.session.receive((A_CNXN, arg0, arg1, payload)))
333
+
334
+ def _handle_open(self, arg0, arg1, payload):
335
+ self._execute_actions(self.session.receive((A_OPEN, arg0, arg1, payload)))
336
+
337
+ def _handle_okay(self, arg0, arg1, payload):
338
+ self._execute_actions(self.session.receive((A_OKAY, arg0, arg1, payload)))
339
+
340
+ def _handle_wrte(self, arg0, arg1, payload):
341
+ self._execute_actions(self.session.receive((A_WRTE, arg0, arg1, payload)))
342
+
343
+ def _handle_clse(self, arg0, arg1, payload):
344
+ self._execute_actions(self.session.receive((A_CLSE, arg0, arg1, payload)))
345
+
346
+ def _handle_stls(self, arg0, arg1, payload):
347
+ self._execute_actions(self.session.receive((A_STLS, arg0, arg1, payload)))
348
+
349
+ def _handle_auth(self, arg0, arg1, payload):
350
+ self._execute_actions(self.session.receive((A_AUTH, arg0, arg1, payload)))
351
+
352
+ def _front_wrte_from_back(self, entry, data):
353
+ """back 收到设备流字节:经 QueueFromBackend 专用执行路径入队(旁路观察 + 切块 + 背压)。"""
354
+ self._execute_action(QueueFromBackend(entry=entry, data=data))
355
+
356
+ def _execute_queue_from_backend(self, entry, data):
357
+ """QueueFromBackend 专用路径。字面序与历史 _front_wrte_from_back 一致;不改 _try_flush。"""
358
+ if not data:
359
+ return
360
+ if entry.sb is not None:
361
+ try:
362
+ entry.sb.add_server(data)
363
+ except Exception:
364
+ pass
365
+ maxdata = self.codec.max_payload
366
+ with entry.lock:
367
+ if entry.closed:
368
+ return
369
+ pos = 0
370
+ dlen = len(data)
371
+ while pos < dlen:
372
+ entry.send_queue.append(data[pos : pos + maxdata])
373
+ pos += maxdata
374
+ entry.queued_bytes += dlen
375
+ while entry.queued_bytes > MAX_SEND_QUEUE and not entry.closed:
376
+ entry.flow_cv.wait()
377
+ if entry.closed:
378
+ return
379
+ self._try_flush(entry)
380
+
381
+ def _try_flush(self, entry):
382
+ """v1 停等:can_send 且队列非空时取一条发 WRTE,置 can_send=False;
383
+ 排出 chunk 后唤醒背压等待的 back 线程;后端 EOF 时等队列和在途 WRTE 排空再 CLSE。"""
384
+ chunk = None
385
+ should_close = False
386
+ with entry.lock:
387
+ if entry.closed or not entry.can_send:
388
+ return
389
+ if entry.send_queue:
390
+ chunk = entry.send_queue.popleft()
391
+ entry.queued_bytes -= len(chunk)
392
+ entry.can_send = False
393
+ entry.flow_cv.notify_all()
394
+ elif entry.back_eof:
395
+ should_close = True
396
+ if should_close:
397
+ self._front_clse(entry)
398
+ return
399
+ if chunk is None:
400
+ return
401
+ try:
402
+ self._front_send(
403
+ self.codec.encode(A_WRTE, entry.local_id, entry.remote_id, chunk)
404
+ )
405
+ except Exception as e:
406
+ self.logger.event(f"[!] front sendall(wrte) 失败: {e}")
407
+ self._front_clse(entry)
408
+
409
+ def _back_eof(self, entry):
410
+ """记录后端 EOF;若还有在途或排队 WRTE,延迟到最后一次 OKAY 后再关闭。"""
411
+ with entry.lock:
412
+ if entry.closed:
413
+ return
414
+ entry.back_eof = True
415
+ self._try_flush(entry)
416
+
417
+ def _front_clse(self, entry):
418
+ if entry is None:
419
+ return
420
+ self._close_entry(entry)
421
+
422
+ def _close_entry(self, entry, send_clse=True, close_arg0=None, close_arg1=None):
423
+ """一次性完成 stream 状态、等待者、后端、观察器和 registry 的收尾。"""
424
+ with entry.lock:
425
+ if entry.closed:
426
+ return False
427
+ entry.closed = True
428
+ entry.state = "closed"
429
+ entry.flow_cv.notify_all()
430
+ if send_clse:
431
+ arg0 = entry.local_id if close_arg0 is None else close_arg0
432
+ arg1 = entry.remote_id if close_arg1 is None else close_arg1
433
+ try:
434
+ self._front_send(self.codec.encode(A_CLSE, arg0, arg1))
435
+ except Exception:
436
+ pass
437
+ if entry.back is not None:
438
+ try:
439
+ entry.back.stop()
440
+ except Exception:
441
+ pass
442
+ if entry.sb is not None:
443
+ try:
444
+ entry.sb.close()
445
+ except Exception:
446
+ pass
447
+ self.stream_table.remove_if_same(entry)
448
+ return True
449
+
450
+ def _find_by_local(self, local_id):
451
+ return self.stream_table.find_by_local(local_id)
452
+
453
+ def _find_by_remote(self, remote_id):
454
+ return self.stream_table.find_by_remote(remote_id)
455
+
456
+ def _find_by_pair(self, local_id, remote_id):
457
+ return self.stream_table.find_by_pair(local_id, remote_id)
458
+
459
+ def _front_send(self, raw):
460
+ """front sendall 串行化 + --debug per-apacket 落盘(outgoing = device→program,标 "s")。"""
461
+ with self._front_send_lock:
462
+ self.front.sendall(raw)
463
+ if self.debug_logger is not None:
464
+ try:
465
+ self.debug_logger.write_hex(self.conn_id, "s", raw)
466
+ except Exception:
467
+ pass
468
+
469
+ def stop(self):
470
+ self._stop = True
471
+ try:
472
+ self.front.shutdown(socket.SHUT_RDWR)
473
+ except Exception:
474
+ pass
475
+ self._finish()
476
+
477
+ def wait(self, timeout=None):
478
+ done = self._finished.wait(timeout)
479
+ if not done:
480
+ return False
481
+ current = threading.current_thread()
482
+ with self._backend_threads_lock:
483
+ workers = list(self._backend_threads)
484
+ for worker in workers:
485
+ if worker is not current:
486
+ worker.join(timeout)
487
+ return True
488
+
489
+ def _finish(self):
490
+ with self._finish_lock:
491
+ if self._finished.is_set():
492
+ return
493
+ self._stop = True
494
+ entries = self.stream_table.snapshot()
495
+ for e in entries:
496
+ self._close_entry(e, send_clse=False)
497
+ try:
498
+ self.front.shutdown(socket.SHUT_RDWR)
499
+ except Exception:
500
+ pass
501
+ try:
502
+ self.front.close()
503
+ except Exception:
504
+ pass
505
+ self._finished.set()