示例#1
0
    def __init__(self, side, version=DEFAULT_VERSION):
        if side not in (SERVER, CLIENT):
            raise TypeError("side must be SERVER or CLIENT")

        if not version in VERSIONS:
            raise NotImplementedError()
        self.version = version
        self.frame_queue = []
        self.input_buffer = bytearray()
        self.inflater = Inflater(version)
        self.deflater = Deflater(version)

        if side == SERVER:
            self._stream_id = 2
            # TODO: Add stream_id_peer auto-updating in the _parse_frame() method
            self._stream_id_peer = 1
            self._ping_id = 2
        else:
            self._stream_id = 1
            # TODO: Add stream_id_peer auto-updating in the _parse_frame() method
            self._stream_id_peer = 0
            self._ping_id = 1
        
        self._last_stream_id = self._stream_id
示例#2
0
    def __init__(self, side, version=DEFAULT_VERSION):
        if side not in (SERVER, CLIENT):
            raise TypeError("side must be SERVER or CLIENT")

        if not version in VERSIONS:
            raise NotImplementedError()
        self.version = version
        self.frame_queue = []
        self.input_buffer = bytearray()
        self.inflater = Inflater(version)
        self.deflater = Deflater(version)

        if side == SERVER:
            self._stream_id = 2
            self._ping_id = 2
        else:
            self._stream_id = 1
            self._ping_id = 1
