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,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()
|