1# -*- coding: utf-8 -*-
2"""Hypertext Transfer Protocol Version 2."""
3
4import struct
5import codecs
6
7from . import dpkt
8
9
10HTTP2_PREFACE = b'\x50\x52\x49\x20\x2a\x20\x48\x54\x54\x50\x2f\x32\x2e\x30\x0d\x0a\x0d\x0a\x53\x4d\x0d\x0a\x0d\x0a'
11
12# Frame types
13HTTP2_FRAME_DATA = 0
14HTTP2_FRAME_HEADERS = 1
15HTTP2_FRAME_PRIORITY = 2
16HTTP2_FRAME_RST_STREAM = 3
17HTTP2_FRAME_SETTINGS = 4
18HTTP2_FRAME_PUSH_PROMISE = 5
19HTTP2_FRAME_PING = 6
20HTTP2_FRAME_GOAWAY = 7
21HTTP2_FRAME_WINDOW_UPDATE = 8
22HTTP2_FRAME_CONTINUATION = 9
23
24# Flags
25HTTP2_FLAG_END_STREAM = 0x01  # for DATA and HEADERS frames
26HTTP2_FLAG_ACK = 0x01  # for SETTINGS and PING frames
27HTTP2_FLAG_END_HEADERS = 0x04
28HTTP2_FLAG_PADDED = 0x08
29HTTP2_FLAG_PRIORITY = 0x20
30
31# Settings
32HTTP2_SETTINGS_HEADER_TABLE_SIZE = 0x1
33HTTP2_SETTINGS_ENABLE_PUSH = 0x2
34HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS = 0x3
35HTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x4
36HTTP2_SETTINGS_MAX_FRAME_SIZE = 0x5
37HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x6
38
39# Error codes
40HTTP2_NO_ERROR = 0x0
41HTTP2_PROTOCOL_ERROR = 0x1
42HTTP2_INTERNAL_ERROR = 0x2
43HTTP2_FLOW_CONTROL_ERROR = 0x3
44HTTP2_SETTINGS_TIMEOUT = 0x4
45HTTP2_STREAM_CLOSED = 0x5
46HTTP2_FRAME_SIZE_ERROR = 0x6
47HTTP2_REFUSED_STREAM = 0x7
48HTTP2_CANCEL = 0x8
49HTTP2_COMPRESSION_ERROR = 0x9
50HTTP2_CONNECT_ERROR = 0xa
51HTTP2_ENHANCE_YOUR_CALM = 0xb
52HTTP2_INADEQUATE_SECURITY = 0xc
53HTTP2_HTTP_1_1_REQUIRED = 0xd
54
55error_code_str = {
56    HTTP2_NO_ERROR: 'NO_ERROR',
57    HTTP2_PROTOCOL_ERROR: 'PROTOCOL_ERROR',
58    HTTP2_INTERNAL_ERROR: 'INTERNAL_ERROR',
59    HTTP2_FLOW_CONTROL_ERROR: 'FLOW_CONTROL_ERROR',
60    HTTP2_SETTINGS_TIMEOUT: 'SETTINGS_TIMEOUT',
61    HTTP2_STREAM_CLOSED: 'STREAM_CLOSED',
62    HTTP2_FRAME_SIZE_ERROR: 'FRAME_SIZE_ERROR',
63    HTTP2_REFUSED_STREAM: 'REFUSED_STREAM',
64    HTTP2_CANCEL: 'CANCEL',
65    HTTP2_COMPRESSION_ERROR: 'COMPRESSION_ERROR',
66    HTTP2_CONNECT_ERROR: 'CONNECT_ERROR',
67    HTTP2_ENHANCE_YOUR_CALM: 'ENHANCE_YOUR_CALM',
68    HTTP2_INADEQUATE_SECURITY: 'INADEQUATE_SECURITY',
69    HTTP2_HTTP_1_1_REQUIRED: 'HTTP_1_1_REQUIRED',
70}
71
72
73class HTTP2Exception(Exception):
74    pass
75
76
77class Preface(dpkt.Packet):
78    __hdr__ = (
79        ('preface', '24s', HTTP2_PREFACE),
80    )
81
82    def unpack(self, buf):
83        dpkt.Packet.unpack(self, buf)
84        if self.preface != HTTP2_PREFACE:
85            raise HTTP2Exception('Invalid HTTP/2 preface')
86        self.data = ''
87
88
89class Frame(dpkt.Packet):
90    """
91    An HTTP/2 frame as defined in RFC 7540
92    """
93
94    # struct.unpack can't handle the 3-byte int, so we parse it as bytes
95    # (and store it as bytes so dpkt doesn't get confused), and turn it into
96    # an int in a user-facing property
97    __hdr__ = (
98        ('length_bytes', '3s', 0),
99        ('type', 'B', 0),
100        ('flags', 'B', 0),
101        ('stream_id', 'I', 0),
102    )
103
104    def unpack(self, buf):
105        dpkt.Packet.unpack(self, buf)
106        # only take the right number of bytes
107        self.data = self.data[:self.length]
108        if len(self.data) != self.length:
109            raise dpkt.NeedData
110
111    @property
112    def length(self):
113        return struct.unpack('!I', b'\x00' + self.length_bytes)[0]
114
115
116class Priority(dpkt.Packet):
117    """
118    Payload of a PRIORITY frame, also used in HEADERS frame with FLAG_PRIORITY.
119
120    Also used in the HEADERS frame if the PRIORITY flag is set.
121    """
122
123    __hdr__ = (
124        ('stream_dep', 'I', 0),
125        ('weight', 'B', 0),
126    )
127
128    def unpack(self, buf):
129        dpkt.Packet.unpack(self, buf)
130        if len(self.data) != 0:
131            raise HTTP2Exception('Invalid number of bytes in PRIORITY frame')
132        self.exclusive = (self.stream_dep & 0x80000000) != 0
133        self.stream_dep &= 0x7fffffff
134        self.weight += 1
135
136
137class Setting(dpkt.Packet):
138    """
139    A key-value pair used in the SETTINGS frame.
140    """
141
142    __hdr__ = (
143        ('identifier', 'H', 0),
144        ('value', 'I', 0),
145    )
146
147
148class PaddedFrame(Frame):
149    """
150    Abstract class for frame types that support the FLAG_PADDED flag: DATA,
151    HEADERS and PUSH_PROMISE.
152    """
153
154    def unpack(self, buf):
155        Frame.unpack(self, buf)
156        if self.flags & HTTP2_FLAG_PADDED:
157            if self.length == 0:
158                raise HTTP2Exception('Missing padding length in PADDED frame')
159            self.pad_length = struct.unpack('B', self.data[0:1])[0]
160            if self.length <= self.pad_length:
161                raise HTTP2Exception('Missing padding bytes in PADDED frame')
162            self.unpadded_data = self.data[1:-self.pad_length]
163        else:
164            self.unpadded_data = self.data
165
166
167class DataFrame(PaddedFrame):
168    """
169    Frame of type DATA.
170    """
171
172    @property
173    def payload(self):
174        return self.unpadded_data
175
176
177class HeadersFrame(PaddedFrame):
178    """
179    Frame of type HEADERS.
180    """
181
182    def unpack(self, buf):
183        PaddedFrame.unpack(self, buf)
184        if self.flags & HTTP2_FLAG_PRIORITY:
185            if len(self.unpadded_data) < 5:
186                raise HTTP2Exception('Missing stream dependency in HEADERS frame with PRIORITY flag')
187            self.priority = Priority(self.unpadded_data[:5])
188            self.block_fragment = self.unpadded_data[5:]
189        else:
190            self.block_fragment = self.unpadded_data
191
192
193class PriorityFrame(Frame):
194    """
195    Frame of type PRIORITY.
196    """
197
198    def unpack(self, buf):
199        Frame.unpack(self, buf)
200        self.priority = Priority(self.data)
201
202
203class RSTStreamFrame(Frame):
204    """
205    Frame of type RST_STREAM.
206    """
207
208    def unpack(self, buf):
209        Frame.unpack(self, buf)
210        if self.length != 4:
211            raise HTTP2Exception('Invalid number of bytes in RST_STREAM frame (must be 4)')
212        self.error_code = struct.unpack('!I', self.data)[0]
213
214
215class SettingsFrame(Frame):
216    """
217    Frame of type SETTINGS.
218    """
219
220    def unpack(self, buf):
221        Frame.unpack(self, buf)
222        if self.length % 6 != 0:
223            raise HTTP2Exception('Invalid number of bytes in SETTINGS frame (must be multiple of 6)')
224        self.settings = []
225        i = 0
226        while i < self.length:
227            self.settings.append(Setting(self.data[i:i + 6]))
228            i += 6
229
230
231class PushPromiseFrame(PaddedFrame):
232    """
233    Frame of type PUSH_PROMISE.
234    """
235
236    def unpack(self, buf):
237        PaddedFrame.unpack(self, buf)
238        if len(self.unpadded_data) < 4:
239            raise HTTP2Exception('Missing promised stream ID in PUSH_PROMISE frame')
240        self.promised_id = struct.unpack('!I', self.data[:4])[0]
241        self.block_fragment = self.unpadded_data[4:]
242
243
244class PingFrame(Frame):
245    """
246    Frame of type PING.
247    """
248
249    def unpack(self, buf):
250        Frame.unpack(self, buf)
251        if self.length != 8:
252            raise HTTP2Exception('Invalid number of bytes in PING frame (must be 8)')
253
254
255class GoAwayFrame(Frame):
256    """
257    Frame of type GO_AWAY.
258    """
259
260    def unpack(self, buf):
261        Frame.unpack(self, buf)
262        if self.length < 8:
263            raise HTTP2Exception('Invalid number of bytes in GO_AWAY frame')
264        self.last_stream_id = struct.unpack('!I', self.data[:4])[0]
265        self.error_code = struct.unpack('!I', self.data[4:8])[0]
266        self.debug_data = self.data[8:]
267
268
269class WindowUpdateFrame(Frame):
270    """
271    Frame of type WINDOW_UPDATE.
272    """
273
274    def unpack(self, buf):
275        Frame.unpack(self, buf)
276        if self.length != 4:
277            raise HTTP2Exception('Invalid number of bytes in WINDOW_UPDATE frame (must be 4)')
278        self.window_increment = struct.unpack('!I', self.data)[0]
279
280
281class ContinuationFrame(Frame):
282    """
283    Frame of type CONTINUATION.
284    """
285
286    def unpack(self, buf):
287        Frame.unpack(self, buf)
288        self.block_fragment = self.data
289
290
291FRAME_TYPES = {
292    HTTP2_FRAME_DATA: ('DATA', DataFrame),
293    HTTP2_FRAME_HEADERS: ('HEADERS', HeadersFrame),
294    HTTP2_FRAME_PRIORITY: ('PRIORITY', PriorityFrame),
295    HTTP2_FRAME_RST_STREAM: ('RST_STREAM', RSTStreamFrame),
296    HTTP2_FRAME_SETTINGS: ('SETTINGS', SettingsFrame),
297    HTTP2_FRAME_PUSH_PROMISE: ('PUSH_PROMISE', PushPromiseFrame),
298    HTTP2_FRAME_PING: ('PING', PingFrame),
299    HTTP2_FRAME_GOAWAY: ('GOAWAY', GoAwayFrame),
300    HTTP2_FRAME_WINDOW_UPDATE: ('WINDOW_UPDATE', WindowUpdateFrame),
301    HTTP2_FRAME_CONTINUATION: ('CONTINUATION', ContinuationFrame),
302}
303
304
305class FrameFactory(object):
306    def __new__(cls, buf):
307        if len(buf) < 4:
308            raise dpkt.NeedData
309        t = struct.unpack('B', buf[3:4])[0]
310        frame_type = FRAME_TYPES.get(t, None)
311        if frame_type is None:
312            raise HTTP2Exception('Invalid frame type: ' + hex(t))
313        return frame_type[1](buf)
314
315
316def frame_multi_factory(buf, preface=False):
317    """
318    Attempt to parse one or more Frame's out of buf
319
320    Args:
321      buf: string containing HTTP/2 frames. May have an incomplete frame at the
322        end.
323      preface: expect an HTTP/2 preface at the beginning of the buffer.
324
325    Returns:
326      [Frame]
327      int, total bytes consumed, != len(buf) if an incomplete frame was left at
328        the end.
329    """
330    i = 0
331    n = len(buf)
332    frames = []
333
334    if preface:
335        try:
336            p = Preface(buf)
337            i += len(p)
338        except dpkt.NeedData:
339            return [], 0
340
341    while i < n:
342        try:
343            frame = FrameFactory(buf[i:])
344            frames.append(frame)
345            i += len(frame)
346        except dpkt.NeedData:
347            break
348    return frames, i
349
350
351class TestFrame(object):
352    """Some data found in real traffic"""
353
354    @classmethod
355    def setup_class(cls):
356        # First TLS AppData record sent by Firefox (decrypted)
357        record = codecs.decode(b'505249202a20485454502f322e300d0a'
358                               b'0d0a534d0d0a0d0a00000c0400000000'
359                               b'00000400020000000500004000000004'
360                               b'08000000000000bf0001000005020000'
361                               b'00000300000000c80000050200000000'
362                               b'05000000006400000502000000000700'
363                               b'00000000000005020000000009000000'
364                               b'070000000502000000000b0000000300', 'hex')
365        cls.frames, cls.i = frame_multi_factory(record, preface=True)
366
367    def test_frame(self):
368        import pytest
369        # Too short
370        pytest.raises(dpkt.NeedData, Frame, codecs.decode(b'000001'  # length
371                                                          b'0000'  # type, flags
372                                                          b'deadbeef',  # stream id
373                                                          'hex'))
374
375    def test_data(self):
376        # Padded DATA frame
377        frame_data_padded = FrameFactory(codecs.decode(b'000008'  # length
378                                                       b'0008'  # type, flags
379                                                       b'12345678'  # stream id
380                                                       b'05'  # pad length
381                                                       b'abcd'  # data
382                                                       b'1122334455',  # padding
383                                                       'hex'))
384        assert (frame_data_padded.length == 8)
385        assert (frame_data_padded.type == HTTP2_FRAME_DATA)
386        assert (frame_data_padded.flags == HTTP2_FLAG_PADDED)
387        assert (frame_data_padded.stream_id == 0x12345678)
388        assert (frame_data_padded.data == b'\x05\xAB\xCD\x11\x22\x33\x44\x55')
389        assert (frame_data_padded.pad_length == 5)
390        assert (frame_data_padded.unpadded_data == b'\xAB\xCD')
391        assert (frame_data_padded.payload == b'\xAB\xCD')
392
393        # empty DATA frame
394        frame_data_empty_end = FrameFactory(codecs.decode(b'000000'  # length
395                                                          b'0001'  # type, flags
396                                                          b'deadbeef',  # stream id
397                                                          'hex'))
398        assert (frame_data_empty_end.length == 0)
399        assert (frame_data_empty_end.type == HTTP2_FRAME_DATA)
400        assert (frame_data_empty_end.flags == HTTP2_FLAG_END_STREAM)
401        assert (frame_data_empty_end.stream_id == 0xdeadbeef)
402        assert (frame_data_empty_end.data == b'')
403        assert (frame_data_empty_end.unpadded_data == b'')
404        assert (frame_data_empty_end.payload == b'')
405
406        import pytest
407        # Invalid padding
408        with pytest.raises(HTTP2Exception) as e:
409            DataFrame(codecs.decode(b'000000'  # length
410                                    b'0008'  # type, flags
411                                    b'12345678'  # stream id
412                                    b'',  # missing padding
413                                    'hex'))
414        assert (str(e.value) == 'Missing padding length in PADDED frame')
415
416        with pytest.raises(HTTP2Exception) as e:
417            DataFrame(codecs.decode(b'000001'  # length
418                                    b'0008'  # type, flags
419                                    b'12345678'  # stream id
420                                    b'01'
421                                    b'',  # missing padding bytes
422                                    'hex'))
423        assert (str(e.value) == 'Missing padding bytes in PADDED frame')
424
425    def test_headers(self):
426        frame_headers = FrameFactory(codecs.decode(b'000003'  # length
427                                                   b'0100'  # type, flags
428                                                   b'deadbeef'  # stream id
429                                                   b'f00baa',  # block fragment
430                                                   'hex'))
431        assert (frame_headers.length == 3)
432        assert (frame_headers.type == HTTP2_FRAME_HEADERS)
433        assert (frame_headers.flags == 0)
434        assert (frame_headers.stream_id == 0xdeadbeef)
435        assert (frame_headers.data == b'\xF0\x0B\xAA')
436        assert (frame_headers.unpadded_data == b'\xF0\x0B\xAA')
437        assert (frame_headers.block_fragment == b'\xF0\x0B\xAA')
438
439        frame_headers_prio = FrameFactory(codecs.decode(b'000008'  # length
440                                                        b'0120'  # type, flags
441                                                        b'deadbeef'  # stream id
442                                                        b'cafebabe10'  # priority
443                                                        b'f00baa',  # block fragment
444                                                        'hex'))
445        assert (frame_headers_prio.length == 8)
446        assert (frame_headers_prio.type == HTTP2_FRAME_HEADERS)
447        assert (frame_headers_prio.flags == HTTP2_FLAG_PRIORITY)
448        assert (frame_headers_prio.stream_id == 0xdeadbeef)
449        assert (frame_headers_prio.data == b'\xCA\xFE\xBA\xBE\x10\xF0\x0B\xAA')
450        assert (frame_headers_prio.unpadded_data == b'\xCA\xFE\xBA\xBE\x10\xF0\x0B\xAA')
451        assert (frame_headers_prio.priority.exclusive is True)
452        assert (frame_headers_prio.priority.stream_dep == 0x4afebabe)
453        assert (frame_headers_prio.priority.weight == 0x11)
454        assert (frame_headers_prio.block_fragment == b'\xF0\x0B\xAA')
455
456        import pytest
457        # Invalid priority
458        with pytest.raises(HTTP2Exception) as e:
459            HeadersFrame(codecs.decode(b'000002'  # length
460                                       b'0120'  # type, flags
461                                       b'deadbeef'  # stream id
462                                       b'1234',  # invalid priority
463                                       'hex'))
464        assert (str(e.value) == 'Missing stream dependency in HEADERS frame with PRIORITY flag')
465
466    def test_priority(self):
467        frame_priority = FrameFactory(codecs.decode(b'000005'  # length
468                                                    b'0200'  # type, flags
469                                                    b'deadbeef'  # stream id
470                                                    b'cafebabe'  # stream dep
471                                                    b'12',  # weight
472                                                    'hex'))
473        assert (frame_priority.length == 5)
474        assert (frame_priority.type == HTTP2_FRAME_PRIORITY)
475        assert (frame_priority.flags == 0)
476        assert (frame_priority.stream_id == 0xdeadbeef)
477        assert (frame_priority.data == b'\xCA\xFE\xBA\xBE\x12')
478        assert (frame_priority.priority.data == b'')
479        assert (frame_priority.priority.exclusive is True)
480        assert (frame_priority.priority.stream_dep == 0x4afebabe)
481        assert (frame_priority.priority.weight == 0x13)
482
483        import pytest
484        # Invalid length
485        with pytest.raises(HTTP2Exception) as e:
486            PriorityFrame(codecs.decode(b'000006'  # length
487                                        b'0200'  # type, flags
488                                        b'deadbeef'  # stream id
489                                        b'cafebabe'  # stream dep
490                                        b'12'  # weight
491                                        b'00',  # unexpected additional payload
492                                        'hex'))
493        assert (str(e.value) == 'Invalid number of bytes in PRIORITY frame')
494
495    def test_rst_stream(self):
496        frame_rst = FrameFactory(codecs.decode(b'000004'  # length
497                                               b'0300'  # type, flags
498                                               b'deadbeef'  # stream id
499                                               b'0000000c',  # error code
500                                               'hex'))
501        assert (frame_rst.length == 4)
502        assert (frame_rst.type == HTTP2_FRAME_RST_STREAM)
503        assert (frame_rst.flags == 0)
504        assert (frame_rst.stream_id == 0xdeadbeef)
505        assert (frame_rst.data == b'\x00\x00\x00\x0c')
506        assert (frame_rst.error_code == HTTP2_INADEQUATE_SECURITY)
507
508        import pytest
509        # Invalid length
510        with pytest.raises(HTTP2Exception) as e:
511            RSTStreamFrame(codecs.decode(b'000005'  # length
512                                         b'0300'  # type, flags
513                                         b'deadbeef'  # stream id
514                                         b'0000000c'  # error code
515                                         b'00',  # unexpected additional payload
516                                         'hex'))
517        assert (str(e.value) == 'Invalid number of bytes in RST_STREAM frame (must be 4)')
518
519    def test_settings(self):
520        frame_settings = FrameFactory(codecs.decode(b'00000c'  # length
521                                                    b'0400'  # type, flags
522                                                    b'00000000'  # stream id
523                                                    # settings
524                                                    b'0004'  # setting id
525                                                    b'00020000'  # setting value
526                                                    b'0005'  # setting id
527                                                    b'00004000',  # setting value
528                                                    'hex'))
529        assert (frame_settings.length == 12)
530        assert (frame_settings.type == HTTP2_FRAME_SETTINGS)
531        assert (frame_settings.flags == 0)
532        assert (frame_settings.stream_id == 0)
533        assert (len(frame_settings.settings) == 2)
534        assert (frame_settings.settings[0].identifier == HTTP2_SETTINGS_INITIAL_WINDOW_SIZE)
535        assert (frame_settings.settings[0].value == 0x20000)
536        assert (frame_settings.settings[1].identifier == HTTP2_SETTINGS_MAX_FRAME_SIZE)
537        assert (frame_settings.settings[1].value == 0x4000)
538
539        # Settings ack, with empty payload
540        frame_settings_ack = FrameFactory(codecs.decode(b'000000'  # length
541                                                        b'0401'  # type, flags
542                                                        b'00000000',  # stream id
543                                                        'hex'))
544        assert (frame_settings_ack.length == 0)
545        assert (frame_settings_ack.type == HTTP2_FRAME_SETTINGS)
546        assert (frame_settings_ack.flags == HTTP2_FLAG_ACK)
547        assert (frame_settings_ack.stream_id == 0)
548        assert (len(frame_settings_ack.settings) == 0)
549
550        import pytest
551        # Invalid length
552        with pytest.raises(HTTP2Exception) as e:
553            SettingsFrame(codecs.decode(b'000005'  # length
554                                        b'0400'  # type, flags
555                                        b'deadbeef'  # stream id
556                                        b'1234567890',  # invalid length
557                                        'hex'))
558        assert (str(e.value) == 'Invalid number of bytes in SETTINGS frame (must be multiple of 6)')
559
560    def test_push_promise(self):
561        frame_pp = FrameFactory(codecs.decode(b'000007'  # length
562                                              b'0500'  # type, flags
563                                              b'deadbeef'  # stream id
564                                              b'cafebabe'  # promised id
565                                              b'123456',  # some block fragment
566                                              'hex'))
567        assert (frame_pp.length == 7)
568        assert (frame_pp.type == HTTP2_FRAME_PUSH_PROMISE)
569        assert (frame_pp.flags == 0)
570        assert (frame_pp.stream_id == 0xdeadbeef)
571        assert (frame_pp.promised_id == 0xcafebabe)
572        assert (frame_pp.block_fragment == b'\x12\x34\x56')
573
574        import pytest
575        # Invalid length
576        with pytest.raises(HTTP2Exception) as e:
577            PushPromiseFrame(codecs.decode(b'000003'  # length
578                                           b'0500'  # type, flags
579                                           b'deadbeef'  # stream id
580                                           b'cafeba',  # missing promised id
581                                           'hex'))
582        assert (str(e.value) == 'Missing promised stream ID in PUSH_PROMISE frame')
583
584    def test_ping(self):
585        frame_ping = FrameFactory(codecs.decode(b'000008'  # length
586                                                b'0600'  # type, flags
587                                                b'deadbeef'  # stream id
588                                                b'cafebabe12345678',  # user data
589                                                'hex'))
590        assert (frame_ping.length == 8)
591        assert (frame_ping.type == HTTP2_FRAME_PING)
592        assert (frame_ping.flags == 0)
593        assert (frame_ping.stream_id == 0xdeadbeef)
594        assert (frame_ping.data == b'\xCA\xFE\xBA\xBE\x12\x34\x56\x78')
595
596        import pytest
597        # Invalid length
598        with pytest.raises(HTTP2Exception) as e:
599            PingFrame(codecs.decode(b'000005'  # length
600                                    b'0600'  # type, flags
601                                    b'deadbeef'  # stream id
602                                    b'1234567890',  # invalid length
603                                    'hex'))
604        assert (str(e.value) == 'Invalid number of bytes in PING frame (must be 8)')
605
606    def test_goaway(self):
607        frame_goaway = FrameFactory(codecs.decode(b'00000a'  # length
608                                                  b'0700'  # type, flags
609                                                  b'deadbeef'  # stream id
610                                                  b'00000000'  # last stream id
611                                                  b'00000000'  # error code
612                                                  b'cafe',  # debug data
613                                                  'hex'))
614        assert (frame_goaway.length == 10)
615        assert (frame_goaway.type == HTTP2_FRAME_GOAWAY)
616        assert (frame_goaway.flags == 0)
617        assert (frame_goaway.stream_id == 0xdeadbeef)
618        assert (frame_goaway.last_stream_id == 0)
619        assert (frame_goaway.error_code == HTTP2_NO_ERROR)
620        assert (frame_goaway.debug_data == b'\xCA\xFE')
621
622        import pytest
623        # Invalid length
624        with pytest.raises(HTTP2Exception) as e:
625            GoAwayFrame(codecs.decode(b'000005'  # length
626                                      b'0700'  # type, flags
627                                      b'deadbeef'  # stream id
628                                      b'1234567890',  # invalid length
629                                      'hex'))
630        assert (str(e.value) == 'Invalid number of bytes in GO_AWAY frame')
631
632    def test_window_update(self):
633        frame_wu = FrameFactory(codecs.decode(b'000004'  # length
634                                              b'0800'  # type, flags
635                                              b'deadbeef'  # stream id
636                                              b'12345678',  # window increment
637                                              'hex'))
638        assert (frame_wu.length == 4)
639        assert (frame_wu.type == HTTP2_FRAME_WINDOW_UPDATE)
640        assert (frame_wu.flags == 0)
641        assert (frame_wu.stream_id == 0xdeadbeef)
642        assert (frame_wu.window_increment == 0x12345678)
643
644        import pytest
645        # Invalid length
646        with pytest.raises(HTTP2Exception) as e:
647            WindowUpdateFrame(codecs.decode(b'000005'  # length
648                                            b'0800'  # type, flags
649                                            b'deadbeef'  # stream id
650                                            b'1234567890',  # invalid length
651                                            'hex'))
652        assert (str(e.value) == 'Invalid number of bytes in WINDOW_UPDATE frame (must be 4)')
653
654    def test_continuation(self):
655        frame_cont = FrameFactory(codecs.decode(b'000003'  # length
656                                                b'0900'  # type, flags
657                                                b'deadbeef'  # stream id
658                                                b'f00baa',  # block fragment
659                                                'hex'))
660        assert (frame_cont.length == 3)
661        assert (frame_cont.type == HTTP2_FRAME_CONTINUATION)
662        assert (frame_cont.flags == 0)
663        assert (frame_cont.stream_id == 0xdeadbeef)
664        assert (frame_cont.block_fragment == b'\xF0\x0B\xAA')
665
666    def test_factory(self):
667        import pytest
668        # Too short
669        pytest.raises(dpkt.NeedData, FrameFactory, codecs.decode(b'000000', 'hex'))
670
671        # Invalid type
672        with pytest.raises(HTTP2Exception) as e:
673            FrameFactory(codecs.decode(b'000000'  # length
674                                       b'abcd'  # type, flags
675                                       b'deadbeef',  # stream id
676                                       'hex'))
677        assert (str(e.value) == 'Invalid frame type: 0xab')
678
679    def test_preface(self):
680        import pytest
681        # Preface
682        pytest.raises(dpkt.NeedData, Preface,
683                      codecs.decode(b'505249202a20485454502f322e300d0a', 'hex'))
684        pytest.raises(dpkt.NeedData, Preface, b'\x00' * 23)
685        with pytest.raises(HTTP2Exception) as e:
686            Preface(b'\x00' * 24)
687        assert (str(e.value) == 'Invalid HTTP/2 preface')
688
689    def test_multi(self):
690        assert (self.i == 128)
691        assert (len(self.frames) == 7)
692
693        assert (self.frames[0].length == 12)
694        assert (self.frames[1].length == 4)
695        assert (self.frames[2].length == 5)
696        assert (self.frames[3].length == 5)
697        assert (self.frames[4].length == 5)
698        assert (self.frames[5].length == 5)
699        assert (self.frames[6].length == 5)
700
701        assert (self.frames[0].type == HTTP2_FRAME_SETTINGS)
702        assert (self.frames[1].type == HTTP2_FRAME_WINDOW_UPDATE)
703        assert (self.frames[2].type == HTTP2_FRAME_PRIORITY)
704        assert (self.frames[3].type == HTTP2_FRAME_PRIORITY)
705        assert (self.frames[4].type == HTTP2_FRAME_PRIORITY)
706        assert (self.frames[5].type == HTTP2_FRAME_PRIORITY)
707        assert (self.frames[6].type == HTTP2_FRAME_PRIORITY)
708
709        assert (self.frames[0].flags == 0)
710        assert (self.frames[1].flags == 0)
711        assert (self.frames[2].flags == 0)
712        assert (self.frames[3].flags == 0)
713        assert (self.frames[4].flags == 0)
714        assert (self.frames[5].flags == 0)
715        assert (self.frames[6].flags == 0)
716
717        assert (self.frames[0].stream_id == 0)
718        assert (self.frames[1].stream_id == 0)
719        assert (self.frames[2].stream_id == 3)
720        assert (self.frames[3].stream_id == 5)
721        assert (self.frames[4].stream_id == 7)
722        assert (self.frames[5].stream_id == 9)
723        assert (self.frames[6].stream_id == 11)
724
725        frames, i = frame_multi_factory(
726            codecs.decode(b'505249202a20485454502f322e300d0a', 'hex'),
727            preface=True)
728        assert (len(frames) == 0)
729        assert (i == 0)
730
731        # Only preface was parsed
732        frames, i = frame_multi_factory(
733            codecs.decode(b'505249202a20485454502f322e300d0a'
734                          b'0d0a534d0d0a0d0a00000c0400000000', 'hex'),
735            preface=True)
736        assert (len(frames) == 0)
737        assert (i == 24)
738