Beispiel #1
0
def test_bidi():
    b = utils.BiDi(a=1, b=2)
    assert b.a == 1
    assert b.get_name(1) == "a"
    assert b.get_name(5) is None
    tutils.raises(AttributeError, getattr, b, "c")
    tutils.raises(ValueError, utils.BiDi, one=1, two=1)
Beispiel #2
0
from __future__ import (absolute_import, print_function, division)
import struct
import array
import ipaddress

from netlib import tcp, utils


class SocksError(Exception):
    def __init__(self, code, message):
        super(SocksError, self).__init__(message)
        self.code = code


VERSION = utils.BiDi(SOCKS4=0x04, SOCKS5=0x05)

CMD = utils.BiDi(CONNECT=0x01, BIND=0x02, UDP_ASSOCIATE=0x03)

ATYP = utils.BiDi(IPV4_ADDRESS=0x01, DOMAINNAME=0x03, IPV6_ADDRESS=0x04)

REP = utils.BiDi(
    SUCCEEDED=0x00,
    GENERAL_SOCKS_SERVER_FAILURE=0x01,
    CONNECTION_NOT_ALLOWED_BY_RULESET=0x02,
    NETWORK_UNREACHABLE=0x03,
    HOST_UNREACHABLE=0x04,
    CONNECTION_REFUSED=0x05,
    TTL_EXPIRED=0x06,
    COMMAND_NOT_SUPPORTED=0x07,
    ADDRESS_TYPE_NOT_SUPPORTED=0x08,
)
Beispiel #3
0
import struct
import io

from .protocol import Masker
from netlib import tcp
from netlib import utils

DEFAULT = object()

MAX_16_BIT_INT = (1 << 16)
MAX_64_BIT_INT = (1 << 64)

OPCODE = utils.BiDi(
    CONTINUE=0x00,
    TEXT=0x01,
    BINARY=0x02,
    CLOSE=0x08,
    PING=0x09,
    PONG=0x0a
)


