mirror of https://github.com/sgoudham/Enso-Bot.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
5.9 KiB
Python
211 lines
5.9 KiB
Python
"""
|
|
The :mod:`websockets.framing` module implements data framing as specified in
|
|
`section 5 of RFC 6455`_.
|
|
|
|
It deals with a single frame at a time. Anything that depends on the sequence
|
|
of frames is implemented in :mod:`websockets.protocol`.
|
|
|
|
.. _section 5 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-5
|
|
|
|
"""
|
|
|
|
import asyncio
|
|
import collections
|
|
import io
|
|
import random
|
|
import struct
|
|
|
|
from .exceptions import PayloadTooBig, WebSocketProtocolError
|
|
|
|
|
|
try:
|
|
from .speedups import apply_mask
|
|
except ImportError: # pragma: no cover
|
|
from .utils import apply_mask
|
|
|
|
|
|
__all__ = [
|
|
'OP_CONT', 'OP_TEXT', 'OP_BINARY', 'OP_CLOSE', 'OP_PING', 'OP_PONG',
|
|
'Frame', 'read_frame', 'write_frame', 'parse_close', 'serialize_close'
|
|
]
|
|
|
|
OP_CONT, OP_TEXT, OP_BINARY = range(0x00, 0x03)
|
|
OP_CLOSE, OP_PING, OP_PONG = range(0x08, 0x0b)
|
|
|
|
CLOSE_CODES = {
|
|
1000: "OK",
|
|
1001: "going away",
|
|
1002: "protocol error",
|
|
1003: "unsupported type",
|
|
# 1004: - (reserved)
|
|
# 1005: no status code (internal)
|
|
# 1006: connection closed abnormally (internal)
|
|
1007: "invalid data",
|
|
1008: "policy violation",
|
|
1009: "message too big",
|
|
1010: "extension required",
|
|
1011: "unexpected error",
|
|
# 1015: TLS failure (internal)
|
|
}
|
|
|
|
|
|
Frame = collections.namedtuple('Frame', ('fin', 'opcode', 'data'))
|
|
Frame.__doc__ = """WebSocket frame.
|
|
|
|
* ``fin`` is the FIN bit
|
|
* ``opcode`` is the opcode
|
|
* ``data`` is the payload data
|
|
|
|
Only these three fields are needed by higher level code. The MASK bit, payload
|
|
length and masking-key are handled on the fly by :func:`read_frame` and
|
|
:func:`write_frame`.
|
|
|
|
"""
|
|
|
|
|
|
@asyncio.coroutine
|
|
def read_frame(reader, mask, *, max_size=None):
|
|
"""
|
|
Read a WebSocket frame and return a :class:`Frame` object.
|
|
|
|
``reader`` is a coroutine taking an integer argument and reading exactly
|
|
this number of bytes, unless the end of file is reached.
|
|
|
|
``mask`` is a :class:`bool` telling whether the frame should be masked
|
|
i.e. whether the read happens on the server side.
|
|
|
|
If ``max_size`` is set and the payload exceeds this size in bytes,
|
|
:exc:`~websockets.exceptions.PayloadTooBig` is raised.
|
|
|
|
This function validates the frame before returning it and raises
|
|
:exc:`~websockets.exceptions.WebSocketProtocolError` if it contains
|
|
incorrect values.
|
|
|
|
"""
|
|
# Read the header
|
|
data = yield from reader(2)
|
|
head1, head2 = struct.unpack('!BB', data)
|
|
fin = bool(head1 & 0b10000000)
|
|
if head1 & 0b01110000:
|
|
raise WebSocketProtocolError("Reserved bits must be 0")
|
|
opcode = head1 & 0b00001111
|
|
if bool(head2 & 0b10000000) != mask:
|
|
raise WebSocketProtocolError("Incorrect masking")
|
|
length = head2 & 0b01111111
|
|
if length == 126:
|
|
data = yield from reader(2)
|
|
length, = struct.unpack('!H', data)
|
|
elif length == 127:
|
|
data = yield from reader(8)
|
|
length, = struct.unpack('!Q', data)
|
|
if max_size is not None and length > max_size:
|
|
raise PayloadTooBig("Payload exceeds limit "
|
|
"({} > {} bytes)".format(length, max_size))
|
|
if mask:
|
|
mask_bits = yield from reader(4)
|
|
|
|
# Read the data
|
|
data = yield from reader(length)
|
|
if mask:
|
|
data = apply_mask(data, mask_bits)
|
|
|
|
frame = Frame(fin, opcode, data)
|
|
check_frame(frame)
|
|
return frame
|
|
|
|
|
|
def write_frame(frame, writer, mask):
|
|
"""
|
|
Write a WebSocket frame.
|
|
|
|
``frame`` is the :class:`Frame` object to write.
|
|
|
|
``writer`` is a function accepting bytes.
|
|
|
|
``mask`` is a :class:`bool` telling whether the frame should be masked
|
|
i.e. whether the write happens on the client side.
|
|
|
|
This function validates the frame before sending it and raises
|
|
:exc:`~websockets.exceptions.WebSocketProtocolError` if it contains
|
|
incorrect values.
|
|
|
|
"""
|
|
check_frame(frame)
|
|
output = io.BytesIO()
|
|
|
|
# Prepare the header
|
|
head1 = 0b10000000 if frame.fin else 0
|
|
head1 |= frame.opcode
|
|
head2 = 0b10000000 if mask else 0
|
|
length = len(frame.data)
|
|
if length < 0x7e:
|
|
output.write(struct.pack('!BB', head1, head2 | length))
|
|
elif length < 0x10000:
|
|
output.write(struct.pack('!BBH', head1, head2 | 126, length))
|
|
else:
|
|
output.write(struct.pack('!BBQ', head1, head2 | 127, length))
|
|
if mask:
|
|
mask_bits = struct.pack('!I', random.getrandbits(32))
|
|
output.write(mask_bits)
|
|
|
|
# Prepare the data
|
|
if mask:
|
|
data = apply_mask(frame.data, mask_bits)
|
|
else:
|
|
data = frame.data
|
|
output.write(data)
|
|
|
|
# Send the frame
|
|
writer(output.getvalue())
|
|
|
|
|
|
def check_frame(frame):
|
|
"""
|
|
Raise :exc:`~websockets.exceptions.WebSocketProtocolError` if the frame
|
|
contains incorrect values.
|
|
|
|
"""
|
|
if frame.opcode in (OP_CONT, OP_TEXT, OP_BINARY):
|
|
return
|
|
elif frame.opcode in (OP_CLOSE, OP_PING, OP_PONG):
|
|
if len(frame.data) > 125:
|
|
raise WebSocketProtocolError("Control frame too long")
|
|
if not frame.fin:
|
|
raise WebSocketProtocolError("Fragmented control frame")
|
|
else:
|
|
raise WebSocketProtocolError("Invalid opcode")
|
|
|
|
|
|
def parse_close(data):
|
|
"""
|
|
Parse the data in a close frame.
|
|
|
|
Return ``(code, reason)`` when ``code`` is an :class:`int` and ``reason``
|
|
a :class:`str`.
|
|
|
|
Raise :exc:`~websockets.exceptions.WebSocketProtocolError` or
|
|
:exc:`UnicodeDecodeError` if the data is invalid.
|
|
|
|
"""
|
|
length = len(data)
|
|
if length == 0:
|
|
return 1005, ''
|
|
elif length == 1:
|
|
raise WebSocketProtocolError("Close frame too short")
|
|
else:
|
|
code, = struct.unpack('!H', data[:2])
|
|
if not (code in CLOSE_CODES or 3000 <= code < 5000):
|
|
raise WebSocketProtocolError("Invalid status code")
|
|
reason = data[2:].decode('utf-8')
|
|
return code, reason
|
|
|
|
|
|
def serialize_close(code, reason):
|
|
"""
|
|
Serialize the data for a close frame.
|
|
|
|
This is the reverse of :func:`parse_close`.
|
|
|
|
"""
|
|
return struct.pack('!H', code) + reason.encode('utf-8')
|