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    #: The byte that signals SETTINGS_ENABLE_CONNECT_PROTOCOL setting.
399    ENABLE_CONNECT_PROTOCOL = 0x08
400
401    def __init__(self, stream_id=0, settings=None, **kwargs):
402        super(SettingsFrame, self).__init__(stream_id, **kwargs)
403
404        if settings and "ACK" in kwargs.get("flags", ()):
405            raise ValueError("Settings must be empty if ACK flag is set.")
406
407        #: A dictionary of the setting type byte to the value of the setting.
408        self.settings = settings or {}
409
410    def serialize_body(self):
411        return b''.join([_STRUCT_HL.pack(setting & 0xFF, value)
412                         for setting, value in self.settings.items()])
413
414    def parse_body(self, data):
415        body_len = 0
416        for i in range(0, len(data), 6):
417            try:
418                name, value = _STRUCT_HL.unpack(data[i:i+6])
419            except struct.error:
420                raise InvalidFrameError("Invalid SETTINGS body")
421
422            self.settings[name] = value
423            body_len += 6
424
425        self.body_len = body_len
426
427
428class PushPromiseFrame(Padding, Frame):
429    """
430    The PUSH_PROMISE frame is used to notify the peer endpoint in advance of
431    streams the sender intends to initiate.
432    """
433    #: The flags defined for PUSH_PROMISE frames.
434    defined_flags = [
435        Flag('END_HEADERS', 0x04),
436        Flag('PADDED', 0x08)
437    ]
438
439    #: The type byte defined for PUSH_PROMISE frames.
440    type = 0x05
441
442    stream_association = _STREAM_ASSOC_HAS_STREAM
443
444    def __init__(self, stream_id, promised_stream_id=0, data=b'', **kwargs):
445        super(PushPromiseFrame, self).__init__(stream_id, **kwargs)
446
447        #: The stream ID that is promised by this frame.
448        self.promised_stream_id = promised_stream_id
449
450        #: The HPACK-encoded header block for the simulated request on the new
451        #: stream.
452        self.data = data
453
454    def serialize_body(self):
455        padding_data = self.serialize_padding_data()
456        padding = b'\0' * self.total_padding
457        data = _STRUCT_L.pack(self.promised_stream_id)
458        return b''.join([padding_data, data, self.data, padding])
459
460    def parse_body(self, data):
461        padding_data_length = self.parse_padding_data(data)
462
463        try:
464            self.promised_stream_id = _STRUCT_L.unpack(
465                data[padding_data_length:padding_data_length + 4]
466            )[0]
467        except struct.error:
468            raise InvalidFrameError("Invalid PUSH_PROMISE body")
469
470        self.data = data[padding_data_length + 4:].tobytes()
471        self.body_len = len(data)
472
473        if self.total_padding and self.total_padding >= self.body_len:
474            raise InvalidPaddingError("Padding is too long.")
475
476
477class PingFrame(Frame):
478    """
479    The PING frame is a mechanism for measuring a minimal round-trip time from
480    the sender, as well as determining whether an idle connection is still
481    functional. PING frames can be sent from any endpoint.
482    """
483    #: The flags defined for PING frames.
484    defined_flags = [Flag('ACK', 0x01)]
485
486    #: The type byte defined for PING frames.
487    type = 0x06
488
489    stream_association = _STREAM_ASSOC_NO_STREAM
490
491    def __init__(self, stream_id=0, opaque_data=b'', **kwargs):
492        super(PingFrame, self).__init__(stream_id, **kwargs)
493
494        #: The opaque data sent in this PING frame, as a bytestring.
495        self.opaque_data = opaque_data
496
497    def serialize_body(self):
498        if len(self.opaque_data) > 8:
499            raise InvalidFrameError(
500                "PING frame may not have more than 8 bytes of data, got %s" %
501                self.opaque_data
502            )
503
504        data = self.opaque_data
505        data += b'\x00' * (8 - len(self.opaque_data))
506        return data
507
508    def parse_body(self, data):
509        if len(data) != 8:
510            raise InvalidFrameError(
511                "PING frame must have 8 byte length: got %s" % len(data)
512            )
513
514        self.opaque_data = data.tobytes()
515        self.body_len = 8
516
517
518class GoAwayFrame(Frame):
519    """
520    The GOAWAY frame informs the remote peer to stop creating streams on this
521    connection. It can be sent from the client or the server. Once sent, the
522    sender will ignore frames sent on new streams for the remainder of the
523    connection.
524    """
525    #: The flags defined for GOAWAY frames.
526    defined_flags = []
527
528    #: The type byte defined for GOAWAY frames.
529    type = 0x07
530
531    stream_association = _STREAM_ASSOC_NO_STREAM
532
533    def __init__(self,
534                 stream_id=0,
535                 last_stream_id=0,
536                 error_code=0,
537                 additional_data=b'',
538                 **kwargs):
539        super(GoAwayFrame, self).__init__(stream_id, **kwargs)
540
541        #: The last stream ID definitely seen by the remote peer.
542        self.last_stream_id = last_stream_id
543
544        #: The error code for connection teardown.
545        self.error_code = error_code
546
547        #: Any additional data sent in the GOAWAY.
548        self.additional_data = additional_data
549
550    def serialize_body(self):
551        data = _STRUCT_LL.pack(
552            self.last_stream_id & 0x7FFFFFFF,
553            self.error_code
554        )
555        data += self.additional_data
556
557        return data
558
559    def parse_body(self, data):
560        try:
561            self.last_stream_id, self.error_code = _STRUCT_LL.unpack(
562                data[:8]
563            )
564        except struct.error:
565            raise InvalidFrameError("Invalid GOAWAY body.")
566
567        self.body_len = len(data)
568
569        if len(data) > 8:
570            self.additional_data = data[8:].tobytes()
571
572
573class WindowUpdateFrame(Frame):
574    """
575    The WINDOW_UPDATE frame is used to implement flow control.
576
577    Flow control operates at two levels: on each individual stream and on the
578    entire connection.
579
580    Both types of flow control are hop by hop; that is, only between the two
581    endpoints. Intermediaries do not forward WINDOW_UPDATE frames between
582    dependent connections. However, throttling of data transfer by any receiver
583    can indirectly cause the propagation of flow control information toward the
584    original sender.
585    """
586    #: The flags defined for WINDOW_UPDATE frames.
587    defined_flags = []
588
589    #: The type byte defined for WINDOW_UPDATE frames.
590    type = 0x08
591
592    stream_association = _STREAM_ASSOC_EITHER
593
594    def __init__(self, stream_id, window_increment=0, **kwargs):
595        super(WindowUpdateFrame, self).__init__(stream_id, **kwargs)
596
597        #: The amount the flow control window is to be incremented.
598        self.window_increment = window_increment
599
600    def serialize_body(self):
601        return _STRUCT_L.pack(self.window_increment & 0x7FFFFFFF)
602
603    def parse_body(self, data):
604        try:
605            self.window_increment = _STRUCT_L.unpack(data)[0]
606        except struct.error:
607            raise InvalidFrameError("Invalid WINDOW_UPDATE body")
608
609        self.body_len = 4
610
611
612class HeadersFrame(Padding, Priority, Frame):
613    """
614    The HEADERS frame carries name-value pairs. It is used to open a stream.
615    HEADERS frames can be sent on a stream in the "open" or "half closed
616    (remote)" states.
617
618    The HeadersFrame class is actually basically a data frame in this
619    implementation, because of the requirement to control the sizes of frames.
620    A header block fragment that doesn't fit in an entire HEADERS frame needs
621    to be followed with CONTINUATION frames. From the perspective of the frame
622    building code the header block is an opaque data segment.
623    """
624    #: The flags defined for HEADERS frames.
625    defined_flags = [
626        Flag('END_STREAM', 0x01),
627        Flag('END_HEADERS', 0x04),
628        Flag('PADDED', 0x08),
629        Flag('PRIORITY', 0x20),
630    ]
631
632    #: The type byte defined for HEADERS frames.
633    type = 0x01
634
635    stream_association = _STREAM_ASSOC_HAS_STREAM
636
637    def __init__(self, stream_id, data=b'', **kwargs):
638        super(HeadersFrame, self).__init__(stream_id, **kwargs)
639
640        #: The HPACK-encoded header block.
641        self.data = data
642
643    def serialize_body(self):
644        padding_data = self.serialize_padding_data()
645        padding = b'\0' * self.total_padding
646
647        if 'PRIORITY' in self.flags:
648            priority_data = self.serialize_priority_data()
649        else:
650            priority_data = b''
651
652        return b''.join([padding_data, priority_data, self.data, padding])
653
654    def parse_body(self, data):
655        padding_data_length = self.parse_padding_data(data)
656        data = data[padding_data_length:]
657
658        if 'PRIORITY' in self.flags:
659            priority_data_length = self.parse_priority_data(data)
660        else:
661            priority_data_length = 0
662
663        self.body_len = len(data)
664        self.data = (
665            data[priority_data_length:len(data)-self.total_padding].tobytes()
666        )
667
668        if self.total_padding and self.total_padding >= self.body_len:
669            raise InvalidPaddingError("Padding is too long.")
670
671
672class ContinuationFrame(Frame):
673    """
674    The CONTINUATION frame is used to continue a sequence of header block
675    fragments. Any number of CONTINUATION frames can be sent on an existing
676    stream, as long as the preceding frame on the same stream is one of
677    HEADERS, PUSH_PROMISE or CONTINUATION without the END_HEADERS flag set.
678
679    Much like the HEADERS frame, hyper treats this as an opaque data frame with
680    different flags and a different type.
681    """
682    #: The flags defined for CONTINUATION frames.
683    defined_flags = [Flag('END_HEADERS', 0x04)]
684
685    #: The type byte defined for CONTINUATION frames.
686    type = 0x09
687
688    stream_association = _STREAM_ASSOC_HAS_STREAM
689
690    def __init__(self, stream_id, data=b'', **kwargs):
691        super(ContinuationFrame, self).__init__(stream_id, **kwargs)
692
693        #: The HPACK-encoded header block.
694        self.data = data
695
696    def serialize_body(self):
697        return self.data
698
699    def parse_body(self, data):
700        self.data = data.tobytes()
701        self.body_len = len(data)
702
703
704class AltSvcFrame(Frame):
705    """
706    The ALTSVC frame is used to advertise alternate services that the current
707    host, or a different one, can understand. This frame is standardised as
708    part of RFC 7838.
709
710    This frame does no work to validate that the ALTSVC field parameter is
711    acceptable per the rules of RFC 7838.
712
713    .. note:: If the ``stream_id`` of this frame is nonzero, the origin field
714              must have zero length. Conversely, if the ``stream_id`` of this
715              frame is zero, the origin field must have nonzero length. Put
716              another way, a valid ALTSVC frame has ``stream_id != 0`` XOR
717              ``len(origin) != 0``.
718    """
719    type = 0xA
720
721    stream_association = _STREAM_ASSOC_EITHER
722
723    def __init__(self, stream_id, origin=b'', field=b'', **kwargs):
724        super(AltSvcFrame, self).__init__(stream_id, **kwargs)
725
726        if not isinstance(origin, bytes):
727            raise ValueError("AltSvc origin must be bytestring.")
728        if not isinstance(field, bytes):
729            raise ValueError("AltSvc field must be a bytestring.")
730        self.origin = origin
731        self.field = field
732
733    def serialize_body(self):
734        origin_len = _STRUCT_H.pack(len(self.origin))
735        return b''.join([origin_len, self.origin, self.field])
736
737    def parse_body(self, data):
738        try:
739            origin_len = _STRUCT_H.unpack(data[0:2])[0]
740            self.origin = data[2:2+origin_len].tobytes()
741
742            if len(self.origin) != origin_len:
743                raise InvalidFrameError("Invalid ALTSVC frame body.")
744
745            self.field = data[2+origin_len:].tobytes()
746        except (struct.error, ValueError):
747            raise InvalidFrameError("Invalid ALTSVC frame body.")
748
749        self.body_len = len(data)
750
751
752class ExtensionFrame(Frame):
753    """
754    ExtensionFrame is used to wrap frames which are not natively interpretable
755    by hyperframe.
756
757    Although certain byte prefixes are ordained by specification to have
758    certain contextual meanings, frames with other prefixes are not prohibited,
759    and may be used to communicate arbitrary meaning between HTTP/2 peers.
760
761    Thus, hyperframe, rather than raising an exception when such a frame is
762    encountered, wraps it in a generic frame to be properly acted upon by
763    upstream consumers which might have additional context on how to use it.
764
765    .. versionadded:: 5.0.0
766    """
767
768    stream_association = _STREAM_ASSOC_EITHER
769
770    def __init__(self, type, stream_id, **kwargs):
771        super(ExtensionFrame, self).__init__(stream_id, **kwargs)
772        self.type = type
773        self.flag_byte = None
774
775    def parse_flags(self, flag_byte):
776        """
777        For extension frames, we parse the flags by just storing a flag byte.
778        """
779        self.flag_byte = flag_byte
780
781    def parse_body(self, data):
782        self.body = data.tobytes()
783        self.body_len = len(data)
784
785    def serialize(self):
786        """
787        A broad override of the serialize method that ensures that the data
788        comes back out exactly as it came in. This should not be used in most
789        user code: it exists only as a helper method if frames need to be
790        reconstituted.
791        """
792        # Build the frame header.
793        # First, get the flags.
794        flags = self.flag_byte
795
796        header = _STRUCT_HBBBL.pack(
797            (self.body_len >> 8) & 0xFFFF,  # Length spread over top 24 bits
798            self.body_len & 0xFF,
799            self.type,
800            flags,
801            self.stream_id & 0x7FFFFFFF  # Stream ID is 32 bits.
802        )
803
804        return header + self.body
805
806
807_FRAME_CLASSES = [
808    DataFrame,
809    HeadersFrame,
810    PriorityFrame,
811    RstStreamFrame,
812    SettingsFrame,
813    PushPromiseFrame,
814    PingFrame,
815    GoAwayFrame,
816    WindowUpdateFrame,
817    ContinuationFrame,
818    AltSvcFrame,
819]
820#: FRAMES maps the type byte for each frame to the class used to represent that
821#: frame.
822FRAMES = {cls.type: cls for cls in _FRAME_CLASSES}
823