295 lines
8.7 KiB
Python
295 lines
8.7 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__ = [
|
|
'DATA_OPCODES', 'CTRL_OPCODES',
|
|
'OP_CONT', 'OP_TEXT', 'OP_BINARY', 'OP_CLOSE', 'OP_PING', 'OP_PONG',
|
|
'Frame', 'encode_data', 'parse_close', 'serialize_close'
|
|
]
|
|
|
|
DATA_OPCODES = OP_CONT, OP_TEXT, OP_BINARY = 0x00, 0x01, 0x02
|
|
CTRL_OPCODES = OP_CLOSE, OP_PING, OP_PONG = 0x08, 0x09, 0x0a
|
|
|
|
# Close code that are allowed in a close frame.
|
|
# Using a list optimizes `code in EXTERNAL_CLOSE_CODES`.
|
|
EXTERNAL_CLOSE_CODES = [
|
|
1000,
|
|
1001,
|
|
1002,
|
|
1003,
|
|
1007,
|
|
1008,
|
|
1009,
|
|
1010,
|
|
1011,
|
|
]
|
|
|
|
FrameData = collections.namedtuple(
|
|
'FrameData',
|
|
['fin', 'opcode', 'data', 'rsv1', 'rsv2', 'rsv3'],
|
|
)
|
|
|
|
|
|
class Frame(FrameData):
|
|
"""
|
|
WebSocket frame.
|
|
|
|
* ``fin`` is the FIN bit
|
|
* ``rsv1`` is the RSV1 bit
|
|
* ``rsv2`` is the RSV2 bit
|
|
* ``rsv3`` is the RSV3 bit
|
|
* ``opcode`` is the opcode
|
|
* ``data`` is the payload data
|
|
|
|
Only these fields are needed by higher level code. The MASK bit, payload
|
|
length and masking-key are handled on the fly by :meth:`read` and
|
|
:meth:`write`.
|
|
|
|
"""
|
|
def __new__(cls, fin, opcode, data, rsv1=False, rsv2=False, rsv3=False):
|
|
return FrameData.__new__(cls, fin, opcode, data, rsv1, rsv2, rsv3)
|
|
|
|
@classmethod
|
|
@asyncio.coroutine
|
|
def read(cls, reader, *, mask, max_size=None, extensions=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.
|
|
|
|
If ``extensions`` is provided, it's a list of classes with an
|
|
``decode()`` method that transform the frame and return a new frame.
|
|
They are applied in reverse order.
|
|
|
|
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)
|
|
|
|
# While not Pythonic, this is marginally faster than calling bool().
|
|
fin = True if head1 & 0b10000000 else False
|
|
rsv1 = True if head1 & 0b01000000 else False
|
|
rsv2 = True if head1 & 0b00100000 else False
|
|
rsv3 = True if head1 & 0b00010000 else False
|
|
opcode = head1 & 0b00001111
|
|
|
|
if (True if head2 & 0b10000000 else False) != 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 length exceeds size 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 = cls(fin, opcode, data, rsv1, rsv2, rsv3)
|
|
|
|
if extensions is None:
|
|
extensions = []
|
|
for extension in reversed(extensions):
|
|
frame = extension.decode(frame, max_size=max_size)
|
|
|
|
frame.check()
|
|
|
|
return frame
|
|
|
|
def write(frame, writer, *, mask, extensions=None):
|
|
"""
|
|
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.
|
|
|
|
If ``extensions`` is provided, it's a list of classes with an
|
|
``encode()`` method that transform the frame and return a new frame.
|
|
They are applied in order.
|
|
|
|
This function validates the frame before sending it and raises
|
|
:exc:`~websockets.exceptions.WebSocketProtocolError` if it contains
|
|
incorrect values.
|
|
|
|
"""
|
|
# The first parameter is called `frame` rather than `self`,
|
|
# but it's the instance of class to which this method is bound.
|
|
|
|
frame.check()
|
|
|
|
if extensions is None:
|
|
extensions = []
|
|
for extension in extensions:
|
|
frame = extension.encode(frame)
|
|
|
|
output = io.BytesIO()
|
|
|
|
# Prepare the header.
|
|
head1 = (
|
|
(0b10000000 if frame.fin else 0) |
|
|
(0b01000000 if frame.rsv1 else 0) |
|
|
(0b00100000 if frame.rsv2 else 0) |
|
|
(0b00010000 if frame.rsv3 else 0) |
|
|
frame.opcode
|
|
)
|
|
|
|
head2 = 0b10000000 if mask else 0
|
|
|
|
length = len(frame.data)
|
|
if length < 126:
|
|
output.write(struct.pack('!BB', head1, head2 | length))
|
|
elif length < 65536:
|
|
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.
|
|
|
|
# The frame is written in a single call to writer in order to prevent
|
|
# TCP fragmentation. See #68 for details. This also makes it safe to
|
|
# send frames concurrently from multiple coroutines.
|
|
writer(output.getvalue())
|
|
|
|
def check(frame):
|
|
"""
|
|
Check that this frame contains acceptable values.
|
|
|
|
Raise :exc:`~websockets.exceptions.WebSocketProtocolError` if this
|
|
frame contains incorrect values.
|
|
|
|
"""
|
|
# The first parameter is called `frame` rather than `self`,
|
|
# but it's the instance of class to which this method is bound.
|
|
|
|
if frame.rsv1 or frame.rsv2 or frame.rsv3:
|
|
raise WebSocketProtocolError("Reserved bits must be 0")
|
|
|
|
if frame.opcode in DATA_OPCODES:
|
|
return
|
|
elif frame.opcode in CTRL_OPCODES:
|
|
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: {}".format(frame.opcode))
|
|
|
|
|
|
def encode_data(data):
|
|
"""
|
|
Helper that converts :class:`str` or :class:`bytes` to :class:`bytes`.
|
|
|
|
:class:`str` are encoded with UTF-8.
|
|
|
|
"""
|
|
# Expect str or bytes, return bytes.
|
|
if isinstance(data, str):
|
|
return data.encode('utf-8')
|
|
elif isinstance(data, bytes):
|
|
return data
|
|
else:
|
|
raise TypeError("data must be bytes or str")
|
|
|
|
|
|
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 >= 2:
|
|
code, = struct.unpack('!H', data[:2])
|
|
check_close(code)
|
|
reason = data[2:].decode('utf-8')
|
|
return code, reason
|
|
elif length == 0:
|
|
return 1005, ''
|
|
else:
|
|
assert length == 1
|
|
raise WebSocketProtocolError("Close frame too short")
|
|
|
|
|
|
def serialize_close(code, reason):
|
|
"""
|
|
Serialize the data for a close frame.
|
|
|
|
This is the reverse of :func:`parse_close`.
|
|
|
|
"""
|
|
check_close(code)
|
|
return struct.pack('!H', code) + reason.encode('utf-8')
|
|
|
|
|
|
def check_close(code):
|
|
"""
|
|
Check the close code for a close frame.
|
|
|
|
"""
|
|
if not (code in EXTERNAL_CLOSE_CODES or 3000 <= code < 5000):
|
|
raise WebSocketProtocolError("Invalid status code")
|