1# -*- coding: utf-8 -*- 2""" 3hyperframe/frame 4~~~~~~~~~~~~~~~~ 5 6Defines framing logic for HTTP/2. Provides both classes to represent framed 7data and logic for aiding the connection when it comes to reading from the 8socket. 9""" 10import struct 11import binascii 12 13from .exceptions import ( 14 UnknownFrameError, InvalidPaddingError, InvalidFrameError 15) 16from .flags import Flag, Flags 17 18 19# The maximum initial length of a frame. Some frames have shorter maximum 20# lengths. 21FRAME_MAX_LEN = (2 ** 14) 22 23# The maximum allowed length of a frame. 24FRAME_MAX_ALLOWED_LEN = (2 ** 24) - 1 25 26# Stream association enumerations. 27_STREAM_ASSOC_HAS_STREAM = "has-stream" 28_STREAM_ASSOC_NO_STREAM = "no-stream" 29_STREAM_ASSOC_EITHER = "either" 30 31# Structs for packing and unpacking 32_STRUCT_HBBBL = struct.Struct(">HBBBL") 33_STRUCT_LL = struct.Struct(">LL") 34_STRUCT_HL = struct.Struct(">HL") 35_STRUCT_LB = struct.Struct(">LB") 36_STRUCT_L = struct.Struct(">L") 37_STRUCT_H = struct.Struct(">H") 38_STRUCT_B = struct.Struct(">B") 39 40 41class Frame(object): 42 """ 43 The base class for all HTTP/2 frames. 44 """ 45 #: The flags defined on this type of frame. 46 defined_flags = [] 47 48 #: The byte used to define the type of the frame. 49 type = None 50 51 # If 'has-stream', the frame's stream_id must be non-zero. If 'no-stream', 52 # it must be zero. If 'either', it's not checked. 53 stream_association = None 54 55 def __init__(self, stream_id, flags=()): 56 #: The stream identifier for the stream this frame was received on. 57 #: Set to 0 for frames sent on the connection (stream-id 0). 58 self.stream_id = stream_id 59 60 #: The flags set for this frame. 61 self.flags = Flags(self.defined_flags) 62 63 #: The frame length, excluding the nine-byte header. 64 self.body_len = 0 65 66 for flag in flags: 67 self.flags.add(flag) 68 69 if (not self.stream_id and 70 self.stream_association == _STREAM_ASSOC_HAS_STREAM): 71 raise ValueError('Stream ID must be non-zero') 72 if (self.stream_id and 73 self.stream_association == _STREAM_ASSOC_NO_STREAM): 74 raise ValueError('Stream ID must be zero') 75 76 def __repr__(self): 77 flags = ", ".join(self.flags) or "None" 78 body = binascii.hexlify(self.serialize_body()).decode('ascii') 79 if len(body) > 20: 80 body = body[:20] + "..." 81 return ( 82 "{type}(Stream: {stream}; Flags: {flags}): {body}" 83 ).format( 84 type=type(self).__name__, 85 stream=self.stream_id, 86 flags=flags, 87 body=body 88 ) 89 90 @staticmethod 91 def parse_frame_header(header, strict=False): 92 """ 93 Takes a 9-byte frame header and returns a tuple of the appropriate 94 Frame object and the length that needs to be read from the socket. 95 96 This populates the flags field, and determines how long the body is. 97 98 :param strict: Whether to raise an exception when encountering a frame 99 not defined by spec and implemented by hyperframe. 100 101 :raises hyperframe.exceptions.UnknownFrameError: If a frame of unknown 102 type is received. 103 104 .. versionchanged:: 5.0.0 105 Added :param:`strict` to accommodate :class:`ExtensionFrame` 106 """ 107 try: 108 fields = _STRUCT_HBBBL.unpack(header) 109 except struct.error: 110 raise InvalidFrameError("Invalid frame header") 111 112 # First 24 bits are frame length. 113 length = (fields[0] << 8) + fields[1] 114 type = fields[2] 115 flags = fields[3] 116 stream_id = fields[4] & 0x7FFFFFFF 117 118 try: 119 frame = FRAMES[type](stream_id) 120 except KeyError: 121 if strict: 122 raise UnknownFrameError(type, length) 123 frame = ExtensionFrame(type=type, stream_id=stream_id) 124 125 frame.parse_flags(flags) 126 return (frame, length) 127 128 def parse_flags(self, flag_byte): 129 for flag, flag_bit in self.defined_flags: 130 if flag_byte & flag_bit: 131 self.flags.add(flag) 132 133 return self.flags 134 135 def serialize(self): 136 """ 137 Convert a frame into a bytestring, representing the serialized form of 138 the frame. 139 """ 140 body = self.serialize_body() 141 self.body_len = len(body) 142 143 # Build the common frame header. 144 # First, get the flags. 145 flags = 0 146 147 for flag, flag_bit in self.defined_flags: 148 if flag in self.flags: 149 flags |= flag_bit 150 151 header = _STRUCT_HBBBL.pack( 152 (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits 153 self.body_len & 0xFF, 154 self.type, 155 flags, 156 self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits. 157 ) 158 159 return header + body 160 161 def serialize_body(self): 162 raise NotImplementedError() 163 164 def parse_body(self, data): 165 """ 166 Given the body of a frame, parses it into frame data. This populates 167 the non-header parts of the frame: that is, it does not populate the 168 stream ID or flags. 169 170 :param data: A memoryview object containing the body data of the frame. 171 Must not contain *more* data than the length returned by 172 :meth:`parse_frame_header 173 <hyperframe.frame.Frame.parse_frame_header>`. 174 """ 175 raise NotImplementedError() 176 177 178class Padding(object): 179 """ 180 Mixin for frames that contain padding. Defines extra fields that can be 181 used and set by frames that can be padded. 182 """ 183 def __init__(self, stream_id, pad_length=0, **kwargs): 184 super(Padding, self).__init__(stream_id, **kwargs) 185 186 #: The length of the padding to use. 187 self.pad_length = pad_length 188 189 def serialize_padding_data(self): 190 if 'PADDED' in self.flags: 191 return _STRUCT_B.pack(self.pad_length) 192 return b'' 193 194 def parse_padding_data(self, data): 195 if 'PADDED' in self.flags: 196 try: 197 self.pad_length = struct.unpack('!B', data[:1])[0] 198 except struct.error: 199 raise InvalidFrameError("Invalid Padding data") 200 return 1 201 return 0 202 203 @property 204 def total_padding(self): 205 return self.pad_length 206 207 208class Priority(object): 209 """ 210 Mixin for frames that contain priority data. Defines extra fields that can 211 be used and set by frames that contain priority data. 212 """ 213 def __init__(self, 214 stream_id, 215 depends_on=0x0, 216 stream_weight=0x0, 217 exclusive=False, 218 **kwargs): 219 super(Priority, self).__init__(stream_id, **kwargs) 220 221 #: The stream ID of the stream on which this stream depends. 222 self.depends_on = depends_on 223 224 #: The weight of the stream. This is an integer between 0 and 256. 225 self.stream_weight = stream_weight 226 227 #: Whether the exclusive bit was set. 228 self.exclusive = exclusive 229 230 def serialize_priority_data(self): 231 return _STRUCT_LB.pack( 232 self.depends_on + (0x80000000 if self.exclusive else 0), 233 self.stream_weight 234 ) 235 236 def parse_priority_data(self, data): 237 try: 238 self.depends_on, self.stream_weight = _STRUCT_LB.unpack(data[:5]) 239 except struct.error: 240 raise InvalidFrameError("Invalid Priority data") 241 242 self.exclusive = True if self.depends_on >> 31 else False 243 self.depends_on &= 0x7FFFFFFF 244 return 5 245 246 247class DataFrame(Padding, Frame): 248 """ 249 DATA frames convey arbitrary, variable-length sequences of octets 250 associated with a stream. One or more DATA frames are used, for instance, 251 to carry HTTP request or response payloads. 252 """ 253 #: The flags defined for DATA frames. 254 defined_flags = [ 255 Flag('END_STREAM', 0x01), 256 Flag('PADDED', 0x08), 257 ] 258 259 #: The type byte for data frames. 260 type = 0x0 261 262 stream_association = _STREAM_ASSOC_HAS_STREAM 263 264 def __init__(self, stream_id, data=b'', **kwargs): 265 super(DataFrame, self).__init__(stream_id, **kwargs) 266 267 #: The data contained on this frame. 268 self.data = data 269 270 def serialize_body(self): 271 padding_data = self.serialize_padding_data() 272 padding = b'\0' * self.total_padding 273 if isinstance(self.data, memoryview): 274 self.data = self.data.tobytes() 275 return b''.join([padding_data, self.data, padding]) 276 277 def parse_body(self, data): 278 padding_data_length = self.parse_padding_data(data) 279 self.data = ( 280 data[padding_data_length:len(data)-self.total_padding].tobytes() 281 ) 282 self.body_len = len(data) 283 284 if self.total_padding and self.total_padding >= self.body_len: 285 raise InvalidPaddingError("Padding is too long.") 286 287 @property 288 def flow_controlled_length(self): 289 """ 290 The length of the frame that needs to be accounted for when considering 291 flow control. 292 """ 293 padding_len = 0 294 if 'PADDED' in self.flags: 295 # Account for extra 1-byte padding length field, which is still 296 # present if possibly zero-valued. 297 padding_len = self.total_padding + 1 298 return len(self.data) + padding_len 299 300 301class PriorityFrame(Priority, Frame): 302 """ 303 The PRIORITY frame specifies the sender-advised priority of a stream. It 304 can be sent at any time for an existing stream. This enables 305 reprioritisation of existing streams. 306 """ 307 #: The flags defined for PRIORITY frames. 308 defined_flags = [] 309 310 #: The type byte defined for PRIORITY frames. 311 type = 0x02 312 313 stream_association = _STREAM_ASSOC_HAS_STREAM 314 315 def serialize_body(self): 316 return self.serialize_priority_data() 317 318 def parse_body(self, data): 319 self.parse_priority_data(data) 320 self.body_len = len(data) 321 322 323class RstStreamFrame(Frame): 324 """ 325 The RST_STREAM frame allows for abnormal termination of a stream. When sent 326 by the initiator of a stream, it indicates that they wish to cancel the 327 stream or that an error condition has occurred. When sent by the receiver 328 of a stream, it indicates that either the receiver is rejecting the stream, 329 requesting that the stream be cancelled or that an error condition has 330 occurred. 331 """ 332 #: The flags defined for RST_STREAM frames. 333 defined_flags = [] 334 335 #: The type byte defined for RST_STREAM frames. 336 type = 0x03 337 338 stream_association = _STREAM_ASSOC_HAS_STREAM 339 340 def __init__(self, stream_id, error_code=0, **kwargs): 341 super(RstStreamFrame, self).__init__(stream_id, **kwargs) 342 343 #: The error code used when resetting the stream. 344 self.error_code = error_code 345 346 def serialize_body(self): 347 return _STRUCT_L.pack(self.error_code) 348 349 def parse_body(self, data): 350 if len(data) != 4: 351 raise InvalidFrameError( 352 "RST_STREAM must have 4 byte body: actual length %s." % 353 len(data) 354 ) 355 356 try: 357 self.error_code = _STRUCT_L.unpack(data)[0] 358 except struct.error: # pragma: no cover 359 raise InvalidFrameError("Invalid RST_STREAM body") 360 361 self.body_len = 4 362 363 364class SettingsFrame(Frame): 365 """ 366 The SETTINGS frame conveys configuration parameters that affect how 367 endpoints communicate. The parameters are either constraints on peer 368 behavior or preferences. 369 370 Settings are not negotiated. Settings describe characteristics of the 371 sending peer, which are used by the receiving peer. Different values for 372 the same setting can be advertised by each peer. For example, a client 373 might set a high initial flow control window, whereas a server might set a 374 lower value to conserve resources. 375 """ 376 #: The flags defined for SETTINGS frames. 377 defined_flags = [Flag('ACK', 0x01)] 378 379 #: The type byte defined for SETTINGS frames. 380 type = 0x04 381 382 stream_association = _STREAM_ASSOC_NO_STREAM 383 384 # We need to define the known settings, they may as well be class 385 # attributes. 386 #: The byte that signals the SETTINGS_HEADER_TABLE_SIZE setting. 387 HEADER_TABLE_SIZE = 0x01 388 #: The byte that signals the SETTINGS_ENABLE_PUSH setting. 389 ENABLE_PUSH = 0x02 390 #: The byte that signals the SETTINGS_MAX_CONCURRENT_STREAMS setting. 391 MAX_CONCURRENT_STREAMS = 0x03 392 #: The byte that signals the SETTINGS_INITIAL_WINDOW_SIZE setting. 393 INITIAL_WINDOW_SIZE = 0x04 394 #: The byte that signals the SETTINGS_MAX_FRAME_SIZE setting. 395 MAX_FRAME_SIZE = 0x05 396 #: The byte that signals the SETTINGS_MAX_HEADER_LIST_SIZE setting. 397 MAX_HEADER_LIST_SIZE = 0x06 398 399 def __init__(self, stream_id=0, settings=None, **kwargs): 400 super(SettingsFrame, self).__init__(stream_id, **kwargs) 401 402 if settings and "ACK" in kwargs.get("flags", ()): 403 raise ValueError("Settings must be empty if ACK flag is set.") 404 405 #: A dictionary of the setting type byte to the value of the setting. 406 self.settings = settings or {} 407 408 def serialize_body(self): 409 return b''.join([_STRUCT_HL.pack(setting & 0xFF, value) 410 for setting, value in self.settings.items()]) 411 412 def parse_body(self, data): 413 body_len = 0 414 for i in range(0, len(data), 6): 415 try: 416 name, value = _STRUCT_HL.unpack(data[i:i+6]) 417 except struct.error: 418 raise InvalidFrameError("Invalid SETTINGS body") 419 420 self.settings[name] = value 421 body_len += 6 422 423 self.body_len = body_len 424 425 426class PushPromiseFrame(Padding, Frame): 427 """ 428 The PUSH_PROMISE frame is used to notify the peer endpoint in advance of 429 streams the sender intends to initiate. 430 """ 431 #: The flags defined for PUSH_PROMISE frames. 432 defined_flags = [ 433 Flag('END_HEADERS', 0x04), 434 Flag('PADDED', 0x08) 435 ] 436 437 #: The type byte defined for PUSH_PROMISE frames. 438 type = 0x05 439 440 stream_association = _STREAM_ASSOC_HAS_STREAM 441 442 def __init__(self, stream_id, promised_stream_id=0, data=b'', **kwargs): 443 super(PushPromiseFrame, self).__init__(stream_id, **kwargs) 444 445 #: The stream ID that is promised by this frame. 446 self.promised_stream_id = promised_stream_id 447 448 #: The HPACK-encoded header block for the simulated request on the new 449 #: stream. 450 self.data = data 451 452 def serialize_body(self): 453 padding_data = self.serialize_padding_data() 454 padding = b'\0' * self.total_padding 455 data = _STRUCT_L.pack(self.promised_stream_id) 456 return b''.join([padding_data, data, self.data, padding]) 457 458 def parse_body(self, data): 459 padding_data_length = self.parse_padding_data(data) 460 461 try: 462 self.promised_stream_id = _STRUCT_L.unpack( 463 data[padding_data_length:padding_data_length + 4] 464 )[0] 465 except struct.error: 466 raise InvalidFrameError("Invalid PUSH_PROMISE body") 467 468 self.data = data[padding_data_length + 4:].tobytes() 469 self.body_len = len(data) 470 471 if self.total_padding and self.total_padding >= self.body_len: 472 raise InvalidPaddingError("Padding is too long.") 473 474 475class PingFrame(Frame): 476 """ 477 The PING frame is a mechanism for measuring a minimal round-trip time from 478 the sender, as well as determining whether an idle connection is still 479 functional. PING frames can be sent from any endpoint. 480 """ 481 #: The flags defined for PING frames. 482 defined_flags = [Flag('ACK', 0x01)] 483 484 #: The type byte defined for PING frames. 485 type = 0x06 486 487 stream_association = _STREAM_ASSOC_NO_STREAM 488 489 def __init__(self, stream_id=0, opaque_data=b'', **kwargs): 490 super(PingFrame, self).__init__(stream_id, **kwargs) 491 492 #: The opaque data sent in this PING frame, as a bytestring. 493 self.opaque_data = opaque_data 494 495 def serialize_body(self): 496 if len(self.opaque_data) > 8: 497 raise InvalidFrameError( 498 "PING frame may not have more than 8 bytes of data, got %s" % 499 self.opaque_data 500 ) 501 502 data = self.opaque_data 503 data += b'\x00' * (8 - len(self.opaque_data)) 504 return data 505 506 def parse_body(self, data): 507 if len(data) != 8: 508 raise InvalidFrameError( 509 "PING frame must have 8 byte length: got %s" % len(data) 510 ) 511 512 self.opaque_data = data.tobytes() 513 self.body_len = 8 514 515 516class GoAwayFrame(Frame): 517 """ 518 The GOAWAY frame informs the remote peer to stop creating streams on this 519 connection. It can be sent from the client or the server. Once sent, the 520 sender will ignore frames sent on new streams for the remainder of the 521 connection. 522 """ 523 #: The flags defined for GOAWAY frames. 524 defined_flags = [] 525 526 #: The type byte defined for GOAWAY frames. 527 type = 0x07 528 529 stream_association = _STREAM_ASSOC_NO_STREAM 530 531 def __init__(self, 532 stream_id=0, 533 last_stream_id=0, 534 error_code=0, 535 additional_data=b'', 536 **kwargs): 537 super(GoAwayFrame, self).__init__(stream_id, **kwargs) 538 539 #: The last stream ID definitely seen by the remote peer. 540 self.last_stream_id = last_stream_id 541 542 #: The error code for connection teardown. 543 self.error_code = error_code 544 545 #: Any additional data sent in the GOAWAY. 546 self.additional_data = additional_data 547 548 def serialize_body(self): 549 data = _STRUCT_LL.pack( 550 self.last_stream_id & 0x7FFFFFFF, 551 self.error_code 552 ) 553 data += self.additional_data 554 555 return data 556 557 def parse_body(self, data): 558 try: 559 self.last_stream_id, self.error_code = _STRUCT_LL.unpack( 560 data[:8] 561 ) 562 except struct.error: 563 raise InvalidFrameError("Invalid GOAWAY body.") 564 565 self.body_len = len(data) 566 567 if len(data) > 8: 568 self.additional_data = data[8:].tobytes() 569 570 571class WindowUpdateFrame(Frame): 572 """ 573 The WINDOW_UPDATE frame is used to implement flow control. 574 575 Flow control operates at two levels: on each individual stream and on the 576 entire connection. 577 578 Both types of flow control are hop by hop; that is, only between the two 579 endpoints. Intermediaries do not forward WINDOW_UPDATE frames between 580 dependent connections. However, throttling of data transfer by any receiver 581 can indirectly cause the propagation of flow control information toward the 582 original sender. 583 """ 584 #: The flags defined for WINDOW_UPDATE frames. 585 defined_flags = [] 586 587 #: The type byte defined for WINDOW_UPDATE frames. 588 type = 0x08 589 590 stream_association = _STREAM_ASSOC_EITHER 591 592 def __init__(self, stream_id, window_increment=0, **kwargs): 593 super(WindowUpdateFrame, self).__init__(stream_id, **kwargs) 594 595 #: The amount the flow control window is to be incremented. 596 self.window_increment = window_increment 597 598 def serialize_body(self): 599 return _STRUCT_L.pack(self.window_increment & 0x7FFFFFFF) 600 601 def parse_body(self, data): 602 try: 603 self.window_increment = _STRUCT_L.unpack(data)[0] 604 except struct.error: 605 raise InvalidFrameError("Invalid WINDOW_UPDATE body") 606 607 self.body_len = 4 608 609 610class HeadersFrame(Padding, Priority, Frame): 611 """ 612 The HEADERS frame carries name-value pairs. It is used to open a stream. 613 HEADERS frames can be sent on a stream in the "open" or "half closed 614 (remote)" states. 615 616 The HeadersFrame class is actually basically a data frame in this 617 implementation, because of the requirement to control the sizes of frames. 618 A header block fragment that doesn't fit in an entire HEADERS frame needs 619 to be followed with CONTINUATION frames. From the perspective of the frame 620 building code the header block is an opaque data segment. 621 """ 622 #: The flags defined for HEADERS frames. 623 defined_flags = [ 624 Flag('END_STREAM', 0x01), 625 Flag('END_HEADERS', 0x04), 626 Flag('PADDED', 0x08), 627 Flag('PRIORITY', 0x20), 628 ] 629 630 #: The type byte defined for HEADERS frames. 631 type = 0x01 632 633 stream_association = _STREAM_ASSOC_HAS_STREAM 634 635 def __init__(self, stream_id, data=b'', **kwargs): 636 super(HeadersFrame, self).__init__(stream_id, **kwargs) 637 638 #: The HPACK-encoded header block. 639 self.data = data 640 641 def serialize_body(self): 642 padding_data = self.serialize_padding_data() 643 padding = b'\0' * self.total_padding 644 645 if 'PRIORITY' in self.flags: 646 priority_data = self.serialize_priority_data() 647 else: 648 priority_data = b'' 649 650 return b''.join([padding_data, priority_data, self.data, padding]) 651 652 def parse_body(self, data): 653 padding_data_length = self.parse_padding_data(data) 654 data = data[padding_data_length:] 655 656 if 'PRIORITY' in self.flags: 657 priority_data_length = self.parse_priority_data(data) 658 else: 659 priority_data_length = 0 660 661 self.body_len = len(data) 662 self.data = ( 663 data[priority_data_length:len(data)-self.total_padding].tobytes() 664 ) 665 666 if self.total_padding and self.total_padding >= self.body_len: 667 raise InvalidPaddingError("Padding is too long.") 668 669 670class ContinuationFrame(Frame): 671 """ 672 The CONTINUATION frame is used to continue a sequence of header block 673 fragments. Any number of CONTINUATION frames can be sent on an existing 674 stream, as long as the preceding frame on the same stream is one of 675 HEADERS, PUSH_PROMISE or CONTINUATION without the END_HEADERS flag set. 676 677 Much like the HEADERS frame, hyper treats this as an opaque data frame with 678 different flags and a different type. 679 """ 680 #: The flags defined for CONTINUATION frames. 681 defined_flags = [Flag('END_HEADERS', 0x04)] 682 683 #: The type byte defined for CONTINUATION frames. 684 type = 0x09 685 686 stream_association = _STREAM_ASSOC_HAS_STREAM 687 688 def __init__(self, stream_id, data=b'', **kwargs): 689 super(ContinuationFrame, self).__init__(stream_id, **kwargs) 690 691 #: The HPACK-encoded header block. 692 self.data = data 693 694 def serialize_body(self): 695 return self.data 696 697 def parse_body(self, data): 698 self.data = data.tobytes() 699 self.body_len = len(data) 700 701 702class AltSvcFrame(Frame): 703 """ 704 The ALTSVC frame is used to advertise alternate services that the current 705 host, or a different one, can understand. This frame is standardised as 706 part of RFC 7838. 707 708 This frame does no work to validate that the ALTSVC field parameter is 709 acceptable per the rules of RFC 7838. 710 711 .. note:: If the ``stream_id`` of this frame is nonzero, the origin field 712 must have zero length. Conversely, if the ``stream_id`` of this 713 frame is zero, the origin field must have nonzero length. Put 714 another way, a valid ALTSVC frame has ``stream_id != 0`` XOR 715 ``len(origin) != 0``. 716 """ 717 type = 0xA 718 719 stream_association = _STREAM_ASSOC_EITHER 720 721 def __init__(self, stream_id, origin=b'', field=b'', **kwargs): 722 super(AltSvcFrame, self).__init__(stream_id, **kwargs) 723 724 if not isinstance(origin, bytes): 725 raise ValueError("AltSvc origin must be bytestring.") 726 if not isinstance(field, bytes): 727 raise ValueError("AltSvc field must be a bytestring.") 728 self.origin = origin 729 self.field = field 730 731 def serialize_body(self): 732 origin_len = _STRUCT_H.pack(len(self.origin)) 733 return b''.join([origin_len, self.origin, self.field]) 734 735 def parse_body(self, data): 736 try: 737 origin_len = _STRUCT_H.unpack(data[0:2])[0] 738 self.origin = data[2:2+origin_len].tobytes() 739 740 if len(self.origin) != origin_len: 741 raise InvalidFrameError("Invalid ALTSVC frame body.") 742 743 self.field = data[2+origin_len:].tobytes() 744 except (struct.error, ValueError): 745 raise InvalidFrameError("Invalid ALTSVC frame body.") 746 747 self.body_len = len(data) 748 749 750class ExtensionFrame(Frame): 751 """ 752 ExtensionFrame is used to wrap frames which are not natively interpretable 753 by hyperframe. 754 755 Although certain byte prefixes are ordained by specification to have 756 certain contextual meanings, frames with other prefixes are not prohibited, 757 and may be used to communicate arbitrary meaning between HTTP/2 peers. 758 759 Thus, hyperframe, rather than raising an exception when such a frame is 760 encountered, wraps it in a generic frame to be properly acted upon by 761 upstream consumers which might have additional context on how to use it. 762 763 .. versionadded:: 5.0.0 764 """ 765 766 stream_association = _STREAM_ASSOC_EITHER 767 768 def __init__(self, type, stream_id, **kwargs): 769 super(ExtensionFrame, self).__init__(stream_id, **kwargs) 770 self.type = type 771 self.flag_byte = None 772 773 def parse_flags(self, flag_byte): 774 """ 775 For extension frames, we parse the flags by just storing a flag byte. 776 """ 777 self.flag_byte = flag_byte 778 779 def parse_body(self, data): 780 self.body = data.tobytes() 781 self.body_len = len(data) 782 783 def serialize(self): 784 """ 785 A broad override of the serialize method that ensures that the data 786 comes back out exactly as it came in. This should not be used in most 787 user code: it exists only as a helper method if frames need to be 788 reconstituted. 789 """ 790 # Build the frame header. 791 # First, get the flags. 792 flags = self.flag_byte 793 794 header = _STRUCT_HBBBL.pack( 795 (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits 796 self.body_len & 0xFF, 797 self.type, 798 flags, 799 self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits. 800 ) 801 802 return header + self.body 803 804 805_FRAME_CLASSES = [ 806 DataFrame, 807 HeadersFrame, 808 PriorityFrame, 809 RstStreamFrame, 810 SettingsFrame, 811 PushPromiseFrame, 812 PingFrame, 813 GoAwayFrame, 814 WindowUpdateFrame, 815 ContinuationFrame, 816 AltSvcFrame, 817] 818#: FRAMES maps the type byte for each frame to the class used to represent that 819#: frame. 820FRAMES = {cls.type: cls for cls in _FRAME_CLASSES} 821