123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769 |
- # -*- coding: utf-8 -*-
- """
- hyperframe/frame
- ~~~~~~~~~~~~~~~~
- Defines framing logic for HTTP/2. Provides both classes to represent framed
- data and logic for aiding the connection when it comes to reading from the
- socket.
- """
- import collections
- import struct
- import binascii
- from .exceptions import (
- UnknownFrameError, InvalidPaddingError, InvalidFrameError
- )
- from .flags import Flag, Flags
- # The maximum initial length of a frame. Some frames have shorter maximum lengths.
- FRAME_MAX_LEN = (2 ** 14)
- # The maximum allowed length of a frame.
- FRAME_MAX_ALLOWED_LEN = (2 ** 24) - 1
- class Frame(object):
- """
- The base class for all HTTP/2 frames.
- """
- #: The flags defined on this type of frame.
- defined_flags = []
- #: The byte used to define the type of the frame.
- type = None
- # If 'has-stream', the frame's stream_id must be non-zero. If 'no-stream',
- # it must be zero. If 'either', it's not checked.
- stream_association = None
- def __init__(self, stream_id, flags=()):
- #: The stream identifier for the stream this frame was received on.
- #: Set to 0 for frames sent on the connection (stream-id 0).
- self.stream_id = stream_id
- #: The flags set for this frame.
- self.flags = Flags(self.defined_flags)
- #: The frame length, excluding the nine-byte header.
- self.body_len = 0
- for flag in flags:
- self.flags.add(flag)
- if self.stream_association == 'has-stream' and not self.stream_id:
- raise ValueError('Stream ID must be non-zero')
- if self.stream_association == 'no-stream' and self.stream_id:
- raise ValueError('Stream ID must be zero')
- def __repr__(self):
- flags = ", ".join(self.flags) or "None"
- body = binascii.hexlify(self.serialize_body()).decode('ascii')
- if len(body) > 20:
- body = body[:20] + "..."
- return (
- "{type}(Stream: {stream}; Flags: {flags}): {body}"
- ).format(type=type(self).__name__, stream=self.stream_id, flags=flags, body=body)
- @staticmethod
- def parse_frame_header(header):
- """
- Takes a 9-byte frame header and returns a tuple of the appropriate
- Frame object and the length that needs to be read from the socket.
- This populates the flags field, and determines how long the body is.
- :raises hyperframe.exceptions.UnknownFrameError: If a frame of unknown
- type is received.
- """
- try:
- fields = struct.unpack("!HBBBL", header)
- except struct.error:
- raise InvalidFrameError("Invalid frame header")
- # First 24 bits are frame length.
- length = (fields[0] << 8) + fields[1]
- type = fields[2]
- flags = fields[3]
- stream_id = fields[4]
- if type not in FRAMES:
- raise UnknownFrameError(type, length)
- frame = FRAMES[type](stream_id)
- frame.parse_flags(flags)
- return (frame, length)
- def parse_flags(self, flag_byte):
- for flag, flag_bit in self.defined_flags:
- if flag_byte & flag_bit:
- self.flags.add(flag)
- return self.flags
- def serialize(self):
- """
- Convert a frame into a bytestring, representing the serialized form of
- the frame.
- """
- body = self.serialize_body()
- self.body_len = len(body)
- # Build the common frame header.
- # First, get the flags.
- flags = 0
- for flag, flag_bit in self.defined_flags:
- if flag in self.flags:
- flags |= flag_bit
- header = struct.pack(
- "!HBBBL",
- (self.body_len & 0xFFFF00) >> 8, # Length is spread over top 24 bits
- self.body_len & 0x0000FF,
- self.type,
- flags,
- self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits.
- )
- return header + body
- def serialize_body(self):
- raise NotImplementedError()
- def parse_body(self, data):
- """
- Given the body of a frame, parses it into frame data. This populates
- the non-header parts of the frame: that is, it does not populate the
- stream ID or flags.
- :param data: A memoryview object containing the body data of the frame.
- Must not contain *more* data than the length returned by
- :meth:`parse_frame_header
- <hyperframe.frame.Frame.parse_frame_header>`.
- """
- raise NotImplementedError()
- class Padding(object):
- """
- Mixin for frames that contain padding. Defines extra fields that can be
- used and set by frames that can be padded.
- """
- def __init__(self, stream_id, pad_length=0, **kwargs):
- super(Padding, self).__init__(stream_id, **kwargs)
- #: The length of the padding to use.
- self.pad_length = pad_length
- def serialize_padding_data(self):
- if 'PADDED' in self.flags:
- return struct.pack('!B', self.pad_length)
- return b''
- def parse_padding_data(self, data):
- if 'PADDED' in self.flags:
- try:
- self.pad_length = struct.unpack('!B', data[:1])[0]
- except struct.error:
- raise InvalidFrameError("Invalid Padding data")
- return 1
- return 0
- @property
- def total_padding(self):
- return self.pad_length
- class Priority(object):
- """
- Mixin for frames that contain priority data. Defines extra fields that can
- be used and set by frames that contain priority data.
- """
- def __init__(self, stream_id, depends_on=0x0, stream_weight=0x0, exclusive=False, **kwargs):
- super(Priority, self).__init__(stream_id, **kwargs)
- #: The stream ID of the stream on which this stream depends.
- self.depends_on = depends_on
- #: The weight of the stream. This is an integer between 0 and 256.
- self.stream_weight = stream_weight
- #: Whether the exclusive bit was set.
- self.exclusive = exclusive
- def serialize_priority_data(self):
- return struct.pack(
- "!LB",
- self.depends_on | (int(self.exclusive) << 31),
- self.stream_weight
- )
- def parse_priority_data(self, data):
- MASK = 0x80000000
- try:
- self.depends_on, self.stream_weight = struct.unpack(
- "!LB", data[:5]
- )
- except struct.error:
- raise InvalidFrameError("Invalid Priority data")
- self.exclusive = bool(self.depends_on & MASK)
- self.depends_on &= ~MASK
- return 5
- class DataFrame(Padding, Frame):
- """
- DATA frames convey arbitrary, variable-length sequences of octets
- associated with a stream. One or more DATA frames are used, for instance,
- to carry HTTP request or response payloads.
- """
- #: The flags defined for DATA frames.
- defined_flags = [
- Flag('END_STREAM', 0x01),
- Flag('PADDED', 0x08),
- ]
- #: The type byte for data frames.
- type = 0x0
- stream_association = 'has-stream'
- def __init__(self, stream_id, data=b'', **kwargs):
- super(DataFrame, self).__init__(stream_id, **kwargs)
- #: The data contained on this frame.
- self.data = data
- def serialize_body(self):
- padding_data = self.serialize_padding_data()
- padding = b'\0' * self.total_padding
- return b''.join([padding_data, self.data, padding])
- def parse_body(self, data):
- padding_data_length = self.parse_padding_data(data)
- self.data = data[padding_data_length:len(data)-self.total_padding].tobytes()
- self.body_len = len(data)
- if self.total_padding and self.total_padding >= self.body_len:
- raise InvalidPaddingError("Padding is too long.")
- @property
- def flow_controlled_length(self):
- """
- The length of the frame that needs to be accounted for when considering
- flow control.
- """
- padding_len = self.total_padding + 1 if self.total_padding else 0
- return len(self.data) + padding_len
- class PriorityFrame(Priority, Frame):
- """
- The PRIORITY frame specifies the sender-advised priority of a stream. It
- can be sent at any time for an existing stream. This enables
- reprioritisation of existing streams.
- """
- #: The flags defined for PRIORITY frames.
- defined_flags = []
- #: The type byte defined for PRIORITY frames.
- type = 0x02
- stream_association = 'has-stream'
- def serialize_body(self):
- return self.serialize_priority_data()
- def parse_body(self, data):
- self.parse_priority_data(data)
- self.body_len = len(data)
- class RstStreamFrame(Frame):
- """
- The RST_STREAM frame allows for abnormal termination of a stream. When sent
- by the initiator of a stream, it indicates that they wish to cancel the
- stream or that an error condition has occurred. When sent by the receiver
- of a stream, it indicates that either the receiver is rejecting the stream,
- requesting that the stream be cancelled or that an error condition has
- occurred.
- """
- #: The flags defined for RST_STREAM frames.
- defined_flags = []
- #: The type byte defined for RST_STREAM frames.
- type = 0x03
- stream_association = 'has-stream'
- def __init__(self, stream_id, error_code=0, **kwargs):
- super(RstStreamFrame, self).__init__(stream_id, **kwargs)
- #: The error code used when resetting the stream.
- self.error_code = error_code
- def serialize_body(self):
- return struct.pack("!L", self.error_code)
- def parse_body(self, data):
- if len(data) != 4:
- raise InvalidFrameError(
- "RST_STREAM must have 4 byte body: actual length %s." %
- len(data)
- )
- try:
- self.error_code = struct.unpack("!L", data)[0]
- except struct.error: # pragma: no cover
- raise InvalidFrameError("Invalid RST_STREAM body")
- self.body_len = len(data)
- class SettingsFrame(Frame):
- """
- The SETTINGS frame conveys configuration parameters that affect how
- endpoints communicate. The parameters are either constraints on peer
- behavior or preferences.
- Settings are not negotiated. Settings describe characteristics of the
- sending peer, which are used by the receiving peer. Different values for
- the same setting can be advertised by each peer. For example, a client
- might set a high initial flow control window, whereas a server might set a
- lower value to conserve resources.
- """
- #: The flags defined for SETTINGS frames.
- defined_flags = [Flag('ACK', 0x01)]
- #: The type byte defined for SETTINGS frames.
- type = 0x04
- stream_association = 'no-stream'
- # We need to define the known settings, they may as well be class
- # attributes.
- #: The byte that signals the SETTINGS_HEADER_TABLE_SIZE setting.
- HEADER_TABLE_SIZE = 0x01
- #: The byte that signals the SETTINGS_ENABLE_PUSH setting.
- ENABLE_PUSH = 0x02
- #: The byte that signals the SETTINGS_MAX_CONCURRENT_STREAMS setting.
- MAX_CONCURRENT_STREAMS = 0x03
- #: The byte that signals the SETTINGS_INITIAL_WINDOW_SIZE setting.
- INITIAL_WINDOW_SIZE = 0x04
- #: The byte that signals the SETTINGS_MAX_FRAME_SIZE setting.
- MAX_FRAME_SIZE = 0x05
- #: The byte that signals the SETTINGS_MAX_HEADER_LIST_SIZE setting.
- MAX_HEADER_LIST_SIZE = 0x06
- #: The byte that signals the SETTINGS_MAX_FRAME_SIZE setting.
- #: .. deprecated:: 3.2.0
- #: Use :data:`MAX_FRAME_SIZE <SettingsFrame.MAX_FRAME_SIZE>` instead.
- SETTINGS_MAX_FRAME_SIZE = MAX_FRAME_SIZE
- #: The byte that signals the SETTINGS_MAX_HEADER_LIST_SIZE setting.
- #: .. deprecated:: 3.2.0
- # Use :data:`MAX_HEADER_LIST_SIZE <SettingsFrame.MAX_HEADER_LIST_SIZE>` instead.
- SETTINGS_MAX_HEADER_LIST_SIZE = MAX_HEADER_LIST_SIZE
- def __init__(self, stream_id=0, settings=None, **kwargs):
- super(SettingsFrame, self).__init__(stream_id, **kwargs)
- if settings and "ACK" in kwargs.get("flags", ()):
- raise ValueError("Settings must be empty if ACK flag is set.")
- #: A dictionary of the setting type byte to the value of the setting.
- self.settings = settings or {}
- def serialize_body(self):
- settings = [struct.pack("!HL", setting & 0xFF, value)
- for setting, value in self.settings.items()]
- return b''.join(settings)
- def parse_body(self, data):
- for i in range(0, len(data), 6):
- try:
- name, value = struct.unpack("!HL", data[i:i+6])
- except struct.error:
- raise InvalidFrameError("Invalid SETTINGS body")
- self.settings[name] = value
- self.body_len = len(data)
- class PushPromiseFrame(Padding, Frame):
- """
- The PUSH_PROMISE frame is used to notify the peer endpoint in advance of
- streams the sender intends to initiate.
- """
- #: The flags defined for PUSH_PROMISE frames.
- defined_flags = [
- Flag('END_HEADERS', 0x04),
- Flag('PADDED', 0x08)
- ]
- #: The type byte defined for PUSH_PROMISE frames.
- type = 0x05
- stream_association = 'has-stream'
- def __init__(self, stream_id, promised_stream_id=0, data=b'', **kwargs):
- super(PushPromiseFrame, self).__init__(stream_id, **kwargs)
- #: The stream ID that is promised by this frame.
- self.promised_stream_id = promised_stream_id
- #: The HPACK-encoded header block for the simulated request on the new
- #: stream.
- self.data = data
- def serialize_body(self):
- padding_data = self.serialize_padding_data()
- padding = b'\0' * self.total_padding
- data = struct.pack("!L", self.promised_stream_id)
- return b''.join([padding_data, data, self.data, padding])
- def parse_body(self, data):
- padding_data_length = self.parse_padding_data(data)
- try:
- self.promised_stream_id = struct.unpack(
- "!L", data[padding_data_length:padding_data_length + 4]
- )[0]
- except struct.error:
- raise InvalidFrameError("Invalid PUSH_PROMISE body")
- self.data = data[padding_data_length + 4:].tobytes()
- self.body_len = len(data)
- if self.total_padding and self.total_padding >= self.body_len:
- raise InvalidPaddingError("Padding is too long.")
- class PingFrame(Frame):
- """
- The PING frame is a mechanism for measuring a minimal round-trip time from
- the sender, as well as determining whether an idle connection is still
- functional. PING frames can be sent from any endpoint.
- """
- #: The flags defined for PING frames.
- defined_flags = [Flag('ACK', 0x01)]
- #: The type byte defined for PING frames.
- type = 0x06
- stream_association = 'no-stream'
- def __init__(self, stream_id=0, opaque_data=b'', **kwargs):
- super(PingFrame, self).__init__(stream_id, **kwargs)
- #: The opaque data sent in this PING frame, as a bytestring.
- self.opaque_data = opaque_data
- def serialize_body(self):
- if len(self.opaque_data) > 8:
- raise InvalidFrameError(
- "PING frame may not have more than 8 bytes of data, got %s" %
- self.opaque_data
- )
- data = self.opaque_data
- data += b'\x00' * (8 - len(self.opaque_data))
- return data
- def parse_body(self, data):
- if len(data) != 8:
- raise InvalidFrameError(
- "PING frame must have 8 byte length: got %s" % len(data)
- )
- self.opaque_data = data.tobytes()
- self.body_len = len(data)
- class GoAwayFrame(Frame):
- """
- The GOAWAY frame informs the remote peer to stop creating streams on this
- connection. It can be sent from the client or the server. Once sent, the
- sender will ignore frames sent on new streams for the remainder of the
- connection.
- """
- #: The flags defined for GOAWAY frames.
- defined_flags = []
- #: The type byte defined for GOAWAY frames.
- type = 0x07
- stream_association = 'no-stream'
- def __init__(self, stream_id=0, last_stream_id=0, error_code=0, additional_data=b'', **kwargs):
- super(GoAwayFrame, self).__init__(stream_id, **kwargs)
- #: The last stream ID definitely seen by the remote peer.
- self.last_stream_id = last_stream_id
- #: The error code for connection teardown.
- self.error_code = error_code
- #: Any additional data sent in the GOAWAY.
- self.additional_data = additional_data
- def serialize_body(self):
- data = struct.pack(
- "!LL",
- self.last_stream_id & 0x7FFFFFFF,
- self.error_code
- )
- data += self.additional_data
- return data
- def parse_body(self, data):
- try:
- self.last_stream_id, self.error_code = struct.unpack(
- "!LL", data[:8]
- )
- except struct.error:
- raise InvalidFrameError("Invalid GOAWAY body.")
- self.body_len = len(data)
- if len(data) > 8:
- self.additional_data = data[8:].tobytes()
- class WindowUpdateFrame(Frame):
- """
- The WINDOW_UPDATE frame is used to implement flow control.
- Flow control operates at two levels: on each individual stream and on the
- entire connection.
- Both types of flow control are hop by hop; that is, only between the two
- endpoints. Intermediaries do not forward WINDOW_UPDATE frames between
- dependent connections. However, throttling of data transfer by any receiver
- can indirectly cause the propagation of flow control information toward the
- original sender.
- """
- #: The flags defined for WINDOW_UPDATE frames.
- defined_flags = []
- #: The type byte defined for WINDOW_UPDATE frames.
- type = 0x08
- stream_association = 'either'
- def __init__(self, stream_id, window_increment=0, **kwargs):
- super(WindowUpdateFrame, self).__init__(stream_id, **kwargs)
- #: The amount the flow control window is to be incremented.
- self.window_increment = window_increment
- def serialize_body(self):
- return struct.pack("!L", self.window_increment & 0x7FFFFFFF)
- def parse_body(self, data):
- try:
- self.window_increment = struct.unpack("!L", data)[0]
- except struct.error:
- raise InvalidFrameError("Invalid WINDOW_UPDATE body")
- self.body_len = len(data)
- class HeadersFrame(Padding, Priority, Frame):
- """
- The HEADERS frame carries name-value pairs. It is used to open a stream.
- HEADERS frames can be sent on a stream in the "open" or "half closed
- (remote)" states.
- The HeadersFrame class is actually basically a data frame in this
- implementation, because of the requirement to control the sizes of frames.
- A header block fragment that doesn't fit in an entire HEADERS frame needs
- to be followed with CONTINUATION frames. From the perspective of the frame
- building code the header block is an opaque data segment.
- """
- #: The flags defined for HEADERS frames.
- defined_flags = [
- Flag('END_STREAM', 0x01),
- Flag('END_HEADERS', 0x04),
- Flag('PADDED', 0x08),
- Flag('PRIORITY', 0x20),
- ]
- #: The type byte defined for HEADERS frames.
- type = 0x01
- stream_association = 'has-stream'
- def __init__(self, stream_id, data=b'', **kwargs):
- super(HeadersFrame, self).__init__(stream_id, **kwargs)
- #: The HPACK-encoded header block.
- self.data = data
- def serialize_body(self):
- padding_data = self.serialize_padding_data()
- padding = b'\0' * self.total_padding
- if 'PRIORITY' in self.flags:
- priority_data = self.serialize_priority_data()
- else:
- priority_data = b''
- return b''.join([padding_data, priority_data, self.data, padding])
- def parse_body(self, data):
- padding_data_length = self.parse_padding_data(data)
- data = data[padding_data_length:]
- if 'PRIORITY' in self.flags:
- priority_data_length = self.parse_priority_data(data)
- else:
- priority_data_length = 0
- self.body_len = len(data)
- self.data = data[priority_data_length:len(data)-self.total_padding].tobytes()
- if self.total_padding and self.total_padding >= self.body_len:
- raise InvalidPaddingError("Padding is too long.")
- class ContinuationFrame(Frame):
- """
- The CONTINUATION frame is used to continue a sequence of header block
- fragments. Any number of CONTINUATION frames can be sent on an existing
- stream, as long as the preceding frame on the same stream is one of
- HEADERS, PUSH_PROMISE or CONTINUATION without the END_HEADERS flag set.
- Much like the HEADERS frame, hyper treats this as an opaque data frame with
- different flags and a different type.
- """
- #: The flags defined for CONTINUATION frames.
- defined_flags = [Flag('END_HEADERS', 0x04),]
- #: The type byte defined for CONTINUATION frames.
- type = 0x09
- stream_association = 'has-stream'
- def __init__(self, stream_id, data=b'', **kwargs):
- super(ContinuationFrame, self).__init__(stream_id, **kwargs)
- #: The HPACK-encoded header block.
- self.data = data
- def serialize_body(self):
- return self.data
- def parse_body(self, data):
- self.data = data.tobytes()
- self.body_len = len(data)
- Origin = collections.namedtuple('Origin', ['scheme', 'host', 'port'])
- class AltSvcFrame(Frame):
- """
- The ALTSVC frame is used to advertise alternate services that the current
- host, or a different one, can understand.
- """
- type = 0xA
- stream_association = 'no-stream'
- def __init__(self, stream_id=0, host=b'', port=0, protocol_id=b'', max_age=0, origin=None, **kwargs):
- super(AltSvcFrame, self).__init__(stream_id, **kwargs)
- self.host = host
- self.port = port
- self.protocol_id = protocol_id
- self.max_age = max_age
- self.origin = origin
- def serialize_origin(self):
- if self.origin is not None:
- if self.origin.port is None:
- hostport = self.origin.host
- else:
- hostport = self.origin.host + b':' + str(self.origin.port).encode('ascii')
- return self.origin.scheme + b'://' + hostport
- return b''
- def parse_origin(self, data):
- if len(data) > 0:
- data = data.tobytes()
- scheme, hostport = data.split(b'://')
- host, _, port = hostport.partition(b':')
- self.origin = Origin(scheme=scheme, host=host,
- port=int(port) if len(port) > 0 else None)
- def serialize_body(self):
- first = struct.pack("!LHxB", self.max_age, self.port, len(self.protocol_id))
- host_length = struct.pack("!B", len(self.host))
- return b''.join([first, self.protocol_id, host_length, self.host,
- self.serialize_origin()])
- def parse_body(self, data):
- try:
- self.body_len = len(data)
- self.max_age, self.port, protocol_id_length = struct.unpack(
- "!LHxB", data[:8]
- )
- pos = 8
- self.protocol_id = data[pos:pos+protocol_id_length].tobytes()
- pos += protocol_id_length
- host_length = struct.unpack("!B", data[pos:pos+1])[0]
- pos += 1
- self.host = data[pos:pos+host_length].tobytes()
- pos += host_length
- self.parse_origin(data[pos:])
- except (struct.error, ValueError):
- raise InvalidFrameError("Invalid ALTSVC frame body.")
- class BlockedFrame(Frame):
- """
- The BLOCKED frame indicates that the sender is unable to send data due to a
- closed flow control window.
- The BLOCKED frame is used to provide feedback about the performance of flow
- control for the purposes of performance tuning and debugging. The BLOCKED
- frame can be sent by a peer when flow controlled data cannot be sent due to
- the connection- or stream-level flow control. This frame MUST NOT be sent
- if there are other reasons preventing data from being sent, either a lack
- of available data, or the underlying transport being blocked.
- """
- type = 0x0B
- stream_association = 'both'
- defined_flags = []
- def serialize_body(self):
- return b''
- def parse_body(self, data):
- pass
- _FRAME_CLASSES = [
- DataFrame,
- HeadersFrame,
- PriorityFrame,
- RstStreamFrame,
- SettingsFrame,
- PushPromiseFrame,
- PingFrame,
- GoAwayFrame,
- WindowUpdateFrame,
- ContinuationFrame,
- AltSvcFrame,
- BlockedFrame
- ]
- #: FRAMES maps the type byte for each frame to the class used to represent that
- #: frame.
- FRAMES = {cls.type: cls for cls in _FRAME_CLASSES}
|