-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathws.py
More file actions
76 lines (59 loc) · 1.8 KB
/
ws.py
File metadata and controls
76 lines (59 loc) · 1.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""
Minimal WebSocket frame encoder / decoder (RFC 6455).
Only handles binary (opcode 0x02) and close (opcode 0x08) frames.
Client-to-server frames are always masked as required by the spec.
"""
import os
import struct
def ws_encode(data: bytes, opcode: int = 0x02) -> bytes:
"""Encode *data* into a masked binary WebSocket frame."""
head = bytearray([0x80 | opcode]) # FIN + opcode
length = len(data)
if length < 126:
head.append(0x80 | length)
elif length < 0x10000:
head.append(0x80 | 126)
head += struct.pack("!H", length)
else:
head.append(0x80 | 127)
head += struct.pack("!Q", length)
mask = os.urandom(4)
head += mask
masked = bytearray(data)
for i in range(len(masked)):
masked[i] ^= mask[i & 3]
return bytes(head) + bytes(masked)
def ws_decode(buf: bytes):
"""Try to decode one frame from *buf*.
Returns ``(opcode, payload, consumed_bytes)`` or ``None`` if the
buffer does not yet contain a complete frame.
"""
if len(buf) < 2:
return None
opcode = buf[0] & 0x0F
is_masked = buf[1] & 0x80
length = buf[1] & 0x7F
pos = 2
if length == 126:
if len(buf) < 4:
return None
length = struct.unpack("!H", buf[2:4])[0]
pos = 4
elif length == 127:
if len(buf) < 10:
return None
length = struct.unpack("!Q", buf[2:10])[0]
pos = 10
mask = None
if is_masked:
if len(buf) < pos + 4:
return None
mask = buf[pos : pos + 4]
pos += 4
if len(buf) < pos + length:
return None
payload = bytearray(buf[pos : pos + length])
if mask:
for i in range(len(payload)):
payload[i] ^= mask[i & 3]
return opcode, bytes(payload), pos + length