class FrameHeader(object):

    def __init__(
        self,
        opcode=OPCODE.TEXT,
        payload_length=0,
        fin=False,
        rsv1=False,
        rsv2=False,
        rsv3=False,
Beispiel #4
0
class HTTP2StateProtocol(object):

    ERROR_CODES = utils.BiDi(NO_ERROR=0x0,
                             PROTOCOL_ERROR=0x1,
                             INTERNAL_ERROR=0x2,
                             FLOW_CONTROL_ERROR=0x3,
                             SETTINGS_TIMEOUT=0x4,
                             STREAM_CLOSED=0x5,
                             FRAME_SIZE_ERROR=0x6,
                             REFUSED_STREAM=0x7,
                             CANCEL=0x8,
                             COMPRESSION_ERROR=0x9,
                             CONNECT_ERROR=0xa,
                             ENHANCE_YOUR_CALM=0xb,
                             INADEQUATE_SECURITY=0xc,
                             HTTP_1_1_REQUIRED=0xd)

    CLIENT_CONNECTION_PREFACE = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'

    HTTP2_DEFAULT_SETTINGS = {
        hyperframe.frame.SettingsFrame.HEADER_TABLE_SIZE: 4096,
        hyperframe.frame.SettingsFrame.ENABLE_PUSH: 1,
        hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: None,
        hyperframe.frame.SettingsFrame.INITIAL_WINDOW_SIZE: 2**16 - 1,
        hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE: 2**14,
        hyperframe.frame.SettingsFrame.MAX_HEADER_LIST_SIZE: None,
    }

    def __init__(
        self,
        tcp_handler=None,
        rfile=None,
        wfile=None,
        is_server=False,
        dump_frames=False,
        encoder=None,
        decoder=None,
        unhandled_frame_cb=None,
    ):
        self.tcp_handler = tcp_handler or TCPHandler(rfile, wfile)
        self.is_server = is_server
        self.dump_frames = dump_frames
        self.encoder = encoder or Encoder()
        self.decoder = decoder or Decoder()
        self.unhandled_frame_cb = unhandled_frame_cb

        self.http2_settings = self.HTTP2_DEFAULT_SETTINGS.copy()
        self.current_stream_id = None
        self.connection_preface_performed = False

    def read_request(
        self,
        __rfile,
        include_body=True,
        body_size_limit=None,
        allow_empty=False,
    ):
        if body_size_limit is not None:
            raise NotImplementedError()

        self.perform_connection_preface()

        timestamp_start = time.time()
        if hasattr(self.tcp_handler.rfile, "reset_timestamps"):
            self.tcp_handler.rfile.reset_timestamps()

        stream_id, headers, body = self._receive_transmission(
            include_body=include_body, )

        if hasattr(self.tcp_handler.rfile, "first_byte_timestamp"):
            # more accurate timestamp_start
            timestamp_start = self.tcp_handler.rfile.first_byte_timestamp

        timestamp_end = time.time()

        first_line_format, method, scheme, host, port, path = http2.parse_headers(
            headers)

        request = netlib.http.request.Request(
            first_line_format,
            method,
            scheme,
            host,
            port,
            path,
            b"HTTP/2.0",
            headers,
            body,
            timestamp_start,
            timestamp_end,
        )
        request.stream_id = stream_id

        return request

    def read_response(
        self,
        __rfile,
        request_method=b'',
        body_size_limit=None,
        include_body=True,
        stream_id=None,
    ):
        if body_size_limit is not None:
            raise NotImplementedError()

        self.perform_connection_preface()

        timestamp_start = time.time()
        if hasattr(self.tcp_handler.rfile, "reset_timestamps"):
            self.tcp_handler.rfile.reset_timestamps()

        stream_id, headers, body = self._receive_transmission(
            stream_id=stream_id,
            include_body=include_body,
        )

        if hasattr(self.tcp_handler.rfile, "first_byte_timestamp"):
            # more accurate timestamp_start
            timestamp_start = self.tcp_handler.rfile.first_byte_timestamp

        if include_body:
            timestamp_end = time.time()
        else:
            timestamp_end = None

        response = netlib.http.response.Response(
            b"HTTP/2.0",
            int(headers.get(':status', 502)),
            b'',
            headers,
            body,
            timestamp_start=timestamp_start,
            timestamp_end=timestamp_end,
        )
        response.stream_id = stream_id

        return response

    def assemble(self, message):
        if isinstance(message, netlib.http.request.Request):
            return self.assemble_request(message)
        elif isinstance(message, netlib.http.response.Response):
            return self.assemble_response(message)
        else:
            raise ValueError("HTTP message not supported.")

    def assemble_request(self, request):
        assert isinstance(request, netlib.http.request.Request)

        authority = self.tcp_handler.sni if self.tcp_handler.sni else self.tcp_handler.address.host
        if self.tcp_handler.address.port != 443:
            authority += ":%d" % self.tcp_handler.address.port

        headers = request.headers.copy()

        if ':authority' not in headers:
            headers.insert(0, ':authority', authority)
        headers.insert(0, ':scheme', request.scheme)
        headers.insert(0, ':path', request.path)
        headers.insert(0, ':method', request.method)

        if hasattr(request, 'stream_id'):
            stream_id = request.stream_id
        else:
            stream_id = self._next_stream_id()

        return list(
            itertools.chain(
                self._create_headers(headers,
                                     stream_id,
                                     end_stream=(request.body is None
                                                 or len(request.body) == 0)),
                self._create_body(request.body, stream_id)))

    def assemble_response(self, response):
        assert isinstance(response, netlib.http.response.Response)

        headers = response.headers.copy()

        if ':status' not in headers:
            headers.insert(0, b':status', str(response.status_code).encode())

        if hasattr(response, 'stream_id'):
            stream_id = response.stream_id
        else:
            stream_id = self._next_stream_id()

        return list(
            itertools.chain(
                self._create_headers(headers,
                                     stream_id,
                                     end_stream=(response.body is None
                                                 or len(response.body) == 0)),
                self._create_body(response.body, stream_id),
            ))

    def perform_connection_preface(self, force=False):
        if force or not self.connection_preface_performed:
            if self.is_server:
                self.perform_server_connection_preface(force)
            else:
                self.perform_client_connection_preface(force)

    def perform_server_connection_preface(self, force=False):
        if force or not self.connection_preface_performed:
            self.connection_preface_performed = True

            magic_length = len(self.CLIENT_CONNECTION_PREFACE)
            magic = self.tcp_handler.rfile.safe_read(magic_length)
            assert magic == self.CLIENT_CONNECTION_PREFACE

            frm = hyperframe.frame.SettingsFrame(
                settings={
                    hyperframe.frame.SettingsFrame.ENABLE_PUSH: 0,
                    hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: 1,
                })
            self.send_frame(frm, hide=True)
            self._receive_settings(hide=True)

    def perform_client_connection_preface(self, force=False):
        if force or not self.connection_preface_performed:
            self.connection_preface_performed = True

            self.tcp_handler.wfile.write(self.CLIENT_CONNECTION_PREFACE)

            self.send_frame(hyperframe.frame.SettingsFrame(), hide=True)
            self._receive_settings(hide=True)  # server announces own settings
            self._receive_settings(hide=True)  # server acks my settings

    def send_frame(self, frm, hide=False):
        raw_bytes = frm.serialize()
        self.tcp_handler.wfile.write(raw_bytes)
        self.tcp_handler.wfile.flush()
        if not hide and self.dump_frames:  # pragma no cover
            print(frm.human_readable(">>"))

    def read_frame(self, hide=False):
        while True:
            frm = http2.parse_frame(
                *http2.read_raw_frame(self.tcp_handler.rfile))
            if not hide and self.dump_frames:  # pragma no cover
                print(frm.human_readable("<<"))

            if isinstance(frm, hyperframe.frame.PingFrame):
                raw_bytes = hyperframe.frame.PingFrame(
                    flags=['ACK'], payload=frm.payload).serialize()
                self.tcp_handler.wfile.write(raw_bytes)
                self.tcp_handler.wfile.flush()
                continue
            if isinstance(
                    frm,
                    hyperframe.frame.SettingsFrame) and 'ACK' not in frm.flags:
                self._apply_settings(frm.settings, hide)
            if isinstance(frm, hyperframe.frame.DataFrame
                          ) and frm.flow_controlled_length > 0:
                self._update_flow_control_window(frm.stream_id,
                                                 frm.flow_controlled_length)
            return frm

    def check_alpn(self):
        alp = self.tcp_handler.get_alpn_proto_negotiated()
        if alp != b'h2':
            raise NotImplementedError(
                "HTTP2Protocol can not handle unknown ALPN value: %s" % alp)
        return True

    def _handle_unexpected_frame(self, frm):
        if isinstance(frm, hyperframe.frame.SettingsFrame):
            return
        if self.unhandled_frame_cb:
            self.unhandled_frame_cb(frm)

    def _receive_settings(self, hide=False):
        while True:
            frm = self.read_frame(hide)
            if isinstance(frm, hyperframe.frame.SettingsFrame):
                break
            else:
                self._handle_unexpected_frame(frm)

    def _next_stream_id(self):
        if self.current_stream_id is None:
            if self.is_server:
                # servers must use even stream ids
                self.current_stream_id = 2
            else:
                # clients must use odd stream ids
                self.current_stream_id = 1
        else:
            self.current_stream_id += 2
        return self.current_stream_id

    def _apply_settings(self, settings, hide=False):
        for setting, value in settings.items():
            old_value = self.http2_settings[setting]
            if not old_value:
                old_value = '-'
            self.http2_settings[setting] = value

        frm = hyperframe.frame.SettingsFrame(flags=['ACK'])
        self.send_frame(frm, hide)

    def _update_flow_control_window(self, stream_id, increment):
        frm = hyperframe.frame.WindowUpdateFrame(stream_id=0,
                                                 window_increment=increment)
        self.send_frame(frm)
        frm = hyperframe.frame.WindowUpdateFrame(stream_id=stream_id,
                                                 window_increment=increment)
        self.send_frame(frm)

    def _create_headers(self, headers, stream_id, end_stream=True):
        def frame_cls(chunks):
            for i in chunks:
                if i == 0:
                    yield hyperframe.frame.HeadersFrame, i
                else:
                    yield hyperframe.frame.ContinuationFrame, i

        header_block_fragment = self.encoder.encode(headers.fields)

        chunk_size = self.http2_settings[
            hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE]
        chunks = range(0, len(header_block_fragment), chunk_size)
        frms = [
            frm_cls(flags=[],
                    stream_id=stream_id,
                    data=header_block_fragment[i:i + chunk_size])
            for frm_cls, i in frame_cls(chunks)
        ]

        frms[-1].flags.add('END_HEADERS')
        if end_stream:
            frms[0].flags.add('END_STREAM')

        if self.dump_frames:  # pragma no cover
            for frm in frms:
                print(frm.human_readable(">>"))

        return [frm.serialize() for frm in frms]

    def _create_body(self, body, stream_id):
        if body is None or len(body) == 0:
            return b''

        chunk_size = self.http2_settings[
            hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE]
        chunks = range(0, len(body), chunk_size)
        frms = [
            hyperframe.frame.DataFrame(flags=[],
                                       stream_id=stream_id,
                                       data=body[i:i + chunk_size])
            for i in chunks
        ]
        frms[-1].flags.add('END_STREAM')

        if self.dump_frames:  # pragma no cover
            for frm in frms:
                print(frm.human_readable(">>"))

        return [frm.serialize() for frm in frms]

    def _receive_transmission(self, stream_id=None, include_body=True):
        if not include_body:
            raise NotImplementedError()

        body_expected = True

        header_blocks = b''
        body = b''

        while True:
            frm = self.read_frame()
            if ((isinstance(frm, hyperframe.frame.HeadersFrame)
                 or isinstance(frm, hyperframe.frame.ContinuationFrame))
                    and (stream_id is None or frm.stream_id == stream_id)):
                stream_id = frm.stream_id
                header_blocks += frm.data
                if 'END_STREAM' in frm.flags:
                    body_expected = False
                if 'END_HEADERS' in frm.flags:
                    break
            else:
                self._handle_unexpected_frame(frm)

        while body_expected:
            frm = self.read_frame()
            if isinstance(
                    frm,
                    hyperframe.frame.DataFrame) and frm.stream_id == stream_id:
                body += frm.data
                if 'END_STREAM' in frm.flags:
                    break
            else:
                self._handle_unexpected_frame(frm)

        headers = netlib.http.headers.Headers(
            [[k, v] for k, v in self.decoder.decode(header_blocks, raw=True)])

        return stream_id, headers, body
Beispiel #5
0
from netlib import strutils
from netlib import utils
from netlib import human
from .masker import Masker


MAX_16_BIT_INT = (1 << 16)
MAX_64_BIT_INT = (1 << 64)

DEFAULT = object()

# RFC 6455, Section 5.2 - Base Framing Protocol
OPCODE = utils.BiDi(
    CONTINUE=0x00,
    TEXT=0x01,
    BINARY=0x02,
    CLOSE=0x08,
    PING=0x09,
    PONG=0x0a
)

# RFC 6455, Section 7.4.1 - Defined Status Codes
CLOSE_REASON = utils.BiDi(
    NORMAL_CLOSURE=1000,
    GOING_AWAY=1001,
    PROTOCOL_ERROR=1002,
    UNSUPPORTED_DATA=1003,
    RESERVED=1004,
    RESERVED_NO_STATUS=1005,
    RESERVED_ABNORMAL_CLOSURE=1006,
    INVALID_PAYLOAD_DATA=1007,
    POLICY_VIOLATION=1008,
Beispiel #6
0
class HTTP2Protocol(semantics.ProtocolMixin):

    ERROR_CODES = utils.BiDi(NO_ERROR=0x0,
                             PROTOCOL_ERROR=0x1,
                             INTERNAL_ERROR=0x2,
                             FLOW_CONTROL_ERROR=0x3,
                             SETTINGS_TIMEOUT=0x4,
                             STREAM_CLOSED=0x5,
                             FRAME_SIZE_ERROR=0x6,
                             REFUSED_STREAM=0x7,
                             CANCEL=0x8,
                             COMPRESSION_ERROR=0x9,
                             CONNECT_ERROR=0xa,
                             ENHANCE_YOUR_CALM=0xb,
                             INADEQUATE_SECURITY=0xc,
                             HTTP_1_1_REQUIRED=0xd)

    CLIENT_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"

    ALPN_PROTO_H2 = 'h2'

    def __init__(
        self,
        tcp_handler=None,
        rfile=None,
        wfile=None,
        is_server=False,
        dump_frames=False,
        encoder=None,
        decoder=None,
        unhandled_frame_cb=None,
    ):
        self.tcp_handler = tcp_handler or TCPHandler(rfile, wfile)
        self.is_server = is_server
        self.dump_frames = dump_frames
        self.encoder = encoder or Encoder()
        self.decoder = decoder or Decoder()
        self.unhandled_frame_cb = unhandled_frame_cb

        self.http2_settings = frame.HTTP2_DEFAULT_SETTINGS.copy()
        self.current_stream_id = None
        self.connection_preface_performed = False

    def read_request(
        self,
        include_body=True,
        body_size_limit=None,
        allow_empty=False,
    ):
        if body_size_limit is not None:
            raise NotImplementedError()

        self.perform_connection_preface()

        timestamp_start = time.time()
        if hasattr(self.tcp_handler.rfile, "reset_timestamps"):
            self.tcp_handler.rfile.reset_timestamps()

        stream_id, headers, body = self._receive_transmission(
            include_body=include_body, )

        if hasattr(self.tcp_handler.rfile, "first_byte_timestamp"):
            # more accurate timestamp_start
            timestamp_start = self.tcp_handler.rfile.first_byte_timestamp

        timestamp_end = time.time()

        authority = headers.get(':authority', '')
        method = headers.get(':method', 'GET')
        scheme = headers.get(':scheme', 'https')
        path = headers.get(':path', '/')
        host = None
        port = None

        if path == '*' or path.startswith("/"):
            form_in = "relative"
        elif method == 'CONNECT':
            form_in = "authority"
            if ":" in authority:
                host, port = authority.split(":", 1)
            else:
                host = authority
        else:
            form_in = "absolute"
            # FIXME: verify if path or :host contains what we need
            scheme, host, port, _ = utils.parse_url(path)

        if host is None:
            host = 'localhost'
        if port is None:
            port = 80 if scheme == 'http' else 443
        port = int(port)

        request = http.Request(
            form_in,
            method,
            scheme,
            host,
            port,
            path,
            (2, 0),
            headers,
            body,
            timestamp_start,
            timestamp_end,
        )
        # FIXME: We should not do this.
        request.stream_id = stream_id

        return request

    def read_response(
        self,
        request_method='',
        body_size_limit=None,
        include_body=True,
        stream_id=None,
    ):
        if body_size_limit is not None:
            raise NotImplementedError()

        self.perform_connection_preface()

        timestamp_start = time.time()
        if hasattr(self.tcp_handler.rfile, "reset_timestamps"):
            self.tcp_handler.rfile.reset_timestamps()

        stream_id, headers, body = self._receive_transmission(
            stream_id=stream_id,
            include_body=include_body,
        )

        if hasattr(self.tcp_handler.rfile, "first_byte_timestamp"):
            # more accurate timestamp_start
            timestamp_start = self.tcp_handler.rfile.first_byte_timestamp

        if include_body:
            timestamp_end = time.time()
        else:
            timestamp_end = None

        response = http.Response(
            (2, 0),
            int(headers.get(':status', 502)),
            "",
            headers,
            body,
            timestamp_start=timestamp_start,
            timestamp_end=timestamp_end,
        )
        response.stream_id = stream_id

        return response

    def assemble_request(self, request):
        assert isinstance(request, semantics.Request)

        authority = self.tcp_handler.sni if self.tcp_handler.sni else self.tcp_handler.address.host
        if self.tcp_handler.address.port != 443:
            authority += ":%d" % self.tcp_handler.address.port

        headers = request.headers.copy()

        if ':authority' not in headers:
            headers.fields.insert(0, (':authority', bytes(authority)))
        if ':scheme' not in headers:
            headers.fields.insert(0, (':scheme', bytes(request.scheme)))
        if ':path' not in headers:
            headers.fields.insert(0, (':path', bytes(request.path)))
        if ':method' not in headers:
            headers.fields.insert(0, (':method', bytes(request.method)))

        if hasattr(request, 'stream_id'):
            stream_id = request.stream_id
        else:
            stream_id = self._next_stream_id()

        return list(
            itertools.chain(
                self._create_headers(headers,
                                     stream_id,
                                     end_stream=(request.body is None
                                                 or len(request.body) == 0)),
                self._create_body(request.body, stream_id)))

    def assemble_response(self, response):
        assert isinstance(response, semantics.Response)

        headers = response.headers.copy()

        if ':status' not in headers:
            headers.fields.insert(
                0, (':status', bytes(str(response.status_code))))

        if hasattr(response, 'stream_id'):
            stream_id = response.stream_id
        else:
            stream_id = self._next_stream_id()

        return list(
            itertools.chain(
                self._create_headers(headers,
                                     stream_id,
                                     end_stream=(response.body is None
                                                 or len(response.body) == 0)),
                self._create_body(response.body, stream_id),
            ))

    def perform_connection_preface(self, force=False):
        if force or not self.connection_preface_performed:
            if self.is_server:
                self.perform_server_connection_preface(force)
            else:
                self.perform_client_connection_preface(force)

    def perform_server_connection_preface(self, force=False):
        if force or not self.connection_preface_performed:
            self.connection_preface_performed = True

            magic_length = len(self.CLIENT_CONNECTION_PREFACE)
            magic = self.tcp_handler.rfile.safe_read(magic_length)
            assert magic == self.CLIENT_CONNECTION_PREFACE

            frm = frame.SettingsFrame(
                state=self,
                settings={
                    frame.SettingsFrame.SETTINGS.SETTINGS_ENABLE_PUSH:
                    0,
                    frame.SettingsFrame.SETTINGS.SETTINGS_MAX_CONCURRENT_STREAMS:
                    1,
                })
            self.send_frame(frm, hide=True)
            self._receive_settings(hide=True)

    def perform_client_connection_preface(self, force=False):
        if force or not self.connection_preface_performed:
            self.connection_preface_performed = True

            self.tcp_handler.wfile.write(self.CLIENT_CONNECTION_PREFACE)

            self.send_frame(frame.SettingsFrame(state=self), hide=True)
            self._receive_settings(hide=True)  # server announces own settings
            self._receive_settings(hide=True)  # server acks my settings

    def send_frame(self, frm, hide=False):
        raw_bytes = frm.to_bytes()
        self.tcp_handler.wfile.write(raw_bytes)
        self.tcp_handler.wfile.flush()
        if not hide and self.dump_frames:  # pragma no cover
            print(frm.human_readable(">>"))

    def read_frame(self, hide=False):
        while True:
            frm = frame.Frame.from_file(self.tcp_handler.rfile, self)
            if not hide and self.dump_frames:  # pragma no cover
                print(frm.human_readable("<<"))

            if isinstance(frm, frame.PingFrame):
                raw_bytes = frame.PingFrame(flags=frame.Frame.FLAG_ACK,
                                            payload=frm.payload).to_bytes()
                self.tcp_handler.wfile.write(raw_bytes)
                self.tcp_handler.wfile.flush()
                continue
            if isinstance(frm, frame.SettingsFrame
                          ) and not frm.flags & frame.Frame.FLAG_ACK:
                self._apply_settings(frm.settings, hide)
            if isinstance(frm, frame.DataFrame) and frm.length > 0:
                self._update_flow_control_window(frm.stream_id, frm.length)
            return frm

    def check_alpn(self):
        alp = self.tcp_handler.get_alpn_proto_negotiated()
        if alp != self.ALPN_PROTO_H2:
            raise NotImplementedError(
                "HTTP2Protocol can not handle unknown ALP: %s" % alp)
        return True

    def _handle_unexpected_frame(self, frm):
        if isinstance(frm, frame.SettingsFrame):
            return
        if self.unhandled_frame_cb:
            self.unhandled_frame_cb(frm)

    def _receive_settings(self, hide=False):
        while True:
            frm = self.read_frame(hide)
            if isinstance(frm, frame.SettingsFrame):
                break
            else:
                self._handle_unexpected_frame(frm)

    def _next_stream_id(self):
        if self.current_stream_id is None:
            if self.is_server:
                # servers must use even stream ids
                self.current_stream_id = 2
            else:
                # clients must use odd stream ids
                self.current_stream_id = 1
        else:
            self.current_stream_id += 2
        return self.current_stream_id

    def _apply_settings(self, settings, hide=False):
        for setting, value in settings.items():
            old_value = self.http2_settings[setting]
            if not old_value:
                old_value = '-'
            self.http2_settings[setting] = value

        frm = frame.SettingsFrame(state=self, flags=frame.Frame.FLAG_ACK)
        self.send_frame(frm, hide)

    def _update_flow_control_window(self, stream_id, increment):
        frm = frame.WindowUpdateFrame(stream_id=0,
                                      window_size_increment=increment)
        self.send_frame(frm)
        frm = frame.WindowUpdateFrame(stream_id=stream_id,
                                      window_size_increment=increment)
        self.send_frame(frm)

    def _create_headers(self, headers, stream_id, end_stream=True):
        def frame_cls(chunks):
            for i in chunks:
                if i == 0:
                    yield frame.HeadersFrame, i
                else:
                    yield frame.ContinuationFrame, i

        header_block_fragment = self.encoder.encode(headers.fields)

        chunk_size = self.http2_settings[
            frame.SettingsFrame.SETTINGS.SETTINGS_MAX_FRAME_SIZE]
        chunks = range(0, len(header_block_fragment), chunk_size)
        frms = [
            frm_cls(state=self,
                    flags=frame.Frame.FLAG_NO_FLAGS,
                    stream_id=stream_id,
                    header_block_fragment=header_block_fragment[i:i +
                                                                chunk_size])
            for frm_cls, i in frame_cls(chunks)
        ]

        last_flags = frame.Frame.FLAG_END_HEADERS
        if end_stream:
            last_flags |= frame.Frame.FLAG_END_STREAM
        frms[-1].flags = last_flags

        if self.dump_frames:  # pragma no cover
            for frm in frms:
                print(frm.human_readable(">>"))

        return [frm.to_bytes() for frm in frms]

    def _create_body(self, body, stream_id):
        if body is None or len(body) == 0:
            return b''

        chunk_size = self.http2_settings[
            frame.SettingsFrame.SETTINGS.SETTINGS_MAX_FRAME_SIZE]
        chunks = range(0, len(body), chunk_size)
        frms = [
            frame.DataFrame(state=self,
                            flags=frame.Frame.FLAG_NO_FLAGS,
                            stream_id=stream_id,
                            payload=body[i:i + chunk_size]) for i in chunks
        ]
        frms[-1].flags = frame.Frame.FLAG_END_STREAM

        if self.dump_frames:  # pragma no cover
            for frm in frms:
                print(frm.human_readable(">>"))

        return [frm.to_bytes() for frm in frms]

    def _receive_transmission(self, stream_id=None, include_body=True):
        if not include_body:
            raise NotImplementedError()

        body_expected = True

        header_block_fragment = b''
        body = b''

        while True:
            frm = self.read_frame()
            if ((isinstance(frm, frame.HeadersFrame)
                 or isinstance(frm, frame.ContinuationFrame))
                    and (stream_id is None or frm.stream_id == stream_id)):
                stream_id = frm.stream_id
                header_block_fragment += frm.header_block_fragment
                if frm.flags & frame.Frame.FLAG_END_STREAM:
                    body_expected = False
                if frm.flags & frame.Frame.FLAG_END_HEADERS:
                    break
            else:
                self._handle_unexpected_frame(frm)

        while body_expected:
            frm = self.read_frame()
            if isinstance(frm, frame.DataFrame) and frm.stream_id == stream_id:
                body += frm.payload
                if frm.flags & frame.Frame.FLAG_END_STREAM:
                    break
            else:
                self._handle_unexpected_frame(frm)

        headers = http.Headers(
            [[str(k), str(v)]
             for k, v in self.decoder.decode(header_block_fragment)])

        return stream_id, headers, body