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 collections
11import struct
12
13from .flags import Flag, Flags
14
15# The maximum initial length of a frame. Some frames have shorter maximum lengths.
16FRAME_MAX_LEN = (2 ** 14)
17
18# The maximum allowed length of a frame.
19FRAME_MAX_ALLOWED_LEN = (2 ** 24) - 1
20
21
22class Frame(object):
23    """
24    The base class for all HTTP/2 frames.
25    """
26    # The flags defined on this type of frame.
27    defined_flags = []
28
29    # The type of the frame.
30    type = None
31
32    # If 'has-stream', the frame's stream_id must be non-zero. If 'no-stream',
33    # it must be zero. If 'either', it's not checked.
34    stream_association = None
35
36    def __init__(self, stream_id, flags=()):
37        self.stream_id = stream_id
38        self.flags = Flags(self.defined_flags)
39        self.body_len = 0
40
41        for flag in flags:
42            self.flags.add(flag)
43
44        if self.stream_association == 'has-stream' and not self.stream_id:
45            raise ValueError('Stream ID must be non-zero')
46        if self.stream_association == 'no-stream' and self.stream_id:
47            raise ValueError('Stream ID must be zero')
48
49    def __repr__(self):
50        flags = ", ".join(self.flags) or "None"
51        body = self.serialize_body()
52        if len(body) > 100:
53            body = str(body[:100]) + "..."
54        return (
55            "{type}(Stream: {stream}; Flags: {flags}): {body}"
56        ).format(type=type(self).__name__, stream=self.stream_id, flags=flags, body=body)
57
58    @staticmethod
59    def parse_frame_header(header):
60        """
61        Takes a 9-byte frame header and returns a tuple of the appropriate
62        Frame object and the length that needs to be read from the socket.
63        """
64        fields = struct.unpack("!HBBBL", header)
65        # First 24 bits are frame length.
66        length = (fields[0] << 8) + fields[1]
67        type = fields[2]
68        flags = fields[3]
69        stream_id = fields[4]
70
71        if type not in FRAMES:
72            raise ValueError("Unknown frame type %d" % type)
73
74        frame = FRAMES[type](stream_id)
75        frame.parse_flags(flags)
76        return (frame, length)
77
78    def parse_flags(self, flag_byte):
79        for flag, flag_bit in self.defined_flags:
80            if flag_byte & flag_bit:
81                self.flags.add(flag)
82
83        return self.flags
84
85    def serialize(self):
86        body = self.serialize_body()
87        self.body_len = len(body)
88
89        # Build the common frame header.
90        # First, get the flags.
91        flags = 0
92
93        for flag, flag_bit in self.defined_flags:
94            if flag in self.flags:
95                flags |= flag_bit
96
97        header = struct.pack(
98            "!HBBBL",
99            (self.body_len & 0xFFFF00) >> 8,  # Length is spread over top 24 bits
100            self.body_len & 0x0000FF,
101            self.type,
102            flags,
103            self.stream_id & 0x7FFFFFFF  # Stream ID is 32 bits.
104        )
105
106        return header + body
107
108    def serialize_body(self):
109        raise NotImplementedError()
110
111    def parse_body(self, data):
112        raise NotImplementedError()
113
114
115class Padding(object):
116    """
117    Mixin for frames that contain padding.
118    """
119    def __init__(self, stream_id, pad_length=0, **kwargs):
120        super(Padding, self).__init__(stream_id, **kwargs)
121
122        self.pad_length = pad_length
123
124
125    def serialize_padding_data(self):
126        if 'PADDED' in self.flags:
127            return struct.pack('!B', self.pad_length)
128        return b''
129
130    def parse_padding_data(self, data):
131        if 'PADDED' in self.flags:
132            self.pad_length = struct.unpack('!B', data[:1])[0]
133            return 1
134        return 0
135
136    @property
137    def total_padding(self):
138        """Return the total length of the padding, if any."""
139        return self.pad_length
140
141
142class Priority(object):
143    """
144    Mixin for frames that contain priority data.
145    """
146    def __init__(self, stream_id, depends_on=None, stream_weight=None, exclusive=None, **kwargs):
147        super(Priority, self).__init__(stream_id, **kwargs)
148
149        # The stream ID of the stream on which this stream depends.
150        self.depends_on = depends_on
151
152        # The weight of the stream. This is an integer between 0 and 256.
153        self.stream_weight = stream_weight
154
155        # Whether the exclusive bit was set.
156        self.exclusive = exclusive
157
158    def serialize_priority_data(self):
159        return struct.pack(
160            "!LB",
161            self.depends_on | (int(self.exclusive) << 31),
162            self.stream_weight
163        )
164
165    def parse_priority_data(self, data):
166        MASK = 0x80000000
167        self.depends_on, self.stream_weight = struct.unpack(
168            "!LB", data[:5]
169        )
170        self.exclusive = bool(self.depends_on & MASK)
171        self.depends_on &= ~MASK
172        return 5
173
174
175class DataFrame(Padding, Frame):
176    """
177    DATA frames convey arbitrary, variable-length sequences of octets
178    associated with a stream. One or more DATA frames are used, for instance,
179    to carry HTTP request or response payloads.
180    """
181    defined_flags = [
182        Flag('END_STREAM', 0x01),
183        Flag('PADDED', 0x08),
184    ]
185
186    type = 0x0
187
188    stream_association = 'has-stream'
189
190    def __init__(self, stream_id, data=b'', **kwargs):
191        super(DataFrame, self).__init__(stream_id, **kwargs)
192
193        self.data = data
194
195    def serialize_body(self):
196        padding_data = self.serialize_padding_data()
197        padding = b'\0' * self.total_padding
198        return b''.join([padding_data, self.data, padding])
199
200    def parse_body(self, data):
201        padding_data_length = self.parse_padding_data(data)
202        self.data = data[padding_data_length:len(data)-self.total_padding].tobytes()
203        self.body_len = len(data)
204
205    @property
206    def flow_controlled_length(self):
207        """
208        If the frame is padded we need to include the padding length byte in
209        the flow control used.
210        """
211        padding_len = self.total_padding + 1 if self.total_padding else 0
212        return len(self.data) + padding_len
213
214
215class PriorityFrame(Priority, Frame):
216    """
217    The PRIORITY frame specifies the sender-advised priority of a stream. It
218    can be sent at any time for an existing stream. This enables
219    reprioritisation of existing streams.
220    """
221    defined_flags = []
222
223    type = 0x02
224
225    stream_association = 'has-stream'
226
227    def serialize_body(self):
228        return self.serialize_priority_data()
229
230    def parse_body(self, data):
231        self.parse_priority_data(data)
232        self.body_len = len(data)
233
234
235class RstStreamFrame(Frame):
236    """
237    The RST_STREAM frame allows for abnormal termination of a stream. When sent
238    by the initiator of a stream, it indicates that they wish to cancel the
239    stream or that an error condition has occurred. When sent by the receiver
240    of a stream, it indicates that either the receiver is rejecting the stream,
241    requesting that the stream be cancelled or that an error condition has
242    occurred.
243    """
244    defined_flags = []
245
246    type = 0x03
247
248    stream_association = 'has-stream'
249
250    def __init__(self, stream_id, error_code=0, **kwargs):
251        super(RstStreamFrame, self).__init__(stream_id, **kwargs)
252
253        self.error_code = error_code
254
255    def serialize_body(self):
256        return struct.pack("!L", self.error_code)
257
258    def parse_body(self, data):
259        if len(data) != 4:
260            raise ValueError()
261
262        self.error_code = struct.unpack("!L", data)[0]
263        self.body_len = len(data)
264
265
266class SettingsFrame(Frame):
267    """
268    The SETTINGS frame conveys configuration parameters that affect how
269    endpoints communicate. The parameters are either constraints on peer
270    behavior or preferences.
271
272    Settings are not negotiated. Settings describe characteristics of the
273    sending peer, which are used by the receiving peer. Different values for
274    the same setting can be advertised by each peer. For example, a client
275    might set a high initial flow control window, whereas a server might set a
276    lower value to conserve resources.
277    """
278    defined_flags = [Flag('ACK', 0x01)]
279
280    type = 0x04
281
282    stream_association = 'no-stream'
283
284    # We need to define the known settings, they may as well be class
285    # attributes.
286    HEADER_TABLE_SIZE             = 0x01
287    ENABLE_PUSH                   = 0x02
288    MAX_CONCURRENT_STREAMS        = 0x03
289    INITIAL_WINDOW_SIZE           = 0x04
290    SETTINGS_MAX_FRAME_SIZE       = 0x05
291    SETTINGS_MAX_HEADER_LIST_SIZE = 0x06
292
293    def __init__(self, stream_id=0, settings=None, **kwargs):
294        super(SettingsFrame, self).__init__(stream_id, **kwargs)
295
296        if settings and "ACK" in kwargs.get("flags", ()):
297            raise ValueError("Settings must be empty if ACK flag is set.")
298
299        # A dictionary of the setting type byte to the value.
300        self.settings = settings or {}
301
302    def serialize_body(self):
303        settings = [struct.pack("!HL", setting & 0xFF, value)
304                    for setting, value in self.settings.items()]
305        return b''.join(settings)
306
307    def parse_body(self, data):
308        for i in range(0, len(data), 6):
309            name, value = struct.unpack("!HL", data[i:i+6])
310            self.settings[name] = value
311
312        self.body_len = len(data)
313
314
315class PushPromiseFrame(Padding, Frame):
316    """
317    The PUSH_PROMISE frame is used to notify the peer endpoint in advance of
318    streams the sender intends to initiate.
319    """
320    defined_flags = [
321        Flag('END_HEADERS', 0x04),
322        Flag('PADDED', 0x08)
323    ]
324
325    type = 0x05
326
327    stream_association = 'has-stream'
328
329    def __init__(self, stream_id, promised_stream_id=0, data=b'', **kwargs):
330        super(PushPromiseFrame, self).__init__(stream_id, **kwargs)
331
332        self.promised_stream_id = promised_stream_id
333        self.data = data
334
335    def serialize_body(self):
336        padding_data = self.serialize_padding_data()
337        padding = b'\0' * self.total_padding
338        data = struct.pack("!L", self.promised_stream_id)
339        return b''.join([padding_data, data, self.data, padding])
340
341    def parse_body(self, data):
342        padding_data_length = self.parse_padding_data(data)
343        self.promised_stream_id = struct.unpack("!L", data[padding_data_length:padding_data_length + 4])[0]
344        self.data = data[padding_data_length + 4:].tobytes()
345        self.body_len = len(data)
346
347
348class PingFrame(Frame):
349    """
350    The PING frame is a mechanism for measuring a minimal round-trip time from
351    the sender, as well as determining whether an idle connection is still
352    functional. PING frames can be sent from any endpoint.
353    """
354    defined_flags = [Flag('ACK', 0x01)]
355
356    type = 0x06
357
358    stream_association = 'no-stream'
359
360    def __init__(self, stream_id=0, opaque_data=b'', **kwargs):
361        super(PingFrame, self).__init__(stream_id, **kwargs)
362
363        self.opaque_data = opaque_data
364
365    def serialize_body(self):
366        if len(self.opaque_data) > 8:
367            raise ValueError()
368
369        data = self.opaque_data
370        data += b'\x00' * (8 - len(self.opaque_data))
371        return data
372
373    def parse_body(self, data):
374        if len(data) > 8:
375            raise ValueError()
376
377        self.opaque_data = data.tobytes()
378        self.body_len = len(data)
379
380
381class GoAwayFrame(Frame):
382    """
383    The GOAWAY frame informs the remote peer to stop creating streams on this
384    connection. It can be sent from the client or the server. Once sent, the
385    sender will ignore frames sent on new streams for the remainder of the
386    connection.
387    """
388    type = 0x07
389
390    stream_association = 'no-stream'
391
392    def __init__(self, stream_id=0, last_stream_id=0, error_code=0, additional_data=b'', **kwargs):
393        super(GoAwayFrame, self).__init__(stream_id, **kwargs)
394
395        self.last_stream_id = last_stream_id
396        self.error_code = error_code
397        self.additional_data = additional_data
398
399    def serialize_body(self):
400        data = struct.pack(
401            "!LL",
402            self.last_stream_id & 0x7FFFFFFF,
403            self.error_code
404        )
405        data += self.additional_data
406
407        return data
408
409    def parse_body(self, data):
410        self.last_stream_id, self.error_code = struct.unpack("!LL", data[:8])
411        self.body_len = len(data)
412
413        if len(data) > 8:
414            self.additional_data = data[8:].tobytes()
415
416
417class WindowUpdateFrame(Frame):
418    """
419    The WINDOW_UPDATE frame is used to implement flow control.
420
421    Flow control operates at two levels: on each individual stream and on the
422    entire connection.
423
424    Both types of flow control are hop by hop; that is, only between the two
425    endpoints. Intermediaries do not forward WINDOW_UPDATE frames between
426    dependent connections. However, throttling of data transfer by any receiver
427    can indirectly cause the propagation of flow control information toward the
428    original sender.
429    """
430    type = 0x08
431
432    stream_association = 'either'
433
434    def __init__(self, stream_id, window_increment=0, **kwargs):
435        super(WindowUpdateFrame, self).__init__(stream_id, **kwargs)
436
437        self.window_increment = window_increment
438
439    def serialize_body(self):
440        return struct.pack("!L", self.window_increment & 0x7FFFFFFF)
441
442    def parse_body(self, data):
443        self.window_increment = struct.unpack("!L", data)[0]
444        self.body_len = len(data)
445
446
447class HeadersFrame(Padding, Priority, Frame):
448    """
449    The HEADERS frame carries name-value pairs. It is used to open a stream.
450    HEADERS frames can be sent on a stream in the "open" or "half closed
451    (remote)" states.
452
453    The HeadersFrame class is actually basically a data frame in this
454    implementation, because of the requirement to control the sizes of frames.
455    A header block fragment that doesn't fit in an entire HEADERS frame needs
456    to be followed with CONTINUATION frames. From the perspective of the frame
457    building code the header block is an opaque data segment.
458    """
459    type = 0x01
460
461    stream_association = 'has-stream'
462
463    defined_flags = [
464        Flag('END_STREAM', 0x01),
465        Flag('END_HEADERS', 0x04),
466        Flag('PADDED', 0x08),
467        Flag('PRIORITY', 0x20),
468    ]
469
470    def __init__(self, stream_id, data=b'', **kwargs):
471        super(HeadersFrame, self).__init__(stream_id, **kwargs)
472
473        self.data = data
474
475    def serialize_body(self):
476        padding_data = self.serialize_padding_data()
477        padding = b'\0' * self.total_padding
478
479        if 'PRIORITY' in self.flags:
480            priority_data = self.serialize_priority_data()
481        else:
482            priority_data = b''
483
484        return b''.join([padding_data, priority_data, self.data, padding])
485
486    def parse_body(self, data):
487        padding_data_length = self.parse_padding_data(data)
488        data = data[padding_data_length:]
489
490        if 'PRIORITY' in self.flags:
491            priority_data_length = self.parse_priority_data(data)
492        else:
493            priority_data_length = 0
494
495        self.body_len = len(data)
496        self.data = data[priority_data_length:len(data)-self.total_padding].tobytes()
497
498
499class ContinuationFrame(Frame):
500    """
501    The CONTINUATION frame is used to continue a sequence of header block
502    fragments. Any number of CONTINUATION frames can be sent on an existing
503    stream, as long as the preceding frame on the same stream is one of
504    HEADERS, PUSH_PROMISE or CONTINUATION without the END_HEADERS flag set.
505
506    Much like the HEADERS frame, hyper treats this as an opaque data frame with
507    different flags and a different type.
508    """
509    type = 0x09
510
511    stream_association = 'has-stream'
512
513    defined_flags = [Flag('END_HEADERS', 0x04),]
514
515    def __init__(self, stream_id, data=b'', **kwargs):
516        super(ContinuationFrame, self).__init__(stream_id, **kwargs)
517
518        self.data = data
519
520    def serialize_body(self):
521        return self.data
522
523    def parse_body(self, data):
524        self.data = data.tobytes()
525        self.body_len = len(data)
526
527
528Origin = collections.namedtuple('Origin', ['scheme', 'host', 'port'])
529
530
531class AltSvcFrame(Frame):
532    """
533    The ALTSVC frame is used to advertise alternate services that the current
534    host, or a different one, can understand.
535    """
536    type = 0xA
537
538    stream_association = 'no-stream'
539
540    def __init__(self, stream_id=0, host=b'', port=0, protocol_id=b'', max_age=0, origin=None, **kwargs):
541        super(AltSvcFrame, self).__init__(stream_id, **kwargs)
542
543        self.host = host
544        self.port = port
545        self.protocol_id = protocol_id
546        self.max_age = max_age
547        self.origin = origin
548
549    def serialize_origin(self):
550        if self.origin is not None:
551            if self.origin.port is None:
552                hostport = self.origin.host
553            else:
554                hostport = self.origin.host + b':' + str(self.origin.port).encode('ascii')
555            return self.origin.scheme + b'://' + hostport
556        return b''
557
558    def parse_origin(self, data):
559        if len(data) > 0:
560            data = data.tobytes()
561            scheme, hostport = data.split(b'://')
562            host, _, port = hostport.partition(b':')
563            self.origin = Origin(scheme=scheme, host=host,
564                                 port=int(port) if len(port) > 0 else None)
565
566    def serialize_body(self):
567        first = struct.pack("!LHxB", self.max_age, self.port, len(self.protocol_id))
568        host_length = struct.pack("!B", len(self.host))
569        return b''.join([first, self.protocol_id, host_length, self.host,
570                         self.serialize_origin()])
571
572    def parse_body(self, data):
573        self.body_len = len(data)
574        self.max_age, self.port, protocol_id_length = struct.unpack("!LHxB", data[:8])
575        pos = 8
576        self.protocol_id = data[pos:pos+protocol_id_length].tobytes()
577        pos += protocol_id_length
578        host_length = struct.unpack("!B", data[pos:pos+1])[0]
579        pos += 1
580        self.host = data[pos:pos+host_length].tobytes()
581        pos += host_length
582        self.parse_origin(data[pos:])
583
584
585class BlockedFrame(Frame):
586    """
587    The BLOCKED frame indicates that the sender is unable to send data due to a
588    closed flow control window.
589
590    The BLOCKED frame is used to provide feedback about the performance of flow
591    control for the purposes of performance tuning and debugging. The BLOCKED
592    frame can be sent by a peer when flow controlled data cannot be sent due to
593    the connection- or stream-level flow control. This frame MUST NOT be sent
594    if there are other reasons preventing data from being sent, either a lack
595    of available data, or the underlying transport being blocked.
596    """
597    type = 0x0B
598
599    stream_association = 'both'
600
601    defined_flags = []
602
603    def serialize_body(self):
604        return b''
605
606    def parse_body(self, data):
607        pass
608
609
610# A map of type byte to frame class.
611_FRAME_CLASSES = [
612    DataFrame,
613    HeadersFrame,
614    PriorityFrame,
615    RstStreamFrame,
616    SettingsFrame,
617    PushPromiseFrame,
618    PingFrame,
619    GoAwayFrame,
620    WindowUpdateFrame,
621    ContinuationFrame,
622    AltSvcFrame,
623    BlockedFrame
624]
625FRAMES = {cls.type: cls for cls in _FRAME_CLASSES}
626