示例#3
0
class Context(object):
    def __init__(self, side, version=DEFAULT_VERSION):
        if side not in (SERVER, CLIENT):
            raise TypeError("side must be SERVER or CLIENT")

        if not version in VERSIONS:
            raise NotImplementedError()
        self.version = version
        self.frame_queue = []
        self.input_buffer = bytearray()
        self.inflater = Inflater(version)
        self.deflater = Deflater(version)

        if side == SERVER:
            self._stream_id = 2
            # TODO: Add stream_id_peer auto-updating in the _parse_frame() method
            self._stream_id_peer = 1
            self._ping_id = 2
        else:
            self._stream_id = 1
            # TODO: Add stream_id_peer auto-updating in the _parse_frame() method
            self._stream_id_peer = 0
            self._ping_id = 1
        
        self._last_stream_id = self._stream_id
        
        
    @property
    def next_stream_id(self):
        self._last_stream_id = self._stream_id
        self._stream_id += 2
        return self._last_stream_id

    @property
    def next_ping_id(self):
        pid = self._ping_id
        self._ping_id += 2
        return pid

    def incoming(self, chunk):
        self.input_buffer.extend(chunk)

    def get_frame(self):
        frame, bytes_parsed = self._parse_frame(self.input_buffer)
        if bytes_parsed:
            self.input_buffer = self.input_buffer[bytes_parsed:]
        return frame

    def put_frame(self, frame):
        if not isinstance(frame, Frame):
            raise TypeError("frame must be a valid Frame object")
        self.frame_queue.append(frame)

    def outgoing(self):
        out = bytearray()
        while len(self.frame_queue) > 0:
            frame = self.frame_queue.pop(0)
            out.extend(self._encode_frame(frame))
        return out

    def _parse_header_chunk(self, compressed_data, version):
        # Zlib dictionary selection
        chunk = self.inflater.decompress(compressed_data)
        
        length_size = 2 if version == 2 else 4
        headers = {}

        #first two bytes: number of pairs
        num_values = get_int_from_stream(chunk[0:length_size], 'big')

        #after that...
        cursor = length_size
        for _ in range(num_values):
            #two/four bytes: length of name
            name_length = get_int_from_stream(chunk[cursor:cursor+length_size], 'big')
            cursor += length_size

            #next name_length bytes: name
            name = chunk[cursor:cursor+name_length].decode('UTF-8')
            cursor += name_length

            #two/four bytes: length of value
            value_length = get_int_from_stream(chunk[cursor:cursor+length_size], 'big')
            cursor += length_size

            #next value_length bytes: value
            value = chunk[cursor:cursor+value_length].decode('UTF-8')
            cursor += value_length

            if name_length == 0 or value_length == 0:
                raise SpdyProtocolError("zero-length name or value in n/v block")
            if name in headers:
                raise SpdyProtocolError("duplicate name in n/v block")
            headers[name] = value

        return headers

    def _parse_settings_id_values_v2(self, number_of_entries, data):
        id_value_pairs = {}
        cursor = 0
        for _ in range(number_of_entries):
            # 3B = ID
            id = get_int_from_stream(data[cursor:cursor+3], 'little')
            cursor += 3
            # 1B = ID_Flag
            id_flag = get_int_from_stream(data[cursor:cursor+1], 'big')
            cursor += 1
            # 4B = Value
            value = get_int_from_stream(data[cursor:cursor+4], 'big')
            cursor += 4
            id_value_pairs[id] = (id_flag, value)
        return id_value_pairs

    def _parse_settings_id_values_v3(self, number_of_entries, data):
        id_value_pairs = {}
        cursor = 0
        for _ in range(number_of_entries):
            # 1B = ID_Flag
            id_flag = get_int_from_stream(data[cursor:cursor+1], 'big')
            cursor += 1
            # 3B = ID
            id = get_int_from_stream(data[cursor:cursor+3], 'big')
            cursor += 3
            # 4B = Value
            value = get_int_from_stream(data[cursor:cursor+4], 'big')
            cursor += 4
            id_value_pairs[id] = (id_flag, value)
        return id_value_pairs

    def _parse_frame(self, chunk):
        if len(chunk) < 8:
            return (None, 0)

        #first bit: control or data frame?
        control_frame = (chunk[0] & _first_bit == _first_bit)

        if control_frame:
            #second byte (and rest of first, after the first bit): spdy version
            spdy_version = get_int_from_stream(chunk[0:2], 'big') & _last_15_bits
            if spdy_version != self.version:
                raise SpdyProtocolError("incorrect SPDY version")

            #third and fourth byte: frame type
            frame_type = get_int_from_stream(chunk[2:4], 'big')
            if not frame_type in FRAME_TYPES:
                raise SpdyProtocolError("invalid frame type: {0}".format(frame_type))

            #fifth byte: flags
            flags = chunk[4]

            #sixth, seventh and eighth bytes: length
            length = get_int_from_stream(chunk[5:8], 'big')
            frame_length = length + 8
            if len(chunk) < frame_length:
                return (None, 0)

            #the rest is data
            data = chunk[8:frame_length]

            bits = bitarray()
            bits.frombytes(bytes(data))
            frame_cls = FRAME_TYPES[frame_type]

            args = {
                'version': spdy_version,
                'flags': flags
            }

            for key, num_bits in frame_cls.definition(spdy_version):
                if not key:
                    bits = bits[num_bits:]
                    continue

                if num_bits == -1:
                    value = bits
                else:
                    value = bits[:num_bits]
                    bits = bits[num_bits:]

                if key == 'headers': #headers are compressed
                    args[key] = self._parse_header_chunk(value.tobytes(), self.version)
                elif key == 'id_value_pairs':
                    if self.version == 2:
                        args[key] = self._parse_settings_id_values_v2(args['number_of_entries'], \
                                                            value.tobytes())
                    else:
                        args[key] = self._parse_settings_id_values_v3(args['number_of_entries'], \
                                                            value.tobytes())
                else:
                    #we have to pad values on the left, because bitarray will assume
                    #that you want it padded from the right
                    gap = len(value) % 8
                    if gap:
                        zeroes = bitarray(8 - gap)
                        zeroes.setall(False)
                        value = zeroes + value
                    args[key] = get_int_from_stream(value.tobytes(), 'big')

                if num_bits == -1:
                    break

            frame = frame_cls(**args)

        else: #data frame
            #first four bytes, except the first bit: stream_id
            stream_id = get_int_from_stream(chunk[0:4], 'big') & _last_31_bits

            #fifth byte: flags
            flags = chunk[4]

            #sixth, seventh and eight bytes: length
            length = get_int_from_stream(chunk[5:8], 'big')
            frame_length = 8 + length
            if len(chunk) < frame_length:
                return (0, None)

            data = chunk[8:frame_length]
            frame = DataFrame(stream_id, data)

        return (frame, frame_length)

    def _encode_header_chunk(self, headers, version):
        chunk = bytearray()
        length_size = 2 if version == 2 else 4
        
        #first two bytes: number of pairs
        chunk.extend(get_stream_from_int(len(headers), length_size, 'big'))

        #after that...
        for name, value in headers.items():
            name = bytes(name.encode('utf-8'))
            value = bytes(value.encode('utf-8'))

            #two bytes: length of name
            chunk.extend(get_stream_from_int(len(name), length_size, 'big'))

            #next name_length bytes: name
            chunk.extend(name)

            #two bytes: length of value
            chunk.extend(get_stream_from_int(len(value), length_size, 'big'))

            #next value_length bytes: value
            chunk.extend(value)
            
        return self.deflater.compress(bytes(chunk))

    def _encode_settings_id_values_v2(self, id_values_dict):
        chunk = bytearray()
        for id, (id_flag, value) in id_values_dict.items():
            # 3B = ID
            chunk.extend(get_stream_from_int(id, 3, 'little'))
            # 1B = ID_Flag
            chunk.extend(get_stream_from_int(id_flag, 1, 'big'))
            # 4B = Value
            chunk.extend(get_stream_from_int(value, 4, 'big'))
        return bytes(chunk)

    def _encode_settings_id_values_v3(self, id_values_dict):
        chunk = bytearray()
        for id, (id_flag, value) in id_values_dict.items():
            # 1B = ID_Flag
            chunk.extend(get_stream_from_int(id_flag, 1, 'big'))
            # 3B = ID
            chunk.extend(get_stream_from_int(id, 3, 'big'))
            # 4B = Value
            chunk.extend(get_stream_from_int(value, 4, 'big'))
        return bytes(chunk)

    def _encode_frame(self, frame):
        out = bytearray()

        if frame.is_control:
            #first two bytes: version
            out.extend(get_stream_from_int(frame.version, 2, 'big'))

            #set the first bit to control
            out[0] = out[0] | _first_bit

            #third and fourth: frame type
            out.extend(get_stream_from_int(frame.frame_type, 2, 'big'))

            #fifth: flags
            out.append(frame.flags)

            bits = bitarray()
            for key, num_bits in frame.definition(self.version):

                if not key: # is False
                    zeroes = bitarray(num_bits)
                    zeroes.setall(False)
                    bits += zeroes
                    continue

                value = getattr(frame, key)
                if key == 'headers':
                    chunk = bitarray()
                    chunk.frombytes(self._encode_header_chunk(value, frame.version))
                elif key == 'id_value_pairs':
                    chunk = bitarray()
                    if frame.version == 2:
                        chunk_content = self._encode_settings_id_values_v2(value)
                    else:
                        chunk_content = self._encode_settings_id_values_v3(value)
                    chunk.frombytes(chunk_content)
                else:
                    chunk = bitarray(bin(value)[2:])
                    zeroes = bitarray(num_bits - len(chunk))
                    zeroes.setall(False)
                    chunk = zeroes + chunk #pad with zeroes

                bits += chunk
                if num_bits == -1:
                    break
            data = bits.tobytes()

            #sixth, seventh and eighth bytes: length
            out.extend(get_stream_from_int(len(data), 3, 'big'))
            # the rest is data
            out.extend(data)

        else: #data frame

            #first four bytes: stream_id
            out.extend(get_stream_from_int(frame.stream_id, 4, 'big'))

            #fifth: flags
            out.append(frame.flags)

            #sixth, seventh and eighth bytes: length
            data_length = len(frame.data)
            out.extend(get_stream_from_int(data_length, 3, 'big'))

            #rest is data
            out.extend(frame.data)

        return out