diff --git a/scapy/contrib/http2.py b/scapy/contrib/http2.py new file mode 100644 index 0000000000000000000000000000000000000000..7c632856a3821cb91058b6905afa5f3c9438f472 --- /dev/null +++ b/scapy/contrib/http2.py @@ -0,0 +1,2692 @@ +############################################################################# +## ## +## http2.py --- HTTP/2 support for Scapy ## +## see RFC7540 and RFC7541 ## +## for more informations ## +## ## +## Copyright (C) 2016 Florian Maury <florian.maury@ssi.gouv.fr> ## +## ## +## This program is free software; you can redistribute it and/or modify it ## +## under the terms of the GNU General Public License version 2 as ## +## published by the Free Software Foundation. ## +## ## +## This program is distributed in the hope that it will be useful, but ## +## WITHOUT ANY WARRANTY; without even the implied warranty of ## +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## +## General Public License for more details. ## +## ## +############################################################################# +"""http2 Module +Implements packets and fields required to encode/decode HTTP/2 Frames +and HPack encoded headers + +scapy.contrib.status=loads +scapy.contrib.description=HTTP/2 (RFC 7540, RFC 7541) +""" + +import abc +import types +import re +import StringIO +import struct + +# Only required if using mypy-lang for static typing +# Most symbols are used in mypy-interpreted "comments". +# Sized must be one of the superclasses of a class implementing __len__ +try: + from typing import Optional, List, Union, Callable, Any, Tuple, Sized +except ImportError: + class Sized(object): pass + +import scapy.fields as fields +import scapy.packet as packet +import scapy.config as config +import scapy.base_classes as base_classes +import scapy.volatile as volatile +import scapy.error as error + +######################################################################################################################## +################################################ HPACK Integer Fields ################################################## +######################################################################################################################## + +class HPackMagicBitField(fields.BitField): + """ HPackMagicBitField is a BitField variant that cannot be assigned another + value than the default one. This field must not be used where there is + potential for fuzzing. OTOH, this field makes sense (for instance, if the + magic bits are used by a dispatcher to select the payload class) + """ + + __slots__ = ['_magic'] + + def __init__(self, name, default, size): + # type: (str, int, int) -> None + """ + @param str name: this field instance name. + @param int default: this field only valid value. + @param int size: this bitfield bitlength. + @return None + @raise AssertionError + """ + assert(default >= 0) + # size can be negative if encoding is little-endian (see rev property of bitfields) + assert(size != 0) + self._magic = default + super(HPackMagicBitField, self).__init__(name, default, size) + + def addfield(self, pkt, s, val): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int, int]], int) -> Union[str, Tuple[str, int, int]] + """ + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. + @param str|(str, int, long) s: either a str if 0 == size%8 or a tuple with the string to add this field to, the + number of bits already generated and the generated value so far. + @param int val: unused; must be equal to default value + @return str|(str, int, long): the s string extended with this field machine representation + @raise AssertionError + """ + assert val == self._magic, 'val parameter must value {}; received: {}'.format(self._magic, val) + return super(HPackMagicBitField, self).addfield(pkt, s, self._magic) + + def getfield(self, pkt, s): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int]]) -> Tuple[Union[Tuple[str, int], str], int] + """ + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. + @param str|(str, int) s: either a str if size%8==0 or a tuple with the string to parse from and the number of + bits already consumed by previous bitfield-compatible fields. + @return (str|(str, int), int): Returns the remaining string and the parsed value. May return a tuple if there + are remaining bits to parse in the first byte. Returned value is equal to default value + @raise AssertionError + """ + r = super(HPackMagicBitField, self).getfield(pkt, s) + assert ( + isinstance(r, tuple) + and len(r) == 2 + and isinstance(r[1], (int, long)) + ), 'Second element of BitField.getfield return value expected to be an int or a long; API change detected' + assert r[1] == self._magic, 'Invalid value parsed from s; error in class guessing detected!' + return r + + def h2i(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused + @param int x: unused; must be equal to default value + @return int; default value + @raise AssertionError + """ + assert x == self._magic, \ + 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) + return super(HPackMagicBitField, self).h2i(pkt, self._magic) + + def i2h(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused + @param int x: unused; must be equal to default value + @return int; default value + @raise AssertionError + """ + assert x == self._magic, \ + 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) + return super(HPackMagicBitField, self).i2h(pkt, self._magic) + + def m2i(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused + @param int x: must be the machine representatino of the default value + @return int; default value + @raise AssertionError + """ + r = super(HPackMagicBitField, self).m2i(pkt, x) + assert r == self._magic, 'Invalid value parsed from m2i; error in class guessing detected!' + return r + + def i2m(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused + @param int x: unused; must be equal to default value + @return int; default value + @raise AssertionError + """ + assert x == self._magic, \ + 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) + return super(HPackMagicBitField, self).i2m(pkt, self._magic) + + def any2i(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused + @param int x: unused; must be equal to default value + @return int; default value + @raise AssertionError + """ + assert x == self._magic, \ + 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) + return super(HPackMagicBitField, self).any2i(pkt, self._magic) + + +class AbstractUVarIntField(fields.Field): + """AbstractUVarIntField represents an integer as defined in RFC7541 + """ + + __slots__ = ['_max_value', 'size', 'rev'] + """ + :var int size: the bit length of the prefix of this AbstractUVarIntField. It + represents the complement of the number of MSB that are used in the + current byte for other purposes by some other BitFields + :var int _max_value: the maximum value that can be stored in the + sole prefix. If the integer equals or exceeds this value, the max prefix + value is assigned to the size first bits and the multibyte representation + is used + :var bool rev: is a fake property, also emulated for the sake of + compatibility with Bitfields + """ + + def __init__(self, name, default, size): + # type: (str, Optional[int], int) -> None + """ + @param str name: the name of this field instance + @param int|None default: positive, null or None default value for this field instance. + @param int size: the number of bits to consider in the first byte. Valid range is ]0;8] + @return None + @raise AssertionError + """ + assert(isinstance(default, types.NoneType) or (isinstance(default, (int, long)) and default >= 0)) + assert(0 < size <= 8) + super(AbstractUVarIntField, self).__init__(name, default) + self.size = size + self._max_value = (1 << self.size) - 1 + + # Configuring the fake property that is useless for this class but that is + # expected from BitFields + self.rev = False + + def h2i(self, pkt, x): + # type: (Optional[packet.Packet], Optional[int]) -> Optional[int] + """ + @param packet.Packet|None pkt: unused. + @param int|None x: the value to convert. + @return int|None: the converted value. + @raise AssertionError + """ + assert(not isinstance(x, (int, long)) or x >= 0) + return x + + def i2h(self, pkt, x): + # type: (Optional[packet.Packet], Optional[int]) -> Optional[int] + """ + @param packet.Packet|None pkt: unused. + @param int|None x: the value to convert. + @return: int|None: the converted value. + """ + return x + + def _detect_multi_byte(self, fb): + # type: (str) -> bool + """ _detect_multi_byte returns whether the AbstractUVarIntField is represented on + multiple bytes or not. + + A multibyte representation is indicated by all of the first size bits being set + + @param str fb: first byte, as a character. + @return bool: True if multibyte repr detected, else False. + @raise AssertionError + """ + assert(len(fb) == 1) + return (ord(fb) & self._max_value) == self._max_value + + def _parse_multi_byte(self, s): + # type: (str) -> int + """ _parse_multi_byte parses x as a multibyte representation to get the + int value of this AbstractUVarIntField. + + @param str s: the multibyte string to parse. + @return int: The parsed int value represented by this AbstractUVarIntField. + @raise: AssertionError + @raise: Scapy_Exception if the input value encodes an integer larger than 1<<64 + """ + + assert(len(s) >= 2) + + l = len(s) + + value = 0 + i = 1 + byte = ord(s[i]) + # For CPU sake, stops at an arbitrary large number! + max_value = 1 << 64 + # As long as the MSG is set, an another byte must be read + while byte & 0x80: + value += (byte ^ 0x80) << (7 * (i - 1)) + if value > max_value: + raise error.Scapy_Exception( + 'out-of-bound value: the string encodes a value that is too large (>2^{64}): {}'.format(value) + ) + i += 1 + assert i < l, 'EINVAL: x: out-of-bound read: the string ends before the AbstractUVarIntField!' + byte = ord(s[i]) + value += byte << (7 * (i - 1)) + value += self._max_value + + assert(value >= 0) + return value + + def m2i(self, pkt, x): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int]]) -> int + """ + A tuple is expected for the "x" param only if "size" is different than 8. If a tuple is received, some bits + were consumed by another field. This field consumes the remaining bits, therefore the int of the tuple must + equal "size". + + @param packet.Packet|None pkt: unused. + @param str|(str, int) x: the string to convert. If bits were consumed by a previous bitfield-compatible field. + @raise AssertionError + """ + assert(isinstance(x, str) or (isinstance(x, tuple) and x[1] >= 0)) + + if isinstance(x, tuple): + assert (8 - x[1]) == self.size, 'EINVAL: x: not enough bits remaining in current byte to read the prefix' + val = x[0] + else: + assert isinstance(x, str) and self.size == 8, 'EINVAL: x: tuple expected when prefix_len is not a full byte' + val = x + + if self._detect_multi_byte(val[0]): + ret = self._parse_multi_byte(val) + else: + ret = ord(val[0]) & self._max_value + + assert(ret >= 0) + return ret + + def i2m(self, pkt, x): + # type: (Optional[packet.Packet], int) -> str + """ + @param packet.Packet|None pkt: unused. + @param int x: the value to convert. + @return str: the converted value. + @raise AssertionError + """ + assert(x >= 0) + + if x < self._max_value: + return chr(x) + else: + # The sl list join is a performance trick, because string + # concatenation is not efficient with Python immutable strings + sl = [chr(self._max_value)] + x -= self._max_value + while x >= 0x80: + sl.append(chr(0x80 | (x & 0x7F))) + x >>= 7 + sl.append(chr(x)) + return ''.join(sl) + + def any2i(self, pkt, x): + # type: (Optional[packet.Packet], Union[None, str, int]) -> Optional[int] + """ + A "x" value as a string is parsed as a binary encoding of a UVarInt. An int is considered an internal value. + None is returned as is. + + @param packet.Packet|None pkt: the packet containing this field; probably unused. + @param str|int|None x: the value to convert. + @return int|None: the converted value. + @raise AssertionError + """ + if isinstance(x, types.NoneType): + return x + if isinstance(x, (int, long)): + assert(x >= 0) + ret = self.h2i(pkt, x) + assert(isinstance(ret, (int, long)) and ret >= 0) + return ret + elif isinstance(x, str): + ret = self.m2i(pkt, x) + assert (isinstance(ret, (int, long)) and ret >= 0) + return ret + assert False, 'EINVAL: x: No idea what the parameter format is' + + def i2repr(self, pkt, x): + # type: (Optional[packet.Packet], Optional[int]) -> str + """ + @param packet.Packet|None pkt: probably unused. + @param x: int|None: the positive, null or none value to convert. + @return str: the representation of the value. + """ + return repr(self.i2h(pkt, x)) + + def addfield(self, pkt, s, val): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int, int]], int) -> str + """ An AbstractUVarIntField prefix always consumes the remaining bits + of a BitField;if no current BitField is in use (no tuple in + entry) then the prefix length is 8 bits and the whole byte is to + be consumed + @param packet.Packet|None pkt: the packet containing this field. Probably unused. + @param str|(str, int, long) s: the string to append this field to. A tuple indicates that some bits were already + generated by another bitfield-compatible field. This MUST be the case if "size" is not 8. The int is the + number of bits already generated in the first byte of the str. The long is the value that was generated by the + previous bitfield-compatible fields. + @param int val: the positive or null value to be added. + @return str: s concatenated with the machine representation of this field. + @raise AssertionError + """ + assert(val >= 0) + if isinstance(s, str): + assert self.size == 8, 'EINVAL: s: tuple expected when prefix_len is not a full byte' + return s + self.i2m(pkt, val) + + # s is a tuple + assert(s[1] >= 0) + assert(s[2] >= 0) + assert (8 - s[1]) == self.size, 'EINVAL: s: not enough bits remaining in current byte to read the prefix' + + if val >= self._max_value: + return s[0] + chr((s[2] << self.size) + self._max_value) + self.i2m(pkt, val)[1:] + # This AbstractUVarIntField is only one byte long; setting the prefix value + # and appending the resulting byte to the string + return s[0] + chr((s[2] << self.size) + ord(self.i2m(pkt, val))) + + @staticmethod + def _detect_bytelen_from_str(s): + # type: (str) -> int + """ _detect_bytelen_from_str returns the length of the machine + representation of an AbstractUVarIntField starting at the beginning + of s and which is assumed to expand over multiple bytes + (value > _max_prefix_value). + + @param str s: the string to parse. It is assumed that it is a multibyte int. + @return The bytelength of the AbstractUVarIntField. + @raise AssertionError + """ + assert(len(s) >= 2) + l = len(s) + + i = 1 + while ord(s[i]) & 0x80 > 0: + i += 1 + assert i < l, 'EINVAL: s: out-of-bound read: unfinished AbstractUVarIntField detected' + ret = i + 1 + + assert(ret >= 0) + return ret + + def i2len(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + @param packet.Packet|None pkt: unused. + @param int x: the positive or null value whose binary size if requested. + @raise AssertionError + """ + assert(x >= 0) + if x < self._max_value: + return 1 + + # x is expressed over multiple bytes + x -= self._max_value + i = 1 + if x == 0: + i += 1 + while x > 0: + x >>= 7 + i += 1 + + ret = i + assert(ret >= 0) + return ret + + def getfield(self, pkt, s): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int]]) -> Tuple[str, int] + """ + @param packet.Packet|None pkt: the packet instance containing this field; probably unused. + @param str|(str, int) s: the input value to get this field value from. If size is 8, s is a string, else + it is a tuple containing the value and an int indicating the number of bits already consumed in the first byte + of the str. The number of remaining bits to consume in the first byte must be equal to "size". + @return (str, int): the remaining bytes of s and the parsed value. + @raise AssertionError + """ + if isinstance(s, tuple): + assert(len(s) == 2) + temp = s # type: Tuple[str, int] + ts, ti = temp + assert(ti >= 0) + assert 8 - ti == self.size, 'EINVAL: s: not enough bits remaining in current byte to read the prefix' + val = ts + else: + assert isinstance(s, str) and self.size == 8, 'EINVAL: s: tuple expected when prefix_len is not a full byte' + val = s + + if self._detect_multi_byte(val[0]): + l = self._detect_bytelen_from_str(val) + else: + l = 1 + + ret = val[l:], self.m2i(pkt, s) + assert(ret[1] >= 0) + return ret + + def randval(self): + # type: () -> volatile.VolatileValue + """ + @return volatile.VolatileValue: a volatile value for this field "long"-compatible internal value. + """ + return volatile.RandLong() + + +class UVarIntField(AbstractUVarIntField): + def __init__(self, name, default, size): + # type: (str, int, int) -> None + """ + @param str name: the name of this field instance. + @param default: the default value for this field instance. default must be positive or null. + @raise AssertionError + """ + assert(default >= 0) + assert(0 < size <= 8) + + super(UVarIntField, self).__init__(name, default, size) + self.size = size + self._max_value = (1 << self.size) - 1 + + # Configuring the fake property that is useless for this class but that is + # expected from BitFields + self.rev = False + + def h2i(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ h2i is overloaded to restrict the acceptable x values (not None) + + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. + @param int x: the value to convert. + @return int: the converted value. + @raise AssertionError + """ + ret = super(UVarIntField, self).h2i(pkt, x) + assert(not isinstance(ret, types.NoneType) and ret >= 0) + return ret + + def i2h(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ i2h is overloaded to restrict the acceptable x values (not None) + + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. + @param int x: the value to convert. + @return int: the converted value. + @raise AssertionError + """ + ret = super(UVarIntField, self).i2h(pkt, x) + assert(not isinstance(ret, types.NoneType) and ret >= 0) + return ret + + def any2i(self, pkt, x): + # type: (Optional[packet.Packet], Union[str, int]) -> int + """ any2i is overloaded to restrict the acceptable x values (not None) + + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. + @param str|int x: the value to convert. + @return int: the converted value. + @raise AssertionError + """ + ret = super(UVarIntField, self).any2i(pkt, x) + assert(not isinstance(ret, types.NoneType) and ret >= 0) + return ret + + def i2repr(self, pkt, x): + # type: (Optional[packet.Packet], int) -> str + """ i2repr is overloaded to restrict the acceptable x values (not None) + + @param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. + @param int x: the value to convert. + @return str: the converted value. + """ + return super(UVarIntField, self).i2repr(pkt, x) + + +class FieldUVarLenField(AbstractUVarIntField): + __slots__ = ['_length_of', '_adjust'] + + def __init__(self, name, default, size, length_of, adjust=lambda x: x): + # type: (str, Optional[int], int, str, Callable[[int], int]) -> None + """ Initializes a FieldUVarLenField + + @param str name: The name of this field instance. + @param int|None default: the default value of this field instance. + @param int size: the number of bits that are occupied by this field in the first byte of a binary string. + size must be in the range ]0;8]. + @param str length_of: The name of the field this field value is measuring/representing. + @param callable adjust: A function that modifies the value computed from the "length_of" field. + + adjust can be used for instance to add a constant to the length_of field + length. For instance, let's say that i2len of the length_of field + returns 2. If adjust is lambda x: x+1 In that case, this field will + value 3 at build time. + @return None + @raise AssertionError + """ + assert(default is None or default >= 0) + assert(0 < size <= 8) + + super(FieldUVarLenField, self).__init__(name, default, size) + self._length_of = length_of + self._adjust = adjust + + def addfield(self, pkt, s, val): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int, int]], Optional[int]) -> str + """ + @param packet.Packet|None pkt: the packet instance containing this field instance. This parameter must not be + None if the val parameter is. + @param str|(str, int, long) s: the string to append this field to. A tuple indicates that some bits were already + generated by another bitfield-compatible field. This MUST be the case if "size" is not 8. The int is the + number of bits already generated in the first byte of the str. The long is the value that was generated by the + previous bitfield-compatible fields. + @param int|None val: the positive or null value to be added. If None, the value is computed from pkt. + @return str: s concatenated with the machine representation of this field. + @raise AssertionError + """ + if val is None: + assert isinstance(pkt, packet.Packet), \ + 'EINVAL: pkt: Packet expected when val is None; received {}'.format(type(pkt)) + val = self._compute_value(pkt) + return super(FieldUVarLenField, self).addfield(pkt, s, val) + + def i2m(self, pkt, x): + # type: (Optional[packet.Packet], Optional[int]) -> str + """ + @param packet.Packet|None pkt: the packet instance containing this field instance. This parameter must not be + None if the x parameter is. + @param int|None x: the positive or null value to be added. If None, the value is computed from pkt. + @return str + @raise AssertionError + """ + if x is None: + assert isinstance(pkt, packet.Packet), \ + 'EINVAL: pkt: Packet expected when x is None; received {}'.format(type(pkt)) + x = self._compute_value(pkt) + return super(FieldUVarLenField, self).i2m(pkt, x) + + def _compute_value(self, pkt): + # type: (packet.Packet) -> int + """ Computes the value of this field based on the provided packet and + the length_of field and the adjust callback + + @param packet.Packet pkt: the packet from which is computed this field value. + @return int: the computed value for this field. + @raise KeyError: the packet nor its payload do not contain an attribute + with the length_of name. + @raise AssertionError + @raise KeyError if _length_of is not one of pkt fields + """ + fld, fval = pkt.getfield_and_val(self._length_of) + val = fld.i2len(pkt, fval) + ret = self._adjust(val) + assert(ret >= 0) + return ret + +######################################################################################################################## +################################################ HPACK String Fields ################################################### +######################################################################################################################## + +class HPackStringsInterface(Sized): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def __str__(self): pass + + @abc.abstractmethod + def origin(self): pass + + @abc.abstractmethod + def __len__(self): pass + + +class HPackLiteralString(HPackStringsInterface): + """ HPackLiteralString is a string. This class is used as a marker and + implements an interface in common with HPackZString + """ + __slots__ = ['_s'] + + def __init__(self, s): + # type: (str) -> None + self._s = s + + def __str__(self): + # type: () -> str + return self._s + + def origin(self): + # type: () -> str + return self._s + + def __len__(self): + # type: () -> int + return len(self._s) + + +class EOS(object): + """ Simple "marker" to designate the End Of String symbol in the huffman table + """ + + +class HuffmanNode(object): + """ HuffmanNode is an entry of the binary tree used for encoding/decoding + HPack compressed HTTP/2 headers + """ + + __slots__ = ['l', 'r'] + """@var l: the left branch of this node + @var r: the right branch of this Node + + These variables can value None (leaf node), another HuffmanNode, or a + symbol. Symbols are either a character or the End Of String symbol (class + EOS) + """ + + def __init__(self, l, r): + # type: (Union[None, HuffmanNode, EOS, str], Union[None, HuffmanNode, EOS, str]) -> None + self.l = l + self.r = r + + def __getitem__(self, b): + # type: (int) -> Union[None, HuffmanNode, EOS, str] + return self.r if b else self.l + + def __setitem__(self, b, val): + # type: (int, Union[None, HuffmanNode, EOS, str]) -> None + if b: + self.r = val + else: + self.l = val + + def __str__(self): + # type: () -> str + return self.__repr__() + + def __repr__(self): + # type: () -> str + return '({}, {})'.format(self.l, self.r) + + +class InvalidEncodingException(Exception): + """ InvalidEncodingException is raised when a supposedly huffman-encoded + string is decoded and a decoding error arises + """ + + +class HPackZString(HPackStringsInterface): + __slots__ = ['_s', '_encoded'] + + # From RFC 7541 + # Tuple is (code,code bitlength) + # The bitlength is required to know how long the left padding + # (implicit 0's) there are + static_huffman_code = [ + (0x1ff8, 13), + (0x7fffd8, 23), + (0xfffffe2, 28), + (0xfffffe3, 28), + (0xfffffe4, 28), + (0xfffffe5, 28), + (0xfffffe6, 28), + (0xfffffe7, 28), + (0xfffffe8, 28), + (0xffffea, 24), + (0x3ffffffc, 30), + (0xfffffe9, 28), + (0xfffffea, 28), + (0x3ffffffd, 30), + (0xfffffeb, 28), + (0xfffffec, 28), + (0xfffffed, 28), + (0xfffffee, 28), + (0xfffffef, 28), + (0xffffff0, 28), + (0xffffff1, 28), + (0xffffff2, 28), + (0x3ffffffe, 30), + (0xffffff3, 28), + (0xffffff4, 28), + (0xffffff5, 28), + (0xffffff6, 28), + (0xffffff7, 28), + (0xffffff8, 28), + (0xffffff9, 28), + (0xffffffa, 28), + (0xffffffb, 28), + (0x14, 6), + (0x3f8, 10), + (0x3f9, 10), + (0xffa, 12), + (0x1ff9, 13), + (0x15, 6), + (0xf8, 8), + (0x7fa, 11), + (0x3fa, 10), + (0x3fb, 10), + (0xf9, 8), + (0x7fb, 11), + (0xfa, 8), + (0x16, 6), + (0x17, 6), + (0x18, 6), + (0x0, 5), + (0x1, 5), + (0x2, 5), + (0x19, 6), + (0x1a, 6), + (0x1b, 6), + (0x1c, 6), + (0x1d, 6), + (0x1e, 6), + (0x1f, 6), + (0x5c, 7), + (0xfb, 8), + (0x7ffc, 15), + (0x20, 6), + (0xffb, 12), + (0x3fc, 10), + (0x1ffa, 13), + (0x21, 6), + (0x5d, 7), + (0x5e, 7), + (0x5f, 7), + (0x60, 7), + (0x61, 7), + (0x62, 7), + (0x63, 7), + (0x64, 7), + (0x65, 7), + (0x66, 7), + (0x67, 7), + (0x68, 7), + (0x69, 7), + (0x6a, 7), + (0x6b, 7), + (0x6c, 7), + (0x6d, 7), + (0x6e, 7), + (0x6f, 7), + (0x70, 7), + (0x71, 7), + (0x72, 7), + (0xfc, 8), + (0x73, 7), + (0xfd, 8), + (0x1ffb, 13), + (0x7fff0, 19), + (0x1ffc, 13), + (0x3ffc, 14), + (0x22, 6), + (0x7ffd, 15), + (0x3, 5), + (0x23, 6), + (0x4, 5), + (0x24, 6), + (0x5, 5), + (0x25, 6), + (0x26, 6), + (0x27, 6), + (0x6, 5), + (0x74, 7), + (0x75, 7), + (0x28, 6), + (0x29, 6), + (0x2a, 6), + (0x7, 5), + (0x2b, 6), + (0x76, 7), + (0x2c, 6), + (0x8, 5), + (0x9, 5), + (0x2d, 6), + (0x77, 7), + (0x78, 7), + (0x79, 7), + (0x7a, 7), + (0x7b, 7), + (0x7ffe, 15), + (0x7fc, 11), + (0x3ffd, 14), + (0x1ffd, 13), + (0xffffffc, 28), + (0xfffe6, 20), + (0x3fffd2, 22), + (0xfffe7, 20), + (0xfffe8, 20), + (0x3fffd3, 22), + (0x3fffd4, 22), + (0x3fffd5, 22), + (0x7fffd9, 23), + (0x3fffd6, 22), + (0x7fffda, 23), + (0x7fffdb, 23), + (0x7fffdc, 23), + (0x7fffdd, 23), + (0x7fffde, 23), + (0xffffeb, 24), + (0x7fffdf, 23), + (0xffffec, 24), + (0xffffed, 24), + (0x3fffd7, 22), + (0x7fffe0, 23), + (0xffffee, 24), + (0x7fffe1, 23), + (0x7fffe2, 23), + (0x7fffe3, 23), + (0x7fffe4, 23), + (0x1fffdc, 21), + (0x3fffd8, 22), + (0x7fffe5, 23), + (0x3fffd9, 22), + (0x7fffe6, 23), + (0x7fffe7, 23), + (0xffffef, 24), + (0x3fffda, 22), + (0x1fffdd, 21), + (0xfffe9, 20), + (0x3fffdb, 22), + (0x3fffdc, 22), + (0x7fffe8, 23), + (0x7fffe9, 23), + (0x1fffde, 21), + (0x7fffea, 23), + (0x3fffdd, 22), + (0x3fffde, 22), + (0xfffff0, 24), + (0x1fffdf, 21), + (0x3fffdf, 22), + (0x7fffeb, 23), + (0x7fffec, 23), + (0x1fffe0, 21), + (0x1fffe1, 21), + (0x3fffe0, 22), + (0x1fffe2, 21), + (0x7fffed, 23), + (0x3fffe1, 22), + (0x7fffee, 23), + (0x7fffef, 23), + (0xfffea, 20), + (0x3fffe2, 22), + (0x3fffe3, 22), + (0x3fffe4, 22), + (0x7ffff0, 23), + (0x3fffe5, 22), + (0x3fffe6, 22), + (0x7ffff1, 23), + (0x3ffffe0, 26), + (0x3ffffe1, 26), + (0xfffeb, 20), + (0x7fff1, 19), + (0x3fffe7, 22), + (0x7ffff2, 23), + (0x3fffe8, 22), + (0x1ffffec, 25), + (0x3ffffe2, 26), + (0x3ffffe3, 26), + (0x3ffffe4, 26), + (0x7ffffde, 27), + (0x7ffffdf, 27), + (0x3ffffe5, 26), + (0xfffff1, 24), + (0x1ffffed, 25), + (0x7fff2, 19), + (0x1fffe3, 21), + (0x3ffffe6, 26), + (0x7ffffe0, 27), + (0x7ffffe1, 27), + (0x3ffffe7, 26), + (0x7ffffe2, 27), + (0xfffff2, 24), + (0x1fffe4, 21), + (0x1fffe5, 21), + (0x3ffffe8, 26), + (0x3ffffe9, 26), + (0xffffffd, 28), + (0x7ffffe3, 27), + (0x7ffffe4, 27), + (0x7ffffe5, 27), + (0xfffec, 20), + (0xfffff3, 24), + (0xfffed, 20), + (0x1fffe6, 21), + (0x3fffe9, 22), + (0x1fffe7, 21), + (0x1fffe8, 21), + (0x7ffff3, 23), + (0x3fffea, 22), + (0x3fffeb, 22), + (0x1ffffee, 25), + (0x1ffffef, 25), + (0xfffff4, 24), + (0xfffff5, 24), + (0x3ffffea, 26), + (0x7ffff4, 23), + (0x3ffffeb, 26), + (0x7ffffe6, 27), + (0x3ffffec, 26), + (0x3ffffed, 26), + (0x7ffffe7, 27), + (0x7ffffe8, 27), + (0x7ffffe9, 27), + (0x7ffffea, 27), + (0x7ffffeb, 27), + (0xffffffe, 28), + (0x7ffffec, 27), + (0x7ffffed, 27), + (0x7ffffee, 27), + (0x7ffffef, 27), + (0x7fffff0, 27), + (0x3ffffee, 26), + (0x3fffffff, 30) + ] + + static_huffman_tree = None + + @classmethod + def _huffman_encode_char(cls, c): + # type: (Union[str, EOS]) -> Tuple[int, int] + """ huffman_encode_char assumes that the static_huffman_tree was + previously initialized + + @param str|EOS c: a symbol to encode + @return (int, int): the bitstring of the symbol and its bitlength + @raise AssertionError + """ + if isinstance(c, EOS): + return cls.static_huffman_code[-1] + else: + assert(len(c) == 1) + return cls.static_huffman_code[ord(c)] + + @classmethod + def huffman_encode(cls, s): + # type: (str) -> Tuple[int, int] + """ huffman_encode returns the bitstring and the bitlength of the + bitstring representing the string provided as a parameter + + @param str s: the string to encode + @return (int, int): the bitstring of s and its bitlength + @raise AssertionError + """ + i = 0 + ibl = 0 + for c in s: + val, bl = cls._huffman_encode_char(c) + i = (i << bl) + val + ibl += bl + + padlen = 8 - (ibl % 8) + if padlen != 8: + val, bl = cls._huffman_encode_char(EOS()) + i = (i << padlen) + (val >> (bl - padlen)) + ibl += padlen + + ret = i, ibl + assert(ret[0] >= 0) + assert (ret[1] >= 0) + return ret + + @classmethod + def huffman_decode(cls, i, ibl): + # type: (int, int) -> str + """ huffman_decode decodes the bitstring provided as parameters. + + @param int i: the bitstring to decode + @param int ibl: the bitlength of i + @return str: the string decoded from the bitstring + @raise AssertionError, InvalidEncodingException + """ + assert(i >= 0) + assert(ibl >= 0) + + if isinstance(cls.static_huffman_tree, types.NoneType): + cls.huffman_compute_decode_tree() + assert(not isinstance(cls.static_huffman_tree, types.NoneType)) + + s = [] + j = 0 + interrupted = False + cur = cls.static_huffman_tree + cur_sym = 0 + cur_sym_bl = 0 + while j < ibl: + b = (i >> (ibl - j - 1)) & 1 + cur_sym = (cur_sym << 1) + b + cur_sym_bl += 1 + elmt = cur[b] + + if isinstance(elmt, HuffmanNode): + interrupted = True + cur = elmt + if isinstance(cur, types.NoneType): + raise AssertionError() + elif isinstance(elmt, EOS): + raise InvalidEncodingException('Huffman decoder met the full EOS symbol') + elif isinstance(elmt, str): + interrupted = False + s.append(elmt) + cur = cls.static_huffman_tree + cur_sym = 0 + cur_sym_bl = 0 + else: + raise InvalidEncodingException('Should never happen, so incidentally it will') + j += 1 + + if interrupted: + # Interrupted values true if the bitstring ends in the middle of a + # symbol; this symbol must be, according to RFC7541 par5.2 the MSB + # of the EOS symbol + if cur_sym_bl > 7: + raise InvalidEncodingException('Huffman decoder is detecting padding longer than 7 bits') + eos_symbol = cls.static_huffman_code[-1] + eos_msb = eos_symbol[0] >> (eos_symbol[1] - cur_sym_bl) + if eos_msb != cur_sym: + raise InvalidEncodingException('Huffman decoder is detecting unexpected padding format') + return ''.join(s) + + @classmethod + def huffman_conv2str(cls, bit_str, bit_len): + # type: (int, int) -> str + """ huffman_conv2str converts a bitstring of bit_len bitlength into a + binary string. It DOES NOT compress/decompress the bitstring! + + @param int bit_str: the bitstring to convert. + @param int bit_len: the bitlength of bit_str. + @return str: the converted bitstring as a bytestring. + @raise AssertionError + """ + assert(bit_str >= 0) + assert(bit_len >= 0) + + byte_len = bit_len/8 + rem_bit = bit_len % 8 + if rem_bit != 0: + bit_str <<= 8 - rem_bit + byte_len += 1 + + # As usual the list/join tricks is a performance trick to build + # efficiently a Python string + s = [] # type: List[str] + i = 0 + while i < byte_len: + s.insert(0, chr((bit_str >> (i*8)) & 0xFF)) + i += 1 + return ''.join(s) + + @classmethod + def huffman_conv2bitstring(cls, s): + # type: (str) -> Tuple[int, int] + """ huffman_conv2bitstring converts a string into its bitstring + representation. It returns a tuple: the bitstring and its bitlength. + This function DOES NOT compress/decompress the string! + + @param str s: the bytestring to convert. + @return (int, int): the bitstring of s, and its bitlength. + @raise AssertionError + """ + i = 0 + ibl = len(s) * 8 + for c in s: + i = (i << 8) + ord(c) + + ret = i, ibl + assert(ret[0] >= 0) + assert(ret[1] >= 0) + return ret + + @classmethod + def huffman_compute_decode_tree(cls): + # type: () -> None + """ huffman_compute_decode_tree initializes/builds the static_huffman_tree + + @return None + @raise InvalidEncodingException if there is an encoding problem + """ + cls.static_huffman_tree = HuffmanNode(None, None) + i = 0 + for entry in cls.static_huffman_code: + parent = cls.static_huffman_tree + for idx in xrange(entry[1] - 1, -1, -1): + b = (entry[0] >> idx) & 1 + if isinstance(parent[b], str): + raise InvalidEncodingException('Huffman unique prefix violation :/') + if idx == 0: + parent[b] = chr(i) if i < 256 else EOS() + elif parent[b] is None: + parent[b] = HuffmanNode(None, None) + parent = parent[b] + i += 1 + + def __init__(self, s): + # type: (str) -> None + self._s = s + i, ibl = type(self).huffman_encode(s) + self._encoded = type(self).huffman_conv2str(i, ibl) + + def __str__(self): + # type: () -> str + return self._encoded + + def origin(self): + # type: () -> str + return self._s + + def __len__(self): + # type: () -> int + return len(self._encoded) + + +class HPackStrLenField(fields.Field): + """ HPackStrLenField is a StrLenField variant specialized for HTTP/2 HPack + + This variant uses an internal representation that implements HPackStringsInterface. + """ + __slots__ = ['_length_from', '_type_from'] + + def __init__(self, name, default, length_from, type_from): + # type: (str, HPackStringsInterface, Callable[[packet.Packet], int], str) -> None + super(HPackStrLenField, self).__init__(name, default) + self._length_from = length_from + self._type_from = type_from + + def addfield(self, pkt, s, val): + # type: (Optional[packet.Packet], str, HPackStringsInterface) -> str + return s + self.i2m(pkt, val) + + @staticmethod + def _parse(t, s): + # type: (bool, str) -> HPackStringsInterface + """ + @param bool t: whether this string is a huffman compressed string. + @param str s: the string to parse. + @return HPackStringsInterface: either a HPackLiteralString or HPackZString, depending on t. + @raise InvalidEncodingException + """ + if t: + i, ibl = HPackZString.huffman_conv2bitstring(s) + return HPackZString(HPackZString.huffman_decode(i, ibl)) + return HPackLiteralString(s) + + def getfield(self, pkt, s): + # type: (packet.Packet, str) -> Tuple[str, HPackStringsInterface] + """ + @param packet.Packet pkt: the packet instance containing this field instance. + @param str s: the string to parse this field from. + @return (str, HPackStringsInterface): the remaining string after this field was carved out & the extracted + value. + @raise KeyError if "type_from" is not a field of pkt or its payloads. + @raise InvalidEncodingException + """ + l = self._length_from(pkt) + t = pkt.getfieldval(self._type_from) == 1 + return s[l:], self._parse(t, s[:l]) + + def i2h(self, pkt, x): + # type: (Optional[packet.Packet], HPackStringsInterface) -> str + fmt = '' + if isinstance(x, HPackLiteralString): + fmt = "HPackLiteralString({})" + elif isinstance(x, HPackZString): + fmt = "HPackZString({})" + return fmt.format(x.origin()) + + def h2i(self, pkt, x): + # type: (packet.Packet, str) -> HPackStringsInterface + return HPackLiteralString(x) + + def m2i(self, pkt, x): + # type: (packet.Packet, str) -> HPackStringsInterface + """ + @param packet.Packet pkt: the packet instance containing this field instance. + @param str x: the string to parse. + @return HPackStringsInterface: the internal type of the value parsed from x. + @raise AssertionError + @raise InvalidEncodingException + @raise KeyError if _type_from is not one of pkt fields. + """ + t = pkt.getfieldval(self._type_from) + l = self._length_from(pkt) + + assert t is not None and l is not None, 'Conversion from string impossible: no type or length specified' + + return self._parse(t == 1, x[:l]) + + def any2i(self, pkt, x): + # type: (Optional[packet.Packet], Union[str, HPackStringsInterface]) -> HPackStringsInterface + """ + @param packet.Packet|None pkt: the packet instance containing this field instance. + @param str|HPackStringsInterface x: the value to convert + @return HPackStringsInterface: the Scapy internal value for this field + @raise AssertionError, InvalidEncodingException + """ + if isinstance(x, str): + assert(isinstance(pkt, packet.Packet)) + return self.m2i(pkt, x) + assert(isinstance(x, HPackStringsInterface)) + return x + + def i2m(self, pkt, x): + # type: (Optional[packet.Packet], HPackStringsInterface) -> str + return str(x) + + def i2len(self, pkt, x): + # type: (Optional[packet.Packet], HPackStringsInterface) -> int + return len(x) + + def i2repr(self, pkt, x): + # type: (Optional[packet.Packet], HPackStringsInterface) -> str + return repr(self.i2h(pkt, x)) + +######################################################################################################################## +################################################ HPACK Packets ######################################################### +######################################################################################################################## + +class HPackHdrString(packet.Packet): + """ HPackHdrString is a packet that that is serialized into a RFC7541 par5.2 + string literal repr. + """ + name = 'HPack Header String' + fields_desc = [ + fields.BitEnumField('type', None, 1, {0: 'Literal', 1: 'Compressed'}), + FieldUVarLenField('len', None, 7, length_of='data'), + HPackStrLenField( + 'data', HPackLiteralString(''), + length_from=lambda pkt: pkt.getfieldval('len'), + type_from='type' + ) + ] + + def guess_payload_class(self, payload): + # type: (str) -> base_classes.Packet_metaclass + # Trick to tell scapy that the remaining bytes of the currently + # dissected string is not a payload of this packet but of some other + # underlayer packet + return config.conf.padding_layer + + def self_build(self, field_pos_list=None): + # type: (Any) -> str + """self_build is overridden because type and len are determined at + build time, based on the "data" field internal type + """ + if self.getfieldval('type') is None: + self.type = 1 if isinstance(self.getfieldval('data'), HPackZString) else 0 + return super(HPackHdrString, self).self_build(field_pos_list) + + +class HPackHeaders(packet.Packet): + """HPackHeaders uses the "dispatch_hook" trick of Packet_metaclass to select + the correct HPack header packet type. For this, the first byte of the string + to dissect is snooped on. + """ + @classmethod + def dispatch_hook(cls, s=None, *_args, **_kwds): + # type: (Optional[str], *Any, **Any) -> base_classes.Packet_metaclass + """dispatch_hook returns the subclass of HPackHeaders that must be used + to dissect the string. + """ + if s is None: + return config.conf.raw_layer + fb = ord(s[0]) + if fb & 0x80 != 0: + return HPackIndexedHdr + if fb & 0x40 != 0: + return HPackLitHdrFldWithIncrIndexing + if fb & 0x20 != 0: + return HPackDynamicSizeUpdate + return HPackLitHdrFldWithoutIndexing + + def guess_payload_class(self, payload): + # type: (str) -> base_classes.Packet_metaclass + return config.conf.padding_layer + + +class HPackIndexedHdr(HPackHeaders): + """ HPackIndexedHdr implements RFC 7541 par6.1 + """ + name = 'HPack Indexed Header Field' + fields_desc = [ + HPackMagicBitField('magic', 1, 1), + UVarIntField('index', 2, 7) # Default "2" is ":method GET" + ] + + +class HPackLitHdrFldWithIncrIndexing(HPackHeaders): + """ HPackLitHdrFldWithIncrIndexing implements RFC 7541 par6.2.1 + """ + name = 'HPack Literal Header With Incremental Indexing' + fields_desc = [ + HPackMagicBitField('magic', 1, 2), + UVarIntField('index', 0, 6), # Default is New Name + fields.ConditionalField( + fields.PacketField('hdr_name', None, HPackHdrString), + lambda pkt: pkt.getfieldval('index') == 0 + ), + fields.PacketField('hdr_value', None, HPackHdrString) + ] + + +class HPackLitHdrFldWithoutIndexing(HPackHeaders): + """ HPackLitHdrFldWithIncrIndexing implements RFC 7541 par6.2.2 + and par6.2.3 + """ + name = 'HPack Literal Header Without Indexing (or Never Indexing)' + fields_desc = [ + HPackMagicBitField('magic', 0, 3), + fields.BitEnumField( + 'never_index', 0, 1, + {0: "Don't Index", 1: 'Never Index'} + ), + UVarIntField('index', 0, 4), # Default is New Name + fields.ConditionalField( + fields.PacketField('hdr_name', None, HPackHdrString), + lambda pkt: pkt.getfieldval('index') == 0 + ), + fields.PacketField('hdr_value', None, HPackHdrString) + ] + + +class HPackDynamicSizeUpdate(HPackHeaders): + """ HPackDynamicSizeUpdate implements RFC 7541 par6.3 + """ + name = 'HPack Dynamic Size Update' + fields_desc = [ + HPackMagicBitField('magic', 1, 3), + UVarIntField('max_size', 0, 5) + ] + +######################################################################################################################## +############################################# HTTP/2 Frames ############################################################ +######################################################################################################################## + +class H2FramePayload(packet.Packet): + """ H2FramePayload is an empty class that is a super class of all Scapy + HTTP/2 Frame Packets + """ + +############################################# HTTP/2 Data Frame Packets ################################################ + +class H2DataFrame(H2FramePayload): + """ H2DataFrame implements RFC7540 par6.1 + This packet is the Data Frame to use when there is no padding. + """ + type_id = 0 + END_STREAM_FLAG = 0 # 0x1 + PADDED_FLAG = 3 # 0x8 + flags = { + END_STREAM_FLAG: fields.MultiFlagsEntry('ES', 'End Stream'), + PADDED_FLAG: fields.MultiFlagsEntry('P', 'Padded') + } + + name = 'HTTP/2 Data Frame' + fields_desc = [ + fields.StrField('data', '') + ] + + +class H2PaddedDataFrame(H2DataFrame): + """ H2DataFrame implements RFC7540 par6.1 + This packet is the Data Frame to use when there is padding. + """ + __slots__ = ['s_len'] + + name = 'HTTP/2 Padded Data Frame' + fields_desc = [ + fields.FieldLenField('padlen', None, length_of='padding', fmt="B"), + fields.StrLenField('data', '', + length_from=lambda pkt: pkt.get_data_len() + ), + fields.StrLenField('padding', '', + length_from=lambda pkt: pkt.getfieldval('padlen') + ) + ] + + def get_data_len(self): + # type: () -> int + """ get_data_len computes the length of the data field + + To do this computation, the length of the padlen field and the actual + padding is subtracted to the string that was provided to the pre_dissect + fun of the pkt parameter + @return int; length of the data part of the HTTP/2 frame packet provided as parameter + @raise AssertionError + """ + padding_len = self.getfieldval('padlen') + fld, fval = self.getfield_and_val('padlen') + padding_len_len = fld.i2len(self, fval) + + ret = self.s_len - padding_len_len - padding_len + assert(ret >= 0) + return ret + + def pre_dissect(self, s): + # type: (str) -> str + """pre_dissect is filling the s_len property of this instance. This + property is later used during the getfield call of the "data" field when + trying to evaluate the length of the StrLenField! This "trick" works + because the underlayer packet (H2Frame) is assumed to override the + "extract_padding" method and to only provide to this packet the data + necessary for this packet. Tricky, tricky, will break some day probably! + """ + self.s_len = len(s) + return s + + +############################################# HTTP/2 Header Frame Packets ############################################## + +class H2AbstractHeadersFrame(H2FramePayload): + """Superclass of all variants of HTTP/2 Header Frame Packets. + May be used for type checking. + """ + +class H2HeadersFrame(H2AbstractHeadersFrame): + """ H2HeadersFrame implements RFC 7540 par6.2 Headers Frame + when there is no padding and no priority informations + + The choice of decomposing into four classes is probably preferable to having + numerous conditional fields based on the underlayer :/ + """ + type_id = 1 + END_STREAM_FLAG = 0 # 0x1 + END_HEADERS_FLAG = 2 # 0x4 + PADDED_FLAG = 3 # 0x8 + PRIORITY_FLAG = 5 # 0x20 + flags = { + END_STREAM_FLAG: fields.MultiFlagsEntry('ES', 'End Stream'), + END_HEADERS_FLAG: fields.MultiFlagsEntry('EH', 'End Headers'), + PADDED_FLAG: fields.MultiFlagsEntry('P', 'Padded'), + PRIORITY_FLAG: fields.MultiFlagsEntry('+', 'Priority') + } + + name = 'HTTP/2 Headers Frame' + fields_desc = [ + fields.PacketListField('hdrs', [], HPackHeaders) + ] + + +class H2PaddedHeadersFrame(H2AbstractHeadersFrame): + """ H2PaddedHeadersFrame is the variant of H2HeadersFrame where padding flag + is set and priority flag is cleared + """ + __slots__ = ['s_len'] + + name = 'HTTP/2 Headers Frame with Padding' + fields_desc = [ + fields.FieldLenField('padlen', None, length_of='padding', fmt='B'), + fields.PacketListField('hdrs', [], HPackHeaders, + length_from=lambda pkt: pkt.get_hdrs_len() + ), + fields.StrLenField('padding', '', + length_from=lambda pkt: pkt.getfieldval('padlen') + ) + ] + + def get_hdrs_len(self): + # type: () -> int + """ get_hdrs_len computes the length of the hdrs field + + To do this computation, the length of the padlen field and the actual + padding is subtracted to the string that was provided to the pre_dissect + fun of the pkt parameter. + @return int; length of the data part of the HTTP/2 frame packet provided as parameter + @raise AssertionError + """ + padding_len = self.getfieldval('padlen') + fld, fval = self.getfield_and_val('padlen') + padding_len_len = fld.i2len(self, fval) + + ret = self.s_len - padding_len_len - padding_len + assert(ret >= 0) + return ret + + def pre_dissect(self, s): + # type: (str) -> str + """pre_dissect is filling the s_len property of this instance. This + property is later used during the parsing of the hdrs PacketListField + when trying to evaluate the length of the PacketListField! This "trick" + works because the underlayer packet (H2Frame) is assumed to override the + "extract_padding" method and to only provide to this packet the data + necessary for this packet. Tricky, tricky, will break some day probably! + """ + self.s_len = len(s) + return s + + +class H2PriorityHeadersFrame(H2AbstractHeadersFrame): + """ H2PriorityHeadersFrame is the variant of H2HeadersFrame where priority flag + is set and padding flag is cleared + """ + __slots__ = ['s_len'] + + name = 'HTTP/2 Headers Frame with Priority' + fields_desc = [ + fields.BitField('exclusive', 0, 1), + fields.BitField('stream_dependency', 0, 31), + fields.ByteField('weight', 0), + # This PacketListField will consume all remaining bytes; not a problem + # because the underlayer (H2Frame) overrides "extract_padding" so that + # this Packet only get to parser what it needs to + fields.PacketListField('hdrs', [], HPackHeaders), + ] + + +class H2PaddedPriorityHeadersFrame(H2AbstractHeadersFrame): + """ H2PaddedPriorityHeadersFrame is the variant of H2HeadersFrame where + both priority and padding flags are set + """ + __slots__ = ['s_len'] + + name = 'HTTP/2 Headers Frame with Padding and Priority' + fields_desc = [ + fields.FieldLenField('padlen', None, length_of='padding', fmt='B'), + fields.BitField('exclusive', 0, 1), + fields.BitField('stream_dependency', 0, 31), + fields.ByteField('weight', 0), + fields.PacketListField('hdrs', [], HPackHeaders, + length_from=lambda pkt: pkt.get_hdrs_len() + ), + fields.StrLenField('padding', '', + length_from=lambda pkt: pkt.getfieldval('padlen') + ) + ] + + def get_hdrs_len(self): + # type: () -> int + """ get_hdrs_len computes the length of the hdrs field + + To do this computation, the length of the padlen field, the priority + information fields and the actual padding is subtracted to the string + that was provided to the pre_dissect fun of the pkt parameter. + @return int: the length of the hdrs field + @raise AssertionError + """ + + padding_len = self.getfieldval('padlen') + fld, fval = self.getfield_and_val('padlen') + padding_len_len = fld.i2len(self, fval) + bit_cnt = self.get_field('exclusive').size + bit_cnt += self.get_field('stream_dependency').size + fld, fval = self.getfield_and_val('weight') + weight_len = fld.i2len(self, fval) + ret = (self.s_len + - padding_len_len + - padding_len + - (bit_cnt / 8) + - weight_len + ) + assert(ret >= 0) + return ret + + def pre_dissect(self, s): + # type: (str) -> str + """pre_dissect is filling the s_len property of this instance. This + property is later used during the parsing of the hdrs PacketListField + when trying to evaluate the length of the PacketListField! This "trick" + works because the underlayer packet (H2Frame) is assumed to override the + "extract_padding" method and to only provide to this packet the data + necessary for this packet. Tricky, tricky, will break some day probably! + """ + self.s_len = len(s) + return s + +########################################### HTTP/2 Priority Frame Packets ############################################## + +class H2PriorityFrame(H2FramePayload): + """ H2PriorityFrame implements RFC 7540 par6.3 + """ + type_id = 2 + name = 'HTTP/2 Priority Frame' + fields_desc = [ + fields.BitField('exclusive', 0, 1), + fields.BitField('stream_dependency', 0, 31), + fields.ByteField('weight', 0) + ] + +################################################# HTTP/2 Errors ######################################################## + +class H2ErrorCodes(object): + """ H2ErrorCodes is an enumeration of the error codes defined in + RFC7540 par7. + This enumeration is not part of any frame because the error codes are in + common with H2ResetFrame and H2GoAwayFrame. + """ + + NO_ERROR = 0x0 + PROTOCOL_ERROR = 0x1 + INTERNAL_ERROR = 0x2 + FLOW_CONTROL_ERROR = 0x3 + SETTINGS_TIMEOUT = 0x4 + STREAM_CLOSED = 0x5 + FRAME_SIZE_ERROR = 0x6 + REFUSED_STREAM = 0x7 + CANCEL = 0x8 + COMPRESSION_ERROR = 0x9 + CONNECT_ERROR = 0xa + ENHANCE_YOUR_CALM = 0xb + INADEQUATE_SECURITY = 0xc + HTTP_1_1_REQUIRED = 0xd + + literal = { + NO_ERROR: 'No error', + PROTOCOL_ERROR: 'Protocol error', + INTERNAL_ERROR: 'Internal error', + FLOW_CONTROL_ERROR: 'Flow control error', + SETTINGS_TIMEOUT: 'Settings timeout', + STREAM_CLOSED: 'Stream closed', + FRAME_SIZE_ERROR: 'Frame size error', + REFUSED_STREAM: 'Refused stream', + CANCEL: 'Cancel', + COMPRESSION_ERROR: 'Compression error', + CONNECT_ERROR: 'Control error', + ENHANCE_YOUR_CALM: 'Enhance your calm', + INADEQUATE_SECURITY: 'Inadequate security', + HTTP_1_1_REQUIRED: 'HTTP/1.1 required' + } + + +########################################### HTTP/2 Reset Frame Packets ################################################# + +class H2ResetFrame(H2FramePayload): + """ H2ResetFrame implements RFC 7540 par6.4 + """ + type_id = 3 + name = 'HTTP/2 Reset Frame' + fields_desc = [ + fields.EnumField('error', 0, H2ErrorCodes.literal, fmt='!I') + ] + + +########################################### HTTP/2 Settings Frame Packets ############################################## + +class H2Setting(packet.Packet): + """ H2Setting implements a setting, as defined in RFC7540 par6.5.1 + """ + SETTINGS_HEADER_TABLE_SIZE = 0x1 + SETTINGS_ENABLE_PUSH = 0x2 + SETTINGS_MAX_CONCURRENT_STREAMS = 0x3 + SETTINGS_INITIAL_WINDOW_SIZE = 0x4 + SETTINGS_MAX_FRAME_SIZE = 0x5 + SETTINGS_MAX_HEADER_LIST_SIZE = 0x6 + + name = 'HTTP/2 Setting' + fields_desc = [ + fields.EnumField('id', 0, { + SETTINGS_HEADER_TABLE_SIZE: 'Header table size', + SETTINGS_ENABLE_PUSH: 'Enable push', + SETTINGS_MAX_CONCURRENT_STREAMS: 'Max concurrent streams', + SETTINGS_INITIAL_WINDOW_SIZE: 'Initial window size', + SETTINGS_MAX_FRAME_SIZE: 'Max frame size', + SETTINGS_MAX_HEADER_LIST_SIZE: 'Max header list size' + }, fmt='!H'), + fields.IntField('value', 0) + ] + + def guess_payload_class(self, payload): + # type: (str) -> base_classes.Packet_metaclass + return config.conf.padding_layer + + +class H2SettingsFrame(H2FramePayload): + """ H2SettingsFrame implements RFC7540 par6.5 + """ + type_id = 4 + ACK_FLAG = 0 # 0x1 + flags = { + ACK_FLAG: fields.MultiFlagsEntry('A', 'ACK') + } + + name = 'HTTP/2 Settings Frame' + fields_desc = [ + fields.PacketListField('settings', [], H2Setting) + ] + + def __init__(self, *args, **kwargs): + """__init__ initializes this H2SettingsFrame + + If a _pkt arg is provided (by keyword), then this is an initialization + from a string to dissect and therefore the length of the string to + dissect have distinctive characteristics that we might want to check. + This is possible because the underlayer packet (H2Frame) overrides + extract_padding method to provided only the string that must be parsed + by this packet! + @raise AssertionError + """ + + # RFC7540 par6.5 p36 + assert( + len(args) == 0 or ( + isinstance(args[0], str) + and len(args[0]) % 6 == 0 + ) + ), 'Invalid settings frame; length is not a multiple of 6' + super(H2SettingsFrame, self).__init__(*args, **kwargs) + +######################################## HTTP/2 Push Promise Frame Packets ############################################# + +class H2PushPromiseFrame(H2FramePayload): + """ H2PushPromiseFrame implements RFC7540 par6.6. This packet + is the variant to use when the underlayer padding flag is cleared + """ + type_id = 5 + END_HEADERS_FLAG = 2 # 0x4 + PADDED_FLAG = 3 # 0x8 + flags = { + END_HEADERS_FLAG: fields.MultiFlagsEntry('EH', 'End Headers'), + PADDED_FLAG: fields.MultiFlagsEntry('P', 'Padded') + } + + name = 'HTTP/2 Push Promise Frame' + fields_desc = [ + fields.BitField('reserved', 0, 1), + fields.BitField('stream_id', 0, 31), + fields.PacketListField('hdrs', [], HPackHeaders) + ] + + +class H2PaddedPushPromiseFrame(H2PushPromiseFrame): + """ H2PaddedPushPromiseFrame implements RFC7540 par6.6. This + packet is the variant to use when the underlayer padding flag is set + """ + __slots__ = ['s_len'] + + name = 'HTTP/2 Padded Push Promise Frame' + fields_desc = [ + fields.FieldLenField('padlen', None, length_of='padding', fmt='B'), + fields.BitField('reserved', 0, 1), + fields.BitField('stream_id', 0, 31), + fields.PacketListField('hdrs', [], HPackHeaders, + length_from=lambda pkt: pkt.get_hdrs_len() + ), + fields.StrLenField('padding', '', + length_from=lambda pkt: pkt.getfieldval('padlen') + ) + ] + + def get_hdrs_len(self): + # type: () -> int + """ get_hdrs_len computes the length of the hdrs field + + To do this computation, the length of the padlen field, reserved, + stream_id and the actual padding is subtracted to the string that was + provided to the pre_dissect fun of the pkt parameter. + @return int: the length of the hdrs field + @raise AssertionError + """ + fld, padding_len = self.getfield_and_val('padlen') + padding_len_len = fld.i2len(self, padding_len) + bit_len = self.get_field('reserved').size + bit_len += self.get_field('stream_id').size + + ret = (self.s_len + - padding_len_len + - padding_len + - (bit_len/8) + ) + assert(ret >= 0) + return ret + + def pre_dissect(self, s): + # type: (str) -> str + """pre_dissect is filling the s_len property of this instance. This + property is later used during the parsing of the hdrs PacketListField + when trying to evaluate the length of the PacketListField! This "trick" + works because the underlayer packet (H2Frame) is assumed to override the + "extract_padding" method and to only provide to this packet the data + necessary for this packet. Tricky, tricky, will break some day probably! + """ + self.s_len = len(s) + return s + +############################################### HTTP/2 Ping Frame Packets ############################################## + +class H2PingFrame(H2FramePayload): + """ H2PingFrame implements the RFC 7540 par6.7 + """ + type_id = 6 + ACK_FLAG = 0 # 0x1 + flags = { + ACK_FLAG: fields.MultiFlagsEntry('A', 'ACK') + } + + name = 'HTTP/2 Ping Frame' + fields_desc = [ + fields.LongField('opaque', 0) + ] + + def __init__(self, *args, **kwargs): + """ + @raise AssertionError + """ + # RFC7540 par6.7 p42 + assert( + len(args) == 0 or ( + isinstance(args[0], str) + and len(args[0]) == 8 + ) + ), 'Invalid ping frame; length is not 8' + super(H2PingFrame, self).__init__(*args, **kwargs) + + +############################################# HTTP/2 GoAway Frame Packets ############################################## + +class H2GoAwayFrame(H2FramePayload): + """ H2GoAwayFrame implements the RFC 7540 par6.8 + """ + type_id = 7 + + name = 'HTTP/2 Go Away Frame' + fields_desc = [ + fields.BitField('reserved', 0, 1), + fields.BitField('last_stream_id', 0, 31), + fields.EnumField('error', 0, H2ErrorCodes.literal, fmt='!I'), + fields.StrField('additional_data', '') + ] + +###################################### HTTP/2 Window Update Frame Packets ############################################## + +class H2WindowUpdateFrame(H2FramePayload): + """ H2WindowUpdateFrame implements the RFC 7540 par6.9 + """ + type_id = 8 + + name = 'HTTP/2 Window Update Frame' + fields_desc = [ + fields.BitField('reserved', 0, 1), + fields.BitField('win_size_incr', 0, 31) + ] + + def __init__(self, *args, **kwargs): + """ + @raise AssertionError + """ + # RFC7540 par6.9 p46 + assert( + len(args) == 0 or ( + isinstance(args[0], str) + and len(args[0]) == 4 + ) + ), 'Invalid window update frame; length is not 4' + super(H2WindowUpdateFrame, self).__init__(*args, **kwargs) + +####################################### HTTP/2 Continuation Frame Packets ############################################## + +class H2ContinuationFrame(H2FramePayload): + """ H2ContinuationFrame implements the RFC 7540 par6.10 + """ + type_id = 9 + END_HEADERS_FLAG = 2 # Ox4 + flags = { + END_HEADERS_FLAG: fields.MultiFlagsEntry('EH', 'End Headers') + } + + name = 'HTTP/2 Continuation Frame' + fields_desc = [ + fields.PacketListField('hdrs', [], HPackHeaders) + ] + +########################################## HTTP/2 Base Frame Packets ################################################### + +class H2Frame(packet.Packet): + """ H2Frame implements the frame structure as defined in RFC 7540 par4.1 + + This packet may have a payload (one of the H2FramePayload) or none, in some + rare cases such as settings acknowledgement) + """ + name = 'HTTP/2 Frame' + fields_desc = [ + fields.X3BytesField('len', None), + fields.EnumField('type', None, { + 0: 'DataFrm', + 1: 'HdrsFrm', + 2: 'PrioFrm', + 3: 'RstFrm', + 4: 'SetFrm', + 5: 'PushFrm', + 6: 'PingFrm', + 7: 'GoawayFrm', + 8: 'WinFrm', + 9: 'ContFrm' + }, "b"), + fields.MultiFlagsField('flags', set(), 8, { + H2DataFrame.type_id: H2DataFrame.flags, + H2HeadersFrame.type_id: H2HeadersFrame.flags, + H2PushPromiseFrame.type_id: H2PushPromiseFrame.flags, + H2SettingsFrame.type_id: H2SettingsFrame.flags, + H2PingFrame.type_id: H2PingFrame.flags, + H2ContinuationFrame.type_id: H2ContinuationFrame.flags, + }, + depends_on=lambda pkt: pkt.getfieldval('type') + ), + fields.BitField('reserved', 0, 1), + fields.BitField('stream_id', 0, 31) + ] + + def guess_payload_class(self, payload): + # type: (str) -> base_classes.Packet_metaclass + """ guess_payload_class returns the Class object to use for parsing a payload + This function uses the H2Frame.type field value to decide which payload to parse. The implement cannot be + performed using the simple bind_layers helper because sometimes the selection of which Class object to return + also depends on the H2Frame.flags value. + + @param payload: + @return: + """ + if len(payload) == 0: + return packet.NoPayload + + t = self.getfieldval('type') + if t == H2DataFrame.type_id: + if H2DataFrame.flags[H2DataFrame.PADDED_FLAG].short in self.getfieldval('flags'): + return H2PaddedDataFrame + return H2DataFrame + + if t == H2HeadersFrame.type_id: + if H2HeadersFrame.flags[H2HeadersFrame.PADDED_FLAG].short in self.getfieldval('flags'): + if H2HeadersFrame.flags[H2HeadersFrame.PRIORITY_FLAG].short in self.getfieldval('flags'): + return H2PaddedPriorityHeadersFrame + else: + return H2PaddedHeadersFrame + elif H2HeadersFrame.flags[H2HeadersFrame.PRIORITY_FLAG].short in self.getfieldval('flags'): + return H2PriorityHeadersFrame + return H2HeadersFrame + + if t == H2PriorityFrame.type_id: + return H2PriorityFrame + + if t == H2ResetFrame.type_id: + return H2ResetFrame + + if t == H2SettingsFrame.type_id: + return H2SettingsFrame + + if t == H2PushPromiseFrame.type_id: + if H2PushPromiseFrame.flags[H2PushPromiseFrame.PADDED_FLAG].short in self.getfieldval('flags'): + return H2PaddedPushPromiseFrame + return H2PushPromiseFrame + + if t == H2PingFrame.type_id: + return H2PingFrame + + if t == H2GoAwayFrame.type_id: + return H2GoAwayFrame + + if t == H2WindowUpdateFrame.type_id: + return H2WindowUpdateFrame + + if t == H2ContinuationFrame.type_id: + return H2ContinuationFrame + + return config.conf.padding_layer + + def extract_padding(self, s): + # type: (str) -> Tuple[str, str] + """ + @param str s: the string from which to tell the padding and the payload data apart + @return (str, str): the padding and the payload data strings + @raise AssertionError + """ + assert isinstance(self.len, (int, long)) and self.len >= 0, 'Invalid length: negative len?' + assert len(s) >= self.len, 'Invalid length: string too short for this length' + return s[:self.len], s[self.len:] + + def post_build(self, p, pay): + # type: (str, str) -> str + """ + @param str p: the stringified packet + @param str pay: the stringified payload + @return str: the stringified packet and payload, with the packet length field "patched" + @raise AssertionError + """ + # This logic, while awkward in the post_build and more reasonable in + # a self_build is implemented here for performance tricks reason + if self.getfieldval('len') is None: + assert(len(pay) < (1 << 24)), 'Invalid length: payload is too long' + p = struct.pack('!L', len(pay))[1:] + p[3:] + return super(H2Frame, self).post_build(p, pay) + +class H2Seq(packet.Packet): + """ H2Seq is a helper packet that contains several H2Frames and their + payload. This packet can be used, for instance, while reading manually from + a TCP socket. + """ + name = 'HTTP/2 Frame Sequence' + fields_desc = [ + fields.PacketListField('frames', [], H2Frame) + ] + + def guess_payload_class(self, payload): + # type: (str) -> base_classes.Packet_metaclass + return config.conf.padding_layer + + +packet.bind_layers(H2Frame, H2DataFrame, {'type': H2DataFrame.type_id}) +packet.bind_layers(H2Frame, H2PaddedDataFrame, {'type': H2DataFrame.type_id}) +packet.bind_layers(H2Frame, H2HeadersFrame, {'type': H2HeadersFrame.type_id}) +packet.bind_layers(H2Frame, H2PaddedHeadersFrame, {'type': H2HeadersFrame.type_id}) +packet.bind_layers(H2Frame, H2PriorityHeadersFrame, {'type': H2HeadersFrame.type_id}) +packet.bind_layers(H2Frame, H2PaddedPriorityHeadersFrame, {'type': H2HeadersFrame.type_id}) +packet.bind_layers(H2Frame, H2PriorityFrame, {'type': H2PriorityFrame.type_id}) +packet.bind_layers(H2Frame, H2ResetFrame, {'type': H2ResetFrame.type_id}) +packet.bind_layers(H2Frame, H2SettingsFrame, {'type': H2SettingsFrame.type_id}) +packet.bind_layers(H2Frame, H2PingFrame, {'type': H2PingFrame.type_id}) +packet.bind_layers(H2Frame, H2PushPromiseFrame, {'type': H2PushPromiseFrame.type_id}) +packet.bind_layers(H2Frame, H2PaddedPushPromiseFrame, {'type': H2PaddedPushPromiseFrame.type_id}) +packet.bind_layers(H2Frame, H2GoAwayFrame, {'type': H2GoAwayFrame.type_id}) +packet.bind_layers(H2Frame, H2WindowUpdateFrame, {'type': H2WindowUpdateFrame.type_id}) +packet.bind_layers(H2Frame, H2ContinuationFrame, {'type': H2ContinuationFrame.type_id}) + + +########################################## HTTP/2 Connection Preface ################################################### +# From RFC 7540 par3.5 +H2_CLIENT_CONNECTION_PREFACE = '505249202a20485454502f322e300d0a0d0a534d0d0a0d0a'.decode('hex') + + +######################################################################################################################## +################################################### HTTP/2 Helpers ##################################################### +######################################################################################################################## + +class HPackHdrEntry(Sized): + """ HPackHdrEntry is an entry of the HPackHdrTable helper + + Each HPackHdrEntry instance is a header line (name and value). Names are + normalized (lowercased), according to RFC 7540 par8.1.2 + """ + __slots__ = ['_name', '_len', '_value'] + + def __init__(self, name, value): + # type: (str, str) -> None + """ + @raise AssertionError + """ + assert(len(name) > 0) + + self._name = name.lower() + self._value = value + + # 32 bytes is an RFC-hardcoded value: see RFC 7541 par4.1 + self._len = (32 + len(self._name) + len(self._value)) + + def name(self): + # type: () -> str + return self._name + + def value(self): + # type: () -> str + return self._value + + def size(self): + # type: () -> int + """ size returns the "length" of the header entry, as defined in + RFC 7541 par4.1. + """ + return self._len + + __len__ = size + + def __str__(self): + # type: () -> str + """ __str__ returns the header as it would be formated in textual format + """ + if self._name.startswith(':'): + return "{} {}".format(self._name, self._value) + else: + return "{}: {}".format(self._name, self._value) + + +class HPackHdrTable(Sized): + """ HPackHdrTable is a helper class that implements some of the logic + associated with indexing of headers (read and write operations in this + "registry". THe HPackHdrTable also implements convenience functions to easily + convert to and from textual representation and binary representation of + a HTTP/2 requests + """ + __slots__ = [ + '_dynamic_table', + '_dynamic_table_max_size', + '_dynamic_table_cap_size', + '_regexp' + ] + """:var _dynamic_table: the list containing entries requested to be added by + the peer and registered with a register() call + :var _dynamic_table_max_size: the current maximum size of the dynamic table + in bytes. This value is updated with the Dynamic Table Size Update messages + defined in RFC 7541 par6.3 + :var _dynamic_table_cap_size: the maximum size of the dynamic table in + bytes. This value is updated with the SETTINGS_HEADER_TABLE_SIZE HTTP/2 + setting. + """ + + # Manually imported from RFC 7541 Appendix A + _static_entries = { + 1: HPackHdrEntry(':authority', ''), + 2: HPackHdrEntry(':method', 'GET'), + 3: HPackHdrEntry(':method', 'POST'), + 4: HPackHdrEntry(':path', '/'), + 5: HPackHdrEntry(':path', '/index.html'), + 6: HPackHdrEntry(':scheme', 'http'), + 7: HPackHdrEntry(':scheme', 'https'), + 8: HPackHdrEntry(':status', '200'), + 9: HPackHdrEntry(':status', '204'), + 10: HPackHdrEntry(':status', '206'), + 11: HPackHdrEntry(':status', '304'), + 12: HPackHdrEntry(':status', '400'), + 13: HPackHdrEntry(':status', '404'), + 14: HPackHdrEntry(':status', '500'), + 15: HPackHdrEntry('accept-charset', ''), + 16: HPackHdrEntry('accept-encoding', 'gzip, deflate'), + 17: HPackHdrEntry('accept-language', ''), + 18: HPackHdrEntry('accept-ranges', ''), + 19: HPackHdrEntry('accept', ''), + 20: HPackHdrEntry('access-control-allow-origin', ''), + 21: HPackHdrEntry('age', ''), + 22: HPackHdrEntry('allow', ''), + 23: HPackHdrEntry('authorization', ''), + 24: HPackHdrEntry('cache-control', ''), + 25: HPackHdrEntry('content-disposition', ''), + 26: HPackHdrEntry('content-encoding', ''), + 27: HPackHdrEntry('content-language', ''), + 28: HPackHdrEntry('content-length', ''), + 29: HPackHdrEntry('content-location', ''), + 30: HPackHdrEntry('content-range', ''), + 31: HPackHdrEntry('content-type', ''), + 32: HPackHdrEntry('cookie', ''), + 33: HPackHdrEntry('date', ''), + 34: HPackHdrEntry('etag', ''), + 35: HPackHdrEntry('expect', ''), + 36: HPackHdrEntry('expires', ''), + 37: HPackHdrEntry('from', ''), + 38: HPackHdrEntry('host', ''), + 39: HPackHdrEntry('if-match', ''), + 40: HPackHdrEntry('if-modified-since', ''), + 41: HPackHdrEntry('if-none-match', ''), + 42: HPackHdrEntry('if-range', ''), + 43: HPackHdrEntry('if-unmodified-since', ''), + 44: HPackHdrEntry('last-modified', ''), + 45: HPackHdrEntry('link', ''), + 46: HPackHdrEntry('location', ''), + 47: HPackHdrEntry('max-forwards', ''), + 48: HPackHdrEntry('proxy-authenticate', ''), + 49: HPackHdrEntry('proxy-authorization', ''), + 50: HPackHdrEntry('range', ''), + 51: HPackHdrEntry('referer', ''), + 52: HPackHdrEntry('refresh', ''), + 53: HPackHdrEntry('retry-after', ''), + 54: HPackHdrEntry('server', ''), + 55: HPackHdrEntry('set-cookie', ''), + 56: HPackHdrEntry('strict-transport-security', ''), + 57: HPackHdrEntry('transfer-encoding', ''), + 58: HPackHdrEntry('user-agent', ''), + 59: HPackHdrEntry('vary', ''), + 60: HPackHdrEntry('via', ''), + 61: HPackHdrEntry('www-authenticate', ''), + } + + # The value of this variable cannot be determined at declaration time. It is + # initialized by an init_static_table call + _static_entries_last_idx = None + _regexp = None + + @classmethod + def init_static_table(cls): + # type: () -> None + cls._static_entries_last_idx = max(cls._static_entries.keys()) + + def __init__(self, dynamic_table_max_size=4096, dynamic_table_cap_size=4096): + # type: (int, int) -> None + """ + @param int dynamic_table_max_size: the current maximum size of the dynamic entry table in bytes + @param int dynamic_table_cap_size: the maximum-maximum size of the dynamic entry table in bytes + @raises AssertionError + """ + if isinstance(type(self)._static_entries_last_idx, types.NoneType): + type(self).init_static_table() + + assert dynamic_table_max_size <= dynamic_table_cap_size, \ + 'EINVAL: dynamic_table_max_size too large; expected value is less or equal to dynamic_table_cap_size' + + self._dynamic_table = [] # type: List[HPackHdrEntry] + self._dynamic_table_max_size = dynamic_table_max_size + self._dynamic_table_cap_size = dynamic_table_cap_size + + def __getitem__(self, idx): + # type: (int) -> HPackHdrEntry + """Gets an element from the header tables (static or dynamic indifferently) + + @param int idx: the index number of the entry to retrieve. If the index + value is superior to the last index of the static entry table, then the + dynamic entry type is requested, following the procedure described in + RFC 7541 par2.3.3 + @return HPackHdrEntry: the entry defined at this requested index. If the entry does not exist, KeyError is + raised + @raise KeyError, AssertionError + """ + assert(idx >= 0) + if idx > type(self)._static_entries_last_idx: + idx -= type(self)._static_entries_last_idx + 1 + if idx >= len(self._dynamic_table): + raise KeyError( + 'EINVAL: idx: out-of-bound read: {}; maximum index: {}'.format(idx, len(self._dynamic_table)) + ) + return self._dynamic_table[idx] + return type(self)._static_entries[idx] + + def resize(self, ns): + # type: (int) -> None + """Resize the dynamic table. If the new size (ns) must be between 0 and + the cap size. If the new size is lower than the current size of the + dynamic table, entries are evicted. + @param int ns: the new size of the dynamic table + @raise AssertionError + """ + assert 0 <= ns <= self._dynamic_table_cap_size, \ + 'EINVAL: ns: out-of-range value; expected value is in the range [0;{}['.format(self._dynamic_table_cap_size) + + old_size = self._dynamic_table_max_size + self._dynamic_table_max_size = ns + if old_size > self._dynamic_table_max_size: + self._reduce_dynamic_table() + + def recap(self, nc): + # type: (int) -> None + """recap changes the maximum size limit of the dynamic table. It also + proceeds to a resize(), if the new size is lower than the previous one. + @param int nc: the new cap of the dynamic table (that is the maximum-maximum size) + @raise AssertionError + """ + assert(nc >= 0) + t = self._dynamic_table_cap_size > nc + self._dynamic_table_cap_size = nc + + if t: + # The RFC is not clear about whether this resize should happen; + # we do it anyway + self.resize(nc) + + def _reduce_dynamic_table(self, new_entry_size=0): + # type: (int) -> None + """_reduce_dynamic_table evicts entries from the dynamic table until it + fits in less than the current size limit. The optional parameter, + new_entry_size, allows the resize to happen so that a new entry of this + size fits in. + @param int new_entry_size: if called before adding a new entry, the size of the new entry in bytes (following + the RFC7541 definition of the size of an entry) + @raise AssertionError + """ + assert(new_entry_size >= 0) + cur_sz = len(self) + dyn_tbl_sz = len(self._dynamic_table) + while dyn_tbl_sz > 0 and cur_sz + new_entry_size > self._dynamic_table_max_size: + last_elmt_sz = len(self._dynamic_table[-1]) + self._dynamic_table.pop() + dyn_tbl_sz -= 1 + cur_sz -= last_elmt_sz + + def register(self, hdrs): + # type: (Union[HPackLitHdrFldWithIncrIndexing, H2Frame, List[HPackHeaders]]) -> None + """register adds to this table the instances of + HPackLitHdrFldWithIncrIndexing provided as parameters. + + A H2Frame with a H2HeadersFrame payload can be provided, as much as a + python list of HPackHeaders or a single HPackLitHdrFldWithIncrIndexing + instance. + @param HPackLitHdrFldWithIncrIndexing|H2Frame|list of HPackHeaders hdrs: the header(s) to register + @raise AssertionError + """ + if isinstance(hdrs, H2Frame): + hdrs = [hdr for hdr in hdrs.payload.hdrs if isinstance(hdr, HPackLitHdrFldWithIncrIndexing)] + elif isinstance(hdrs, HPackLitHdrFldWithIncrIndexing): + hdrs = [hdrs] + else: + hdrs = [hdr for hdr in hdrs if isinstance(hdr, HPackLitHdrFldWithIncrIndexing)] + + for hdr in hdrs: + if hdr.index == 0: + hdr_name = hdr.hdr_name.getfieldval('data').origin() + else: + idx = int(hdr.index) + hdr_name = self[idx].name() + hdr_value = hdr.hdr_value.getfieldval('data').origin() + + # Note: we do not delete any existing hdrentry with the same names + # and values, as dictated by RFC 7541 par2.3.2 + + entry = HPackHdrEntry(hdr_name, hdr_value) + # According to RFC7541 par4.4, "Before a new entry is added to + # the dynamic table, entries are evicted + # from the end of the dynamic table until the size of the dynamic + # table is less than or equal to (maximum size - new entry size) + # or until the table is empty" + # Also, "It is not an error to attempt to add an entry that is + # larger than the maximum size; an attempt to add an entry larger + # than the maximum size causes the table to be emptied of all + # existing entries and results in an empty table" + # For this reason, we first call the _reduce_dynamic_table and + # then throw an assertion error if the new entry does not fit in + new_entry_len = len(entry) + self._reduce_dynamic_table(new_entry_len) + assert(new_entry_len <= self._dynamic_table_max_size) + self._dynamic_table.insert(0, entry) + + def get_idx_by_name(self, name): + # type: (str) -> Optional[int] + """ get_idx_by_name returns the index of a matching registered header + + This implementation will prefer returning a static entry index whenever + possible. If multiple matching header name are found in the static + table, there is insurance that the first entry (lowest index number) + will be returned. + If no matching header is found, this method returns None. + """ + name = name.lower() + for k in type(self)._static_entries.keys(): + if type(self)._static_entries[k].name() == name: + return k + for k in xrange(0, len(self._dynamic_table)): + if self._dynamic_table[k].name() == name: + return type(self)._static_entries_last_idx + k + 1 + return None + + def get_idx_by_name_and_value(self, name, value): + # type: (str, str) -> Optional[int] + """ get_idx_by_name_and_value returns the index of a matching registered + header + + This implementation will prefer returning a static entry index whenever + possible. If multiple matching headers are found in the dynamic table, + the lowest index is returned + If no matching header is found, this method returns None. + """ + name = name.lower() + for k in type(self)._static_entries.keys(): + elmt = type(self)._static_entries[k] + if elmt.name() == name and elmt.value() == value: + return k + for k in xrange(0, len(self._dynamic_table)): + elmt = self._dynamic_table[k] + if elmt.name() == name and elmt.value() == value: + return type(self)._static_entries_last_idx + k + 1 + return None + + def __len__(self): + # type: () -> int + """ __len__ returns the summed length of all dynamic entries + """ + return sum([len(x) for x in self._dynamic_table]) + + def gen_txt_repr(self, hdrs, register=True): + # type: (Union[H2Frame, List[HPackHeaders]], Optional[bool]) -> str + """ gen_txt_repr returns a "textual" representation of the provided + headers. + + The output of this function is compatible with the input of + parse_txt_hdrs. + @param H2Frame|list of HPackHeaders hdrs: the list of headers to convert to textual representation + @param bool: whether incremental headers should be added to the dynamic table as we generate the text + representation + @return str: the textual representation of the provided headers + @raise AssertionError + """ + l = [] + if isinstance(hdrs, H2Frame): + hdrs = hdrs.payload.hdrs + + for hdr in hdrs: + try: + if isinstance(hdr, HPackIndexedHdr): + l.append('{}'.format(self[hdr.index])) + elif isinstance(hdr, ( + HPackLitHdrFldWithIncrIndexing, + HPackLitHdrFldWithoutIndexing + )): + if hdr.index != 0: + name = self[hdr.index].name() + else: + name = hdr.hdr_name.getfieldval('data').origin() + if name.startswith(':'): + l.append( + '{} {}'.format( + name, + hdr.hdr_value.getfieldval('data').origin() + ) + ) + else: + l.append( + '{}: {}'.format( + name, + hdr.hdr_value.getfieldval('data').origin() + ) + ) + if register and isinstance(hdr, HPackLitHdrFldWithIncrIndexing): + self.register(hdr) + except KeyError as e: # raised when an index is out-of-bound + print(e) + continue + return '\n'.join(l) + + @staticmethod + def _optimize_header_length_and_packetify(s): + # type: (str) -> HPackHdrString + # type: (str) -> HPackHdrString + zs = HPackZString(s) + if len(zs) >= len(s): + return HPackHdrString(data=HPackLiteralString(s)) + return HPackHdrString(data=zs) + + def _convert_a_header_to_a_h2_header(self, hdr_name, hdr_value, is_sensitive, should_index): + # type: (str, str, Callable[[str, str], bool], Callable[[str], bool]) -> Tuple[HPackHeaders, int] + """ _convert_a_header_to_a_h2_header builds a HPackHeaders from a header + name and a value. It returns a HPackIndexedHdr whenever possible. If not, + it returns a HPackLitHdrFldWithoutIndexing or a + HPackLitHdrFldWithIncrIndexing, based on the should_index callback. + HPackLitHdrFldWithoutIndexing is forced if the is_sensitive callback + returns True and its never_index bit is set. + """ + + # If both name and value are already indexed + idx = self.get_idx_by_name_and_value(hdr_name, hdr_value) + if idx is not None: + return HPackIndexedHdr(index=idx), len(self[idx]) + + # The value is not indexed for this headers + + hdr_value = self._optimize_header_length_and_packetify(hdr_value) + + # Searching if the header name is indexed + idx = self.get_idx_by_name(hdr_name) + if idx is not None: + if is_sensitive( + hdr_name, + hdr_value.getfieldval('data').origin() + ): + return HPackLitHdrFldWithoutIndexing( + never_index=1, + index=idx, + hdr_value=hdr_value + ), len( + HPackHdrEntry( + self[idx].name(), + hdr_value.getfieldval('data').origin() + ) + ) + if should_index(hdr_name): + return HPackLitHdrFldWithIncrIndexing( + index=idx, + hdr_value=hdr_value + ), len( + HPackHdrEntry( + self[idx].name(), + hdr_value.getfieldval('data').origin() + ) + ) + return HPackLitHdrFldWithoutIndexing( + index=idx, + hdr_value=hdr_value + ), len( + HPackHdrEntry( + self[idx].name(), + hdr_value.getfieldval('data').origin() + ) + ) + + hdr_name = self._optimize_header_length_and_packetify(hdr_name) + + if is_sensitive( + hdr_name.getfieldval('data').origin(), + hdr_value.getfieldval('data').origin() + ): + return HPackLitHdrFldWithoutIndexing( + never_index=1, + index=0, + hdr_name=hdr_name, + hdr_value=hdr_value + ), len( + HPackHdrEntry( + hdr_name.getfieldval('data').origin(), + hdr_value.getfieldval('data').origin() + ) + ) + if should_index(hdr_name.getfieldval('data').origin()): + return HPackLitHdrFldWithIncrIndexing( + index=0, + hdr_name=hdr_name, + hdr_value=hdr_value + ), len( + HPackHdrEntry( + hdr_name.getfieldval('data').origin(), + hdr_value.getfieldval('data').origin() + ) + ) + return HPackLitHdrFldWithoutIndexing( + index=0, + hdr_name=hdr_name, + hdr_value=hdr_value + ), len( + HPackHdrEntry( + hdr_name.getfieldval('data').origin(), + hdr_value.getfieldval('data').origin() + ) + ) + + def _parse_header_line(self, l): + # type: (str) -> Union[Tuple[None, None], Tuple[str, str]] + + if type(self)._regexp is None: + type(self)._regexp = re.compile(r'^(?::([a-z\-0-9]+)|([a-z\-0-9]+):)\s+(.+)$') + + hdr_line = l.rstrip() + grp = type(self)._regexp.match(hdr_line) + + if grp is None or len(grp.groups()) != 3: + return None, None + + if grp.group(1) is not None: + hdr_name = ':'+grp.group(1) + else: + hdr_name = grp.group(2) + return hdr_name.lower(), grp.group(3) + + def parse_txt_hdrs(self, + s, # type: str + stream_id=1, # type: int + body=None, # type: Optional[str] + max_frm_sz=4096, # type: int + max_hdr_lst_sz=0, # type: int + is_sensitive=lambda n, v: False, # type: Callable[[str, str], bool] + should_index=lambda x: False, # type: Callable[[str], bool] + register=True, # type: bool + ): + # type: (...) -> H2Seq + """ parse_txt_hdrs parses headers expressed in text and converts them + into a series of H2Frames with the "correct" flags. A body can be provided + in which case, the data frames are added, bearing the End Stream flag, + instead of the H2HeadersFrame/H2ContinuationFrame. The generated frames + may respect max_frm_sz (SETTINGS_MAX_FRAME_SIZE) and + max_hdr_lst_sz (SETTINGS_MAX_HEADER_LIST_SIZE) if provided. The headers + are split into multiple headers fragment (and H2Frames) to respect these + limits. Also, a callback can be provided to tell if a header should be + never indexed (sensitive headers, such as cookies), and another callback + say if the header should be registered into the index table at all. + For an header to be registered, the is_sensitive callback must return + False AND the should_index callback should return True. This is the + default behavior. + + @param str s: the string to parse for headers + @param int stream_id: the stream id to use in the generated H2Frames + @param str|None body: the eventual body of the request, that is added to the generated frames + @param int max_frm_sz: the maximum frame size. This is used to split the headers and data frames according to + the maximum frame size negociated for this connection + @param int max_hdr_lst_sz: the maximum size of a "header fragment" as defined in RFC7540 + @param callable is_sensitive: callback that returns True if the provided header is sensible and must be stored + in a header packet requesting this header never to be indexed + @param callable should_index: callback that returns True if the provided header should be stored in a header + packet requesting indexation in the dynamic header table. + @param bool register: whether to register new headers with incremental indexing as we parse them + @raise Exception + """ + + sio = StringIO.StringIO(s) + + base_frm_len = len(str(H2Frame())) + + ret = H2Seq() + cur_frm = H2HeadersFrame() # type: Union[H2HeadersFrame, H2ContinuationFrame] + cur_hdr_sz = 0 + + # For each line in the headers str to parse + for hdr_line in sio: + hdr_name, hdr_value = self._parse_header_line(hdr_line) + if hdr_name is None: + continue + + new_hdr, new_hdr_len = self._convert_a_header_to_a_h2_header( + hdr_name, hdr_value, is_sensitive, should_index + ) + new_hdr_bin_len = len(str(new_hdr)) + + if register and isinstance(new_hdr, HPackLitHdrFldWithIncrIndexing): + self.register(new_hdr) + + # The new header binary length (+ base frame size) must not exceed + # the maximum frame size or it will just never fit. Also, the + # header entry length (as specified in RFC7540 par6.5.2) must not + # exceed the maximum length of a header fragment or it will just + # never fit + if (new_hdr_bin_len + base_frm_len > max_frm_sz + or (max_hdr_lst_sz != 0 and new_hdr_len > max_hdr_lst_sz) + ): + raise Exception('Header too long: {}'.format(hdr_name)) + + if (max_frm_sz < len(str(cur_frm)) + base_frm_len + new_hdr_len + or ( + max_hdr_lst_sz != 0 + and max_hdr_lst_sz < cur_hdr_sz + new_hdr_len + ) + ): + flags = set() + if isinstance(cur_frm, H2HeadersFrame) and not body: + flags.add('ES') + ret.frames.append(H2Frame(stream_id=stream_id, flags=flags)/cur_frm) + cur_frm = H2ContinuationFrame() + cur_hdr_sz = 0 + + hdr_list = cur_frm.hdrs + hdr_list += new_hdr + cur_hdr_sz += new_hdr_len + + flags = {'EH'} + if isinstance(cur_frm, H2HeadersFrame) and not body: + flags.add('ES') + ret.frames.append(H2Frame(stream_id=stream_id, flags=flags)/cur_frm) + + if body: + base_data_frm_len = len(str(H2DataFrame())) + sio = StringIO.StringIO(body) + frgmt = sio.read(max_frm_sz - base_data_frm_len - base_frm_len) + while frgmt: + nxt_frgmt = sio.read(max_frm_sz - base_data_frm_len - base_frm_len) + flags = set() + if len(nxt_frgmt) == 0: + flags.add('ES') + ret.frames.append( + H2Frame(stream_id=stream_id, flags=flags)/H2DataFrame(data=frgmt) + ) + frgmt = nxt_frgmt + return ret diff --git a/scapy/contrib/http2.uts b/scapy/contrib/http2.uts new file mode 100644 index 0000000000000000000000000000000000000000..58cd91affdcf19d4e4002e92117d627cfebfd498 --- /dev/null +++ b/scapy/contrib/http2.uts @@ -0,0 +1,2319 @@ +% HTTP/2 Campaign +# Frames expressed as binary str were generated using the solicit and hpack-rs +# Rust crates (https://github.com/mlalic/solicit, https://github.com/mlalic/hpack-rs) +# except Continuation Frames, Priority Frames and Push Promise Frames that we generated +# using Go x/net/http2 and x/net/http2/hpack modules. + ++ Syntax check += Configuring Scapy +~ http2 frame hpack build dissect data headers priority settings rststream pushpromise ping goaway winupdate continuation hpackhdrtable helpers + +import scapy.config +scapy.config.conf.debug_dissector=True +import scapy.packet +import scapy.fields +import scapy.contrib.http2 as h2 +import re +flags_bit_pattern = re.compile(r'''^\s+flags\s+=\s+\[.*['"]bit [0-9]+['"].*\]$''', re.M) +def expect_exception(e, c): + try: + eval(c) + return False + except e: + return True + ++ HTTP/2 UVarIntField Test Suite + += HTTP/2 UVarIntField.any2i +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) +expect_exception(AssertionError, 'f.any2i(None, None)') +assert(f.any2i(None, 0) == 0) +assert(f.any2i(None, 3) == 3) +assert(f.any2i(None, 1<<5) == 1<<5) +assert(f.any2i(None, 1<<16) == 1<<16) +f = h2.UVarIntField('value', 0, 8) +assert(f.any2i(None, '\x1E') == 30) + += HTTP/2 UVarIntField.m2i on full byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 8) +assert(f.m2i(None, '\x00') == 0) +assert(f.m2i(None, '\x03') == 3) +assert(f.m2i(None, '\xFE') == 254) +assert(f.m2i(None, '\xFF\x00') == 255) +assert(f.m2i(None, '\xFF\xFF\x03') == 766) #0xFF + (0xFF ^ 0x80) + (3<<7) + += HTTP/2 UVarIntField.m2i on partial byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) +assert(f.m2i(None, ('\x00', 3)) == 0) +assert(f.m2i(None, ('\x03', 3)) == 3) +assert(f.m2i(None, ('\x1e', 3)) == 30) +assert(f.m2i(None, ('\x1f\x00', 3)) == 31) +assert(f.m2i(None, ('\x1f\xe1\xff\x03', 3)) == 65536) + += HTTP/2 UVarIntField.getfield on full byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 8) + +r = f.getfield(None, '\x00\x00') +assert(r[0] == '\x00') +assert(r[1] == 0) + +r = f.getfield(None, '\x03\x00') +assert(r[0] == '\x00') +assert(r[1] == 3) + +r = f.getfield(None, '\xFE\x00') +assert(r[0] == '\x00') +assert(r[1] == 254) + +r = f.getfield(None, '\xFF\x00\x00') +assert(r[0] == '\x00') +assert(r[1] == 255) + +r = f.getfield(None, '\xFF\xFF\x03\x00') +assert(r[0] == '\x00') +assert(r[1] == 766) + += HTTP/2 UVarIntField.getfield on partial byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) + +r = f.getfield(None, ('\x00\x00', 3)) +assert(r[0] == '\x00') +assert(r[1] == 0) + +r = f.getfield(None, ('\x03\x00', 3)) +assert(r[0] == '\x00') +assert(r[1] == 3) + +r = f.getfield(None, ('\x1e\x00', 3)) +assert(r[0] == '\x00') +assert(r[1] == 30) + +r = f.getfield(None, ('\x1f\x00\x00', 3)) +assert(r[0] == '\x00') +assert(r[1] == 31) + +r = f.getfield(None, ('\x1f\xe1\xff\x03\x00', 3)) +assert(r[0] == '\x00') +assert(r[1] == 65536) + += HTTP/2 UVarIntField.i2m on full byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 8) +assert(f.i2m(None, 0) == '\x00') +assert(f.i2m(None, 3) == '\x03') +assert(f.i2m(None, 254).lower() == '\xfe') +assert(f.i2m(None, 255).lower() == '\xff\x00') +assert(f.i2m(None, 766).lower() == '\xff\xff\x03') + += HTTP/2 UVarIntField.i2m on partial byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) +assert(f.i2m(None, 0) == '\x00') +assert(f.i2m(None, 3) == '\x03') +assert(f.i2m(None, 30).lower() == '\x1e') +assert(f.i2m(None, 31).lower() == '\x1f\x00') +assert(f.i2m(None, 65536).lower() == '\x1f\xe1\xff\x03') + += HTTP/2 UVarIntField.addfield on full byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 8) + +assert(f.addfield(None, 'toto', 0) == 'toto\x00') +assert(f.addfield(None, 'toto', 3) == 'toto\x03') +assert(f.addfield(None, 'toto', 254).lower() == 'toto\xfe') +assert(f.addfield(None, 'toto', 255).lower() == 'toto\xff\x00') +assert(f.addfield(None, 'toto', 766).lower() == 'toto\xff\xff\x03') + += HTTP/2 UVarIntField.addfield on partial byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) + +assert(f.addfield(None, ('toto', 3, 4), 0) == 'toto\x80') +assert(f.addfield(None, ('toto', 3, 4), 3) == 'toto\x83') +assert(f.addfield(None, ('toto', 3, 4), 30).lower() == 'toto\x9e') +assert(f.addfield(None, ('toto', 3, 4), 31).lower() == 'toto\x9f\x00') +assert(f.addfield(None, ('toto', 3, 4), 65536).lower() == 'toto\x9f\xe1\xff\x03') + += HTTP/2 UVarIntField.i2len on full byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 8) + +assert(f.i2len(None, 0) == 1) +assert(f.i2len(None, 3) == 1) +assert(f.i2len(None, 254) == 1) +assert(f.i2len(None, 255) == 2) +assert(f.i2len(None, 766) == 3) + += HTTP/2 UVarIntField.i2len on partial byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) + +assert(f.i2len(None, 0) == 1) +assert(f.i2len(None, 3) == 1) +assert(f.i2len(None, 30) == 1) +assert(f.i2len(None, 31) == 2) +assert(f.i2len(None, 65536) == 4) + ++ HTTP/2 FieldUVarLenField Test Suite + += HTTP/2 FieldUVarLenField.i2m without adjustment +~ http2 frame field fielduvarlenfield + + +f = h2.FieldUVarLenField('len', None, 8, length_of='data') +class TrivialPacket(Packet): + name = 'Trivial Packet' + fields_desc= [ + f, + StrField('data', '') + ] + +assert(f.i2m(TrivialPacket(data='a'*5), None) == '\x05') +assert(f.i2m(TrivialPacket(data='a'*255), None).lower() == '\xff\x00') +assert(f.i2m(TrivialPacket(data='a'), 2) == '\x02') +assert(f.i2m(None, 2) == '\x02') +assert(f.i2m(None, 0) == '\x00') + += HTTP/2 FieldUVarLenField.i2m with adjustment +~ http2 frame field fielduvarlenfield + +class TrivialPacket(Packet): + name = 'Trivial Packet' + fields_desc= [ + f, + StrField('data', '') + ] + +f = h2.FieldUVarLenField('value', None, 8, length_of='data', adjust=lambda x: x-1) +assert(f.i2m(TrivialPacket(data='a'*5), None) == '\x04') +assert(f.i2m(TrivialPacket(data='a'*255), None).lower() == '\xfe') +#Adjustement does not affect non-None value! +assert(f.i2m(TrivialPacket(data='a'*3), 2) == '\x02') + ++ HTTP/2 HPackZString Test Suite + += HTTP/2 HPackZString Compression +~ http2 hpack huffman + +string = 'Test' +s = h2.HPackZString(string) +assert(len(s) == 3) +assert(str(s) == "\xdeT'") +assert(s.origin() == string) + +string = 'a'*65535 +s = h2.HPackZString(string) +assert(len(s) == 40960) +assert(str(s) == ('\x18\xc61\x8cc' * 8191) + '\x18\xc61\x8c\x7f') +assert(s.origin() == string) + += HTTP/2 HPackZString Decompression +~ http2 hpack huffman + +s = "\xdeT'" +i, ibl = h2.HPackZString.huffman_conv2bitstring(s) +assert('Test' == h2.HPackZString.huffman_decode(i, ibl)) + +s = ('\x18\xc61\x8cc' * 8191) + '\x18\xc61\x8c\x7f' +i, ibl = h2.HPackZString.huffman_conv2bitstring(s) +assert('a'*65535 == h2.HPackZString.huffman_decode(i, ibl)) + +assert( + expect_exception(h2.InvalidEncodingException, + 'h2.HPackZString.huffman_decode(*h2.HPackZString.huffman_conv2bitstring("\xdeT"))') +) + ++ HTTP/2 HPackStrLenField Test Suite + += HTTP/2 HPackStrLenField.m2i +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') +class TrivialPacket(Packet): + name = 'Trivial Packet' + fields_desc = [ + IntField('type', None), + IntField('len', None), + f + ] + +s = f.m2i(TrivialPacket(type=0, len=4), 'Test') +assert(isinstance(s, h2.HPackLiteralString)) +assert(s.origin() == 'Test') + +s = f.m2i(TrivialPacket(type=1, len=3), "\xdeT'") +assert(isinstance(s, h2.HPackZString)) +assert(s.origin() == 'Test') + += HTTP/2 HPackStrLenField.any2i +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') +class TrivialPacket(Packet): + name = 'Trivial Packet' + fields_desc = [ + IntField('type', None), + IntField('len', None), + f + ] + +s = f.any2i(TrivialPacket(type=0, len=4), 'Test') +assert(isinstance(s, h2.HPackLiteralString)) +assert(s.origin() == 'Test') + +s = f.any2i(TrivialPacket(type=1, len=3), "\xdeT'") +assert(isinstance(s, h2.HPackZString)) +assert(s.origin() == 'Test') + +s = h2.HPackLiteralString('Test') +s2 = f.any2i(TrivialPacket(type=0, len=4), s) +assert(s.origin() == s2.origin()) + +s = h2.HPackZString('Test') +s2 = f.any2i(TrivialPacket(type=1, len=3), s) +assert(s.origin() == s2.origin()) + +s = h2.HPackLiteralString('Test') +s2 = f.any2i(None, s) +assert(s.origin() == s2.origin()) + +s = h2.HPackZString('Test') +s2 = f.any2i(None, s) +assert(s.origin() == s2.origin()) + +# Verifies that one can fuzz +s = h2.HPackLiteralString('Test') +s2 = f.any2i(TrivialPacket(type=1, len=1), s) +assert(s.origin() == s2.origin()) + += HTTP/2 HPackStrLenField.i2m +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') + +s = 'Test' +s2 = f.i2m(None, h2.HPackLiteralString(s)) +assert(s == s2) + +s = 'Test' +s2 = f.i2m(None, h2.HPackZString(s)) +assert(s2 == "\xdeT'") + += HTTP/2 HPackStrLenField.addfield +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') + +s = 'Test' +s2 = f.addfield(None, 'toto', h2.HPackLiteralString(s)) +assert('toto' + s == s2) + +s = 'Test' +s2 = f.addfield(None, 'toto', h2.HPackZString(s)) +assert(s2 == "toto\xdeT'") + += HTTP/2 HPackStrLenField.getfield +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') +class TrivialPacket(Packet): + name = 'Trivial Packet' + fields_desc = [ + IntField('type', None), + IntField('len', None), + f + ] + +r = f.getfield(TrivialPacket(type=0, len=4), 'TestToto') +assert(isinstance(r, tuple)) +assert(r[0] == 'Toto') +assert(isinstance(r[1], h2.HPackLiteralString)) +assert(r[1].origin() == 'Test') + +r = f.getfield(TrivialPacket(type=1, len=3), "\xdeT'Toto") +assert(isinstance(r, tuple)) +assert(r[0] == 'Toto') +assert(isinstance(r[1], h2.HPackZString)) +assert(r[1].origin() == 'Test') + += HTTP/2 HPackStrLenField.i2h / i2repr +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') + +s = 'Test' +assert(f.i2h(None, h2.HPackLiteralString(s)) == 'HPackLiteralString(Test)') +assert(f.i2repr(None, h2.HPackLiteralString(s)) == repr('HPackLiteralString(Test)')) + +assert(f.i2h(None, h2.HPackZString(s)) == 'HPackZString(Test)') +assert(f.i2repr(None, h2.HPackZString(s)) == repr('HPackZString(Test)')) + += HTTP/2 HPackStrLenField.i2len +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') + +s = 'Test' +assert(f.i2len(None, h2.HPackLiteralString(s)) == 4) +assert(f.i2len(None, h2.HPackZString(s)) == 3) + ++ HTTP/2 HPackMagicBitField Test Suite +# Magic bits are not supposed to be modified and if they are anyway, they must +# be assigned the magic|default value only... + += HTTP/2 HPackMagicBitField.addfield +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) +r = f.addfield(None, 'toto', 3) +assert(isinstance(r, tuple)) +assert(r[0] == 'toto') +assert(r[1] == 2) +assert(r[2] == 3) + +r = f.addfield(None, ('toto', 2, 1) , 3) +assert(isinstance(r, tuple)) +assert(r[0] == 'toto') +assert(r[1] == 4) +assert(r[2] == 7) + +assert(expect_exception(AssertionError, 'f.addfield(None, "toto", 2)')) + += HTTP/2 HPackMagicBitField.getfield +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) + +r = f.getfield(None, '\xc0') +assert(isinstance(r, tuple)) +assert(len(r) == 2) +assert(isinstance(r[0], tuple)) +assert(len(r[0]) == 2) +assert(r[0][0] == '\xc0') +assert(r[0][1] == 2) +assert(r[1] == 3) + +r = f.getfield(None, ('\x03', 6)) +assert(isinstance(r, tuple)) +assert(len(r) == 2) +assert(isinstance(r[0], str)) +assert(r[0] == '') +assert(r[1] == 3) + +expect_exception(AssertionError, 'f.getfield(None, "\x80")') + += HTTP/2 HPackMagicBitField.h2i +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) +assert(f.h2i(None, 3) == 3) +expect_exception(AssertionError, 'f.h2i(None, 2)') + += HTTP/2 HPackMagicBitField.m2i +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) +assert(f.m2i(None, 3) == 3) +expect_exception(AssertionError, 'f.m2i(None, 2)') + += HTTP/2 HPackMagicBitField.i2m +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) +assert(f.i2m(None, 3) == 3) +expect_exception(AssertionError, 'f.i2m(None, 2)') + += HTTP/2 HPackMagicBitField.any2i +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) +assert(f.any2i(None, 3) == 3) +expect_exception(AssertionError, 'f.any2i(None, 2)') + ++ HTTP/2 HPackHdrString Test Suite + += HTTP/2 Dissect HPackHdrString +~ http2 pack dissect hpackhdrstring + +p = h2.HPackHdrString('\x04Test') +assert(p.type == 0) +assert(p.len == 4) +assert(isinstance(p.getfieldval('data'), h2.HPackLiteralString)) +assert(p.getfieldval('data').origin() == 'Test') + +p = h2.HPackHdrString("\x83\xdeT'") +assert(p.type == 1) +assert(p.len == 3) +assert(isinstance(p.getfieldval('data'), h2.HPackZString)) +assert(p.getfieldval('data').origin() == 'Test') + += HTTP/2 Build HPackHdrString +~ http2 hpack build hpackhdrstring + +p = h2.HPackHdrString(data=h2.HPackLiteralString('Test')) +assert(str(p) == '\x04Test') + +p = h2.HPackHdrString(data=h2.HPackZString('Test')) +assert(str(p) == "\x83\xdeT'") + +#Fuzzing-able tests +p = h2.HPackHdrString(type=1, len=3, data=h2.HPackLiteralString('Test')) +assert(str(p) == '\x83Test') + ++ HTTP/2 HPackIndexedHdr Test Suite + += HTTP/2 Dissect HPackIndexedHdr +~ http2 hpack dissect hpackindexedhdr + +p = h2.HPackIndexedHdr('\x80') +assert(p.magic == 1) +assert(p.index == 0) + +p = h2.HPackIndexedHdr('\xFF\x00') +assert(p.magic == 1) +assert(p.index == 127) + += HTTP/2 Build HPackIndexedHdr +~ http2 hpack build hpackindexedhdr + +p = h2.HPackIndexedHdr(index=0) +assert(str(p) == '\x80') + +p = h2.HPackIndexedHdr(index=127) +assert(str(p) == '\xFF\x00') + ++ HTTP/2 HPackLitHdrFldWithIncrIndexing Test Suite + += HTTP/2 Dissect HPackLitHdrFldWithIncrIndexing without indexed name +~ http2 hpack dissect hpacklithdrfldwithincrindexing + +p = h2.HPackLitHdrFldWithIncrIndexing('\x40\x04Test\x04Toto') +assert(p.magic == 1) +assert(p.index == 0) +assert(isinstance(p.hdr_name, h2.HPackHdrString)) +assert(p.hdr_name.type == 0) +assert(p.hdr_name.len == 4) +assert(p.hdr_name.getfieldval('data').origin() == 'Test') +assert(isinstance(p.hdr_value, h2.HPackHdrString)) +assert(p.hdr_value.type == 0) +assert(p.hdr_value.len == 4) +assert(p.hdr_value.getfieldval('data').origin() == 'Toto') + += HTTP/2 Dissect HPackLitHdrFldWithIncrIndexing with indexed name +~ http2 hpack dissect hpacklithdrfldwithincrindexing + +p = h2.HPackLitHdrFldWithIncrIndexing('\x41\x04Toto') +assert(p.magic == 1) +assert(p.index == 1) +assert(p.hdr_name is None) +assert(isinstance(p.hdr_value, h2.HPackHdrString)) +assert(p.hdr_value.type == 0) +assert(p.hdr_value.len == 4) +assert(p.hdr_value.getfieldval('data').origin() == 'Toto') + + += HTTP/2 Build HPackLitHdrFldWithIncrIndexing without indexed name +~ http2 hpack build hpacklithdrfldwithincrindexing + +p = h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('Test')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Toto')) +) +assert(str(p) == '\x40\x04Test\x04Toto') + += HTTP/2 Build HPackLitHdrFldWithIncrIndexing with indexed name +~ http2 hpack build hpacklithdrfldwithincrindexing + +p = h2.HPackLitHdrFldWithIncrIndexing( + index=1, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Toto')) +) +assert(str(p) == '\x41\x04Toto') + ++ HTTP/2 HPackLitHdrFldWithoutIndexing Test Suite + += HTTP/2 Dissect HPackLitHdrFldWithoutIndexing : don't index and no index +~ http2 hpack dissect hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing('\x00\x04Test\x04Toto') +assert(p.magic == 0) +assert(p.never_index == 0) +assert(p.index == 0) +assert(isinstance(p.hdr_name, h2.HPackHdrString)) +assert(p.hdr_name.type == 0) +assert(p.hdr_name.len == 4) +assert(isinstance(p.hdr_name.getfieldval('data'), h2.HPackLiteralString)) +assert(p.hdr_name.getfieldval('data').origin() == 'Test') +assert(isinstance(p.hdr_value, h2.HPackHdrString)) +assert(p.hdr_value.type == 0) +assert(p.hdr_value.len == 4) +assert(isinstance(p.hdr_value.getfieldval('data'), h2.HPackLiteralString)) +assert(p.hdr_value.getfieldval('data').origin() == 'Toto') + += HTTP/2 Dissect HPackLitHdrFldWithoutIndexing : never index index and no index +~ http2 hpack dissect hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing('\x10\x04Test\x04Toto') +assert(p.magic == 0) +assert(p.never_index == 1) +assert(p.index == 0) +assert(isinstance(p.hdr_name, h2.HPackHdrString)) +assert(p.hdr_name.type == 0) +assert(p.hdr_name.len == 4) +assert(isinstance(p.hdr_name.getfieldval('data'), h2.HPackLiteralString)) +assert(p.hdr_name.getfieldval('data').origin() == 'Test') +assert(isinstance(p.hdr_value, h2.HPackHdrString)) +assert(p.hdr_value.type == 0) +assert(p.hdr_value.len == 4) +assert(isinstance(p.hdr_value.getfieldval('data'), h2.HPackLiteralString)) +assert(p.hdr_value.getfieldval('data').origin() == 'Toto') + += HTTP/2 Dissect HPackLitHdrFldWithoutIndexing : never index and indexed name +~ http2 hpack dissect hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing('\x11\x04Toto') +assert(p.magic == 0) +assert(p.never_index == 1) +assert(p.index == 1) +assert(p.hdr_name is None) +assert(isinstance(p.hdr_value, h2.HPackHdrString)) +assert(p.hdr_value.type == 0) +assert(p.hdr_value.len == 4) +assert(isinstance(p.hdr_value.getfieldval('data'), h2.HPackLiteralString)) +assert(p.hdr_value.getfieldval('data').origin() == 'Toto') + += HTTP/2 Build HPackLitHdrFldWithoutIndexing : don't index and no index +~ http2 hpack build hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('Test')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Toto')) +) +assert(str(p) == '\x00\x04Test\x04Toto') + += HTTP/2 Build HPackLitHdrFldWithoutIndexing : never index index and no index +~ http2 hpack build hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing( + never_index=1, + hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('Test')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Toto')) +) +assert(str(p) == '\x10\x04Test\x04Toto') + += HTTP/2 Build HPackLitHdrFldWithoutIndexing : never index and indexed name +~ http2 hpack build hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing( + never_index=1, + index=1, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Toto')) +) +assert(str(p) == '\x11\x04Toto') + ++ HTTP/2 HPackDynamicSizeUpdate Test Suite + += HTTP/2 Dissect HPackDynamicSizeUpdate +~ http2 hpack dissect hpackdynamicsizeupdate + +p = h2.HPackDynamicSizeUpdate('\x25') +assert(p.magic == 1) +assert(p.max_size == 5) +p = h2.HPackDynamicSizeUpdate('\x3F\x00') +assert(p.magic == 1) +assert(p.max_size == 31) + += HTTP/2 Build HPackDynamicSizeUpdate +~ http2 hpack build hpackdynamicsizeupdate + +p = h2.HPackDynamicSizeUpdate(max_size=5) +assert(str(p) == '\x25') +p = h2.HPackDynamicSizeUpdate(max_size=31) +assert(str(p) == '\x3F\x00') + ++ HTTP/2 Data Frame Test Suite + += HTTP/2 Dissect Data Frame: Simple data frame +~ http2 frame dissect data + +pkt = h2.H2Frame('\x00\x00\x04\x00\x00\x00\x00\x00\x01ABCD') +assert(pkt.type == 0) +assert(pkt.len == 4) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2DataFrame)) +assert(pkt[h2.H2DataFrame]) +assert(pkt.payload.data == 'ABCD') +assert(isinstance(pkt.payload.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Data Frame: Simple data frame +~ http2 frame build data + +pkt = h2.H2Frame(stream_id = 1)/h2.H2DataFrame(data='ABCD') +assert(str(pkt) == '\x00\x00\x04\x00\x00\x00\x00\x00\x01ABCD') +try: + pkt.show2(dump=True) + assert(True) +except: + assert(False) + += HTTP/2 Dissect Data Frame: Simple data frame with padding +~ http2 frame dissect data + +pkt = h2.H2Frame('\x00\x00\r\x00\x08\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00') #Padded data frame +assert(pkt.type == 0) +assert(pkt.len == 13) +assert(len(pkt.flags) == 1) +assert('P' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PaddedDataFrame)) +assert(pkt[h2.H2PaddedDataFrame]) +assert(pkt.payload.padlen == 8) +assert(pkt.payload.data == 'ABCD') +assert(pkt.payload.padding == '\x00'*8) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(isinstance(pkt.payload.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Data Frame: Simple data frame with padding +~ http2 frame build data + +pkt = h2.H2Frame(flags = {'P'}, stream_id = 1)/h2.H2PaddedDataFrame(data='ABCD', padding='\x00'*8) +assert(str(pkt) == '\x00\x00\r\x00\x08\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00') +try: + pkt.show2(dump=True) + assert(True) +except: + assert(False) + += HTTP/2 Dissect Data Frame: Simple data frame with padding and end stream flag +~ http2 frame dissect data + +pkt = h2.H2Frame('\x00\x00\r\x00\t\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00') #Padded data frame with end stream flag +assert(pkt.type == 0) +assert(pkt.len == 13) +assert(len(pkt.flags) == 2) +assert('P' in pkt.flags) +assert('ES' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PaddedDataFrame)) +assert(pkt[h2.H2PaddedDataFrame]) +assert(pkt.payload.padlen == 8) +assert(pkt.payload.data == 'ABCD') +assert(pkt.payload.padding == '\x00'*8) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(isinstance(pkt.payload.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Data Frame: Simple data frame with padding and end stream flag +~ http2 frame build data + +pkt = h2.H2Frame(flags = {'P', 'ES'}, stream_id=1)/h2.H2PaddedDataFrame(data='ABCD', padding='\x00'*8) +assert(str(pkt) == '\x00\x00\r\x00\t\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00') +try: + pkt.show2(dump=True) + assert(True) +except: + assert(False) + ++ HTTP/2 Headers Frame Test Suite + += HTTP/2 Dissect Headers Frame: Simple header frame +~ http2 frame dissect headers + +pkt = h2.H2Frame('\x00\x00\x0e\x01\x00\x00\x00\x00\x01\x88\x0f\x10\ntext/plain') #Header frame +assert(pkt.type == 1) +assert(pkt.len == 14) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2HeadersFrame)) +assert(pkt[h2.H2HeadersFrame]) +hf = pkt[h2.H2HeadersFrame] +assert(len(hf.hdrs) == 2) +assert(isinstance(hf.hdrs[0], h2.HPackIndexedHdr)) +assert(hf.hdrs[0].magic == 1) +assert(hf.hdrs[0].index == 8) +assert(isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing)) +assert(hf.hdrs[1].magic == 0) +assert(hf.hdrs[1].never_index == 0) +assert(hf.hdrs[1].index == 31) +assert(hf.hdrs[1].hdr_name is None) +assert(expect_exception(AttributeError, 'hf.hdrs[1].non_existant')) +assert(isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString)) +s = hf.hdrs[1].hdr_value +assert(s.type == 0) +assert(s.len == 10) +assert(s.getfieldval('data').origin() == 'text/plain') +assert(isinstance(hf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Headers Frame: Simple header frame +~ http2 frame build headers + +p = h2.H2Frame(stream_id=1)/h2.H2HeadersFrame(hdrs=[ + h2.HPackIndexedHdr(index=8), + h2.HPackLitHdrFldWithoutIndexing( + index=31, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) + ) + ] +) +assert(str(p) == '\x00\x00\x0e\x01\x00\x00\x00\x00\x01\x88\x0f\x10\ntext/plain') + += HTTP/2 Dissect Headers Frame: Header frame with padding +~ http2 frame dissect headers + +pkt = h2.H2Frame('\x00\x00\x17\x01\x08\x00\x00\x00\x01\x08\x88\x0f\x10\ntext/plain\x00\x00\x00\x00\x00\x00\x00\x00') #Header frame with padding +assert(pkt.type == 1) +assert(pkt.len == 23) +assert(len(pkt.flags) == 1) +assert('P' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PaddedHeadersFrame)) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(pkt[h2.H2PaddedHeadersFrame]) +hf = pkt[h2.H2PaddedHeadersFrame] +assert(hf.padlen == 8) +assert(hf.padding == '\x00' * 8) +assert(len(hf.hdrs) == 2) +assert(isinstance(hf.hdrs[0], h2.HPackIndexedHdr)) +assert(hf.hdrs[0].magic == 1) +assert(hf.hdrs[0].index == 8) +assert(isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing)) +assert(hf.hdrs[1].magic == 0) +assert(hf.hdrs[1].never_index == 0) +assert(hf.hdrs[1].index == 31) +assert(hf.hdrs[1].hdr_name is None) +assert(expect_exception(AttributeError, 'hf.hdrs[1].non_existant')) +assert(isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString)) +s = hf.hdrs[1].hdr_value +assert(s.type == 0) +assert(s.len == 10) +assert(s.getfieldval('data').origin() == 'text/plain') +assert(isinstance(hf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Headers Frame: Header frame with padding +~ http2 frame build headers + +p = h2.H2Frame(flags={'P'}, stream_id=1)/h2.H2PaddedHeadersFrame( + hdrs=[ + h2.HPackIndexedHdr(index=8), + h2.HPackLitHdrFldWithoutIndexing( + index=31, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) + ) + ], + padding='\x00'*8, +) +assert(str(p) == '\x00\x00\x17\x01\x08\x00\x00\x00\x01\x08\x88\x0f\x10\ntext/plain\x00\x00\x00\x00\x00\x00\x00\x00') + += HTTP/2 Dissect Headers Frame: Header frame with priority +~ http2 frame dissect headers + +pkt = h2.H2Frame('\x00\x00\x13\x01 \x00\x00\x00\x01\x00\x00\x00\x02d\x88\x0f\x10\ntext/plain') #Header frame with priority +assert(pkt.type == 1) +assert(pkt.len == 19) +assert(len(pkt.flags) == 1) +assert('+' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PriorityHeadersFrame)) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(pkt[h2.H2PriorityHeadersFrame]) +hf = pkt[h2.H2PriorityHeadersFrame] +assert(hf.exclusive == 0) +assert(hf.stream_dependency == 2) +assert(hf.weight == 100) +assert(len(hf.hdrs) == 2) +assert(isinstance(hf.hdrs[0], h2.HPackIndexedHdr)) +assert(hf.hdrs[0].magic == 1) +assert(hf.hdrs[0].index == 8) +assert(isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing)) +assert(hf.hdrs[1].magic == 0) +assert(hf.hdrs[1].never_index == 0) +assert(hf.hdrs[1].index == 31) +assert(hf.hdrs[1].hdr_name is None) +assert(expect_exception(AttributeError, 'hf.hdrs[1].non_existant')) +assert(isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString)) +s = hf.hdrs[1].hdr_value +assert(s.type == 0) +assert(s.len == 10) +assert(s.getfieldval('data').origin() == 'text/plain') +assert(isinstance(hf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Headers Frame: Header frame with priority +~ http2 frame build headers + +p = h2.H2Frame(flags={'+'}, stream_id=1)/h2.H2PriorityHeadersFrame( + exclusive=0, + stream_dependency=2, + weight=100, + hdrs=[ + h2.HPackIndexedHdr(index=8), + h2.HPackLitHdrFldWithoutIndexing( + index=31, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) + ) + ] +) +assert(str(p) == '\x00\x00\x13\x01 \x00\x00\x00\x01\x00\x00\x00\x02d\x88\x0f\x10\ntext/plain') + += HTTP/2 Dissect Headers Frame: Header frame with priority and padding and flags +~ http2 frame dissect headers + +pkt = h2.H2Frame('\x00\x00\x1c\x01-\x00\x00\x00\x01\x08\x00\x00\x00\x02d\x88\x0f\x10\ntext/plain\x00\x00\x00\x00\x00\x00\x00\x00') #Header frame with priority and padding and flags ES|EH +assert(pkt.type == 1) +assert(pkt.len == 28) +assert(len(pkt.flags) == 4) +assert('+' in pkt.flags) +assert('P' in pkt.flags) +assert('ES' in pkt.flags) +assert('EH' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PaddedPriorityHeadersFrame)) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(pkt[h2.H2PaddedPriorityHeadersFrame]) +hf = pkt[h2.H2PaddedPriorityHeadersFrame] +assert(hf.padlen == 8) +assert(hf.padding == '\x00' * 8) +assert(hf.exclusive == 0) +assert(hf.stream_dependency == 2) +assert(hf.weight == 100) +assert(len(hf.hdrs) == 2) +assert(isinstance(hf.hdrs[0], h2.HPackIndexedHdr)) +assert(hf.hdrs[0].magic == 1) +assert(hf.hdrs[0].index == 8) +assert(isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing)) +assert(hf.hdrs[1].magic == 0) +assert(hf.hdrs[1].never_index == 0) +assert(hf.hdrs[1].index == 31) +assert(hf.hdrs[1].hdr_name is None) +assert(expect_exception(AttributeError, 'hf.hdrs[1].non_existant')) +assert(isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString)) +s = hf.hdrs[1].hdr_value +assert(s.type == 0) +assert(s.len == 10) +assert(s.getfieldval('data').origin() == 'text/plain') +assert(isinstance(hf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Headers Frame: Header frame with priority and padding and flags +~ http2 frame build headers + +p = h2.H2Frame(flags={'P', '+', 'ES', 'EH'}, stream_id=1)/h2.H2PaddedPriorityHeadersFrame( + exclusive=0, + stream_dependency=2, + weight=100, + padding='\x00'*8, + hdrs=[ + h2.HPackIndexedHdr(index=8), + h2.HPackLitHdrFldWithoutIndexing( + index=31, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) + ) + ] +) + ++ HTTP/2 Priority Frame Test Suite + += HTTP/2 Dissect Priority Frame +~ http2 frame dissect priority + +pkt = h2.H2Frame('\x00\x00\x05\x02\x00\x00\x00\x00\x03\x80\x00\x00\x01d') +assert(pkt.type == 2) +assert(pkt.len == 5) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 3) +assert(isinstance(pkt.payload, h2.H2PriorityFrame)) +assert(pkt[h2.H2PriorityFrame]) +pp = pkt[h2.H2PriorityFrame] +assert(pp.stream_dependency == 1) +assert(pp.exclusive == 1) +assert(pp.weight == 100) + += HTTP/2 Build Priority Frame +~ http2 frame build priority + +p = h2.H2Frame(stream_id=3)/h2.H2PriorityFrame( + exclusive=1, + stream_dependency=1, + weight=100 +) +assert(str(p) == '\x00\x00\x05\x02\x00\x00\x00\x00\x03\x80\x00\x00\x01d') + ++ HTTP/2 Reset Stream Frame Test Suite + += HTTP/2 Dissect Reset Stream Frame: Protocol Error +~ http2 frame dissect rststream + +pkt = h2.H2Frame('\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x00\x01') #Reset stream with protocol error +assert(pkt.type == 3) +assert(pkt.len == 4) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2ResetFrame)) +assert(pkt[h2.H2ResetFrame]) +rf = pkt[h2.H2ResetFrame] +assert(rf.error == 1) +assert(isinstance(rf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Reset Stream Frame: Protocol Error +~ http2 frame build rststream + +p = h2.H2Frame(stream_id=1)/h2.H2ResetFrame(error='Protocol error') +assert(str(p) == '\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x00\x01') + +p = h2.H2Frame(stream_id=1)/h2.H2ResetFrame(error=1) +assert(str(p) == '\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x00\x01') + += HTTP/2 Dissect Reset Stream Frame: Raw 123456 error +~ http2 frame dissect rststream + +pkt = h2.H2Frame('\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x01\xe2@') #Reset stream with raw error +assert(pkt.type == 3) +assert(pkt.len == 4) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2ResetFrame)) +assert(pkt[h2.H2ResetFrame]) +rf = pkt[h2.H2ResetFrame] +assert(rf.error == 123456) +assert(isinstance(rf.payload, scapy.packet.NoPayload)) + += HTTP/2 Dissect Reset Stream Frame: Raw 123456 error +~ http2 frame dissect rststream + +p = h2.H2Frame(stream_id=1)/h2.H2ResetFrame(error=123456) +assert(str(p) == '\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x01\xe2@') + ++ HTTP/2 Settings Frame Test Suite + += HTTP/2 Dissect Settings Frame: Settings Frame +~ http2 frame dissect settings + +pkt = h2.H2Frame('\x00\x00$\x04\x00\x00\x00\x00\x00\x00\x01\x07[\xcd\x15\x00\x02\x00\x00\x00\x01\x00\x03\x00\x00\x00{\x00\x04\x00\x12\xd6\x87\x00\x05\x00\x01\xe2@\x00\x06\x00\x00\x00{') #Settings frame +assert(pkt.type == 4) +assert(pkt.len == 36) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2SettingsFrame)) +assert(pkt[h2.H2SettingsFrame]) +sf = pkt[h2.H2SettingsFrame] +assert(len(sf.settings) == 6) +assert(isinstance(sf.settings[0], h2.H2Setting)) +assert(sf.settings[0].id == 1) +assert(sf.settings[0].value == 123456789) +assert(isinstance(sf.settings[1], h2.H2Setting)) +assert(sf.settings[1].id == 2) +assert(sf.settings[1].value == 1) +assert(isinstance(sf.settings[2], h2.H2Setting)) +assert(sf.settings[2].id == 3) +assert(sf.settings[2].value == 123) +assert(isinstance(sf.settings[3], h2.H2Setting)) +assert(sf.settings[3].id == 4) +assert(sf.settings[3].value == 1234567) +assert(isinstance(sf.settings[4], h2.H2Setting)) +assert(sf.settings[4].id == 5) +assert(sf.settings[4].value == 123456) +assert(isinstance(sf.settings[5], h2.H2Setting)) +assert(sf.settings[5].id == 6) +assert(sf.settings[5].value == 123) +assert(isinstance(sf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Settings Frame: Settings Frame +~ http2 frame build settings + +p = h2.H2Frame()/h2.H2SettingsFrame(settings=[ + h2.H2Setting(id='Header table size',value=123456789), + h2.H2Setting(id='Enable push', value=1), + h2.H2Setting(id='Max concurrent streams', value=123), + h2.H2Setting(id='Initial window size', value=1234567), + h2.H2Setting(id='Max frame size', value=123456), + h2.H2Setting(id='Max header list size', value=123) + ] +) +assert(str(p) == '\x00\x00$\x04\x00\x00\x00\x00\x00\x00\x01\x07[\xcd\x15\x00\x02\x00\x00\x00\x01\x00\x03\x00\x00\x00{\x00\x04\x00\x12\xd6\x87\x00\x05\x00\x01\xe2@\x00\x06\x00\x00\x00{') + += HTTP/2 Dissect Settings Frame: Incomplete Settings Frame +~ http2 frame dissect settings + +#We use here the decode('hex') method because null-bytes are rejected by eval() +assert(expect_exception(AssertionError, 'h2.H2Frame("0000240400000000000001075bcd1500020000000100030000007b00040012d68700050001e2400006000000".decode("hex"))')) + += HTTP/2 Dissect Settings Frame: Settings Frame acknowledgement +~ http2 frame dissect settings + +pkt = h2.H2Frame('\x00\x00\x00\x04\x01\x00\x00\x00\x00') #Settings frame w/ ack flag +assert(pkt.type == 4) +assert(pkt.len == 0) +assert(len(pkt.flags) == 1) +assert('A' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(isinstance(pkt.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Settings Frame: Settings Frame acknowledgement +~ http2 frame build settings + +p = h2.H2Frame(type=h2.H2SettingsFrame.type_id, flags={'A'}) +assert(str(p) == '\x00\x00\x00\x04\x01\x00\x00\x00\x00') + ++ HTTP/2 Push Promise Frame Test Suite + += HTTP/2 Dissect Push Promise Frame: no flag & headers with compression and hdr_name +~ http2 frame dissect pushpromise + +pkt = h2.H2Frame('\x00\x00\x15\x05\x00\x00\x00\x00\x01\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') +assert(pkt.type == 5) +assert(pkt.len == 21) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PushPromiseFrame)) +assert(pkt[h2.H2PushPromiseFrame]) +pf = pkt[h2.H2PushPromiseFrame] +assert(pf.reserved == 0) +assert(pf.stream_id == 3) +assert(len(pf.hdrs) == 1) +assert(isinstance(pf.payload, scapy.packet.NoPayload)) +hdr = pf.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.type == 1) +assert(hdr.hdr_name.len == 12) +assert(hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.type == 0) +assert(hdr.hdr_value.len == 2) +assert(hdr.hdr_value.getfieldval('data').origin() == 'Me') + += HTTP/2 Build Push Promise Frame: no flag & headers with compression and hdr_name +~ http2 frame build pushpromise + +p = h2.H2Frame(stream_id=1)/h2.H2PushPromiseFrame(stream_id=3,hdrs=[ + h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')), + ) +]) +assert(str(p) == '\x00\x00\x15\x05\x00\x00\x00\x00\x01\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') + += HTTP/2 Dissect Push Promise Frame: with padding, the flag END_Header & headers with compression and hdr_name +~ http2 frame dissect pushpromise + +pkt = h2.H2Frame('\x00\x00\x1e\x05\x0c\x00\x00\x00\x01\x08\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me\x00\x00\x00\x00\x00\x00\x00\x00') +assert(pkt.type == 5) +assert(pkt.len == 30) +assert(len(pkt.flags) == 2) +assert('P' in pkt.flags) +assert('EH' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(isinstance(pkt.payload, h2.H2PaddedPushPromiseFrame)) +assert(pkt[h2.H2PaddedPushPromiseFrame]) +pf = pkt[h2.H2PaddedPushPromiseFrame] +assert(pf.padlen == 8) +assert(pf.padding == '\x00'*8) +assert(pf.stream_id == 3) +assert(len(pf.hdrs) == 1) +hdr = pf.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.type == 1) +assert(hdr.hdr_name.len == 12) +assert(hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.type == 0) +assert(hdr.hdr_value.len == 2) +assert(hdr.hdr_value.getfieldval('data').origin() == 'Me') + += HTTP/2 Build Push Promise Frame: with padding, the flag END_Header & headers with compression and hdr_name +~ http2 frame build pushpromise + +p = h2.H2Frame(flags={'P', 'EH'}, stream_id=1)/h2.H2PaddedPushPromiseFrame( + stream_id=3, + hdrs=[ + h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) + ) + ], + padding='\x00'*8 +) +assert(str(p) == '\x00\x00\x1e\x05\x0c\x00\x00\x00\x01\x08\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me\x00\x00\x00\x00\x00\x00\x00\x00') + ++ HTTP/2 Ping Frame Test Suite + += HTTP/2 Dissect Ping Frame: Ping frame +~ http2 frame dissect ping + +pkt = h2.H2Frame('\x00\x00\x08\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@') #Ping frame with payload +assert(pkt.type == 6) +assert(pkt.len == 8) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2PingFrame)) +assert(pkt[h2.H2PingFrame]) +pf = pkt[h2.H2PingFrame] +assert(pf.opaque == 123456) +assert(isinstance(pf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Ping Frame: Ping frame +~ http2 frame build ping + +p = h2.H2Frame()/h2.H2PingFrame(opaque=123456) +assert(str(p) == '\x00\x00\x08\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@') + += HTTP/2 Dissect Ping Frame: Pong frame +~ http2 frame dissect ping + +pkt = h2.H2Frame('\x00\x00\x08\x06\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@') #Pong frame +assert(pkt.type == 6) +assert(pkt.len == 8) +assert(len(pkt.flags) == 1) +assert('A' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2PingFrame)) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(pkt[h2.H2PingFrame]) +pf = pkt[h2.H2PingFrame] +assert(pf.opaque == 123456) +assert(isinstance(pf.payload, scapy.packet.NoPayload)) + += HTTP/2 Dissect Ping Frame: Pong frame +~ http2 frame dissect ping + +p = h2.H2Frame(flags={'A'})/h2.H2PingFrame(opaque=123456) +assert(str(p) == '\x00\x00\x08\x06\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@') + ++ HTTP/2 Go Away Frame Test Suite + += HTTP/2 Dissect Go Away Frame: No error +~ http2 frame dissect goaway + +pkt = h2.H2Frame('\x00\x00\x08\x07\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00') #Go Away for no particular reason :) +assert(pkt.type == 7) +assert(pkt.len == 8) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2GoAwayFrame)) +assert(pkt[h2.H2GoAwayFrame]) +gf = pkt[h2.H2GoAwayFrame] +assert(gf.reserved == 0) +assert(gf.last_stream_id == 1) +assert(gf.error == 0) +assert(len(gf.additional_data) == 0) +assert(isinstance(gf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Go Away Frame: No error +~ http2 frame build goaway + +p = h2.H2Frame()/h2.H2GoAwayFrame(last_stream_id=1, error='No error') +assert(str(p) == '\x00\x00\x08\x07\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00') + += HTTP/2 Dissect Go Away Frame: Arbitrary error with additional data +~ http2 frame dissect goaway + +pkt = h2.H2Frame('\x00\x00\x10\x07\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\xe2@\x00\x00\x00\x00\x00\x00\x00\x00') #Go Away with debug data +assert(pkt.type == 7) +assert(pkt.len == 16) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2GoAwayFrame)) +assert(pkt[h2.H2GoAwayFrame]) +gf = pkt[h2.H2GoAwayFrame] +assert(gf.reserved == 0) +assert(gf.last_stream_id == 2) +assert(gf.error == 123456) +assert(gf.additional_data == 8*'\x00') +assert(isinstance(gf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Go Away Frame: Arbitrary error with additional data +~ http2 frame build goaway + +p = h2.H2Frame()/h2.H2GoAwayFrame( + last_stream_id=2, + error=123456, + additional_data='\x00'*8 +) +assert(str(p) == '\x00\x00\x10\x07\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\xe2@\x00\x00\x00\x00\x00\x00\x00\x00') + ++ HTTP/2 Window Update Frame Test Suite + += HTTP/2 Dissect Window Update Frame: global +~ http2 frame dissect winupdate + +pkt = h2.H2Frame('\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x01\xe2@') #Window update with increment for connection +assert(pkt.type == 8) +assert(pkt.len == 4) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2WindowUpdateFrame)) +assert(pkt[h2.H2WindowUpdateFrame]) +wf = pkt[h2.H2WindowUpdateFrame] +assert(wf.reserved == 0) +assert(wf.win_size_incr == 123456) +assert(isinstance(wf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Window Update Frame: global +~ http2 frame build winupdate + +p = h2.H2Frame()/h2.H2WindowUpdateFrame(win_size_incr=123456) +assert(str(p) == '\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x01\xe2@') + += HTTP/2 Dissect Window Update Frame: a stream +~ http2 frame dissect winupdate + +pkt = h2.H2Frame('\x00\x00\x04\x08\x00\x00\x00\x00\x01\x00\x01\xe2@') #Window update with increment for a stream +assert(pkt.type == 8) +assert(pkt.len == 4) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2WindowUpdateFrame)) +assert(pkt[h2.H2WindowUpdateFrame]) +wf = pkt[h2.H2WindowUpdateFrame] +assert(wf.reserved == 0) +assert(wf.win_size_incr == 123456) +assert(isinstance(wf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Window Update Frame: a stream +~ http2 frame build winupdate + +p = h2.H2Frame(stream_id=1)/h2.H2WindowUpdateFrame(win_size_incr=123456) +assert(str(p) == '\x00\x00\x04\x08\x00\x00\x00\x00\x01\x00\x01\xe2@') + ++ HTTP/2 Continuation Frame Test Suite + += HTTP/2 Dissect Continuation Frame: no flag & headers with compression and hdr_name +~ http2 frame dissect continuation + +pkt = h2.H2Frame('\x00\x00\x11\t\x00\x00\x00\x00\x01@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') +assert(pkt.type == 9) +assert(pkt.len == 17) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2ContinuationFrame)) +assert(pkt[h2.H2ContinuationFrame]) +hf = pkt[h2.H2ContinuationFrame] +assert(len(hf.hdrs) == 1) +assert(isinstance(hf.payload, scapy.packet.NoPayload)) +hdr = hf.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.type == 1) +assert(hdr.hdr_name.len == 12) +assert(hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.type == 0) +assert(hdr.hdr_value.len == 2) +assert(hdr.hdr_value.getfieldval('data').origin() == 'Me') + += HTTP/2 Build Continuation Frame: no flag & headers with compression and hdr_name +~ http2 frame build continuation + +p = h2.H2Frame(stream_id=1)/h2.H2ContinuationFrame( + hdrs=[ + h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) + ) + ] +) +assert(str(p) == '\x00\x00\x11\t\x00\x00\x00\x00\x01@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') + += HTTP/2 Dissect Continuation Frame: flag END_Header & headers with compression, sensitive flag and hdr_name +~ http2 frame dissect continuation + +pkt = h2.H2Frame('\x00\x00\x11\t\x04\x00\x00\x00\x01\x10\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') +assert(pkt.type == 9) +assert(pkt.len == 17) +assert(len(pkt.flags) == 1) +assert('EH' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(isinstance(pkt.payload, h2.H2ContinuationFrame)) +assert(pkt[h2.H2ContinuationFrame]) +hf = pkt[h2.H2ContinuationFrame] +assert(len(hf.hdrs) == 1) +assert(isinstance(hf.payload, scapy.packet.NoPayload)) +hdr = hf.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.type == 1) +assert(hdr.hdr_name.len == 12) +assert(hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.type == 0) +assert(hdr.hdr_value.len == 2) +assert(hdr.hdr_value.getfieldval('data').origin() == 'Me') + += HTTP/2 Build Continuation Frame: flag END_Header & headers with compression, sensitive flag and hdr_name +~ http2 frame build continuation + +p = h2.H2Frame(flags={'EH'}, stream_id=1)/h2.H2ContinuationFrame( + hdrs=[ + h2.HPackLitHdrFldWithoutIndexing( + never_index=1, + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) + ) + ] +) +assert(str(p) == '\x00\x00\x11\t\x04\x00\x00\x00\x01\x10\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') + ++ HTTP/2 HPackHdrTable Test Suite + += HTTP/2 HPackHdrEntry Tests +~ http2 hpack hpackhdrtable + +n = 'X-Requested-With' +v = 'Me' +h = h2.HPackHdrEntry(n, v) +assert(len(h) == 32 + len(n) + len(v)) +assert(h.name() == n.lower()) +assert(h.value() == v) +assert(str(h) == '{}: {}'.format(n.lower(), v)) + +n = ':status' +v = '200' +h = h2.HPackHdrEntry(n, v) +assert(len(h) == 32 + len(n) + len(v)) +assert(h.name() == n.lower()) +assert(h.value() == v) +assert(str(h) == '{} {}'.format(n.lower(), v)) + += HTTP/2 HPackHdrTable : Querying Static Entries +~ http2 hpack hpackhdrtable + +# In RFC7541, the table is 1-based +assert(expect_exception(KeyError, 'h2.HPackHdrTable()[0]')) + +h = h2.HPackHdrTable() +assert(h[1].name() == ':authority') +assert(h[7].name() == ':scheme') +assert(h[7].value() == 'https') +assert(str(h[14]) == ':status 500') +assert(str(h[16]) == 'accept-encoding: gzip, deflate') + +assert(expect_exception(KeyError, 'h2.HPackHdrTable()[h2.HPackHdrTable._static_entries_last_idx+1]')) + += HTTP/2 HPackHdrTable : Addind Dynamic Entries without overflowing the table +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString('PHPSESSID=abcdef0123456789')) +) +tbl.register(hdr) + +tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString('JSESSID=abcdef0123456789')) +) +tbl.register([hdr,hdr2]) + +tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) +hdr3 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString('Test=abcdef0123456789')) +) +frm = h2.H2Frame(stream_id=1)/h2.H2HeadersFrame(hdrs=[hdr, hdr2, hdr3]) +tbl.register(frm) + + += HTTP/2 HPackHdrTable : Querying Dynamic Entries +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) +hdrv = 'PHPSESSID=abcdef0123456789' +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) +) +tbl.register(hdr) + +hdrv2 = 'JSESSID=abcdef0123456789' +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) +) +tbl.register(hdr2) + +hdr3 = h2.HPackLitHdrFldWithIncrIndexing( + index=0, + hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('x-requested-by')), + hdr_value=h2.HPackHdrString(data=h2.HPackZString('me')) +) +tbl.register(hdr3) + +assert(tbl.get_idx_by_name('x-requested-by') == h2.HPackHdrTable._static_entries_last_idx+1) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+3].value() == hdrv) + += HTTP/2 HPackHdrTable : Addind already registered Dynamic Entries without overflowing the table +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) + +assert(len(tbl) == 0) + +hdrv = 'PHPSESSID=abcdef0123456789' +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) +) +tbl.register(hdr) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv) + +hdr2v = 'JSESSID=abcdef0123456789' +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdr2v)) +) +tbl.register(hdr2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdr2v) + +l = len(tbl) +tbl.register(hdr) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdr2v) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+3].value() == hdrv) + += HTTP/2 HPackHdrTable : Addind Dynamic Entries and overflowing the table +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable(dynamic_table_max_size=80, dynamic_table_cap_size=80) +hdrv = 'PHPSESSID=abcdef0123456789' +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) +) +tbl.register(hdr) +assert(len(tbl) <= 80) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv) + +hdrv2 = 'JSESSID=abcdef0123456789' +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) +) +tbl.register(hdr2) +assert(len(tbl) <= 80) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +try: + tbl[h2.HPackHdrTable._static_entries_last_idx+2] + ret = False +except: + ret = True + +assert(ret) + + += HTTP/2 HPackHdrTable : Resizing +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable() +hdrv = 'PHPSESSID=abcdef0123456789' +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) +) +tbl.register(hdr) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv) + +hdrv2 = 'JSESSID=abcdef0123456789' +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) +) +tbl.register(hdr2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) + +#Resizing to a value higher than cap (default:4096) +try: + tbl.resize(8192) + ret = False +except AssertionError: + ret = True + +assert(ret) +#Resizing to a lower value by that is not small enough to cause eviction +tbl.resize(1024) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) +#Resizing to a higher value but thatt is lower than cap +tbl.resize(2048) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) +#Resizing to a lower value that causes eviction +tbl.resize(80) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +try: + tbl[h2.HPackHdrTable._static_entries_last_idx+2] + ret = False +except: + ret = True + +assert(ret) + += HTTP/2 HPackHdrTable : Recapping +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable() +hdrv = 'PHPSESSID=abcdef0123456789' +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) +) +tbl.register(hdr) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv) + +hdrv2 = 'JSESSID=abcdef0123456789' +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) +) +tbl.register(hdr2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) + +#Recapping to a higher value +tbl.recap(8192) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) + +#Recapping to a low value but without causing eviction +tbl.recap(1024) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) + +#Recapping to a low value that causes evictiontbl.recap(1024) +tbl.recap(80) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +try: + tbl[h2.HPackHdrTable._static_entries_last_idx+2] + ret = False +except: + ret = True + +assert(ret) + += HTTP/2 HPackHdrTable : Generating Textual Representation +~ http2 hpack hpackhdrtable helpers + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +hdrs_lst = [ + h2.HPackIndexedHdr(index=2), #Method Get + h2.HPackLitHdrFldWithIncrIndexing( + index=h.get_idx_by_name(':path'), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('/index.php')) + ), + h2.HPackIndexedHdr(index=7), #Scheme HTTPS + h2.HPackIndexedHdr(index=h2.HPackHdrTable._static_entries_last_idx+2), + h2.HPackLitHdrFldWithIncrIndexing( + index=58, + hdr_value=h2.HPackHdrString(data=h2.HPackZString('Mozilla/5.0 Generated by hand')) + ), + h2.HPackLitHdrFldWithoutIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generated-By')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) + ) +] + +p = h2.H2Frame(stream_id = 1)/h2.H2HeadersFrame(hdrs=hdrs_lst) + +expected_output = ''':method GET +:path /index.php +:scheme https +x-generation-date: 2016-08-11 +user-agent: Mozilla/5.0 Generated by hand +X-Generated-By: Me''' + +assert(h.gen_txt_repr(p) == expected_output) + += HTTP/2 HPackHdrTable : Parsing Textual Representation +~ http2 hpack hpackhdrtable helpers + +body = 'login=titi&passwd=toto' +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +x-generation-software: scapy +'''.format(len(body)) + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) +seq = h.parse_txt_hdrs( + hdrs, + stream_id=1, + body=body, + should_index=lambda name: name in ['user-agent', 'x-generation-software'], + is_sensitive=lambda name, value: name in ['x-generated-by', ':path'] +) +assert(isinstance(seq, h2.H2Seq)) +assert(len(seq.frames) == 2) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 1) +assert('EH' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 9) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 1) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 31) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 28) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(22)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[6] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[7] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 63) +hdr = p.hdrs[8] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generation-software)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(scapy)') + +p = seq.frames[1] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 0) +assert(len(p.flags) == 1) +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2DataFrame)) +pay = p[h2.H2DataFrame] +assert(pay.data == body) + += HTTP/2 HPackHdrTable : Parsing Textual Representation without body +~ http2 hpack hpackhdrtable helpers + +hdrs = ''':method POST +:path /login.php +:scheme https +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +''' + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +# Without body +seq = h.parse_txt_hdrs(hdrs, stream_id=1) +assert(isinstance(seq, h2.H2Seq)) +#This is the first major difference with the first test +assert(len(seq.frames) == 1) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 2) +assert('EH' in p.flags) +#This is the second major difference with the first test +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 6) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 62) + + += HTTP/2 HPackHdrTable : Parsing Textual Representation with too small max frame +~ http2 hpack hpackhdrtable helpers + +body = 'login=titi&passwd=toto' +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +x-long-header: {} +'''.format(len(body), 'a'*5000) + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +#x-long-header is too long to fit in any frames (whose default max size is 4096) +expect_exception(Exception, "seq = h.parse_txt_hdrs('''{}''', stream_id=1)".format(hdrs)) + += HTTP/2 HPackHdrTable : Parsing Textual Representation with very large header and a large authorized frame size +~ http2 hpack hpackhdrtable helpers + +body = 'login=titi&passwd=toto' +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +x-long-header: {} +'''.format(len(body), 'a'*5000) + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +# Now trying to parse it with a max frame size large enough for x-long-header to +# fit in a frame +seq = h.parse_txt_hdrs(hdrs, stream_id=1, max_frm_sz=8192) +assert(isinstance(seq, h2.H2Seq)) +assert(len(seq.frames) == 1) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 2) +assert('EH' in p.flags) +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 9) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 31) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 28) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(22)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[6] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[7] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 62) +hdr = p.hdrs[8] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-long-header)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString({})'.format('a'*5000)) + += HTTP/2 HPackHdrTable : Parsing Textual Representation with two very large headers and a large authorized frame size +~ http2 hpack hpackhdrtable helpers + +body = 'login=titi&passwd=toto' +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +x-long-header: {} +x-long-header: {} +'''.format(len(body), 'a'*5000, 'b'*5000) + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +# Now trying to parse it with a max frame size large enough for x-long-header to +# fit in a frame but a maximum header fragment size that is not large enough to +# store two x-long-header +seq = h.parse_txt_hdrs(hdrs, stream_id=1, max_frm_sz=8192) +assert(isinstance(seq, h2.H2Seq)) +assert(len(seq.frames) == 2) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 1) +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 9) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 31) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 28) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(22)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[6] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[7] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 62) +hdr = p.hdrs[8] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-long-header)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString({})'.format('a'*5000)) +p = seq.frames[1] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 9) +assert(len(p.flags) == 1) +assert('EH' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2ContinuationFrame)) +hdrs_frm = p[h2.H2ContinuationFrame] +assert(len(p.hdrs) == 1) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-long-header)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString({})'.format('b'*5000)) + += HTTP/2 HPackHdrTable : Parsing Textual Representation with two very large headers, a large authorized frame size and a "small" max header list size +~ http2 hpack hpackhdrtable helpers + +body = 'login=titi&passwd=toto' +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +x-long-header: {} +x-long-header: {} +'''.format(len(body), 'a'*5000, 'b'*5000) + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +# Now trying to parse it with a max frame size large enough for x-long-header to +# fit in a frame but and a max header list size that is large enough to fit one +# but not two +seq = h.parse_txt_hdrs(hdrs, stream_id=1, max_frm_sz=8192, max_hdr_lst_sz=5050) +assert(isinstance(seq, h2.H2Seq)) +assert(len(seq.frames) == 3) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 1) +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 8) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 31) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 28) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(22)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[6] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[7] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 62) +p = seq.frames[1] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 9) +assert(len(p.flags) == 0) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2ContinuationFrame)) +hdrs_frm = p[h2.H2ContinuationFrame] +assert(len(p.hdrs) == 1) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-long-header)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString({})'.format('a'*5000)) +p = seq.frames[2] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 9) +assert(len(p.flags) == 1) +assert('EH' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2ContinuationFrame)) +hdrs_frm = p[h2.H2ContinuationFrame] +assert(len(p.hdrs) == 1) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-long-header)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString({})'.format('b'*5000)) + += HTTP/2 HPackHdrTable : Parsing Textual Representation with sensitive headers and non-indexable ones +~ http2 hpack hpackhdrtable helpers + +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +'''.format(len(body)) + +h = h2.HPackHdrTable() +seq = h.parse_txt_hdrs(hdrs, stream_id=1, body=body, is_sensitive=lambda n,v: n in ['x-generation-date'], should_index=lambda x: x != 'x-generated-by') +assert(isinstance(seq, h2.H2Seq)) +assert(len(seq.frames) == 2) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 1) +assert('EH' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 8) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 31) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 28) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(22)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[6] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[7] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generation-date)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(2016-08-11)') +p = seq.frames[1] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 0) +assert(len(p.flags) == 1) +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2DataFrame)) +pay = p[h2.H2DataFrame] +assert(pay.data == body)