# 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']

    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

        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

        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