Source code for ws4py.framing

# -*- coding: utf-8 -*-
from struct import pack, unpack

from ws4py.exc import FrameTooLargeException, ProtocolException
from ws4py.compat import py3k, ord, range

# Frame opcodes defined in the spec.
OPCODE_CONTINUATION = 0x0
OPCODE_TEXT = 0x1
OPCODE_BINARY = 0x2
OPCODE_CLOSE = 0x8
OPCODE_PING = 0x9
OPCODE_PONG = 0xa

__all__ = ['Frame']

[docs]class Frame(object): def __init__(self, opcode=None, body=b'', masking_key=None, fin=0, rsv1=0, rsv2=0, rsv3=0): """ Implements the framing protocol as defined by RFC 6455. .. code-block:: python :linenos: >>> test_mask = 'XXXXXX' # perhaps from os.urandom(4) >>> f = Frame(OPCODE_TEXT, 'hello world', masking_key=test_mask, fin=1) >>> bytes = f.build() >>> bytes.encode('hex') '818bbe04e66ad6618a06d1249105cc6882' >>> f = Frame() >>> f.parser.send(bytes[0]) 1 >>> f.parser.send(bytes[1]) 4 .. seealso:: Data Framing http://tools.ietf.org/html/rfc6455#section-5.2 """ if not isinstance(body, bytes): raise TypeError("The body must be properly encoded") self.opcode = opcode self.body = body self.masking_key = masking_key self.fin = fin self.rsv1 = rsv1 self.rsv2 = rsv2 self.rsv3 = rsv3 self.payload_length = len(body) self._parser = None @property def parser(self): if self._parser is None: self._parser = self._parsing() # Python generators must be initialized once. next(self.parser) return self._parser def _cleanup(self): if self._parser: self._parser.close() self._parser = None
[docs] def build(self): """ Builds a frame from the instance's attributes and returns its bytes representation. """ header = b'' if self.fin > 0x1: raise ValueError('FIN bit parameter must be 0 or 1') if 0x3 <= self.opcode <= 0x7 or 0xB <= self.opcode: raise ValueError('Opcode cannot be a reserved opcode') ## +-+-+-+-+-------+ ## |F|R|R|R| opcode| ## |I|S|S|S| (4) | ## |N|V|V|V| | ## | |1|2|3| | ## +-+-+-+-+-------+ header = pack('!B', ((self.fin << 7) | (self.rsv1 << 6) | (self.rsv2 << 5) | (self.rsv3 << 4) | self.opcode)) ## +-+-------------+-------------------------------+ ## |M| Payload len | Extended payload length | ## |A| (7) | (16/63) | ## |S| | (if payload len==126/127) | ## |K| | | ## +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + ## | Extended payload length continued, if payload len == 127 | ## + - - - - - - - - - - - - - - - +-------------------------------+ if self.masking_key: mask_bit = 1 << 7 else: mask_bit = 0 length = self.payload_length if length < 126: header += pack('!B', (mask_bit | length)) elif length < (1 << 16): header += pack('!B', (mask_bit | 126)) + pack('!H', length) elif length < (1 << 63): header += pack('!B', (mask_bit | 127)) + pack('!Q', length) else: raise FrameTooLargeException() ## + - - - - - - - - - - - - - - - +-------------------------------+ ## | |Masking-key, if MASK set to 1 | ## +-------------------------------+-------------------------------+ ## | Masking-key (continued) | Payload Data | ## +-------------------------------- - - - - - - - - - - - - - - - + ## : Payload Data continued ... : ## + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ## | Payload Data continued ... | ## +---------------------------------------------------------------+ body = self.body if not self.masking_key: return bytes(header + body)
return bytes(header + self.masking_key + self.mask(body)) def _parsing(self): """ Generator to parse bytes into a frame. Yields until enough bytes have been read or an error is met. """ buf = b'' some_bytes = b'' # yield until we get the first header's byte while not some_bytes: some_bytes = (yield 1) first_byte = some_bytes[0] if isinstance(some_bytes, bytearray) else ord(some_bytes[0]) # frame-fin = %x0 ; more frames of this message follow # / %x1 ; final frame of this message self.fin = (first_byte >> 7) & 1 self.rsv1 = (first_byte >> 6) & 1 self.rsv2 = (first_byte >> 5) & 1 self.rsv3 = (first_byte >> 4) & 1 self.opcode = first_byte & 0xf # frame-rsv1 = %x0 ; 1 bit, MUST be 0 unless negotiated otherwise # frame-rsv2 = %x0 ; 1 bit, MUST be 0 unless negotiated otherwise # frame-rsv3 = %x0 ; 1 bit, MUST be 0 unless negotiated otherwise if self.rsv1 or self.rsv2 or self.rsv3: raise ProtocolException() # control frames between 3 and 7 as well as above 0xA are currently reserved if 2 < self.opcode < 8 or self.opcode > 0xA: raise ProtocolException() # control frames cannot be fragmented if self.opcode > 0x7 and self.fin == 0: raise ProtocolException() # do we already have enough some_bytes to continue? some_bytes = some_bytes[1:] if some_bytes and len(some_bytes) > 1 else b'' # Yield until we get the second header's byte while not some_bytes: some_bytes = (yield 1) second_byte = some_bytes[0] if isinstance(some_bytes, bytearray) else ord(some_bytes[0]) mask = (second_byte >> 7) & 1 self.payload_length = second_byte & 0x7f # All control frames MUST have a payload length of 125 some_bytes or less if self.opcode > 0x7 and self.payload_length > 125: raise FrameTooLargeException() if some_bytes and len(some_bytes) > 1: buf = some_bytes[1:] some_bytes = buf else: buf = b'' some_bytes = b'' if self.payload_length == 127: # This will compute the actual application data size if len(buf) < 8: nxt_buf_size = 8 - len(buf) some_bytes = (yield nxt_buf_size) some_bytes = buf + (some_bytes or b'') while len(some_bytes) < 8: b = (yield 8 - len(some_bytes)) if b is not None: some_bytes = some_bytes + b if len(some_bytes) > 8: buf = some_bytes[8:] some_bytes = some_bytes[:8] else: some_bytes = buf[:8] buf = buf[8:] extended_payload_length = some_bytes self.payload_length = unpack( '!Q', extended_payload_length)[0] if self.payload_length > 0x7FFFFFFFFFFFFFFF: raise FrameTooLargeException() elif self.payload_length == 126: if len(buf) < 2: nxt_buf_size = 2 - len(buf) some_bytes = (yield nxt_buf_size) some_bytes = buf + (some_bytes or b'') while len(some_bytes) < 2: b = (yield 2 - len(some_bytes)) if b is not None: some_bytes = some_bytes + b if len(some_bytes) > 2: buf = some_bytes[2:] some_bytes = some_bytes[:2] else: some_bytes = buf[:2] buf = buf[2:] extended_payload_length = some_bytes self.payload_length = unpack( '!H', extended_payload_length)[0] if mask: if len(buf) < 4: nxt_buf_size = 4 - len(buf) some_bytes = (yield nxt_buf_size) some_bytes = buf + (some_bytes or b'') while not some_bytes or len(some_bytes) < 4: b = (yield 4 - len(some_bytes)) if b is not None: some_bytes = some_bytes + b if len(some_bytes) > 4: buf = some_bytes[4:] else: some_bytes = buf[:4] buf = buf[4:] self.masking_key = some_bytes if len(buf) < self.payload_length: nxt_buf_size = self.payload_length - len(buf) some_bytes = (yield nxt_buf_size) some_bytes = buf + (some_bytes or b'') while len(some_bytes) < self.payload_length: l = self.payload_length - len(some_bytes) b = (yield l) if b is not None: some_bytes = some_bytes + b else: if self.payload_length == len(buf): some_bytes = buf else: some_bytes = buf[:self.payload_length] self.body = some_bytes yield
[docs] def mask(self, data): """ Performs the masking or unmasking operation on data using the simple masking algorithm: .. j = i MOD 4 transformed-octet-i = original-octet-i XOR masking-key-octet-j """ masked = bytearray(data) if py3k: key = self.masking_key else: key = map(ord, self.masking_key) for i in range(len(data)): masked[i] = masked[i] ^ key[i%4]
return masked
unmask = mask