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