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.
Enso-Bot/venv/Lib/site-packages/websockets/framing.py

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')