borecli 1.0.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.
bore/frames.py ADDED
@@ -0,0 +1,376 @@
1
+ # borecli/bore/frames.py
2
+
3
+ import json
4
+ import logging
5
+ import time
6
+ import uuid
7
+
8
+ from websockets.exceptions import ConnectionClosed
9
+
10
+ from .protocol import (
11
+ PROTOCOL_VERSION,
12
+ MessageType,
13
+ )
14
+
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ # ============================================================
20
+ # Base Frame
21
+ # ============================================================
22
+
23
+
24
+ def make_frame(
25
+ message_type,
26
+ **payload,
27
+ ):
28
+ """
29
+ Create a BoreHook protocol frame.
30
+ """
31
+
32
+ return {
33
+ "protocol": PROTOCOL_VERSION,
34
+ "frame_id": uuid.uuid4().hex,
35
+ "timestamp": int(time.time() * 1000),
36
+ "type": message_type,
37
+ **payload,
38
+ }
39
+
40
+
41
+
42
+ # ============================================================
43
+ # Tunnel Registration
44
+ # ============================================================
45
+
46
+
47
+ def make_tunnel_register(
48
+ *,
49
+ tunnel_id,
50
+ client="borecli",
51
+ version="1.0",
52
+ ):
53
+
54
+ return make_frame(
55
+
56
+ MessageType.TUNNEL_REGISTER,
57
+
58
+ tunnel_id=tunnel_id,
59
+
60
+ client=client,
61
+
62
+ version=version,
63
+
64
+ )
65
+
66
+
67
+
68
+ # ============================================================
69
+ # HTTP Frames
70
+ # ============================================================
71
+
72
+
73
+ def make_http_request(
74
+ **payload,
75
+ ):
76
+
77
+ return make_frame(
78
+
79
+ MessageType.HTTP_REQUEST,
80
+
81
+ **payload,
82
+
83
+ )
84
+
85
+
86
+
87
+
88
+ def make_http_response(
89
+ *,
90
+ request_id,
91
+ status,
92
+ headers,
93
+ body,
94
+ binary=False,
95
+ ):
96
+
97
+ return make_frame(
98
+
99
+ MessageType.HTTP_RESPONSE,
100
+
101
+ request_id=request_id,
102
+
103
+ status=status,
104
+
105
+ headers=headers,
106
+
107
+ body=body,
108
+
109
+ binary=binary,
110
+
111
+ )
112
+
113
+
114
+
115
+
116
+ def make_http_chunk(
117
+ *,
118
+ request_id,
119
+ chunk_index,
120
+ total_chunks,
121
+ body,
122
+ binary=False,
123
+ status=None,
124
+ headers=None,
125
+ ):
126
+
127
+ frame = make_frame(
128
+
129
+ MessageType.HTTP_RESPONSE_CHUNK,
130
+
131
+ request_id=request_id,
132
+
133
+ chunk_index=chunk_index,
134
+
135
+ total_chunks=total_chunks,
136
+
137
+ body=body,
138
+
139
+ binary=binary,
140
+
141
+ )
142
+
143
+
144
+ if status is not None:
145
+
146
+ frame["status"] = status
147
+
148
+
149
+ if headers is not None:
150
+
151
+ frame["headers"] = headers
152
+
153
+
154
+ return frame
155
+
156
+
157
+
158
+ # ============================================================
159
+ # WebSocket Frames
160
+ # ============================================================
161
+
162
+
163
+ def make_ws_connect(
164
+ **payload,
165
+ ):
166
+
167
+ return make_frame(
168
+ MessageType.WS_CONNECT,
169
+ **payload,
170
+ )
171
+
172
+
173
+
174
+ def make_ws_message(
175
+ **payload,
176
+ ):
177
+
178
+ return make_frame(
179
+ MessageType.WS_MESSAGE,
180
+ **payload,
181
+ )
182
+
183
+
184
+
185
+ def make_ws_close(
186
+ **payload,
187
+ ):
188
+
189
+ return make_frame(
190
+ MessageType.WS_CLOSE,
191
+ **payload,
192
+ )
193
+
194
+
195
+
196
+ # ============================================================
197
+ # Heartbeat
198
+ # ============================================================
199
+
200
+
201
+ def make_ping():
202
+
203
+ return make_frame(
204
+ MessageType.PING
205
+ )
206
+
207
+
208
+
209
+ def make_pong():
210
+
211
+ return make_frame(
212
+ MessageType.PONG
213
+ )
214
+
215
+
216
+
217
+ # ============================================================
218
+ # Errors / Metrics
219
+ # ============================================================
220
+
221
+
222
+ def make_error(
223
+ message,
224
+ ):
225
+
226
+ return make_frame(
227
+
228
+ MessageType.ERROR,
229
+
230
+ message=message,
231
+
232
+ )
233
+
234
+
235
+
236
+ def make_metrics(
237
+ **payload,
238
+ ):
239
+
240
+ return make_frame(
241
+
242
+ MessageType.METRICS,
243
+
244
+ **payload,
245
+
246
+ )
247
+
248
+
249
+
250
+ # ============================================================
251
+ # Transport
252
+ # ============================================================
253
+
254
+
255
+ async def send_frame(
256
+ websocket,
257
+ frame,
258
+ ):
259
+
260
+ """
261
+ Send protocol frame.
262
+ """
263
+
264
+ payload = json.dumps(
265
+ frame
266
+ )
267
+
268
+ await websocket.send(
269
+ payload
270
+ )
271
+
272
+
273
+
274
+ async def receive_frame(
275
+ websocket,
276
+ ):
277
+
278
+ """
279
+ Receive one protocol frame.
280
+ """
281
+
282
+ try:
283
+
284
+ message = await websocket.recv()
285
+
286
+
287
+ except ConnectionClosed:
288
+
289
+ logger.warning(
290
+ "WebSocket closed."
291
+ )
292
+
293
+ return None
294
+
295
+
296
+ except Exception:
297
+
298
+ logger.exception(
299
+ "Receive frame failed."
300
+ )
301
+
302
+ return None
303
+
304
+
305
+
306
+ if isinstance(
307
+ message,
308
+ bytes,
309
+ ):
310
+
311
+ message = message.decode(
312
+ "utf-8"
313
+ )
314
+
315
+
316
+
317
+ try:
318
+
319
+ frame = json.loads(
320
+ message
321
+ )
322
+
323
+
324
+ except json.JSONDecodeError:
325
+
326
+ logger.warning(
327
+ "Invalid JSON received:"
328
+ " %s",
329
+ message,
330
+ )
331
+
332
+ return None
333
+
334
+
335
+
336
+ if not isinstance(
337
+ frame,
338
+ dict,
339
+ ):
340
+
341
+ logger.warning(
342
+ "Invalid frame type"
343
+ )
344
+
345
+ return None
346
+
347
+
348
+
349
+ protocol = frame.get(
350
+ "protocol"
351
+ )
352
+
353
+
354
+ if protocol != PROTOCOL_VERSION:
355
+
356
+ logger.warning(
357
+ "Protocol mismatch:"
358
+ " %s",
359
+ protocol,
360
+ )
361
+
362
+ return None
363
+
364
+
365
+
366
+ if "type" not in frame:
367
+
368
+ logger.warning(
369
+ "Frame missing type"
370
+ )
371
+
372
+ return None
373
+
374
+
375
+
376
+ return frame
File without changes