| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937 |
- """
- 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.
- """
- from __future__ import annotations
- import binascii
- import struct
- from typing import TYPE_CHECKING, Any
- if TYPE_CHECKING:
- from collections.abc import Iterable # pragma: no cover
- from .exceptions import InvalidDataError, InvalidFrameError, InvalidPaddingError, UnknownFrameError
- 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
- # Stream association enumerations.
- _STREAM_ASSOC_HAS_STREAM = "has-stream"
- _STREAM_ASSOC_NO_STREAM = "no-stream"
- _STREAM_ASSOC_EITHER = "either"
- # Structs for packing and unpacking
- _STRUCT_HBBBL = struct.Struct(">HBBBL")
- _STRUCT_LL = struct.Struct(">LL")
- _STRUCT_HL = struct.Struct(">HL")
- _STRUCT_LB = struct.Struct(">LB")
- _STRUCT_L = struct.Struct(">L")
- _STRUCT_H = struct.Struct(">H")
- _STRUCT_B = struct.Struct(">B")
- class Frame:
- """
- The base class for all HTTP/2 frames.
- """
- #: The flags defined on this type of frame.
- defined_flags: list[Flag] = []
- #: The byte used to define the type of the frame.
- type: int | None = 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: str | None = None
- def __init__(self, stream_id: int, flags: Iterable[str] = ()) -> None:
- #: 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 not self.stream_id and self.stream_association == _STREAM_ASSOC_HAS_STREAM:
- msg = f"Stream ID must be non-zero for {type(self).__name__}"
- raise InvalidDataError(msg)
- if self.stream_id and self.stream_association == _STREAM_ASSOC_NO_STREAM:
- msg = f"Stream ID must be zero for {type(self).__name__} with stream_id={self.stream_id}"
- raise InvalidDataError(msg)
- def __repr__(self) -> str:
- return (
- f"{type(self).__name__}(stream_id={self.stream_id}, flags={self.flags!r}): {self._body_repr()}"
- )
- def _body_repr(self) -> str:
- # More specific implementation may be provided by subclasses of Frame.
- # This fallback shows the serialized (and truncated) body content.
- return _raw_data_repr(self.serialize_body())
- @staticmethod
- def explain(data: memoryview) -> tuple[Frame, int]:
- """
- Takes a bytestring and tries to parse a single frame and print it.
- This function is only provided for debugging purposes.
- :param data: A memoryview object containing the raw data of at least
- one complete frame (header and body).
- .. versionadded:: 6.0.0
- """
- frame, length = Frame.parse_frame_header(data[:9])
- frame.parse_body(data[9:9 + length])
- print(frame) # noqa: T201
- return frame, length
- @staticmethod
- def parse_frame_header(header: memoryview, strict: bool = False) -> tuple[Frame, int]:
- """
- 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.
- :param header: A memoryview object containing the 9-byte frame header
- data of a frame. Must not contain more or less.
- :param strict: Whether to raise an exception when encountering a frame
- not defined by spec and implemented by hyperframe.
- :raises hyperframe.exceptions.UnknownFrameError: If a frame of unknown
- type is received.
- .. versionchanged:: 5.0.0
- Added ``strict`` parameter to accommodate :class:`ExtensionFrame`
- """
- try:
- fields = _STRUCT_HBBBL.unpack(header)
- except struct.error as err:
- msg = "Invalid frame header"
- raise InvalidFrameError(msg) from err
- # First 24 bits are frame length.
- length = (fields[0] << 8) + fields[1]
- typ_e = fields[2]
- flags = fields[3]
- stream_id = fields[4] & 0x7FFFFFFF
- try:
- frame = FRAMES[typ_e](stream_id)
- except KeyError as err:
- if strict:
- raise UnknownFrameError(typ_e, length) from err
- frame = ExtensionFrame(type=typ_e, stream_id=stream_id)
- frame.parse_flags(flags)
- return (frame, length)
- def parse_flags(self, flag_byte: int) -> Flags:
- for flag, flag_bit in self.defined_flags:
- if flag_byte & flag_bit:
- self.flags.add(flag)
- return self.flags
- def serialize(self) -> bytes:
- """
- 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_HBBBL.pack(
- (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits
- self.body_len & 0xFF,
- self.type,
- flags,
- self.stream_id & 0x7FFFFFFF, # Stream ID is 32 bits.
- )
- return header + body
- def serialize_body(self) -> bytes:
- raise NotImplementedError
- def parse_body(self, data: memoryview) -> None:
- """
- 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:
- """
- 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: int, pad_length: int = 0, **kwargs: Any) -> None:
- super().__init__(stream_id, **kwargs) # type: ignore
- #: The length of the padding to use.
- self.pad_length = pad_length
- def serialize_padding_data(self) -> bytes:
- if "PADDED" in self.flags: # type: ignore
- return _STRUCT_B.pack(self.pad_length)
- return b""
- def parse_padding_data(self, data: memoryview) -> int:
- if "PADDED" in self.flags: # type: ignore
- try:
- self.pad_length = struct.unpack("!B", data[:1])[0]
- except struct.error as err:
- msg = "Invalid Padding data"
- raise InvalidFrameError(msg) from err
- return 1
- return 0
- #: .. deprecated:: 5.2.1
- #: Use self.pad_length instead.
- @property
- def total_padding(self) -> int: # pragma: no cover
- import warnings
- warnings.warn(
- "total_padding contains the same information as pad_length.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self.pad_length
- class Priority:
- """
- 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: int,
- depends_on: int = 0x0,
- stream_weight: int = 0x0,
- exclusive: bool = False,
- **kwargs: Any) -> None:
- super().__init__(stream_id, **kwargs) # type: ignore
- #: 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) -> bytes:
- return _STRUCT_LB.pack(
- self.depends_on + (0x80000000 if self.exclusive else 0),
- self.stream_weight,
- )
- def parse_priority_data(self, data: memoryview) -> int:
- try:
- self.depends_on, self.stream_weight = _STRUCT_LB.unpack(data[:5])
- except struct.error as err:
- msg = "Invalid Priority data"
- raise InvalidFrameError(msg) from err
- self.exclusive = bool(self.depends_on >> 31)
- self.depends_on &= 0x7FFFFFFF
- 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 = _STREAM_ASSOC_HAS_STREAM
- def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None:
- super().__init__(stream_id, **kwargs)
- #: The data contained on this frame.
- self.data = data
- def serialize_body(self) -> bytes:
- padding_data = self.serialize_padding_data()
- padding = b"\0" * self.pad_length
- if isinstance(self.data, memoryview):
- self.data = self.data.tobytes()
- return b"".join([padding_data, self.data, padding])
- def parse_body(self, data: memoryview) -> None:
- padding_data_length = self.parse_padding_data(data)
- self.data = (
- data[padding_data_length:len(data)-self.pad_length].tobytes()
- )
- self.body_len = len(data)
- if self.pad_length and self.pad_length >= self.body_len:
- msg = "Padding is too long."
- raise InvalidPaddingError(msg)
- @property
- def flow_controlled_length(self) -> int:
- """
- The length of the frame that needs to be accounted for when considering
- flow control.
- """
- padding_len = 0
- if "PADDED" in self.flags:
- # Account for extra 1-byte padding length field, which is still
- # present if possibly zero-valued.
- padding_len = self.pad_length + 1
- 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: list[Flag] = []
- #: The type byte defined for PRIORITY frames.
- type = 0x02
- stream_association = _STREAM_ASSOC_HAS_STREAM
- def _body_repr(self) -> str:
- return f"exclusive={self.exclusive}, depends_on={self.depends_on}, stream_weight={self.stream_weight}"
- def serialize_body(self) -> bytes:
- return self.serialize_priority_data()
- def parse_body(self, data: memoryview) -> None:
- if len(data) > 5:
- msg = f"PRIORITY must have 5 byte body: actual length {len(data)}."
- raise InvalidFrameError(msg)
- self.parse_priority_data(data)
- self.body_len = 5
- 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: list[Flag] = []
- #: The type byte defined for RST_STREAM frames.
- type = 0x03
- stream_association = _STREAM_ASSOC_HAS_STREAM
- def __init__(self, stream_id: int, error_code: int = 0, **kwargs: Any) -> None:
- super().__init__(stream_id, **kwargs)
- #: The error code used when resetting the stream.
- self.error_code = error_code
- def _body_repr(self) -> str:
- return f"error_code={self.error_code}"
- def serialize_body(self) -> bytes:
- return _STRUCT_L.pack(self.error_code)
- def parse_body(self, data: memoryview) -> None:
- if len(data) != 4:
- msg = f"RST_STREAM must have 4 byte body: actual length {len(data)}."
- raise InvalidFrameError(msg)
- try:
- self.error_code = _STRUCT_L.unpack(data)[0]
- except struct.error as err: # pragma: no cover
- msg = "Invalid RST_STREAM body"
- raise InvalidFrameError(msg) from err
- self.body_len = 4
- 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 = _STREAM_ASSOC_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 SETTINGS_ENABLE_CONNECT_PROTOCOL setting.
- ENABLE_CONNECT_PROTOCOL = 0x08
- def __init__(self, stream_id: int = 0, settings: dict[int, int] | None = None, **kwargs: Any) -> None:
- super().__init__(stream_id, **kwargs)
- if settings and "ACK" in kwargs.get("flags", ()):
- msg = "Settings must be empty if ACK flag is set."
- raise InvalidDataError(msg)
- #: A dictionary of the setting type byte to the value of the setting.
- self.settings: dict[int, int] = settings or {}
- def _body_repr(self) -> str:
- return f"settings={self.settings}"
- def serialize_body(self) -> bytes:
- return b"".join([_STRUCT_HL.pack(setting & 0xFF, value)
- for setting, value in self.settings.items()])
- def parse_body(self, data: memoryview) -> None:
- if "ACK" in self.flags and len(data) > 0:
- msg = f"SETTINGS ack frame must not have payload: got {len(data)} bytes"
- raise InvalidDataError(msg)
- body_len = 0
- for i in range(0, len(data), 6):
- try:
- name, value = _STRUCT_HL.unpack(data[i:i+6])
- except struct.error as err:
- msg = "Invalid SETTINGS body"
- raise InvalidFrameError(msg) from err
- self.settings[name] = value
- body_len += 6
- self.body_len = body_len
- 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 = _STREAM_ASSOC_HAS_STREAM
- def __init__(self, stream_id: int, promised_stream_id: int = 0, data: bytes = b"", **kwargs: Any) -> None:
- super().__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 _body_repr(self) -> str:
- return f"promised_stream_id={self.promised_stream_id}, data={_raw_data_repr(self.data)}"
- def serialize_body(self) -> bytes:
- padding_data = self.serialize_padding_data()
- padding = b"\0" * self.pad_length
- data = _STRUCT_L.pack(self.promised_stream_id)
- return b"".join([padding_data, data, self.data, padding])
- def parse_body(self, data: memoryview) -> None:
- padding_data_length = self.parse_padding_data(data)
- try:
- self.promised_stream_id = _STRUCT_L.unpack(
- data[padding_data_length:padding_data_length + 4],
- )[0]
- except struct.error as err:
- msg = "Invalid PUSH_PROMISE body"
- raise InvalidFrameError(msg) from err
- self.data = (
- data[padding_data_length + 4:len(data)-self.pad_length].tobytes()
- )
- self.body_len = len(data)
- if self.promised_stream_id == 0 or self.promised_stream_id % 2 != 0:
- msg = f"Invalid PUSH_PROMISE promised stream id: {self.promised_stream_id}"
- raise InvalidDataError(msg)
- if self.pad_length and self.pad_length >= self.body_len:
- msg = "Padding is too long."
- raise InvalidPaddingError(msg)
- 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 = _STREAM_ASSOC_NO_STREAM
- def __init__(self, stream_id: int = 0, opaque_data: bytes = b"", **kwargs: Any) -> None:
- super().__init__(stream_id, **kwargs)
- #: The opaque data sent in this PING frame, as a bytestring.
- self.opaque_data = opaque_data
- def _body_repr(self) -> str:
- return f"opaque_data={self.opaque_data!r}"
- def serialize_body(self) -> bytes:
- if len(self.opaque_data) > 8:
- msg = f"PING frame may not have more than 8 bytes of data, got {len(self.opaque_data)}"
- raise InvalidFrameError(msg)
- data = self.opaque_data
- data += b"\x00" * (8 - len(self.opaque_data))
- return data
- def parse_body(self, data: memoryview) -> None:
- if len(data) != 8:
- msg = f"PING frame must have 8 byte length: got {len(data)}"
- raise InvalidFrameError(msg)
- self.opaque_data = data.tobytes()
- self.body_len = 8
- 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: list[Flag] = []
- #: The type byte defined for GOAWAY frames.
- type = 0x07
- stream_association = _STREAM_ASSOC_NO_STREAM
- def __init__(self,
- stream_id: int = 0,
- last_stream_id: int = 0,
- error_code: int = 0,
- additional_data: bytes = b"",
- **kwargs: Any) -> None:
- super().__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 _body_repr(self) -> str:
- return f"last_stream_id={self.last_stream_id}, error_code={self.error_code}, additional_data={self.additional_data!r}"
- def serialize_body(self) -> bytes:
- data = _STRUCT_LL.pack(
- self.last_stream_id & 0x7FFFFFFF,
- self.error_code,
- )
- data += self.additional_data
- return data
- def parse_body(self, data: memoryview) -> None:
- try:
- self.last_stream_id, self.error_code = _STRUCT_LL.unpack(
- data[:8],
- )
- except struct.error as err:
- msg = "Invalid GOAWAY body."
- raise InvalidFrameError(msg) from err
- 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: list[Flag] = []
- #: The type byte defined for WINDOW_UPDATE frames.
- type = 0x08
- stream_association = _STREAM_ASSOC_EITHER
- def __init__(self, stream_id: int, window_increment: int = 0, **kwargs: Any) -> None:
- super().__init__(stream_id, **kwargs)
- #: The amount the flow control window is to be incremented.
- self.window_increment = window_increment
- def _body_repr(self) -> str:
- return f"window_increment={self.window_increment}"
- def serialize_body(self) -> bytes:
- return _STRUCT_L.pack(self.window_increment & 0x7FFFFFFF)
- def parse_body(self, data: memoryview) -> None:
- if len(data) > 4:
- msg = f"WINDOW_UPDATE frame must have 4 byte length: got {len(data)}"
- raise InvalidFrameError(msg)
- try:
- self.window_increment = _STRUCT_L.unpack(data)[0]
- except struct.error as err:
- msg = "Invalid WINDOW_UPDATE body"
- raise InvalidFrameError(msg) from err
- if not 1 <= self.window_increment <= 2**31-1:
- msg = "WINDOW_UPDATE increment must be between 1 to 2^31-1"
- raise InvalidDataError(msg)
- self.body_len = 4
- 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 = _STREAM_ASSOC_HAS_STREAM
- def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None:
- super().__init__(stream_id, **kwargs)
- #: The HPACK-encoded header block.
- self.data = data
- def _body_repr(self) -> str:
- return f"exclusive={self.exclusive}, depends_on={self.depends_on}, stream_weight={self.stream_weight}, data={_raw_data_repr(self.data)}"
- def serialize_body(self) -> bytes:
- padding_data = self.serialize_padding_data()
- padding = b"\0" * self.pad_length
- 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: memoryview) -> None:
- 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.pad_length].tobytes()
- )
- if self.pad_length and self.pad_length >= self.body_len:
- msg = "Padding is too long."
- raise InvalidPaddingError(msg)
- 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 = _STREAM_ASSOC_HAS_STREAM
- def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None:
- super().__init__(stream_id, **kwargs)
- #: The HPACK-encoded header block.
- self.data = data
- def _body_repr(self) -> str:
- return f"data={_raw_data_repr(self.data)}"
- def serialize_body(self) -> bytes:
- return self.data
- def parse_body(self, data: memoryview) -> None:
- self.data = data.tobytes()
- self.body_len = len(data)
- class AltSvcFrame(Frame):
- """
- The ALTSVC frame is used to advertise alternate services that the current
- host, or a different one, can understand. This frame is standardised as
- part of RFC 7838.
- This frame does no work to validate that the ALTSVC field parameter is
- acceptable per the rules of RFC 7838.
- .. note:: If the ``stream_id`` of this frame is nonzero, the origin field
- must have zero length. Conversely, if the ``stream_id`` of this
- frame is zero, the origin field must have nonzero length. Put
- another way, a valid ALTSVC frame has ``stream_id != 0`` XOR
- ``len(origin) != 0``.
- """
- type = 0x0A
- stream_association = _STREAM_ASSOC_EITHER
- def __init__(self, stream_id: int, origin: bytes = b"", field: bytes = b"", **kwargs: Any) -> None:
- super().__init__(stream_id, **kwargs)
- if not isinstance(origin, bytes):
- msg = "AltSvc origin must be a bytestring."
- raise InvalidDataError(msg)
- if not isinstance(field, bytes):
- msg = "AltSvc field must be a bytestring."
- raise InvalidDataError(msg)
- self.origin = origin
- self.field = field
- def _body_repr(self) -> str:
- return f"origin={self.origin!r}, field={self.field!r}"
- def serialize_body(self) -> bytes:
- origin_len = _STRUCT_H.pack(len(self.origin))
- return b"".join([origin_len, self.origin, self.field])
- def parse_body(self, data: memoryview) -> None:
- try:
- origin_len = _STRUCT_H.unpack(data[0:2])[0]
- self.origin = data[2:2+origin_len].tobytes()
- if len(self.origin) != origin_len:
- msg = "Invalid ALTSVC frame body."
- raise InvalidFrameError(msg)
- self.field = data[2+origin_len:].tobytes()
- except (struct.error, ValueError) as err:
- msg = "Invalid ALTSVC frame body."
- raise InvalidFrameError(msg) from err
- self.body_len = len(data)
- class ExtensionFrame(Frame):
- """
- ExtensionFrame is used to wrap frames which are not natively interpretable
- by hyperframe.
- Although certain byte prefixes are ordained by specification to have
- certain contextual meanings, frames with other prefixes are not prohibited,
- and may be used to communicate arbitrary meaning between HTTP/2 peers.
- Thus, hyperframe, rather than raising an exception when such a frame is
- encountered, wraps it in a generic frame to be properly acted upon by
- upstream consumers which might have additional context on how to use it.
- .. versionadded:: 5.0.0
- """
- stream_association = _STREAM_ASSOC_EITHER
- def __init__(self, type: int, stream_id: int, flag_byte: int = 0x0, body: bytes = b"", **kwargs: Any) -> None: # noqa: A002
- super().__init__(stream_id, **kwargs)
- self.type = type
- self.flag_byte = flag_byte
- self.body = body
- def _body_repr(self) -> str:
- return f"type={self.type}, flag_byte={self.flag_byte}, body={_raw_data_repr(self.body)}"
- def parse_flags(self, flag_byte: int) -> None: # type: ignore
- """
- For extension frames, we parse the flags by just storing a flag byte.
- """
- self.flag_byte = flag_byte
- def parse_body(self, data: memoryview) -> None:
- self.body = data.tobytes()
- self.body_len = len(data)
- def serialize(self) -> bytes:
- """
- A broad override of the serialize method that ensures that the data
- comes back out exactly as it came in. This should not be used in most
- user code: it exists only as a helper method if frames need to be
- reconstituted.
- """
- # Build the frame header.
- # First, get the flags.
- flags = self.flag_byte
- header = _STRUCT_HBBBL.pack(
- (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits
- self.body_len & 0xFF,
- self.type,
- flags,
- self.stream_id & 0x7FFFFFFF, # Stream ID is 32 bits.
- )
- return header + self.body
- def _raw_data_repr(data: bytes | None) -> str:
- if not data:
- return "None"
- r = binascii.hexlify(data).decode("ascii")
- if len(r) > 20:
- r = r[:20] + "..."
- return "<hex:" + r + ">"
- _FRAME_CLASSES: list[type[Frame]] = [
- DataFrame,
- HeadersFrame,
- PriorityFrame,
- RstStreamFrame,
- SettingsFrame,
- PushPromiseFrame,
- PingFrame,
- GoAwayFrame,
- WindowUpdateFrame,
- ContinuationFrame,
- AltSvcFrame,
- ]
- #: 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}
|