codekingpro/portable-devtools
115k
1"""2Framing logic for HTTP/2.3 4Provides both classes to represent framed5data and logic for aiding the connection when it comes to reading from the6socket.7"""8from __future__ import annotations9 10import binascii11import struct12from typing import TYPE_CHECKING, Any13 14if TYPE_CHECKING:15 from collections.abc import Iterable # pragma: no cover16 17from .exceptions import InvalidDataError, InvalidFrameError, InvalidPaddingError, UnknownFrameError18from .flags import Flag, Flags19 20# The maximum initial length of a frame. Some frames have shorter maximum21# lengths.22FRAME_MAX_LEN = (2 ** 14)23 24# The maximum allowed length of a frame.25FRAME_MAX_ALLOWED_LEN = (2 ** 24) - 126 27# Stream association enumerations.28_STREAM_ASSOC_HAS_STREAM = "has-stream"29_STREAM_ASSOC_NO_STREAM = "no-stream"30_STREAM_ASSOC_EITHER = "either"31 32# Structs for packing and unpacking33_STRUCT_HBBBL = struct.Struct(">HBBBL")34_STRUCT_LL = struct.Struct(">LL")35_STRUCT_HL = struct.Struct(">HL")36_STRUCT_LB = struct.Struct(">LB")37_STRUCT_L = struct.Struct(">L")38_STRUCT_H = struct.Struct(">H")39_STRUCT_B = struct.Struct(">B")40 41 42class Frame:43 """44 The base class for all HTTP/2 frames.45 """46 47 #: The flags defined on this type of frame.48 defined_flags: list[Flag] = []49 50 #: The byte used to define the type of the frame.51 type: int | None = None52 53 # If 'has-stream', the frame's stream_id must be non-zero. If 'no-stream',54 # it must be zero. If 'either', it's not checked.55 stream_association: str | None = None56 57 def __init__(self, stream_id: int, flags: Iterable[str] = ()) -> None:58 #: The stream identifier for the stream this frame was received on.59 #: Set to 0 for frames sent on the connection (stream-id 0).60 self.stream_id = stream_id61 62 #: The flags set for this frame.63 self.flags = Flags(self.defined_flags)64 65 #: The frame length, excluding the nine-byte header.66 self.body_len = 067 68 for flag in flags:69 self.flags.add(flag)70 71 if not self.stream_id and self.stream_association == _STREAM_ASSOC_HAS_STREAM:72 msg = f"Stream ID must be non-zero for {type(self).__name__}"73 raise InvalidDataError(msg)74 if self.stream_id and self.stream_association == _STREAM_ASSOC_NO_STREAM:75 msg = f"Stream ID must be zero for {type(self).__name__} with stream_id={self.stream_id}"76 raise InvalidDataError(msg)77 78 def __repr__(self) -> str:79 return (80 f"{type(self).__name__}(stream_id={self.stream_id}, flags={self.flags!r}): {self._body_repr()}"81 )82 83 def _body_repr(self) -> str:84 # More specific implementation may be provided by subclasses of Frame.85 # This fallback shows the serialized (and truncated) body content.86 return _raw_data_repr(self.serialize_body())87 88 @staticmethod89 def explain(data: memoryview) -> tuple[Frame, int]:90 """91 Takes a bytestring and tries to parse a single frame and print it.92 93 This function is only provided for debugging purposes.94 95 :param data: A memoryview object containing the raw data of at least96 one complete frame (header and body).97 98 .. versionadded:: 6.0.099 """100 frame, length = Frame.parse_frame_header(data[:9])101 frame.parse_body(data[9:9 + length])102 print(frame) # noqa: T201103 return frame, length104 105 @staticmethod106 def parse_frame_header(header: memoryview, strict: bool = False) -> tuple[Frame, int]:107 """108 Takes a 9-byte frame header and returns a tuple of the appropriate109 Frame object and the length that needs to be read from the socket.110 111 This populates the flags field, and determines how long the body is.112 113 :param header: A memoryview object containing the 9-byte frame header114 data of a frame. Must not contain more or less.115 116 :param strict: Whether to raise an exception when encountering a frame117 not defined by spec and implemented by hyperframe.118 119 :raises hyperframe.exceptions.UnknownFrameError: If a frame of unknown120 type is received.121 122 .. versionchanged:: 5.0.0123 Added ``strict`` parameter to accommodate :class:`ExtensionFrame`124 """125 try:126 fields = _STRUCT_HBBBL.unpack(header)127 except struct.error as err:128 msg = "Invalid frame header"129 raise InvalidFrameError(msg) from err130 131 # First 24 bits are frame length.132 length = (fields[0] << 8) + fields[1]133 typ_e = fields[2]134 flags = fields[3]135 stream_id = fields[4] & 0x7FFFFFFF136 137 try:138 frame = FRAMES[typ_e](stream_id)139 except KeyError as err:140 if strict:141 raise UnknownFrameError(typ_e, length) from err142 frame = ExtensionFrame(type=typ_e, stream_id=stream_id)143 144 frame.parse_flags(flags)145 return (frame, length)146 147 def parse_flags(self, flag_byte: int) -> Flags:148 for flag, flag_bit in self.defined_flags:149 if flag_byte & flag_bit:150 self.flags.add(flag)151 152 return self.flags153 154 def serialize(self) -> bytes:155 """156 Convert a frame into a bytestring, representing the serialized form of157 the frame.158 """159 body = self.serialize_body()160 self.body_len = len(body)161 162 # Build the common frame header.163 # First, get the flags.164 flags = 0165 166 for flag, flag_bit in self.defined_flags:167 if flag in self.flags:168 flags |= flag_bit169 170 header = _STRUCT_HBBBL.pack(171 (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits172 self.body_len & 0xFF,173 self.type,174 flags,175 self.stream_id & 0x7FFFFFFF, # Stream ID is 32 bits.176 )177 178 return header + body179 180 def serialize_body(self) -> bytes:181 raise NotImplementedError182 183 def parse_body(self, data: memoryview) -> None:184 """185 Given the body of a frame, parses it into frame data. This populates186 the non-header parts of the frame: that is, it does not populate the187 stream ID or flags.188 189 :param data: A memoryview object containing the body data of the frame.190 Must not contain *more* data than the length returned by191 :meth:`parse_frame_header192 <hyperframe.frame.Frame.parse_frame_header>`.193 """194 raise NotImplementedError195 196 197class Padding:198 """199 Mixin for frames that contain padding. Defines extra fields that can be200 used and set by frames that can be padded.201 """202 203 def __init__(self, stream_id: int, pad_length: int = 0, **kwargs: Any) -> None:204 super().__init__(stream_id, **kwargs) # type: ignore205 206 #: The length of the padding to use.207 self.pad_length = pad_length208 209 def serialize_padding_data(self) -> bytes:210 if "PADDED" in self.flags: # type: ignore211 return _STRUCT_B.pack(self.pad_length)212 return b""213 214 def parse_padding_data(self, data: memoryview) -> int:215 if "PADDED" in self.flags: # type: ignore216 try:217 self.pad_length = struct.unpack("!B", data[:1])[0]218 except struct.error as err:219 msg = "Invalid Padding data"220 raise InvalidFrameError(msg) from err221 return 1222 return 0223 224 #: .. deprecated:: 5.2.1225 #: Use self.pad_length instead.226 @property227 def total_padding(self) -> int: # pragma: no cover228 import warnings229 warnings.warn(230 "total_padding contains the same information as pad_length.",231 DeprecationWarning,232 stacklevel=2,233 )234 return self.pad_length235 236 237class Priority:238 """239 Mixin for frames that contain priority data. Defines extra fields that can240 be used and set by frames that contain priority data.241 """242 243 def __init__(self,244 stream_id: int,245 depends_on: int = 0x0,246 stream_weight: int = 0x0,247 exclusive: bool = False,248 **kwargs: Any) -> None:249 super().__init__(stream_id, **kwargs) # type: ignore250 251 #: The stream ID of the stream on which this stream depends.252 self.depends_on = depends_on253 254 #: The weight of the stream. This is an integer between 0 and 256.255 self.stream_weight = stream_weight256 257 #: Whether the exclusive bit was set.258 self.exclusive = exclusive259 260 def serialize_priority_data(self) -> bytes:261 return _STRUCT_LB.pack(262 self.depends_on + (0x80000000 if self.exclusive else 0),263 self.stream_weight,264 )265 266 def parse_priority_data(self, data: memoryview) -> int:267 try:268 self.depends_on, self.stream_weight = _STRUCT_LB.unpack(data[:5])269 except struct.error as err:270 msg = "Invalid Priority data"271 raise InvalidFrameError(msg) from err272 273 self.exclusive = bool(self.depends_on >> 31)274 self.depends_on &= 0x7FFFFFFF275 return 5276 277 278class DataFrame(Padding, Frame):279 """280 DATA frames convey arbitrary, variable-length sequences of octets281 associated with a stream. One or more DATA frames are used, for instance,282 to carry HTTP request or response payloads.283 """284 285 #: The flags defined for DATA frames.286 defined_flags = [287 Flag("END_STREAM", 0x01),288 Flag("PADDED", 0x08),289 ]290 291 #: The type byte for data frames.292 type = 0x0293 294 stream_association = _STREAM_ASSOC_HAS_STREAM295 296 def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None:297 super().__init__(stream_id, **kwargs)298 299 #: The data contained on this frame.300 self.data = data301 302 def serialize_body(self) -> bytes:303 padding_data = self.serialize_padding_data()304 padding = b"\0" * self.pad_length305 if isinstance(self.data, memoryview):306 self.data = self.data.tobytes()307 return b"".join([padding_data, self.data, padding])308 309 def parse_body(self, data: memoryview) -> None:310 padding_data_length = self.parse_padding_data(data)311 self.data = (312 data[padding_data_length:len(data)-self.pad_length].tobytes()313 )314 self.body_len = len(data)315 316 if self.pad_length and self.pad_length >= self.body_len:317 msg = "Padding is too long."318 raise InvalidPaddingError(msg)319 320 @property321 def flow_controlled_length(self) -> int:322 """323 The length of the frame that needs to be accounted for when considering324 flow control.325 """326 padding_len = 0327 if "PADDED" in self.flags:328 # Account for extra 1-byte padding length field, which is still329 # present if possibly zero-valued.330 padding_len = self.pad_length + 1331 return len(self.data) + padding_len332 333 334class PriorityFrame(Priority, Frame):335 """336 The PRIORITY frame specifies the sender-advised priority of a stream. It337 can be sent at any time for an existing stream. This enables338 reprioritisation of existing streams.339 """340 341 #: The flags defined for PRIORITY frames.342 defined_flags: list[Flag] = []343 344 #: The type byte defined for PRIORITY frames.345 type = 0x02346 347 stream_association = _STREAM_ASSOC_HAS_STREAM348 349 def _body_repr(self) -> str:350 return f"exclusive={self.exclusive}, depends_on={self.depends_on}, stream_weight={self.stream_weight}"351 352 def serialize_body(self) -> bytes:353 return self.serialize_priority_data()354 355 def parse_body(self, data: memoryview) -> None:356 if len(data) > 5:357 msg = f"PRIORITY must have 5 byte body: actual length {len(data)}."358 raise InvalidFrameError(msg)359 360 self.parse_priority_data(data)361 self.body_len = 5362 363 364class RstStreamFrame(Frame):365 """366 The RST_STREAM frame allows for abnormal termination of a stream. When sent367 by the initiator of a stream, it indicates that they wish to cancel the368 stream or that an error condition has occurred. When sent by the receiver369 of a stream, it indicates that either the receiver is rejecting the stream,370 requesting that the stream be cancelled or that an error condition has371 occurred.372 """373 374 #: The flags defined for RST_STREAM frames.375 defined_flags: list[Flag] = []376 377 #: The type byte defined for RST_STREAM frames.378 type = 0x03379 380 stream_association = _STREAM_ASSOC_HAS_STREAM381 382 def __init__(self, stream_id: int, error_code: int = 0, **kwargs: Any) -> None:383 super().__init__(stream_id, **kwargs)384 385 #: The error code used when resetting the stream.386 self.error_code = error_code387 388 def _body_repr(self) -> str:389 return f"error_code={self.error_code}"390 391 def serialize_body(self) -> bytes:392 return _STRUCT_L.pack(self.error_code)393 394 def parse_body(self, data: memoryview) -> None:395 if len(data) != 4:396 msg = f"RST_STREAM must have 4 byte body: actual length {len(data)}."397 raise InvalidFrameError(msg)398 399 try:400 self.error_code = _STRUCT_L.unpack(data)[0]401 except struct.error as err: # pragma: no cover402 msg = "Invalid RST_STREAM body"403 raise InvalidFrameError(msg) from err404 405 self.body_len = 4406 407 408class SettingsFrame(Frame):409 """410 The SETTINGS frame conveys configuration parameters that affect how411 endpoints communicate. The parameters are either constraints on peer412 behavior or preferences.413 414 Settings are not negotiated. Settings describe characteristics of the415 sending peer, which are used by the receiving peer. Different values for416 the same setting can be advertised by each peer. For example, a client417 might set a high initial flow control window, whereas a server might set a418 lower value to conserve resources.419 """420 421 #: The flags defined for SETTINGS frames.422 defined_flags = [Flag("ACK", 0x01)]423 424 #: The type byte defined for SETTINGS frames.425 type = 0x04426 427 stream_association = _STREAM_ASSOC_NO_STREAM428 429 # We need to define the known settings, they may as well be class430 # attributes.431 #: The byte that signals the SETTINGS_HEADER_TABLE_SIZE setting.432 HEADER_TABLE_SIZE = 0x01433 #: The byte that signals the SETTINGS_ENABLE_PUSH setting.434 ENABLE_PUSH = 0x02435 #: The byte that signals the SETTINGS_MAX_CONCURRENT_STREAMS setting.436 MAX_CONCURRENT_STREAMS = 0x03437 #: The byte that signals the SETTINGS_INITIAL_WINDOW_SIZE setting.438 INITIAL_WINDOW_SIZE = 0x04439 #: The byte that signals the SETTINGS_MAX_FRAME_SIZE setting.440 MAX_FRAME_SIZE = 0x05441 #: The byte that signals the SETTINGS_MAX_HEADER_LIST_SIZE setting.442 MAX_HEADER_LIST_SIZE = 0x06443 #: The byte that signals SETTINGS_ENABLE_CONNECT_PROTOCOL setting.444 ENABLE_CONNECT_PROTOCOL = 0x08445 446 def __init__(self, stream_id: int = 0, settings: dict[int, int] | None = None, **kwargs: Any) -> None:447 super().__init__(stream_id, **kwargs)448 449 if settings and "ACK" in kwargs.get("flags", ()):450 msg = "Settings must be empty if ACK flag is set."451 raise InvalidDataError(msg)452 453 #: A dictionary of the setting type byte to the value of the setting.454 self.settings: dict[int, int] = settings or {}455 456 def _body_repr(self) -> str:457 return f"settings={self.settings}"458 459 def serialize_body(self) -> bytes:460 return b"".join([_STRUCT_HL.pack(setting & 0xFF, value)461 for setting, value in self.settings.items()])462 463 def parse_body(self, data: memoryview) -> None:464 if "ACK" in self.flags and len(data) > 0:465 msg = f"SETTINGS ack frame must not have payload: got {len(data)} bytes"466 raise InvalidDataError(msg)467 468 body_len = 0469 for i in range(0, len(data), 6):470 try:471 name, value = _STRUCT_HL.unpack(data[i:i+6])472 except struct.error as err:473 msg = "Invalid SETTINGS body"474 raise InvalidFrameError(msg) from err475 476 self.settings[name] = value477 body_len += 6478 479 self.body_len = body_len480 481 482class PushPromiseFrame(Padding, Frame):483 """484 The PUSH_PROMISE frame is used to notify the peer endpoint in advance of485 streams the sender intends to initiate.486 """487 488 #: The flags defined for PUSH_PROMISE frames.489 defined_flags = [490 Flag("END_HEADERS", 0x04),491 Flag("PADDED", 0x08),492 ]493 494 #: The type byte defined for PUSH_PROMISE frames.495 type = 0x05496 497 stream_association = _STREAM_ASSOC_HAS_STREAM498 499 def __init__(self, stream_id: int, promised_stream_id: int = 0, data: bytes = b"", **kwargs: Any) -> None:500 super().__init__(stream_id, **kwargs)501 502 #: The stream ID that is promised by this frame.503 self.promised_stream_id = promised_stream_id504 505 #: The HPACK-encoded header block for the simulated request on the new506 #: stream.507 self.data = data508 509 def _body_repr(self) -> str:510 return f"promised_stream_id={self.promised_stream_id}, data={_raw_data_repr(self.data)}"511 512 def serialize_body(self) -> bytes:513 padding_data = self.serialize_padding_data()514 padding = b"\0" * self.pad_length515 data = _STRUCT_L.pack(self.promised_stream_id)516 return b"".join([padding_data, data, self.data, padding])517 518 def parse_body(self, data: memoryview) -> None:519 padding_data_length = self.parse_padding_data(data)520 521 try:522 self.promised_stream_id = _STRUCT_L.unpack(523 data[padding_data_length:padding_data_length + 4],524 )[0]525 except struct.error as err:526 msg = "Invalid PUSH_PROMISE body"527 raise InvalidFrameError(msg) from err528 529 self.data = (530 data[padding_data_length + 4:len(data)-self.pad_length].tobytes()531 )532 self.body_len = len(data)533 534 if self.promised_stream_id == 0 or self.promised_stream_id % 2 != 0:535 msg = f"Invalid PUSH_PROMISE promised stream id: {self.promised_stream_id}"536 raise InvalidDataError(msg)537 538 if self.pad_length and self.pad_length >= self.body_len:539 msg = "Padding is too long."540 raise InvalidPaddingError(msg)541 542 543class PingFrame(Frame):544 """545 The PING frame is a mechanism for measuring a minimal round-trip time from546 the sender, as well as determining whether an idle connection is still547 functional. PING frames can be sent from any endpoint.548 """549 550 #: The flags defined for PING frames.551 defined_flags = [Flag("ACK", 0x01)]552 553 #: The type byte defined for PING frames.554 type = 0x06555 556 stream_association = _STREAM_ASSOC_NO_STREAM557 558 def __init__(self, stream_id: int = 0, opaque_data: bytes = b"", **kwargs: Any) -> None:559 super().__init__(stream_id, **kwargs)560 561 #: The opaque data sent in this PING frame, as a bytestring.562 self.opaque_data = opaque_data563 564 def _body_repr(self) -> str:565 return f"opaque_data={self.opaque_data!r}"566 567 def serialize_body(self) -> bytes:568 if len(self.opaque_data) > 8:569 msg = f"PING frame may not have more than 8 bytes of data, got {len(self.opaque_data)}"570 raise InvalidFrameError(msg)571 572 data = self.opaque_data573 data += b"\x00" * (8 - len(self.opaque_data))574 return data575 576 def parse_body(self, data: memoryview) -> None:577 if len(data) != 8:578 msg = f"PING frame must have 8 byte length: got {len(data)}"579 raise InvalidFrameError(msg)580 581 self.opaque_data = data.tobytes()582 self.body_len = 8583 584 585class GoAwayFrame(Frame):586 """587 The GOAWAY frame informs the remote peer to stop creating streams on this588 connection. It can be sent from the client or the server. Once sent, the589 sender will ignore frames sent on new streams for the remainder of the590 connection.591 """592 593 #: The flags defined for GOAWAY frames.594 defined_flags: list[Flag] = []595 596 #: The type byte defined for GOAWAY frames.597 type = 0x07598 599 stream_association = _STREAM_ASSOC_NO_STREAM600 601 def __init__(self,602 stream_id: int = 0,603 last_stream_id: int = 0,604 error_code: int = 0,605 additional_data: bytes = b"",606 **kwargs: Any) -> None:607 super().__init__(stream_id, **kwargs)608 609 #: The last stream ID definitely seen by the remote peer.610 self.last_stream_id = last_stream_id611 612 #: The error code for connection teardown.613 self.error_code = error_code614 615 #: Any additional data sent in the GOAWAY.616 self.additional_data = additional_data617 618 def _body_repr(self) -> str:619 return f"last_stream_id={self.last_stream_id}, error_code={self.error_code}, additional_data={self.additional_data!r}"620 621 def serialize_body(self) -> bytes:622 data = _STRUCT_LL.pack(623 self.last_stream_id & 0x7FFFFFFF,624 self.error_code,625 )626 data += self.additional_data627 628 return data629 630 def parse_body(self, data: memoryview) -> None:631 try:632 self.last_stream_id, self.error_code = _STRUCT_LL.unpack(633 data[:8],634 )635 except struct.error as err:636 msg = "Invalid GOAWAY body."637 raise InvalidFrameError(msg) from err638 639 self.body_len = len(data)640 641 if len(data) > 8:642 self.additional_data = data[8:].tobytes()643 644 645class WindowUpdateFrame(Frame):646 """647 The WINDOW_UPDATE frame is used to implement flow control.648 649 Flow control operates at two levels: on each individual stream and on the650 entire connection.651 652 Both types of flow control are hop by hop; that is, only between the two653 endpoints. Intermediaries do not forward WINDOW_UPDATE frames between654 dependent connections. However, throttling of data transfer by any receiver655 can indirectly cause the propagation of flow control information toward the656 original sender.657 """658 659 #: The flags defined for WINDOW_UPDATE frames.660 defined_flags: list[Flag] = []661 662 #: The type byte defined for WINDOW_UPDATE frames.663 type = 0x08664 665 stream_association = _STREAM_ASSOC_EITHER666 667 def __init__(self, stream_id: int, window_increment: int = 0, **kwargs: Any) -> None:668 super().__init__(stream_id, **kwargs)669 670 #: The amount the flow control window is to be incremented.671 self.window_increment = window_increment672 673 def _body_repr(self) -> str:674 return f"window_increment={self.window_increment}"675 676 def serialize_body(self) -> bytes:677 return _STRUCT_L.pack(self.window_increment & 0x7FFFFFFF)678 679 def parse_body(self, data: memoryview) -> None:680 if len(data) > 4:681 msg = f"WINDOW_UPDATE frame must have 4 byte length: got {len(data)}"682 raise InvalidFrameError(msg)683 684 try:685 self.window_increment = _STRUCT_L.unpack(data)[0]686 except struct.error as err:687 msg = "Invalid WINDOW_UPDATE body"688 raise InvalidFrameError(msg) from err689 690 if not 1 <= self.window_increment <= 2**31-1:691 msg = "WINDOW_UPDATE increment must be between 1 to 2^31-1"692 raise InvalidDataError(msg)693 694 self.body_len = 4695 696 697class HeadersFrame(Padding, Priority, Frame):698 """699 The HEADERS frame carries name-value pairs. It is used to open a stream.700 HEADERS frames can be sent on a stream in the "open" or "half closed701 (remote)" states.702 703 The HeadersFrame class is actually basically a data frame in this704 implementation, because of the requirement to control the sizes of frames.705 A header block fragment that doesn't fit in an entire HEADERS frame needs706 to be followed with CONTINUATION frames. From the perspective of the frame707 building code the header block is an opaque data segment.708 """709 710 #: The flags defined for HEADERS frames.711 defined_flags = [712 Flag("END_STREAM", 0x01),713 Flag("END_HEADERS", 0x04),714 Flag("PADDED", 0x08),715 Flag("PRIORITY", 0x20),716 ]717 718 #: The type byte defined for HEADERS frames.719 type = 0x01720 721 stream_association = _STREAM_ASSOC_HAS_STREAM722 723 def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None:724 super().__init__(stream_id, **kwargs)725 726 #: The HPACK-encoded header block.727 self.data = data728 729 def _body_repr(self) -> str:730 return f"exclusive={self.exclusive}, depends_on={self.depends_on}, stream_weight={self.stream_weight}, data={_raw_data_repr(self.data)}"731 732 def serialize_body(self) -> bytes:733 padding_data = self.serialize_padding_data()734 padding = b"\0" * self.pad_length735 736 if "PRIORITY" in self.flags:737 priority_data = self.serialize_priority_data()738 else:739 priority_data = b""740 741 return b"".join([padding_data, priority_data, self.data, padding])742 743 def parse_body(self, data: memoryview) -> None:744 padding_data_length = self.parse_padding_data(data)745 data = data[padding_data_length:]746 747 if "PRIORITY" in self.flags:748 priority_data_length = self.parse_priority_data(data)749 else:750 priority_data_length = 0751 752 self.body_len = len(data)753 self.data = (754 data[priority_data_length:len(data)-self.pad_length].tobytes()755 )756 757 if self.pad_length and self.pad_length >= self.body_len:758 msg = "Padding is too long."759 raise InvalidPaddingError(msg)760 761 762class ContinuationFrame(Frame):763 """764 The CONTINUATION frame is used to continue a sequence of header block765 fragments. Any number of CONTINUATION frames can be sent on an existing766 stream, as long as the preceding frame on the same stream is one of767 HEADERS, PUSH_PROMISE or CONTINUATION without the END_HEADERS flag set.768 769 Much like the HEADERS frame, hyper treats this as an opaque data frame with770 different flags and a different type.771 """772 773 #: The flags defined for CONTINUATION frames.774 defined_flags = [Flag("END_HEADERS", 0x04)]775 776 #: The type byte defined for CONTINUATION frames.777 type = 0x09778 779 stream_association = _STREAM_ASSOC_HAS_STREAM780 781 def __init__(self, stream_id: int, data: bytes = b"", **kwargs: Any) -> None:782 super().__init__(stream_id, **kwargs)783 784 #: The HPACK-encoded header block.785 self.data = data786 787 def _body_repr(self) -> str:788 return f"data={_raw_data_repr(self.data)}"789 790 def serialize_body(self) -> bytes:791 return self.data792 793 def parse_body(self, data: memoryview) -> None:794 self.data = data.tobytes()795 self.body_len = len(data)796 797 798class AltSvcFrame(Frame):799 """800 The ALTSVC frame is used to advertise alternate services that the current801 host, or a different one, can understand. This frame is standardised as802 part of RFC 7838.803 804 This frame does no work to validate that the ALTSVC field parameter is805 acceptable per the rules of RFC 7838.806 807 .. note:: If the ``stream_id`` of this frame is nonzero, the origin field808 must have zero length. Conversely, if the ``stream_id`` of this809 frame is zero, the origin field must have nonzero length. Put810 another way, a valid ALTSVC frame has ``stream_id != 0`` XOR811 ``len(origin) != 0``.812 """813 814 type = 0x0A815 816 stream_association = _STREAM_ASSOC_EITHER817 818 def __init__(self, stream_id: int, origin: bytes = b"", field: bytes = b"", **kwargs: Any) -> None:819 super().__init__(stream_id, **kwargs)820 821 if not isinstance(origin, bytes):822 msg = "AltSvc origin must be a bytestring."823 raise InvalidDataError(msg)824 if not isinstance(field, bytes):825 msg = "AltSvc field must be a bytestring."826 raise InvalidDataError(msg)827 self.origin = origin828 self.field = field829 830 def _body_repr(self) -> str:831 return f"origin={self.origin!r}, field={self.field!r}"832 833 def serialize_body(self) -> bytes:834 origin_len = _STRUCT_H.pack(len(self.origin))835 return b"".join([origin_len, self.origin, self.field])836 837 def parse_body(self, data: memoryview) -> None:838 try:839 origin_len = _STRUCT_H.unpack(data[0:2])[0]840 self.origin = data[2:2+origin_len].tobytes()841 842 if len(self.origin) != origin_len:843 msg = "Invalid ALTSVC frame body."844 raise InvalidFrameError(msg)845 846 self.field = data[2+origin_len:].tobytes()847 except (struct.error, ValueError) as err:848 msg = "Invalid ALTSVC frame body."849 raise InvalidFrameError(msg) from err850 851 self.body_len = len(data)852 853 854class ExtensionFrame(Frame):855 """856 ExtensionFrame is used to wrap frames which are not natively interpretable857 by hyperframe.858 859 Although certain byte prefixes are ordained by specification to have860 certain contextual meanings, frames with other prefixes are not prohibited,861 and may be used to communicate arbitrary meaning between HTTP/2 peers.862 863 Thus, hyperframe, rather than raising an exception when such a frame is864 encountered, wraps it in a generic frame to be properly acted upon by865 upstream consumers which might have additional context on how to use it.866 867 .. versionadded:: 5.0.0868 """869 870 stream_association = _STREAM_ASSOC_EITHER871 872 def __init__(self, type: int, stream_id: int, flag_byte: int = 0x0, body: bytes = b"", **kwargs: Any) -> None: # noqa: A002873 super().__init__(stream_id, **kwargs)874 self.type = type875 self.flag_byte = flag_byte876 self.body = body877 878 def _body_repr(self) -> str:879 return f"type={self.type}, flag_byte={self.flag_byte}, body={_raw_data_repr(self.body)}"880 881 def parse_flags(self, flag_byte: int) -> None: # type: ignore882 """883 For extension frames, we parse the flags by just storing a flag byte.884 """885 self.flag_byte = flag_byte886 887 def parse_body(self, data: memoryview) -> None:888 self.body = data.tobytes()889 self.body_len = len(data)890 891 def serialize(self) -> bytes:892 """893 A broad override of the serialize method that ensures that the data894 comes back out exactly as it came in. This should not be used in most895 user code: it exists only as a helper method if frames need to be896 reconstituted.897 """898 # Build the frame header.899 # First, get the flags.900 flags = self.flag_byte901 902 header = _STRUCT_HBBBL.pack(903 (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits904 self.body_len & 0xFF,905 self.type,906 flags,907 self.stream_id & 0x7FFFFFFF, # Stream ID is 32 bits.908 )909 910 return header + self.body911 912 913def _raw_data_repr(data: bytes | None) -> str:914 if not data:915 return "None"916 r = binascii.hexlify(data).decode("ascii")917 if len(r) > 20:918 r = r[:20] + "..."919 return "<hex:" + r + ">"920 921 922_FRAME_CLASSES: list[type[Frame]] = [923 DataFrame,924 HeadersFrame,925 PriorityFrame,926 RstStreamFrame,927 SettingsFrame,928 PushPromiseFrame,929 PingFrame,930 GoAwayFrame,931 WindowUpdateFrame,932 ContinuationFrame,933 AltSvcFrame,934]935#: FRAMES maps the type byte for each frame to the class used to represent that936#: frame.937FRAMES = {cls.type: cls for cls in _FRAME_CLASSES}938 