diff --git a/doc/notebooks/HTTP_2_Tuto.ipynb b/doc/notebooks/HTTP_2_Tuto.ipynb new file mode 100644 index 000000000..9b3885c66 --- /dev/null +++ b/doc/notebooks/HTTP_2_Tuto.ipynb @@ -0,0 +1,2550 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:50ffc723dfcf9f5650b542c1b77933eeaa2df6f665494225ce2aba661b86885e" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "HTTP/2 Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tutorial aims at creating an HTTP/2 session using Scapy. The frontpage of Google will be fetched. The favicon will also be loaded as a dependency of the frontpage. Finally, a Google query will be submitted. The first queries will be generated using some Scapy helpers. The last one will be generated by hand, to better illustrate the low level APIs.\n", + "\n", + "This tutorial can be run without any privileges (no root, no CAP_NET_ADMIN, no CAP_NET_RAW). One can select \"Cell -> Run All\" to perform the three queries." + ] + }, + { + "cell_type": "heading", + "level": 2, + "metadata": {}, + "source": [ + "Building the socket" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we need to build an TLS socket to the HTTP server, and to negotiate the HTTP/2 protocol as the next protocol. Doing so requires a fairly recent version of the Python ssl module. We indeed need support of ALPN (https://www.rfc-editor.org/rfc/rfc7301.txt).\n", + "We build our TCP socket first." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import socket\n", + "dn = 'www.google.fr'\n", + "\n", + "# Get the IP address of a Google HTTP endpoint\n", + "l = socket.getaddrinfo(dn, 443, socket.INADDR_ANY, socket.SOCK_STREAM, socket.IPPROTO_TCP)\n", + "assert len(l) > 0, 'No address found :('\n", + "\n", + "s = socket.socket(l[0][0], l[0][1], l[0][2])\n", + "s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n", + "s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)\n", + "ip_and_port = l[0][4]" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 99 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now build our SSL context and we wrap the previously defined socket in it." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import ssl\n", + "# Testing support for ALPN\n", + "assert(ssl.HAS_ALPN)\n", + "\n", + "# Building the SSL context\n", + "ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)\n", + "ssl_ctx.set_ciphers(':'.join([ # List from ANSSI TLS guide v.1.1 p.51\n", + " 'ECDHE-ECDSA-AES256-GCM-SHA384',\n", + " 'ECDHE-RSA-AES256-GCM-SHA384',\n", + " 'ECDHE-ECDSA-AES128-GCM-SHA256',\n", + " 'ECDHE-RSA-AES128-GCM-SHA256',\n", + " 'ECDHE-ECDSA-AES256-SHA384',\n", + " 'ECDHE-RSA-AES256-SHA384',\n", + " 'ECDHE-ECDSA-AES128-SHA256',\n", + " 'ECDHE-RSA-AES128-SHA256',\n", + " 'ECDHE-ECDSA-CAMELLIA256-SHA384',\n", + " 'ECDHE-RSA-CAMELLIA256-SHA384',\n", + " 'ECDHE-ECDSA-CAMELLIA128-SHA256',\n", + " 'ECDHE-RSA-CAMELLIA128-SHA256',\n", + " 'DHE-RSA-AES256-GCM-SHA384',\n", + " 'DHE-RSA-AES128-GCM-SHA256',\n", + " 'DHE-RSA-AES256-SHA256',\n", + " 'DHE-RSA-AES128-SHA256',\n", + " 'AES256-GCM-SHA384',\n", + " 'AES128-GCM-SHA256',\n", + " 'AES256-SHA256',\n", + " 'AES128-SHA256',\n", + " 'CAMELLIA128-SHA256'\n", + " ])) \n", + "ssl_ctx.set_alpn_protocols(['h2']) # h2 is a RFC7540-hardcoded value\n", + "ssl_sock = ssl_ctx.wrap_socket(s, server_hostname=dn)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 100 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We then connect the socket to the TCP endpoint." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "ssl_sock.connect(ip_and_port)\n", + "assert('h2' == ssl_sock.selected_alpn_protocol())" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 101 + }, + { + "cell_type": "heading", + "level": 2, + "metadata": {}, + "source": [ + "Reading the server settings and acknowledging them." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With HTTP/2, the server is the first to talk, sending its settings for the HTTP/2 session. Let's read them. For this, we wrap the TLS connection into a Scapy SuperSocket for easier management." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import scapy.supersocket as supersocket\n", + "import scapy.contrib.http2 as h2\n", + "import scapy.config\n", + "scapy.config.conf.debug_dissector = True\n", + "ss = supersocket.SSLStreamSocket(ssl_sock, basecls=h2.H2Frame)\n", + "srv_set = ss.recv()\n", + "srv_set.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HTTP/2 Frame ]### \n", + " len = 0x12\n", + " type = SetFrm\n", + " flags = set([])\n", + " reserved = 0L\n", + " stream_id = 0L\n", + "###[ HTTP/2 Settings Frame ]### \n", + " \\settings \\\n", + " |###[ HTTP/2 Setting ]### \n", + " | id = Max concurrent streams\n", + " | value = 100\n", + " |###[ HTTP/2 Setting ]### \n", + " | id = Initial window size\n", + " | value = 1048576\n", + " |###[ HTTP/2 Setting ]### \n", + " | id = Max header list size\n", + " | value = 16384\n", + "\n" + ] + } + ], + "prompt_number": 102 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's make a note of the server settings for later usage.\n", + "We define variables for the server settings. They are assigned the RFC-defined default values. These values are overwritten by the settings provided by the server, if they are provided." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "srv_max_frm_sz = 1<<14\n", + "srv_hdr_tbl_sz = 4096\n", + "srv_max_hdr_tbl_sz = 0\n", + "srv_global_window = 1<<14\n", + "for setting in srv_set.payload.settings:\n", + " if setting.id == h2.H2Setting.SETTINGS_HEADER_TABLE_SIZE:\n", + " srv_hdr_tbl_sz = setting.value\n", + " elif setting.id == h2.H2Setting.SETTINGS_MAX_HEADER_LIST_SIZE:\n", + " srv_max_hdr_lst_sz = setting.value\n", + " elif setting.id == h2.H2Setting.SETTINGS_INITIAL_WINDOW_SIZE:\n", + " srv_global_window = setting.value" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 103 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "HTTP/2 is a very polite protocol. We need to acknowledge the server settings. For this, we first need to send a constant string, which is a connection preface. This serves the purpose of confirming to the server that the HTTP/2 protocol is understood by the client. Scapy builds the appropriate packet for us to send from this constant string." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import scapy.packet as packet\n", + "\n", + "# We verify that the server window is large enough for us to send some data.\n", + "srv_global_window -= len(h2.H2_CLIENT_CONNECTION_PREFACE)\n", + "assert(srv_global_window >= 0)\n", + "\n", + "ss.send(packet.Raw(h2.H2_CLIENT_CONNECTION_PREFACE))" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 104, + "text": [ + "24" + ] + } + ], + "prompt_number": 104 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we build the acknowledgment frame and we send our own settings in another frame. We will define very LARGE values (maximum values as defined in the RFC7540, in most cases), just so that we don't end up having to handle window management in this tutorial." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "set_ack = h2.H2Frame(flags={'A'})/h2.H2SettingsFrame()\n", + "set_ack.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HTTP/2 Frame ]### \n", + " len = None\n", + " type = SetFrm\n", + " flags = set(['ACK (A)'])\n", + " reserved = 0\n", + " stream_id = 0\n", + "###[ HTTP/2 Settings Frame ]### \n", + " \\settings \\\n", + "\n" + ] + } + ], + "prompt_number": 105 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "own_set = h2.H2Frame()/h2.H2SettingsFrame()\n", + "max_frm_sz = (1 << 24) - 1\n", + "max_hdr_tbl_sz = (1 << 16) - 1\n", + "win_sz = (1 << 31) - 1\n", + "own_set.settings = [\n", + " h2.H2Setting(id = h2.H2Setting.SETTINGS_ENABLE_PUSH, value=0),\n", + " h2.H2Setting(id = h2.H2Setting.SETTINGS_INITIAL_WINDOW_SIZE, value=win_sz),\n", + " h2.H2Setting(id = h2.H2Setting.SETTINGS_HEADER_TABLE_SIZE, value=max_hdr_tbl_sz),\n", + " h2.H2Setting(id = h2.H2Setting.SETTINGS_MAX_FRAME_SIZE, value=max_frm_sz),\n", + "]" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 106 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We then send the two frames and then read the acknowledgment of our settings from the server. We set up a loop because the first frames that we may read could be PING frames or window management frames." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "h2seq = h2.H2Seq()\n", + "h2seq.frames = [\n", + " set_ack,\n", + " own_set\n", + "]\n", + "# We verify that the server window is large enough for us to send our frames.\n", + "srv_global_window -= len(str(h2seq))\n", + "assert(srv_global_window >= 0)\n", + "ss.send(h2seq)\n", + "\n", + "# Loop until an acknowledgement for our settings is received\n", + "new_frame = None\n", + "while isinstance(new_frame, type(None)) or not (\n", + " new_frame.type == h2.H2SettingsFrame.type_id \n", + " and 'A' in new_frame.flags\n", + " ):\n", + " if not isinstance(new_frame, type(None)):\n", + " # If we received a frame about window management \n", + " if new_frame.type == h2.H2WindowUpdateFrame.type_id:\n", + " # For this tutorial, we don't care about stream-specific windows, but we should :)\n", + " if new_frame.stream_id == 0:\n", + " srv_global_window += new_frame.payload.win_size_incr\n", + " # If we received a Ping frame, we acknowledge the ping, \n", + " # just by setting the ACK flag (A), and sending back the query\n", + " elif new_frame.type == h2.H2PingFrame.type_id:\n", + " new_flags = new_frame.getfieldval('flags')\n", + " new_flags.add('A')\n", + " new_frame.flags = new_flags\n", + " srv_global_window -= len(str(new_frame))\n", + " assert(srv_global_window >= 0)\n", + " ss.send(new_frame)\n", + " else:\n", + " assert new_frame.type != h2.H2ResetFrame.type_id \\\n", + " and new_frame.type != h2.H2GoAwayFrame.type_id, \\\n", + " \"Error received; something is not right!\"\n", + " try:\n", + " new_frame = ss.recv()\n", + " new_frame.show()\n", + " except:\n", + " import time\n", + " time.sleep(1)\n", + " new_frame = None" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HTTP/2 Frame ]### \n", + " len = 0x4\n", + " type = WinFrm\n", + " flags = set([])\n", + " reserved = 0L\n", + " stream_id = 0L\n", + "###[ HTTP/2 Window Update Frame ]### \n", + " reserved = 0L\n", + " win_size_incr= 983041L\n", + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x0\n", + " type = SetFrm\n", + " flags = set(['ACK (A)'])\n", + " reserved = 0L\n", + " stream_id = 0L\n", + "\n" + ] + } + ], + "prompt_number": 107 + }, + { + "cell_type": "heading", + "level": 2, + "metadata": {}, + "source": [ + "Build the form query with the helpers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are now building a query for the frontpage https://www.google.fr/. We use the HTTP/2 Scapy Module helpers to build this query. The parse_txt_hdrs helper receives various parameters regarding the size of the header blocks and the frame to automatically split the values on multiple frames, if need be. It also receives two callbacks to know which flavour of HPack header encoding we should apply.\n", + "\n", + "We either use the server settings if they were specified or the default values.\n", + "\n", + "You may note that we say that cookies are sensitive, regardless of their content. We would do something a bit smarter, if we knew what is the name of the cookies that are sensitive, and those that are merely informative. In HTTP/2 cookies are split into multiple \"cookie\" headers, while in HTTP/1.1, they are all stored inside the same header.\n", + "\n", + "As the client of this HTTP/2 connection, we need to use odd stream ids, per RFC7540. Since this is the first query, we will use the stream id 1." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "tblhdr = h2.HPackHdrTable()\n", + "qry_frontpage = tblhdr.parse_txt_hdrs(\n", + " ''':method GET\n", + ":path /\n", + ":authority www.google.fr\n", + ":scheme https\n", + "accept-encoding: gzip, deflate\n", + "accept-language: fr-FR\n", + "accept: text/html\n", + "user-agent: Scapy HTTP/2 Module\n", + "''',\n", + " stream_id=1,\n", + " max_frm_sz=srv_max_frm_sz,\n", + " max_hdr_lst_sz=srv_max_hdr_lst_sz,\n", + " is_sensitive=lambda hdr_name, hdr_val: hdr_name in ['cookie'],\n", + " should_index=lambda x: x in [\n", + " 'x-requested-with', \n", + " 'user-agent', \n", + " 'accept-language',\n", + " ':authority',\n", + " 'accept',\n", + " ]\n", + ")\n", + "qry_frontpage.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HTTP/2 Frame Sequence ]### \n", + " \\frames \\\n", + " |###[ HTTP/2 Frame ]### \n", + " | len = None\n", + " | type = HdrsFrm\n", + " | flags = set(['End Stream (ES)', 'End Headers (EH)'])\n", + " | reserved = 0\n", + " | stream_id = 1\n", + " |###[ HTTP/2 Headers Frame ]### \n", + " | \\hdrs \\\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 2\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 4\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 1\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = None\n", + " | | | len = None\n", + " | | | data = 'HPackZString(www.google.fr)'\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 7\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 16\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 17\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = None\n", + " | | | len = None\n", + " | | | data = 'HPackZString(fr-FR)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 19\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = None\n", + " | | | len = None\n", + " | | | data = 'HPackZString(text/html)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 58\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = None\n", + " | | | len = None\n", + " | | | data = 'HPackZString(Scapy HTTP/2 Module)'\n", + "\n" + ] + } + ], + "prompt_number": 108 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The previous helper updated the HPackHdrTable structure with the headers that should be indexed. We don't need to look inside that table if we only use helpers. For the sake of this tutorial, though, we will." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "for i in xrange(max(tblhdr._static_entries.keys()) + 1, max(tblhdr._static_entries.keys()) + 1 + len(tblhdr._dynamic_table)):\n", + " print('Header: {} Value: {}'.format(tblhdr[i].name(), tblhdr[i].value()))" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "Header: user-agent Value: Scapy HTTP/2 Module\n", + "Header: accept Value: text/html\n", + "Header: accept-language Value: fr-FR\n", + "Header: :authority Value: www.google.fr\n" + ] + } + ], + "prompt_number": 109 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also build a query for the favicon." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "qry_icon = tblhdr.parse_txt_hdrs(\n", + " ''':method GET\n", + ":path /favicon.ico\n", + ":authority www.google.fr\n", + ":scheme https\n", + "accept-encoding: gzip, deflate\n", + "accept-language: fr-FR\n", + "accept: image/x-icon; image/vnd.microsoft.icon\n", + "user-agent: Scapy HTTP/2 Module\n", + "''',\n", + " stream_id=3,\n", + " max_frm_sz=srv_max_frm_sz,\n", + " max_hdr_lst_sz=srv_max_hdr_tbl_sz,\n", + " is_sensitive=lambda hdr_name, hdr_val: hdr_name in ['cookie'],\n", + " should_index=lambda x: x in [\n", + " 'x-requested-with', \n", + " 'user-agent', \n", + " 'accept-language',\n", + " ':authority',\n", + " 'accept',\n", + " ]\n", + ")\n", + "qry_icon.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HTTP/2 Frame Sequence ]### \n", + " \\frames \\\n", + " |###[ HTTP/2 Frame ]### \n", + " | len = None\n", + " | type = HdrsFrm\n", + " | flags = set(['End Stream (ES)', 'End Headers (EH)'])\n", + " | reserved = 0\n", + " | stream_id = 3\n", + " |###[ HTTP/2 Headers Frame ]### \n", + " | \\hdrs \\\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 2\n", + " | |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", + " | | magic = 0\n", + " | | never_index= Don't Index\n", + " | | index = 4\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = None\n", + " | | | len = None\n", + " | | | data = 'HPackZString(/favicon.ico)'\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 65\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 7\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 16\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 64\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 19\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = None\n", + " | | | len = None\n", + " | | | data = 'HPackZString(image/x-icon; image/vnd.microsoft.icon)'\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 63\n", + "\n" + ] + } + ], + "prompt_number": 110 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You may note that several of the headers that are in common between the two queries (the one for the frontpage and the one for the /favicon.ico) are compressed in the second query. They are only refered to by an index number of the dynamic HPack Header Table.\n", + "We now alter the favicon query to be dependent of the query for the form." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "real_qry_icon = h2.H2Frame(\n", + " stream_id=qry_icon.frames[0][h2.H2Frame].stream_id,\n", + " flags={'+'}.union(qry_icon.frames[0][h2.H2Frame].flags),\n", + ") / h2.H2PriorityHeadersFrame(\n", + " hdrs=qry_icon.frames[0][h2.H2HeadersFrame].hdrs,\n", + " stream_dependency=1,\n", + " weight=32,\n", + " exclusive=0\n", + ")\n", + "real_qry_icon.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HTTP/2 Frame ]### \n", + " len = None\n", + " type = HdrsFrm\n", + " flags = set(['End Stream (ES)', 'End Headers (EH)', 'Priority (+)'])\n", + " reserved = 0\n", + " stream_id = 3\n", + "###[ HTTP/2 Headers Frame with Priority ]### \n", + " exclusive = 0\n", + " stream_dependency= 1\n", + " weight = 32\n", + " \\hdrs \\\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 2\n", + " |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", + " | magic = 0\n", + " | never_index= Don't Index\n", + " | index = 4\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = None\n", + " | | len = None\n", + " | | data = 'HPackZString(/favicon.ico)'\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 65\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 7\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 16\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 64\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 19\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = None\n", + " | | len = None\n", + " | | data = 'HPackZString(image/x-icon; image/vnd.microsoft.icon)'\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 63\n", + "\n" + ] + } + ], + "prompt_number": 111 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now send both queries to the server." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "h2seq = h2.H2Seq()\n", + "h2seq.frames = [\n", + " qry_frontpage.frames[0],\n", + " real_qry_icon\n", + "]\n", + "srv_global_window -= len(str(h2seq))\n", + "assert(srv_global_window >= 0)\n", + "ss.send(h2seq)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 112, + "text": [ + "117" + ] + } + ], + "prompt_number": 112 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's read the answers!" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# The stream variable will contain all read frames; we will read on until stream 1 and stream 3 are closed by the server.\n", + "stream = h2.H2Seq()\n", + "# Number of streams closed by the server\n", + "closed_stream = 0\n", + "\n", + "new_frame = None\n", + "while True:\n", + " if not isinstance(new_frame, type(None)):\n", + " if new_frame.stream_id in [1, 3]:\n", + " stream.frames.append(new_frame)\n", + " if 'ES' in new_frame.flags:\n", + " closed_stream += 1\n", + " # If we read a PING frame, we acknowledge it by sending the same frame back, with the ACK flag set.\n", + " elif new_frame.stream_id == 0 and new_frame.type == h2.H2PingFrame.type_id:\n", + " new_flags = new_frame.getfieldval('flags')\n", + " new_flags.add('A')\n", + " new_frame.flags = new_flags\n", + " ss.send(new_frame)\n", + " \n", + " # If two streams were closed, we don't need to perform the next operations\n", + " if closed_stream >= 2:\n", + " break\n", + " try:\n", + " new_frame = ss.recv()\n", + " new_frame.show()\n", + " except:\n", + " import time\n", + " time.sleep(1)\n", + " new_frame = None\n", + "\n", + "stream.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HTTP/2 Frame ]### \n", + " len = 0xe1\n", + " type = HdrsFrm\n", + " flags = set(['End Headers (EH)'])\n", + " reserved = 0L\n", + " stream_id = 3L\n", + "###[ HTTP/2 Headers Frame ]### \n", + " \\hdrs \\\n", + " |###[ HPack Dynamic Size Update ]### \n", + " | magic = 1\n", + " | max_size = 12288\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 8\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 59\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 11\n", + " | | data = 'HPackZString(Accept-Encoding)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 26\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 3\n", + " | | data = 'HPackZString(gzip)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 31\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 9\n", + " | | data = 'HPackZString(image/x-icon)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 33\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 22\n", + " | | data = 'HPackZString(Thu, 08 Dec 2016 06:23:59 GMT)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 36\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 22\n", + " | | data = 'HPackZString(Fri, 16 Dec 2016 06:23:59 GMT)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 44\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 22\n", + " | | data = 'HPackZString(Thu, 08 Dec 2016 01:00:57 GMT)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 0\n", + " | \\hdr_name \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 16\n", + " | | data = 'HPackZString(x-content-type-options)'\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 5\n", + " | | data = 'HPackZString(nosniff)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 54\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 3\n", + " | | data = 'HPackZString(sffe)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 28\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 3\n", + " | | data = 'HPackZString(1494)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 0\n", + " | \\hdr_name \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 12\n", + " | | data = 'HPackZString(x-xss-protection)'\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 10\n", + " | | data = 'HPackZString(1; mode=block)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 24\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 16\n", + " | | data = 'HPackZString(public, max-age=691200)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 21\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 5\n", + " | | data = 'HPackZString(472252)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 0\n", + " | \\hdr_name \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 5\n", + " | | data = 'HPackZString(alt-svc)'\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 28\n", + " | | data = 'HPackZString(quic=\":443\"; ma=2592000; v=\"35,34\")'\n", + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x5d6\n", + " type = DataFrm\n", + " flags = set(['End Stream (ES)'])\n", + " reserved = 0L\n", + " stream_id = 3L\n", + "###[ HTTP/2 Data Frame ]### \n", + " data = '\\x1f\\x8b\\x08\\x00\\x00\\tn\\x88\\x02\\xff\\xbcX{PTU\\x18?\\xa6\\x8d\\x89\\xa5\\xf4G\\xff5\\x13\\xf60\\xffh\\xcc1\\x11\\xc7L\\xb3\\x07>\\xffp\\x94\\x10L\\x1b\\xd3L+kF\\'\\xb5\\x19\\xc7Rg\\xc2\\xb4@KtD _\\x91\\x80\\x8e\\x82\\x8a\\xa8\\x83\\nhI\\xa9\\xa8\\x08B\\x98\\xaf|\\xdf]`\\xc1}\\xb0{\\xf7;\\xbf\\xe6\\\\\\xeee\\xee\\xde\\xbd\\x97\\xdd\\xb5\\xb53\\xf3\\rg\\xcf\\xfd\\xbe\\xefw\\xcew\\xbe\\xd7\\x81\\xb1n\\xec1\\x16\\x1b+\\xfe\\xc6\\xb1y=\\x18\\xeb\\xcf\\x18\\x8b\\x8b\\xeb\\xf8\\x9d\\x1f\\xcbXF\\x0f\\xc6\\x060\\xc6b\\xc5:\\xebXWF\\x0f\\x16r\\x00\\x18DD\\x1b\\x89\\xa8\\x81\\x88\\xbc*\\xd5\\x13Q&\\xe7|\\xa0\\x95\\x1c\\xe7\\xbc\\x17\\x11e!\\xc4 \\xa2\\r\\x00\\x9e0\\x91\\xad\\x10\\xdf}\\xe4\\xc5\\x81\\xbfvb\\xf1\\x91\\x19H\\xd95\\x1c)\\x85\\xc3\\xb1\\xf0P*v\\xd7\\xe5\\xa2]vk:\\x8e\\xebuh\\xb8v\\xd7},(M\\xc1\\x94\\xfc!\\xa6\\x94\\xb17\\x05\\xdcq\\xafs\\x1f\\xday\\x15\\\\\\xbf\\x17\\x0bJ\\xa7*|\\x02s\\xfb\\xf9\\x1fQ{\\xff\\x0c.I\\xd5(\\xac\\xcdF\\xd6\\x8e\\xf1p\\xa4\\x8d\\x86;w.\\xc0\\xb9\\xa2C\\xd8\\x83\\x886\\x89\\xf9\\xeaKg1\\xa1p,\\x92\\x0b\\x87\\xa1N\\xaa\\x0e>\\xf7\\x9d\\x068W%\\xc2\\xf9\\xed[\\xf07\\x9c\\xd0\\xf6\\x90ID\\x8db\\xfe\\xca\\xc9V<]\\xd6\\x84\\xf4\\x0bE\\x96\\xb6k\\xdf\\xb3R\\x91o/I\\xd7\\xe4\\xc5\\xbd\\xf8\\xc4<\\xe6\\xa8\\x8c\\xc7\\xcbd\\x94\\xd8x\\x80\\x8c\\xe07\\x92\\xe7\\xd7E\\x9a\\xbcW\\x93\\xef}\\xacC\\xfe\\xa0=\\x0c\\xf9\\xc2\\xa5z\\xf9\\xcbb\\x1e\\xff\\x87\\x1f1\\xfb\\xbc\\xf8\\xec\\x177\\xc2\\x1d\\xaa\\x8f)w\\xf7\\xd39\\x19\\t\\x93\\xed\\x18\\x96(\\xe1|\\xad\\xcf\\x84\\xd7T~#\\xe7|\\xb0\\x98{\\xbd\\x1c\\xc9\\xb3\\x9a\\x10\\xff\\xb6\\x84\\xd7\\xc7\\xd9\\xb0!\\xc7\\x89s5>\\\\\\xa8\\xf5ac\\xae\\x13\\x93?h\\xc2\\x8d\\x9b~\\xa3\\x8aA\\xaa\\xff\\xe4\\x8a\\x1f\\xf7$B\\xca\\xecfE\\x87\\x19\\xcd_\\xec\\xd0co\\xd2\\xc5L\\x0c\\x11\\x9d\\xd0\\xf6\\x91\\xb7\\xdb\\x8d\\x19\\x9f4c\\xc4x\\x1b\\x86\\x8f\\x910\\xed\\xe3fl/p)\\xdfT\\xd9\\n\\xe1\\xf3\\x86\\xb8\\x8b\\xd1\\xf6\\x11\\xc2fYFYC,\\r\\x16<\\xe2^\\xc4\\xdd\\xaa\\xd4\\xa8\\xfa\\xe9 #\\xbf\\xa3/c\\xe5\\xdd\\x19[\\xde\\xad\\x83B\\r\\x8dO\\xc8\\x08\\xd9\\x01j\\x8eyS\\x9fgb\\xd9C\\x0f\\xce\\xf9S\\x9c\\xf3\\xa9\\xea\\x19\\xaa\\x88H\\xd2\\xe5!I]\\x136H\\x06\\xf0$\\x8b\\xd2\\xe0\\x9c\\xbfHD9D\\xe4\\x8a\\xc0\\x7f]D\\xb4\\x99s\\xfe\\xc2\\x7f\\xc0\\x15\\xb9o\\r\\x11\\xc9x\\xc8\\xa1\\xde\\xf1*c^\\r\\xf3\\xcc5\\x88\\xd2 \\xa2\\xf3\\x00\\x9e\\x0f\\x07[\\xad3\\x92\\x99\\x1e\\xc9y\\x07{\\xeb\\xb7ae\\xf9|\\xcc)\\x1e\\xa7\\xe4\\xd4\\xe4\\x82\\x04\\xcc.J\\xc4\\xb2\\xa3s\\x90W\\xb3\\x01W\\x9b\\x1b\\xac\\xf6p\\x8fs\\xfej\\x18\\xe7\\x0e\\xc2\\xb6\\xb9\\xeeb\\xed\\xefK\\x91T0\\xd4\\xb2\\x86\\xe8\\xe9\\xebcsq\\xa5\\xb9\\xdet\\x0fVvP\\xe3\\xfc\\xa2Q\\xe6\\xe4\\x8d\\xc3\\x98\\xbe{dX\\xb8z\\x12v)\\xae\\xc9\\xb6\\xba\\x8b \\x7f \\xa2\\xef\\x8d\\xbc\\xc5\\r;\"\\xc6\\xd5hf^<\\xfe\\xcc\\x18\\to\\xc5\\x16\\xb3=\\xac2\\xd8\\xfd%\\xa3\\x9fW^/\\xb5\\xd4\\xfd\\xc5\\xc1$l\\xa9NGic!\\x0e]\\xde\\xa5\\xdc\\xfb\\xd2\\xb2\\x8f\\x90\\x94\\x1f\\xaf|_\\xb25\\x017W\\x8f\\xee\\xacKZ]\\xd5\\xc7\\x85>6\\x8d\\xf9U\\xf8\\xd9\\xfb&6\\x9f\\xbbo\"\\xce\\xdc\\xae\\xb4\\xf4\\xf3\\xeb-\\x8d\\xc8/\\x9e\\x8dVC]t\\xadK\\x02\\xf7\\xba\\x8d{\\xd8\\xac\\x9e\\xbd\\x0f\\x11\\x05|\\\\yjm\\x10\\xf6\\xa2\\xc3\\xd3\\xd1\\xe6u\\x84\\x0e6\\xbf\\x0f\\x9e\\x9dK\\x82j\\xb3\\xaf\\xaa (G\\x89<\\xc99O\\xd5\\xaf_ss\\xf4*s\\xe3\\xb5\\xa2\\xb4N\\xecYE\\x89h\\xf1\\xd8\\xc3\\x8ew\\xeey\\xa0\\x9cY\\x8f\\xef\\xce\\x9a\\x19\\xcc\\xc7y\\xb2\\xc8\\xad\\xfa\\xb5\\xef\\xae\\x91\\xd2o\\x08\\xeaW\\xb2\\x1f\\x93\\xf2G\\xa0\\xecJQ\\xc49\\xc7w*?\\xc8\\x06\\xdcq7\\xa8f\\x12\\xd1i\\xfd\\xda\\x98j\\x7f\\'\\xbe\\xa0\\x81\\x95\\xb7 \\x93/\\xf2\\x9c\\']\\r\\xc2\\x97\\xeb+\\x8c\\xf8\\xa2f\\x05\\x18\\xf6\\xd9J9\\x00?\\xa5\\xc6\\xdf%\\x8eY\\x1ffE\\xbe\\xaaB#\\xbe\\xa4\\xf5y\\xda\\xe8y4\\x10\\x7f\\xd9\\xdf\\x145|c.\\xd0\\xf7\\x99\\xff\\x07\\xbe\\xef\\xb7<3\\xfc\\xa6\\xae\\xec\\x9fz1z\\xf6\\x97\\xeb\\x8e\\x9b\\xd9?\\xc0\\xff\\x12\\xcf\\x06\\xfa_\\xbf=^\\xc82\\x1e\\xc9P\\xfd/ \\xfe\\xd2t\\xf1\\xf7\\xccz\\x17\\x86\\x8c\\xb3a\\xff!\\xcf\\xa3\\xc2\\x17\\xfd\\xda4\\xfd\\xda\\x157G\\xcf\\xc32\\xe2\\x96\\xb4v\\xf6\\xd7c\\xdf\\xb3\\xa3\\xd9AQ\\xc7\\x17\\xfd$\\x80\\xbeD\\x14p\\xc0\\xcf\\xd79\\x83z\\xfc\\x0f\\xe7\\xb7\\xa0\\xad\\x8d\\x87\\xd4\\xe9t\\xf1\\xb0{D\\xd1\\xd3\\xaa\\xf5\\' 0n\\xdd\\xf1\\xe3\\x8d\\xf1\\xb6\\xa0=L\\x9a\\xde\\x84\\xaa3^K\\x9d\\xa7N{11\\xc5\\x8e\\x1f2\\x1f\\x84\\x83\\x9f\\xa3\\xab\\xbf/\\x13Q\\x80\\xa3\\x97\\x1c\\xf1X\\xbewR\\xe74c}\\xb6\\x13{\\x0ex\\xb0\\xb7\\xc4\\x83\\xccl\\xa7\\xf2\\x96\\xd1\\xf3\\xa4e\\xb4i\\xcfa3lY\\xf4Z\\x86\\xfe#\\xdd\\xc8\\xb7u\\xa7\\xcbr\\x0f\\xe1\\xd0\\x8a5mV\\xf8kLz\\xbf\\xdeDTg\\xe4\\x15v\\x189\\xc1\\x161\\xf6\\xa8\\x896\\x1c9\\xden\\x86]c\\xf5N\\xe3\\x9c\\xf7\\'\"\\x9bQ\\xe6\\xf6]?\\xbeZ\\xd1\\x8a\\xa1\\xef\\x84\\xc6\\x15<\\x8b\\xbfiUdL\\xb0%\\xa3\\xdd-\\xde\\x8963\\xbb\\t\\xbf\\xfc9\\xcf\\x85O\\xbft(1)\\xde\\xe4\\t\\xefJH\\x9cb\\xc7\\xbc\\x85-\\xd8\\xbc\\xcd\\x85\\x7fn\\xf9\\xadl.\\x99\\xbd3\\xbb\\xb0C]\\x14\\xf3LM\\xa8s[\\xf4\\xe3\\xe9\\xc6\\xb8\\x88\\x10W\\x16}uW\\xef\\xf20l!bs\\x8b1G\\x85\\xc0u\\x8b\\x9eV\\xf4\\xd5Q|\\x07\\xf7\\x11\\xb9Z}\\x0b\\x9f\\x16uS\\xf7\\x7f\\x04\\xbb\\xba\\x96#\\xfaI\\xc1\\x1b\\xb6\\x9d\\xcb\\xbb\\x03\\x8c\\xc1\\xcf\\xd8\\xa8v\\xc6\\x9es0\\xd6\\xf7:c=\\xcb\\x19\\xeb.h9c\\xdd\\x04E\\xba_MN\\xd3#t\\n\\xdd\\x02C`\\tL\\x81\\xfdo\\x00\\x00\\x00\\xff\\xff\\xc6\\xf9Yo6\\x15\\x00\\x00'\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x8\n", + " type = PingFrm\n", + " flags = set([])\n", + " reserved = 0L\n", + " stream_id = 0L\n", + "###[ HTTP/2 Ping Frame ]### \n", + " opaque = 0\n", + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x167\n", + " type = HdrsFrm\n", + " flags = set(['End Headers (EH)'])\n", + " reserved = 0L\n", + " stream_id = 1L\n", + "###[ HTTP/2 Headers Frame ]### \n", + " \\hdrs \\\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 8\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 33\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 22\n", + " | | data = 'HPackZString(Tue, 13 Dec 2016 17:34:51 GMT)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 36\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Literal\n", + " | | len = 2\n", + " | | data = 'HPackLiteralString(-1)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 24\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 13\n", + " | | data = 'HPackZString(private, max-age=0)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 31\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 22\n", + " | | data = 'HPackZString(text/html; charset=ISO-8859-1)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 0\n", + " | \\hdr_name \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Literal\n", + " | | len = 3\n", + " | | data = 'HPackLiteralString(p3p)'\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 81\n", + " | | data = 'HPackZString(CP=\"This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\")'\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 78\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 54\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Literal\n", + " | | len = 3\n", + " | | data = 'HPackLiteralString(gws)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 28\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 3\n", + " | | data = 'HPackZString(4420)'\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 72\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 0\n", + " | \\hdr_name \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 11\n", + " | | data = 'HPackZString(x-frame-options)'\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 9\n", + " | | data = 'HPackZString(SAMEORIGIN)'\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 55\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 165\n", + " | | data = 'HPackZString(NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly)'\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 71\n", + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x122b\n", + " type = DataFrm\n", + " flags = set(['End Stream (ES)', 'Padded (P)'])\n", + " reserved = 0L\n", + " stream_id = 1L\n", + "###[ HTTP/2 Padded Data Frame ]### \n", + " padlen = 230\n", + " data = '\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xc5:\\xebz\\xdb\\xb6\\x92\\xff\\xfb\\x144\\xfcU\\x96\\xd6\\xb4DR7R4\\x9d\\x93:n\\xe2n\\xda\\xa4\\'\\xc9I\\xcf\\xa6\\xa9>\\x90\\x84(\\xc6\\xbc\\x99\\x00e;\\xb2\\xdem\\x1fg\\xf3\\x16;\\x03^D\\xc9N\\xd2o\\xff\\xec\\x97DC\\x003\\x03\\xcc\\x053\\x03 \\xa7\\x07~\\xea\\x89\\xbb\\x8c)K\\x11Gg\\xa7\\xf8\\xab\\x84\\x82\\xc5\\xdcK3\\xe6\\x10\"\\x1b\\x88\\xe0\\x90\\xa5\\x10\\xd9l0\\xe0\\xde\\x92\\xc5\\xb4\\x9f\\xe6\\xc1\\xe0=s_\\xd3\\x80\\x11%\\xa2I\\xe0\\x90EN\\x80\\x03\\xa3\\xfe\\xd9i\\xcc\\x04U\\xbc4\\x11,\\x11\\x0e\\x11\\xecV\\x0c\\x90\\xb5\\xadxK\\x9as&\\x9cwo\\x7f>1\\x89\\x82\\x8bA\\x18\\xc3L|\\xe0\\xe64\\xf1\\xc3$\\x18\\x04i\\x1aD,\\x18\\xe8\\xb7\\xf5\\xe7\\x9c\\x0b\\x18\\xa3\\xb9?\\xf7\\xd2(\\xcd\\xe7\\xbaa\\xfaY?K\\x82R\\x88,O3\\x87H6\\xc0]\\x84\"bg\\xcf%\\xe5\\xe9\\xa0l\\x9dr/\\x0f3q\\xd6]\\x14\\x89\\'\\xc24\\xe9\\xf6\\xd67a\\xe2\\xa77\\xfdr\\ng}uq9;z\\xf5\\xf9\\xe7\\xdf\\xdf\\xff2?y9z\\xf5_\\xf4ul%\"xu\\xa4^]\\xfc\\xf1\\x1a\\x06\\xa7cmj\\xe8\\xaa>\\x1c\\xeb\\x966T\\x87SM3FCu\\xa4\\x19\\x96\\xa9\\x8f\\x01\\x0e\\x8d\\xc9t\\x8a\\xd0\\xd4t\\x03\\xe0H7-\\x0b\\xe1pd\\xc9\\xf6\\xd8\\x1c\\xe9\\x08\\xcd\\xe1\\x08\\xf1\\xc6\\xe3\\xe9\\x08\\xe9&\\xc6d2A8\\x9e\\x9a\\xd8?\\x99\\x9a\\x13\\r\\xa19\\x1eKh\\x99C\\xb3\\x84\\x92~jL4C\\xc2\\xe9\\x14\\xe9\\xa7C\\xe0%\\xe1\\xd40%\\xb4\\xc68/p\\xb7\\xa6\\x12Zc9>\\xd1\\xac\\x12ZC\\xbd\\x84r}Ss$\\xf9\\x03\\x1cO$\\x9cNF\\x08-]\\x93mk4\\x92\\xf3Y\\xb0R\\tM\\xab\\x1c\\xb7\\xc6\\x08A\\xdc\\t\\xcec\\xea\\xda\\xb0\\x86\\xc8\\xd744\\xc9\\xcf4\\xf4\\xc9XBC\\x97\\xe3\\xc6D\\x97\\xe3\\xa0\\x069>\\x02\\rJ8\\x94\\xfa4a\\xbd\\xb2\\x7f\\xac\\x8d%\\xfexbH8\\xd1t]B\\xc3\\xd2$4\\'\\x12\\x7f:\\xd5%\\xfd\\xd4\\x92\\xfa7\\x91a\\t\\x87\\x93\\x12\\x8e\\xe4\\xb89)\\xf9[\\x9aV\\xc2R>\\xd3\\x1a\\x96\\xeb\\xb6\\x86\\xa3\\xb2=2\\xe5<\\xd6X\\xea\\xc5\\xb4&V\\xd9?\\x1d\\xe9\\x15D\\xfe\\xc0\\xc6\\x9cH8\\x1c\\x1b\\x12\\x8e4\\xbd\\x84\\xd2\\xae\\x16hf\\xa2\\x9aCM\\xd3\\xac\\x12\\x1aSC\\xc2\\xd1\\xd4TA\\xba\\xc9\\x04\\xfc\\x06\\xe0t\\x08\\xf3!\\x1c\\x81^\\x10\\x9ac\\xb3\\x84\\x96l\\x9b\\xda\\xb8\\x82\\x13\\x89o\\x8eA~\\x84\\x16\\xe8\\x07\\xa0\\x05\\x9c$D\\x7f\\xd45\\x03&4\\x87\\xe8\\xa8\\xf0a\\x18G*-\\xc4\\xb2\\xe0,\\x9fi\\xea\\x15\\xf7\\xf8\\xec\\xc8\\xb3\\x04(\\x91B\\xe8j8\\xc4\\xd1Cr\\x05\\x97\\xdb`\\xdc\\xb40\\xa8\\xea\\xf6\\xd6\"\\xbf[7\\xd4\\xdd\\x8b<\\x071iO=\\xd0ap\\x03\\xf3x\\xcb\\xae\\xd7[o\\x1a\\x06\"\\x8c\\xd9\\xc3I\\xba\\t\\xbbQ\\x9eQ\\xc1z(\\xfd[\\xc0\\xe9\\xf6\\x1a\\x92(\\rv&U=\\xd5W\\x83\\xde\\x9a:[\\x84wy\\xd4\\x1a\\xb2\\xc3E\\x97\\x90\\x03\\x07\\xd4\\x0e\\xea\\x06\\xd6\\x97\\x18\\x84mT?k\\x88F\\x1e\\xbde\\xc0\\xdc\\x8fE\\xe6\\xd1WT~$K\\x8a\\x90q(\\xbdi\\xc4\\x99\\xed\\x83l1\\x1c=\\xfa8CU\\x1a+\\x8e\\xf2\\xf0\\xe2\"Q:\\x1d\\xa5\\xfe\\xeeb\\xb9\\xa1t\\x1b\\xe2r\\xb2\\xde\\xba\\xa9\\xc9\\xbb=Y\\xeb\\xc2?{\\xf3\\x03\\xa2\\x1e\\xe0\\x9cP_\\xd7\\x14\\x8bNg\\xfb\\xdd\\xbf\\xeeK\\x1d\\x03\\xdb\\xa63p\\xaf\\xdb8\\xd8l\\xa1m~\\x80?xn\\xdc*\\xde\\x0fWJ\\x085g\\x9c\\x04.9S\\xea6&\\xd4\\xb3\\xd3$u\\xe1\\xd7U\\xbc\\x88r\\x0e}\\xfa\\xd9?\\x99\\x07Q\\r\\xfe\\x9d\\x0e\\\\\\xc0\\xa6\\xdb\\xa1\\xcaz\\xe5\\xf1j0\\xb8\\xb9iJ\\xf9E\\x0ej\\r\\x96\\xd9\\x93%TsyGP\\x17j3r&%\\xe6\\xa7\\x03\\xfaM>1$\\x8d\\x16#l\\xb6\\xf9D\\xe4\\xecW\\xe8\\xfa\\x1e\\x17\\xdc~5\\x17/\\x8d\\x07m\\x16&9{\\r\\xc3\\xdfc\\x81\\x02\\xdd\\x817\\x17n\\xc5\"\\x88\\x9c\\x9f\\xffY\\xb2\\xd0\\xc9\\xd9\\xbf\\xd3\\xe2-\\x0c}\\x8f\\x0b\\x98\\xba-Nr\\xc3w\\xf5\\x02\\xce\\xfb\\xd4\\x13\\x05l3\\xf1\\xe5o\\xe8&\\x8c\\xdaRa{\\xf0D\\xf2\\x89\\xc9\\xd9sl~\\x8f\\x85\\x9f\\xc3\\xfe\\xdd\\xd1\\x8c$O\\xc9\\xd93\\x1cyH.7py\\xa5\\xb8\\x1f\\xdd\\xc9\\xb7] \\x11\\xd1\\x00`\\x9a!\\x01\\x1f\\xc0N,@\\xf1\\x05HY\\x9c)\\x9d\\x9c^\\x17\\xa9\\x8d\\xf3\\x9d\\x0eJ\\xb7\\x1b\\x80+6\\xfe)\\x0bXE\\x16\\x01\\x0e\\xd6\\x8c\\xb5o\\xf2\\x8c&\\xa5\\xbf&\\xcd\"C\\x8c+\\xd0\\xdf\\x1e]4\\xa3\\x8bGFY\\xd3G[2<\\x10\\x01\\x0e\\x17\"\\xcd\\xefP\\x04p\\x84\\xd2l\\xa4a<:{!\\xc7\\xc3\\xeb\\x82)\\xef\\x99+uw\\x8f\\xda\\xab\\x03[\\x06\\x80\\xe5,\\xf1\\x18\\x7fH\\xfc\\x9a\\xe64\\xfe\\x1f\\x91W[B\\x12\\n\\x08\\xd2L8s\\x88\\xb7\\xe5B\\xe7SmO\\xc9\\xd4\\xf3 \\xee\\n\\xde\\xb6\\xe1\\x1b\\x96\\xafB\\x8f\\xbdL!\\xc2W\\xce\\x95\\xc14`OG\\xe4\\x05\\xeb\\xe0\\xadn\\x98\\x14\\xccy\\xdcR\\xedU\\x9d\\xa7I\\xc2n\\xc1`\\x8f[\\xa6F\\\\V~!\\xd3\\xae\\xf6-\\x8c\\xb2\\x90\\xafQ\\x94\\n\\xd3c\\x98\\xa0 \\xd4\\xc0\\x19,\\x82\\x93\\x93C\\xe00@d`\\x8a\\x82\\xcc\\'\\xdb8\\x15\\x05\\xb4jU\\x9eX\\x97\\x9d\\x86\\xacw\\x15H\\xf9\\xbb\\xe3uu\\xabkM\\x15iL\\'\\x98\\xe2\\x1f\\xcf\\xb6{7\\xdc\\x98\\x05\\xb6\\x97\\xdc\\xd8\\x9a\\xdf,\\xa1@\\x9ao\\xc9\\xab\\xfbncj\\xdcZFy\\xe5\\xddS\\x92\\xf4\\xa4\\xcc\\xceD\\x91\\xb7\\xdb\\x0e)/\\xbb\\x89\"S(\\x08\\x02\\xaa*%\\\\f\\xc8\\x96(\\xd5\\r\\x0bi\\x0e\\xaeQ\\xa7\\x03?\\xdd\\xde\\xae@U~\\x9fN\\xa7\\xed\\xdafR\\x97:\\xed\\xba\\xba9~A\\x92\\xa4\\x98\\xa9\\xe5\\xf1k\\x8a\\x9a\\x90\\x962\\xb0r&\\xb0\\xd6\\x9b\\x9cf\\x0e!g?\\x83\\xf0\\x1e\\xab\\x8c\\xd2\\xfe\\xdd\\x9a\\x1dO\\x08\\n\\x95i\\xce\\xa9\\x8f\\xb9\\xc0\\x82\\xc6\\xb04L\\xaf\\x10?\"\\xa6x,\\x8a*\\xcb8D#\\xb2\\r;\\xcc\\xab\\xda\\x80\\x96+\\xabJ\\x13\\xb0&\\xec\\xf0\\xab\\xedM\\x8c\\xf1\\x8f\\xe4\\xac\\x93\\xb8<\\x83x |9T\\xa1\\x96~\\xd2^\\xf1\\xa9,\\xbc\\xab\\xf9CP/p\\x05\\xcf&\\x97o^\\x9d\\x98\\xe6\\xd8:\\xd1\\xc1\\x00\\xe5\\xfbI\\xe8\\xfb,i(*<\\xdc\\x84%\\xf12\\xfa\\nf9\\xcc\\xd3\"\\xf7\\xd8\\x1eJ\\xcde\\x99\\xeda\\xbb\\xe1\\xcd7\\xb9\\xb9\\xe1\\xf2\\xc1\\xf8v\\xbb\\x10\\x9f\\x93=\\xff\\x1dn\\x0f\\xc3x\\xdeP\\xb4\\x86\\xe1\\xae[`\\xd9\\xd7\\xd4v\\xf5\\xde\\x18\\xd7\\xc7A\\x05\\xdd\\xe4a1\\x07NY\\xe0\\xddg\\x9c\\xe1=\\x9eCR(\\x94\\xea\\xa5@)\\xd7\\x88\\xd9\\xf8rS\\x06(\\xb5W\\xc7\\xf46bI \\xed\\xa7\\x8d\\xccZ\\xa9\\xd7 \\x07\\xf8\\xa7C\\xc6S\\xb2\\xf5\\xa5z\\xcd\\xed\\xe3\\'\\n$#\\xf2V\\x05\\xbb\\x1dXd7Bo\\xfb\\x9a\\xc5=\\\\S\\xa5j\\x91<\\xafu\\xcd\\x0b7\\x0e\\x05i\\xc2};\\x13\\xfc_\\xe7\\xfd\\xe5\\x88\\x86\\x8a\\xcf\\x94\\x88\\xe2\\xebZ\\xe2\\xb5\\'\\xbe\\xc4]\\xedE\\xa1w\\x05\\xce\\xb9\\x90\\xd7S}\\xdc@P\\x93I\\xea\\x9e\\xec\\x815{W\\xccwt[aP]*`\\x91\\xe6\\xda\\njR?M\\xfd\\x08\"\\xd3\\xd1w\\xa4\\xa8\\xf7J\\xb5F8\\xda\\xe1qi/\\xe04;gg\\xb75yo@\\xfd\\x15\\xca\\xe0\\xcf\\xab\\x0b\\xac2}\\xd08\\xb3\\xeb\\xf7\\t\\x07,\\xb5\\xd55E\\xf4/\\xb2P\\xd82\\xc1\\xf7\\xc8\\x02\\xa2)$\\xae4\\xe2_\\xe1\\xf1\\xaa\\x10a\\xc4\\x15\\xf0\\x81\\xa0\\x80\\xc4\\x89i\\x93\\x97Y\\x06\\xe5\\x18\\x08\\x8c82\\x98\\xd4\\xba\\xc7X\\x19\\xb8\\xabZ\\xbd\\xf2\\xf3\\xd1\\xdd\\xa8\\xa3jP\\xcd\\xdb\\xb4\\x817\\x1e\\x1c\\xf3\\x1ei\\'\\xa7\\xca\\r\\xb7a\\xd4\\x1c\\xfeh\\xc7p|\\xac7\\x1d^8\\x91v\\xf4\\xabk\\x06\\xa0I1\\x12}\\x85\\x0f$\\x1b\\xb1;V\\xedI\\xdd\\xc2\\xe3\\x15l\\xb6\\xf6u[\\x1d\\xd5$_\\xc8{-M\\xd6U\\x13\\xf5\\xb1bz\\x83whX=)Y\\xe1\\x82W\\x85\\x82\\x86U\\xc1\\xb0%\\xe1e\\xf6\\xdf\\xc5\\xf7\\x8f`\\x0eHHy\\xc8wm\\xb5\\xad\\x90\\x8b\\x9d\\x1aB\\xd7&\\x96\\xa6\\x8f\\xcc\\x89i\\xe2\\x83\\x98\\xa1\\x19\\xa6aT\\xa7-99_\\xa2\\xf8\\xc7\\xf5\\x13.}l\\xd1.\\x94I}|x&g\\xff\\xad\\xe0\\x03p\\xcaq\\xa7{\\xe25x\\x17\\x11C0\\xf3\\xe4\\xe1Y\\xa5\\x8e\\xdf\\x87\\x08\\t=\\xf5\\xd4u\\xbb\\x9a\\x96v:n\\xa7\\xd3\\xa5\\x07\\xceV\\xae\\xbe\\x94\\xe6\\xfe\\xde\\xdd\\xe9,e\\xdd\\xbe\\x88@Y\\xd5%D\\xc5\\xbf\\x83\\x92%\\xbe\\xbb<\\xe9T\\x8f.\\x90\\xa3\\xabW\\x17\\xc8\\xc6\\xf2\\xd5e\\xf7\\xed\\xe7\\xea\\xe2\\xb2\\xb7\\xf7\\xb2\\xf0\\x95\\xc3\\xf3\\xed\\'\\xee\\x93\\xbd\\x13\\x0bv\\x86\\x8f_7\\xd4\\x9f\\x8a\\xd7u\\x1b\\xa3\\xf1\\xf2\\xa5\\x0fv\\xccC\\x035\\xe7y/\\x87\\x8a\\x92U\\x1a\\xec\\x92\\x927\\xe9\\xd9T\\xde\\x1b\\xb8\\xad{\\x00V\\xeb\\xf9\\xa7\\xbbK\\xbf[\\xae\\xb0\\xd7\\xa7Y\\xc6\\x12\\xff|\\x19F~\\x97\\xf66\\xaa\\xd6\\xdbT\\xc2\\xfa\\xd1\\xa7l\\xfb\\xd6\\xe2\\xaa\\xb4yk\\x01\\xd2\\x02X{H`o\\xb1\\x1doO7_wD\\xe0\\x90\\xc7\\xf8\"$\\xd5\\x07nT\\xbf\"\\xf5\\xea\\x8f\\xfe\"\\xcc\\xb9@\\x14t\\xb2-Y\\xc3i^{\\xdb\\xfc\\xfe~\\xdd\\xbc1\\xce\\xfb\\xf3gE\\x9c]\\xdczL\\x9e*\\xb7\\x02\\xb0\\xdeZ,\\xf3\\xf4Fa\\x9b\\xd6\\x84\\xf8\\x86\\x96\\xf3\\xc6?\\xca\\xa6|i\\xea\\x8bF\\xde\\x9d^\\\\D\\xc4\\x9d\\xfaA\\xb6\\xdbz\\x91\\xb5\\xdb\\xaa\\xeb\\x1e\\r\\x00s0\\x1f\\xc0\\xcf\\xd5\\x9f\\xb7C\\x1fZ\\xfde\\xd6g\\xc9\\xfc\\xdd\\x9b\\xfe\\xfb\\xdf\\x86\\x7fdo>\\x9f\\xfc\\xf4\\xdc\\xec\\xbf\\x1a\\xc48\\xce\\xdd\\xf9\\x92\\xa9\\xfe \\x17\\xd8\\xfa4\\xf0\\x11\\xe8\\x03\\xd9\\xfa\\xecA\\x0c\\xc89~>=\\x7fki\\xe9\\xf3\\x95\\xff\\x82N\\xdf\\xbe4\\xde\\xcf/\\x7f\\xffC\\xe7\\xe3\\x9f^\\xdf\\xfe\\xfb\\xf2\\x05{\\xb7Z\\xfe~\\xa4\\xfe\\xffM\\xdd\\xb3\\xb7\\xa6\\x82\\xea\\xa5VH\\x16{\\xce\\x9a\\xc8i\\xc8lM\\xa0\\x10H\\xc8\\x0cO\\x9e*\\xf1\\xda\\xdfrK\\x92\\x19\\x96\\xb8y\\x94\\xa6\\xf1\\t\\x94\\xd1*\\xf1\\x975\\x82\\xbf\\xbc\\x16\\xcd7\\x07D\\x18]Du\\xcf2\\xe5H\\xdcd\\x0b\\x18\\x0c\\xb9\\x0b\\xc4\\x86\\xa9\\x92O4&\\x10\\xf4\\xc8\\'\\x9e&YM\\x11\\xc5\\xf5W\\xcc\\x03\\x8eK\\xf3B\\x17\\xf8\\x91\\x8b\\xc5\\x82z,\\xc7\\x12.\\xaf+\\x1b\\\\\\xc9]\\x8c\\x83\\x9c\\xd3;\\xf6\\x19*\\x1d\\xe6\\xc19F\\x08\\xa6\\xa4\\xb9X\\xa6\\x01\\xd4QP\\x00\\xcd\\x001\\xf2\\xae\\xee\\x00\\xf3\\x97?\\x0bM3&\\x87C\\xcb\\xde/\\tqr9S\\xa2p\\xbaJ\\xc3\\\\\\xc1\\x84\\x0b\\xdd)\\xbfB)\\xaab\\x08h8\\ry\\x88\\x04\\x19li\\x189\\x973\\xf2\"\\x80\\xc3\\xa9\\x0c\\x1aTqAo\\xca\\x17\\xf1\\x05z3\\xc85\\xf1\\x17\\x86\\x84\\xab\\x14\\x12\\xbc\\x82K\\x18\\x9eW\\t\\xe6OR\\xdfY\\xfcI\\xe4\\xc0\\xc5r\\xe7\\x8a\\xa2D\\x1e\\xd0r\\xac_N\\x8a\\xcb|S2f\\xa8T\\xee\\x86\\xb8\\xc2m\\xc9\\x97\\xe1]\\xa8\\xfc\\x7f\\\\0\\x88\\xa7\\xbf\\xd9\\xc3\\xda{\\xa3\\x92\\x04MV\\xaa;]\\xe5\\xa0m\\xe8\\xcb\\xaeK+\\xc2\\xe2\\xe0T_\\x8d\\xe6\\x0b@\\xfc\\xf0\\x11\\x98y\\xd0\\xa7k\\xf8\\x01\\x8e3\\x06(\\xd2+ XY\\xcb\\xc4\\xba\\xf8\\xed\\xf5\\xbf\\x8c\\xf3k\\xfd_\\xcf\\x9e\\x16\\x93\\xc2\\x1fO\\xde~\\xbe\\xba\\xfb\\x1d\\'\\xf2\\x91\\xf7\\xf6\\xf5\\xba\\x8c%\\xe5\\xfbp+$m\\xa3@\\xcc\\xfcf\\xb3\\xc3w\\xf7(LB\\xb1\\xf5el\\x9579w\\xdd\\xa6S\\xe2U\\xba\\x04\\xd4\\xcd\\xa6\\x1d\\xc7>5\\x11\\xe5\\x13\\xec\\xbaV\\xe36|,\\xbc\\xb7\\x86!\\x00\\xdb\\xf2U\\xb0\\xbe\\x0f/\\x8b\\x12L\\x8cx\\x89\\x8d\\xff\\'\\xf0\\x7f\\x01\\xa9\\x9a\\xd7%#(\\x00\\x00'\n", + " padding = '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "\n", + "###[ HTTP/2 Frame Sequence ]### \n", + " \\frames \\\n", + " |###[ HTTP/2 Frame ]### \n", + " | len = 0xe1\n", + " | type = HdrsFrm\n", + " | flags = set(['End Headers (EH)'])\n", + " | reserved = 0L\n", + " | stream_id = 3L\n", + " |###[ HTTP/2 Headers Frame ]### \n", + " | \\hdrs \\\n", + " | |###[ HPack Dynamic Size Update ]### \n", + " | | magic = 1\n", + " | | max_size = 12288\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 8\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 59\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 11\n", + " | | | data = 'HPackZString(Accept-Encoding)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 26\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 3\n", + " | | | data = 'HPackZString(gzip)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 31\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 9\n", + " | | | data = 'HPackZString(image/x-icon)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 33\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 22\n", + " | | | data = 'HPackZString(Thu, 08 Dec 2016 06:23:59 GMT)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 36\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 22\n", + " | | | data = 'HPackZString(Fri, 16 Dec 2016 06:23:59 GMT)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 44\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 22\n", + " | | | data = 'HPackZString(Thu, 08 Dec 2016 01:00:57 GMT)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 0\n", + " | | \\hdr_name \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 16\n", + " | | | data = 'HPackZString(x-content-type-options)'\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 5\n", + " | | | data = 'HPackZString(nosniff)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 54\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 3\n", + " | | | data = 'HPackZString(sffe)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 28\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 3\n", + " | | | data = 'HPackZString(1494)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 0\n", + " | | \\hdr_name \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 12\n", + " | | | data = 'HPackZString(x-xss-protection)'\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 10\n", + " | | | data = 'HPackZString(1; mode=block)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 24\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 16\n", + " | | | data = 'HPackZString(public, max-age=691200)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 21\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 5\n", + " | | | data = 'HPackZString(472252)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 0\n", + " | | \\hdr_name \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 5\n", + " | | | data = 'HPackZString(alt-svc)'\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 28\n", + " | | | data = 'HPackZString(quic=\":443\"; ma=2592000; v=\"35,34\")'\n", + " |###[ HTTP/2 Frame ]### \n", + " | len = 0x5d6\n", + " | type = DataFrm\n", + " | flags = set(['End Stream (ES)'])\n", + " | reserved = 0L\n", + " | stream_id = 3L\n", + " |###[ HTTP/2 Data Frame ]### \n", + " | data = '\\x1f\\x8b\\x08\\x00\\x00\\tn\\x88\\x02\\xff\\xbcX{PTU\\x18?\\xa6\\x8d\\x89\\xa5\\xf4G\\xff5\\x13\\xf60\\xffh\\xcc1\\x11\\xc7L\\xb3\\x07>\\xffp\\x94\\x10L\\x1b\\xd3L+kF\\'\\xb5\\x19\\xc7Rg\\xc2\\xb4@KtD _\\x91\\x80\\x8e\\x82\\x8a\\xa8\\x83\\nhI\\xa9\\xa8\\x08B\\x98\\xaf|\\xdf]`\\xc1}\\xb0{\\xf7;\\xbf\\xe6\\\\\\xeee\\xee\\xde\\xbd\\x97\\xdd\\xb5\\xb53\\xf3\\rg\\xcf\\xfd\\xbe\\xefw\\xcew\\xbe\\xd7\\x81\\xb1n\\xec1\\x16\\x1b+\\xfe\\xc6\\xb1y=\\x18\\xeb\\xcf\\x18\\x8b\\x8b\\xeb\\xf8\\x9d\\x1f\\xcbXF\\x0f\\xc6\\x060\\xc6b\\xc5:\\xebXWF\\x0f\\x16r\\x00\\x18DD\\x1b\\x89\\xa8\\x81\\x88\\xbc*\\xd5\\x13Q&\\xe7|\\xa0\\x95\\x1c\\xe7\\xbc\\x17\\x11e!\\xc4 \\xa2\\r\\x00\\x9e0\\x91\\xad\\x10\\xdf}\\xe4\\xc5\\x81\\xbfvb\\xf1\\x91\\x19H\\xd95\\x1c)\\x85\\xc3\\xb1\\xf0P*v\\xd7\\xe5\\xa2]vk:\\x8e\\xebuh\\xb8v\\xd7},(M\\xc1\\x94\\xfc!\\xa6\\x94\\xb17\\x05\\xdcq\\xafs\\x1f\\xday\\x15\\\\\\xbf\\x17\\x0bJ\\xa7*|\\x02s\\xfb\\xf9\\x1fQ{\\xff\\x0c.I\\xd5(\\xac\\xcdF\\xd6\\x8e\\xf1p\\xa4\\x8d\\x86;w.\\xc0\\xb9\\xa2C\\xd8\\x83\\x886\\x89\\xf9\\xeaKg1\\xa1p,\\x92\\x0b\\x87\\xa1N\\xaa\\x0e>\\xf7\\x9d\\x068W%\\xc2\\xf9\\xed[\\xf07\\x9c\\xd0\\xf6\\x90ID\\x8db\\xfe\\xca\\xc9V<]\\xd6\\x84\\xf4\\x0bE\\x96\\xb6k\\xdf\\xb3R\\x91o/I\\xd7\\xe4\\xc5\\xbd\\xf8\\xc4<\\xe6\\xa8\\x8c\\xc7\\xcbd\\x94\\xd8x\\x80\\x8c\\xe07\\x92\\xe7\\xd7E\\x9a\\xbcW\\x93\\xef}\\xacC\\xfe\\xa0=\\x0c\\xf9\\xc2\\xa5z\\xf9\\xcbb\\x1e\\xff\\x87\\x1f1\\xfb\\xbc\\xf8\\xec\\x177\\xc2\\x1d\\xaa\\x8f)w\\xf7\\xd39\\x19\\t\\x93\\xed\\x18\\x96(\\xe1|\\xad\\xcf\\x84\\xd7T~#\\xe7|\\xb0\\x98{\\xbd\\x1c\\xc9\\xb3\\x9a\\x10\\xff\\xb6\\x84\\xd7\\xc7\\xd9\\xb0!\\xc7\\x89s5>\\\\\\xa8\\xf5ac\\xae\\x13\\x93?h\\xc2\\x8d\\x9b~\\xa3\\x8aA\\xaa\\xff\\xe4\\x8a\\x1f\\xf7$B\\xca\\xecfE\\x87\\x19\\xcd_\\xec\\xd0co\\xd2\\xc5L\\x0c\\x11\\x9d\\xd0\\xf6\\x91\\xb7\\xdb\\x8d\\x19\\x9f4c\\xc4x\\x1b\\x86\\x8f\\x910\\xed\\xe3fl/p)\\xdfT\\xd9\\n\\xe1\\xf3\\x86\\xb8\\x8b\\xd1\\xf6\\x11\\xc2fYFYC,\\r\\x16<\\xe2^\\xc4\\xdd\\xaa\\xd4\\xa8\\xfa\\xe9 #\\xbf\\xa3/c\\xe5\\xdd\\x19[\\xde\\xad\\x83B\\r\\x8dO\\xc8\\x08\\xd9\\x01j\\x8eyS\\x9fgb\\xd9C\\x0f\\xce\\xf9S\\x9c\\xf3\\xa9\\xea\\x19\\xaa\\x88H\\xd2\\xe5!I]\\x136H\\x06\\xf0$\\x8b\\xd2\\xe0\\x9c\\xbfHD9D\\xe4\\x8a\\xc0\\x7f]D\\xb4\\x99s\\xfe\\xc2\\x7f\\xc0\\x15\\xb9o\\r\\x11\\xc9x\\xc8\\xa1\\xde\\xf1*c^\\r\\xf3\\xcc5\\x88\\xd2 \\xa2\\xf3\\x00\\x9e\\x0f\\x07[\\xad3\\x92\\x99\\x1e\\xc9y\\x07{\\xeb\\xb7ae\\xf9|\\xcc)\\x1e\\xa7\\xe4\\xd4\\xe4\\x82\\x04\\xcc.J\\xc4\\xb2\\xa3s\\x90W\\xb3\\x01W\\x9b\\x1b\\xac\\xf6p\\x8fs\\xfej\\x18\\xe7\\x0e\\xc2\\xb6\\xb9\\xeeb\\xed\\xefK\\x91T0\\xd4\\xb2\\x86\\xe8\\xe9\\xebcsq\\xa5\\xb9\\xdet\\x0fVvP\\xe3\\xfc\\xa2Q\\xe6\\xe4\\x8d\\xc3\\x98\\xbe{dX\\xb8z\\x12v)\\xae\\xc9\\xb6\\xba\\x8b \\x7f \\xa2\\xef\\x8d\\xbc\\xc5\\r;\"\\xc6\\xd5hf^<\\xfe\\xcc\\x18\\to\\xc5\\x16\\xb3=\\xac2\\xd8\\xfd%\\xa3\\x9fW^/\\xb5\\xd4\\xfd\\xc5\\xc1$l\\xa9NGic!\\x0e]\\xde\\xa5\\xdc\\xfb\\xd2\\xb2\\x8f\\x90\\x94\\x1f\\xaf|_\\xb25\\x017W\\x8f\\xee\\xacKZ]\\xd5\\xc7\\x85>6\\x8d\\xf9U\\xf8\\xd9\\xfb&6\\x9f\\xbbo\"\\xce\\xdc\\xae\\xb4\\xf4\\xf3\\xeb-\\x8d\\xc8/\\x9e\\x8dVC]t\\xadK\\x02\\xf7\\xba\\x8d{\\xd8\\xac\\x9e\\xbd\\x0f\\x11\\x05|\\\\yjm\\x10\\xf6\\xa2\\xc3\\xd3\\xd1\\xe6u\\x84\\x0e6\\xbf\\x0f\\x9e\\x9dK\\x82j\\xb3\\xaf\\xaa (G\\x89<\\xc99O\\xd5\\xaf_ss\\xf4*s\\xe3\\xb5\\xa2\\xb4N\\xecYE\\x89h\\xf1\\xd8\\xc3\\x8ew\\xeey\\xa0\\x9cY\\x8f\\xef\\xce\\x9a\\x19\\xcc\\xc7y\\xb2\\xc8\\xad\\xfa\\xb5\\xef\\xae\\x91\\xd2o\\x08\\xeaW\\xb2\\x1f\\x93\\xf2G\\xa0\\xecJQ\\xc49\\xc7w*?\\xc8\\x06\\xdcq7\\xa8f\\x12\\xd1i\\xfd\\xda\\x98j\\x7f\\'\\xbe\\xa0\\x81\\x95\\xb7 \\x93/\\xf2\\x9c\\']\\r\\xc2\\x97\\xeb+\\x8c\\xf8\\xa2f\\x05\\x18\\xf6\\xd9J9\\x00?\\xa5\\xc6\\xdf%\\x8eY\\x1ffE\\xbe\\xaaB#\\xbe\\xa4\\xf5y\\xda\\xe8y4\\x10\\x7f\\xd9\\xdf\\x145|c.\\xd0\\xf7\\x99\\xff\\x07\\xbe\\xef\\xb7<3\\xfc\\xa6\\xae\\xec\\x9fz1z\\xf6\\x97\\xeb\\x8e\\x9b\\xd9?\\xc0\\xff\\x12\\xcf\\x06\\xfa_\\xbf=^\\xc82\\x1e\\xc9P\\xfd/ \\xfe\\xd2t\\xf1\\xf7\\xccz\\x17\\x86\\x8c\\xb3a\\xff!\\xcf\\xa3\\xc2\\x17\\xfd\\xda4\\xfd\\xda\\x157G\\xcf\\xc32\\xe2\\x96\\xb4v\\xf6\\xd7c\\xdf\\xb3\\xa3\\xd9AQ\\xc7\\x17\\xfd$\\x80\\xbeD\\x14p\\xc0\\xcf\\xd79\\x83z\\xfc\\x0f\\xe7\\xb7\\xa0\\xad\\x8d\\x87\\xd4\\xe9t\\xf1\\xb0{D\\xd1\\xd3\\xaa\\xf5\\' 0n\\xdd\\xf1\\xe3\\x8d\\xf1\\xb6\\xa0=L\\x9a\\xde\\x84\\xaa3^K\\x9d\\xa7N{11\\xc5\\x8e\\x1f2\\x1f\\x84\\x83\\x9f\\xa3\\xab\\xbf/\\x13Q\\x80\\xa3\\x97\\x1c\\xf1X\\xbewR\\xe74c}\\xb6\\x13{\\x0ex\\xb0\\xb7\\xc4\\x83\\xccl\\xa7\\xf2\\x96\\xd1\\xf3\\xa4e\\xb4i\\xcfa3lY\\xf4Z\\x86\\xfe#\\xdd\\xc8\\xb7u\\xa7\\xcbr\\x0f\\xe1\\xd0\\x8a5mV\\xf8kLz\\xbf\\xdeDTg\\xe4\\x15v\\x189\\xc1\\x161\\xf6\\xa8\\x896\\x1c9\\xden\\x86]c\\xf5N\\xe3\\x9c\\xf7\\'\"\\x9bQ\\xe6\\xf6]?\\xbeZ\\xd1\\x8a\\xa1\\xef\\x84\\xc6\\x15<\\x8b\\xbfiUdL\\xb0%\\xa3\\xdd-\\xde\\x8963\\xbb\\t\\xbf\\xfc9\\xcf\\x85O\\xbft(1)\\xde\\xe4\\t\\xefJH\\x9cb\\xc7\\xbc\\x85-\\xd8\\xbc\\xcd\\x85\\x7fn\\xf9\\xadl.\\x99\\xbd3\\xbb\\xb0C]\\x14\\xf3LM\\xa8s[\\xf4\\xe3\\xe9\\xc6\\xb8\\x88\\x10W\\x16}uW\\xef\\xf20l!bs\\x8b1G\\x85\\xc0u\\x8b\\x9eV\\xf4\\xd5Q|\\x07\\xf7\\x11\\xb9Z}\\x0b\\x9f\\x16uS\\xf7\\x7f\\x04\\xbb\\xba\\x96#\\xfaI\\xc1\\x1b\\xb6\\x9d\\xcb\\xbb\\x03\\x8c\\xc1\\xcf\\xd8\\xa8v\\xc6\\x9es0\\xd6\\xf7:c=\\xcb\\x19\\xeb.h9c\\xdd\\x04E\\xba_MN\\xd3#t\\n\\xdd\\x02C`\\tL\\x81\\xfdo\\x00\\x00\\x00\\xff\\xff\\xc6\\xf9Yo6\\x15\\x00\\x00'\n", + " |###[ HTTP/2 Frame ]### \n", + " | len = 0x167\n", + " | type = HdrsFrm\n", + " | flags = set(['End Headers (EH)'])\n", + " | reserved = 0L\n", + " | stream_id = 1L\n", + " |###[ HTTP/2 Headers Frame ]### \n", + " | \\hdrs \\\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 8\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 33\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 22\n", + " | | | data = 'HPackZString(Tue, 13 Dec 2016 17:34:51 GMT)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 36\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Literal\n", + " | | | len = 2\n", + " | | | data = 'HPackLiteralString(-1)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 24\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 13\n", + " | | | data = 'HPackZString(private, max-age=0)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 31\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 22\n", + " | | | data = 'HPackZString(text/html; charset=ISO-8859-1)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 0\n", + " | | \\hdr_name \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Literal\n", + " | | | len = 3\n", + " | | | data = 'HPackLiteralString(p3p)'\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 81\n", + " | | | data = 'HPackZString(CP=\"This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\")'\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 78\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 54\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Literal\n", + " | | | len = 3\n", + " | | | data = 'HPackLiteralString(gws)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 28\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 3\n", + " | | | data = 'HPackZString(4420)'\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 72\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 0\n", + " | | \\hdr_name \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 11\n", + " | | | data = 'HPackZString(x-frame-options)'\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 9\n", + " | | | data = 'HPackZString(SAMEORIGIN)'\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 55\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 165\n", + " | | | data = 'HPackZString(NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly)'\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 71\n", + " |###[ HTTP/2 Frame ]### \n", + " | len = 0x122b\n", + " | type = DataFrm\n", + " | flags = set(['End Stream (ES)', 'Padded (P)'])\n", + " | reserved = 0L\n", + " | stream_id = 1L\n", + " |###[ HTTP/2 Padded Data Frame ]### \n", + " | padlen = 230\n", + " | data = '\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xc5:\\xebz\\xdb\\xb6\\x92\\xff\\xfb\\x144\\xfcU\\x96\\xd6\\xb4DR7R4\\x9d\\x93:n\\xe2n\\xda\\xa4\\'\\xc9I\\xcf\\xa6\\xa9>\\x90\\x84(\\xc6\\xbc\\x99\\x00e;\\xb2\\xdem\\x1fg\\xf3\\x16;\\x03^D\\xc9N\\xd2o\\xff\\xec\\x97DC\\x003\\x03\\xcc\\x053\\x03 \\xa7\\x07~\\xea\\x89\\xbb\\x8c)K\\x11Gg\\xa7\\xf8\\xab\\x84\\x82\\xc5\\xdcK3\\xe6\\x10\"\\x1b\\x88\\xe0\\x90\\xa5\\x10\\xd9l0\\xe0\\xde\\x92\\xc5\\xb4\\x9f\\xe6\\xc1\\xe0=s_\\xd3\\x80\\x11%\\xa2I\\xe0\\x90EN\\x80\\x03\\xa3\\xfe\\xd9i\\xcc\\x04U\\xbc4\\x11,\\x11\\x0e\\x11\\xecV\\x0c\\x90\\xb5\\xadxK\\x9as&\\x9cwo\\x7f>1\\x89\\x82\\x8bA\\x18\\xc3L|\\xe0\\xe64\\xf1\\xc3$\\x18\\x04i\\x1aD,\\x18\\xe8\\xb7\\xf5\\xe7\\x9c\\x0b\\x18\\xa3\\xb9?\\xf7\\xd2(\\xcd\\xe7\\xbaa\\xfaY?K\\x82R\\x88,O3\\x87H6\\xc0]\\x84\"bg\\xcf%\\xe5\\xe9\\xa0l\\x9dr/\\x0f3q\\xd6]\\x14\\x89\\'\\xc24\\xe9\\xf6\\xd67a\\xe2\\xa77\\xfdr\\ng}uq9;z\\xf5\\xf9\\xe7\\xdf\\xdf\\xff2?y9z\\xf5_\\xf4ul%\"xu\\xa4^]\\xfc\\xf1\\x1a\\x06\\xa7cmj\\xe8\\xaa>\\x1c\\xeb\\x966T\\x87SM3FCu\\xa4\\x19\\x96\\xa9\\x8f\\x01\\x0e\\x8d\\xc9t\\x8a\\xd0\\xd4t\\x03\\xe0H7-\\x0b\\xe1pd\\xc9\\xf6\\xd8\\x1c\\xe9\\x08\\xcd\\xe1\\x08\\xf1\\xc6\\xe3\\xe9\\x08\\xe9&\\xc6d2A8\\x9e\\x9a\\xd8?\\x99\\x9a\\x13\\r\\xa19\\x1eKh\\x99C\\xb3\\x84\\x92~jL4C\\xc2\\xe9\\x14\\xe9\\xa7C\\xe0%\\xe1\\xd40%\\xb4\\xc68/p\\xb7\\xa6\\x12Zc9>\\xd1\\xac\\x12ZC\\xbd\\x84r}Ss$\\xf9\\x03\\x1cO$\\x9cNF\\x08-]\\x93mk4\\x92\\xf3Y\\xb0R\\tM\\xab\\x1c\\xb7\\xc6\\x08A\\xdc\\t\\xcec\\xea\\xda\\xb0\\x86\\xc8\\xd744\\xc9\\xcf4\\xf4\\xc9XBC\\x97\\xe3\\xc6D\\x97\\xe3\\xa0\\x069>\\x02\\rJ8\\x94\\xfa4a\\xbd\\xb2\\x7f\\xac\\x8d%\\xfexbH8\\xd1t]B\\xc3\\xd2$4\\'\\x12\\x7f:\\xd5%\\xfd\\xd4\\x92\\xfa7\\x91a\\t\\x87\\x93\\x12\\x8e\\xe4\\xb89)\\xf9[\\x9aV\\xc2R>\\xd3\\x1a\\x96\\xeb\\xb6\\x86\\xa3\\xb2=2\\xe5<\\xd6X\\xea\\xc5\\xb4&V\\xd9?\\x1d\\xe9\\x15D\\xfe\\xc0\\xc6\\x9cH8\\x1c\\x1b\\x12\\x8e4\\xbd\\x84\\xd2\\xae\\x16hf\\xa2\\x9aCM\\xd3\\xac\\x12\\x1aSC\\xc2\\xd1\\xd4TA\\xba\\xc9\\x04\\xfc\\x06\\xe0t\\x08\\xf3!\\x1c\\x81^\\x10\\x9ac\\xb3\\x84\\x96l\\x9b\\xda\\xb8\\x82\\x13\\x89o\\x8eA~\\x84\\x16\\xe8\\x07\\xa0\\x05\\x9c$D\\x7f\\xd45\\x03&4\\x87\\xe8\\xa8\\xf0a\\x18G*-\\xc4\\xb2\\xe0,\\x9fi\\xea\\x15\\xf7\\xf8\\xec\\xc8\\xb3\\x04(\\x91B\\xe8j8\\xc4\\xd1Cr\\x05\\x97\\xdb`\\xdc\\xb40\\xa8\\xea\\xf6\\xd6\"\\xbf[7\\xd4\\xdd\\x8b<\\x071iO=\\xd0ap\\x03\\xf3x\\xcb\\xae\\xd7[o\\x1a\\x06\"\\x8c\\xd9\\xc3I\\xba\\t\\xbbQ\\x9eQ\\xc1z(\\xfd[\\xc0\\xe9\\xf6\\x1a\\x92(\\rv&U=\\xd5W\\x83\\xde\\x9a:[\\x84wy\\xd4\\x1a\\xb2\\xc3E\\x97\\x90\\x03\\x07\\xd4\\x0e\\xea\\x06\\xd6\\x97\\x18\\x84mT?k\\x88F\\x1e\\xbde\\xc0\\xdc\\x8fE\\xe6\\xd1WT~$K\\x8a\\x90q(\\xbdi\\xc4\\x99\\xed\\x83l1\\x1c=\\xfa8CU\\x1a+\\x8e\\xf2\\xf0\\xe2\"Q:\\x1d\\xa5\\xfe\\xeeb\\xb9\\xa1t\\x1b\\xe2r\\xb2\\xde\\xba\\xa9\\xc9\\xbb=Y\\xeb\\xc2?{\\xf3\\x03\\xa2\\x1e\\xe0\\x9cP_\\xd7\\x14\\x8bNg\\xfb\\xdd\\xbf\\xeeK\\x1d\\x03\\xdb\\xa63p\\xaf\\xdb8\\xd8l\\xa1m~\\x80?xn\\xdc*\\xde\\x0fWJ\\x085g\\x9c\\x04.9S\\xea6&\\xd4\\xb3\\xd3$u\\xe1\\xd7U\\xbc\\x88r\\x0e}\\xfa\\xd9?\\x99\\x07Q\\r\\xfe\\x9d\\x0e\\\\\\xc0\\xa6\\xdb\\xa1\\xcaz\\xe5\\xf1j0\\xb8\\xb9iJ\\xf9E\\x0ej\\r\\x96\\xd9\\x93%TsyGP\\x17j3r&%\\xe6\\xa7\\x03\\xfaM>1$\\x8d\\x16#l\\xb6\\xf9D\\xe4\\xecW\\xe8\\xfa\\x1e\\x17\\xdc~5\\x17/\\x8d\\x07m\\x16&9{\\r\\xc3\\xdfc\\x81\\x02\\xdd\\x817\\x17n\\xc5\"\\x88\\x9c\\x9f\\xffY\\xb2\\xd0\\xc9\\xd9\\xbf\\xd3\\xe2-\\x0c}\\x8f\\x0b\\x98\\xba-Nr\\xc3w\\xf5\\x02\\xce\\xfb\\xd4\\x13\\x05l3\\xf1\\xe5o\\xe8&\\x8c\\xdaRa{\\xf0D\\xf2\\x89\\xc9\\xd9sl~\\x8f\\x85\\x9f\\xc3\\xfe\\xdd\\xd1\\x8c$O\\xc9\\xd93\\x1cyH.7py\\xa5\\xb8\\x1f\\xdd\\xc9\\xb7] \\x11\\xd1\\x00`\\x9a!\\x01\\x1f\\xc0N,@\\xf1\\x05HY\\x9c)\\x9d\\x9c^\\x17\\xa9\\x8d\\xf3\\x9d\\x0eJ\\xb7\\x1b\\x80+6\\xfe)\\x0bXE\\x16\\x01\\x0e\\xd6\\x8c\\xb5o\\xf2\\x8c&\\xa5\\xbf&\\xcd\"C\\x8c+\\xd0\\xdf\\x1e]4\\xa3\\x8bGFY\\xd3G[2<\\x10\\x01\\x0e\\x17\"\\xcd\\xefP\\x04p\\x84\\xd2l\\xa4a<:{!\\xc7\\xc3\\xeb\\x82)\\xef\\x99+uw\\x8f\\xda\\xab\\x03[\\x06\\x80\\xe5,\\xf1\\x18\\x7fH\\xfc\\x9a\\xe64\\xfe\\x1f\\x91W[B\\x12\\n\\x08\\xd2L8s\\x88\\xb7\\xe5B\\xe7SmO\\xc9\\xd4\\xf3 \\xee\\n\\xde\\xb6\\xe1\\x1b\\x96\\xafB\\x8f\\xbdL!\\xc2W\\xce\\x95\\xc14`OG\\xe4\\x05\\xeb\\xe0\\xadn\\x98\\x14\\xccy\\xdcR\\xedU\\x9d\\xa7I\\xc2n\\xc1`\\x8f[\\xa6F\\\\V~!\\xd3\\xae\\xf6-\\x8c\\xb2\\x90\\xafQ\\x94\\n\\xd3c\\x98\\xa0 \\xd4\\xc0\\x19,\\x82\\x93\\x93C\\xe00@d`\\x8a\\x82\\xcc\\'\\xdb8\\x15\\x05\\xb4jU\\x9eX\\x97\\x9d\\x86\\xacw\\x15H\\xf9\\xbb\\xe3uu\\xabkM\\x15iL\\'\\x98\\xe2\\x1f\\xcf\\xb6{7\\xdc\\x98\\x05\\xb6\\x97\\xdc\\xd8\\x9a\\xdf,\\xa1@\\x9ao\\xc9\\xab\\xfbncj\\xdcZFy\\xe5\\xddS\\x92\\xf4\\xa4\\xcc\\xceD\\x91\\xb7\\xdb\\x0e)/\\xbb\\x89\"S(\\x08\\x02\\xaa*%\\\\f\\xc8\\x96(\\xd5\\r\\x0bi\\x0e\\xaeQ\\xa7\\x03?\\xdd\\xde\\xae@U~\\x9fN\\xa7\\xed\\xdafR\\x97:\\xed\\xba\\xba9~A\\x92\\xa4\\x98\\xa9\\xe5\\xf1k\\x8a\\x9a\\x90\\x962\\xb0r&\\xb0\\xd6\\x9b\\x9cf\\x0e!g?\\x83\\xf0\\x1e\\xab\\x8c\\xd2\\xfe\\xdd\\x9a\\x1dO\\x08\\n\\x95i\\xce\\xa9\\x8f\\xb9\\xc0\\x82\\xc6\\xb04L\\xaf\\x10?\"\\xa6x,\\x8a*\\xcb8D#\\xb2\\r;\\xcc\\xab\\xda\\x80\\x96+\\xabJ\\x13\\xb0&\\xec\\xf0\\xab\\xedM\\x8c\\xf1\\x8f\\xe4\\xac\\x93\\xb8<\\x83x |9T\\xa1\\x96~\\xd2^\\xf1\\xa9,\\xbc\\xab\\xf9CP/p\\x05\\xcf&\\x97o^\\x9d\\x98\\xe6\\xd8:\\xd1\\xc1\\x00\\xe5\\xfbI\\xe8\\xfb,i(*<\\xdc\\x84%\\xf12\\xfa\\nf9\\xcc\\xd3\"\\xf7\\xd8\\x1eJ\\xcde\\x99\\xeda\\xbb\\xe1\\xcd7\\xb9\\xb9\\xe1\\xf2\\xc1\\xf8v\\xbb\\x10\\x9f\\x93=\\xff\\x1dn\\x0f\\xc3x\\xdeP\\xb4\\x86\\xe1\\xae[`\\xd9\\xd7\\xd4v\\xf5\\xde\\x18\\xd7\\xc7A\\x05\\xdd\\xe4a1\\x07NY\\xe0\\xddg\\x9c\\xe1=\\x9eCR(\\x94\\xea\\xa5@)\\xd7\\x88\\xd9\\xf8rS\\x06(\\xb5W\\xc7\\xf46bI \\xed\\xa7\\x8d\\xccZ\\xa9\\xd7 \\x07\\xf8\\xa7C\\xc6S\\xb2\\xf5\\xa5z\\xcd\\xed\\xe3\\'\\n$#\\xf2V\\x05\\xbb\\x1dXd7Bo\\xfb\\x9a\\xc5=\\\\S\\xa5j\\x91<\\xafu\\xcd\\x0b7\\x0e\\x05i\\xc2};\\x13\\xfc_\\xe7\\xfd\\xe5\\x88\\x86\\x8a\\xcf\\x94\\x88\\xe2\\xebZ\\xe2\\xb5\\'\\xbe\\xc4]\\xedE\\xa1w\\x05\\xce\\xb9\\x90\\xd7S}\\xdc@P\\x93I\\xea\\x9e\\xec\\x815{W\\xccwt[aP]*`\\x91\\xe6\\xda\\njR?M\\xfd\\x08\"\\xd3\\xd1w\\xa4\\xa8\\xf7J\\xb5F8\\xda\\xe1qi/\\xe04;gg\\xb75yo@\\xfd\\x15\\xca\\xe0\\xcf\\xab\\x0b\\xac2}\\xd08\\xb3\\xeb\\xf7\\t\\x07,\\xb5\\xd55E\\xf4/\\xb2P\\xd82\\xc1\\xf7\\xc8\\x02\\xa2)$\\xae4\\xe2_\\xe1\\xf1\\xaa\\x10a\\xc4\\x15\\xf0\\x81\\xa0\\x80\\xc4\\x89i\\x93\\x97Y\\x06\\xe5\\x18\\x08\\x8c82\\x98\\xd4\\xba\\xc7X\\x19\\xb8\\xabZ\\xbd\\xf2\\xf3\\xd1\\xdd\\xa8\\xa3jP\\xcd\\xdb\\xb4\\x817\\x1e\\x1c\\xf3\\x1ei\\'\\xa7\\xca\\r\\xb7a\\xd4\\x1c\\xfeh\\xc7p|\\xac7\\x1d^8\\x91v\\xf4\\xabk\\x06\\xa0I1\\x12}\\x85\\x0f$\\x1b\\xb1;V\\xedI\\xdd\\xc2\\xe3\\x15l\\xb6\\xf6u[\\x1d\\xd5$_\\xc8{-M\\xd6U\\x13\\xf5\\xb1bz\\x83whX=)Y\\xe1\\x82W\\x85\\x82\\x86U\\xc1\\xb0%\\xe1e\\xf6\\xdf\\xc5\\xf7\\x8f`\\x0eHHy\\xc8wm\\xb5\\xad\\x90\\x8b\\x9d\\x1aB\\xd7&\\x96\\xa6\\x8f\\xcc\\x89i\\xe2\\x83\\x98\\xa1\\x19\\xa6aT\\xa7-99_\\xa2\\xf8\\xc7\\xf5\\x13.}l\\xd1.\\x94I}|x&g\\xff\\xad\\xe0\\x03p\\xcaq\\xa7{\\xe25x\\x17\\x11C0\\xf3\\xe4\\xe1Y\\xa5\\x8e\\xdf\\x87\\x08\\t=\\xf5\\xd4u\\xbb\\x9a\\x96v:n\\xa7\\xd3\\xa5\\x07\\xceV\\xae\\xbe\\x94\\xe6\\xfe\\xde\\xdd\\xe9,e\\xdd\\xbe\\x88@Y\\xd5%D\\xc5\\xbf\\x83\\x92%\\xbe\\xbb<\\xe9T\\x8f.\\x90\\xa3\\xabW\\x17\\xc8\\xc6\\xf2\\xd5e\\xf7\\xed\\xe7\\xea\\xe2\\xb2\\xb7\\xf7\\xb2\\xf0\\x95\\xc3\\xf3\\xed\\'\\xee\\x93\\xbd\\x13\\x0bv\\x86\\x8f_7\\xd4\\x9f\\x8a\\xd7u\\x1b\\xa3\\xf1\\xf2\\xa5\\x0fv\\xccC\\x035\\xe7y/\\x87\\x8a\\x92U\\x1a\\xec\\x92\\x927\\xe9\\xd9T\\xde\\x1b\\xb8\\xad{\\x00V\\xeb\\xf9\\xa7\\xbbK\\xbf[\\xae\\xb0\\xd7\\xa7Y\\xc6\\x12\\xff|\\x19F~\\x97\\xf66\\xaa\\xd6\\xdbT\\xc2\\xfa\\xd1\\xa7l\\xfb\\xd6\\xe2\\xaa\\xb4yk\\x01\\xd2\\x02X{H`o\\xb1\\x1doO7_wD\\xe0\\x90\\xc7\\xf8\"$\\xd5\\x07nT\\xbf\"\\xf5\\xea\\x8f\\xfe\"\\xcc\\xb9@\\x14t\\xb2-Y\\xc3i^{\\xdb\\xfc\\xfe~\\xdd\\xbc1\\xce\\xfb\\xf3gE\\x9c]\\xdczL\\x9e*\\xb7\\x02\\xb0\\xdeZ,\\xf3\\xf4Fa\\x9b\\xd6\\x84\\xf8\\x86\\x96\\xf3\\xc6?\\xca\\xa6|i\\xea\\x8bF\\xde\\x9d^\\\\D\\xc4\\x9d\\xfaA\\xb6\\xdbz\\x91\\xb5\\xdb\\xaa\\xeb\\x1e\\r\\x00s0\\x1f\\xc0\\xcf\\xd5\\x9f\\xb7C\\x1fZ\\xfde\\xd6g\\xc9\\xfc\\xdd\\x9b\\xfe\\xfb\\xdf\\x86\\x7fdo>\\x9f\\xfc\\xf4\\xdc\\xec\\xbf\\x1a\\xc48\\xce\\xdd\\xf9\\x92\\xa9\\xfe \\x17\\xd8\\xfa4\\xf0\\x11\\xe8\\x03\\xd9\\xfa\\xecA\\x0c\\xc89~>=\\x7fki\\xe9\\xf3\\x95\\xff\\x82N\\xdf\\xbe4\\xde\\xcf/\\x7f\\xffC\\xe7\\xe3\\x9f^\\xdf\\xfe\\xfb\\xf2\\x05{\\xb7Z\\xfe~\\xa4\\xfe\\xffM\\xdd\\xb3\\xb7\\xa6\\x82\\xea\\xa5VH\\x16{\\xce\\x9a\\xc8i\\xc8lM\\xa0\\x10H\\xc8\\x0cO\\x9e*\\xf1\\xda\\xdfrK\\x92\\x19\\x96\\xb8y\\x94\\xa6\\xf1\\t\\x94\\xd1*\\xf1\\x975\\x82\\xbf\\xbc\\x16\\xcd7\\x07D\\x18]Du\\xcf2\\xe5H\\xdcd\\x0b\\x18\\x0c\\xb9\\x0b\\xc4\\x86\\xa9\\x92O4&\\x10\\xf4\\xc8\\'\\x9e&YM\\x11\\xc5\\xf5W\\xcc\\x03\\x8eK\\xf3B\\x17\\xf8\\x91\\x8b\\xc5\\x82z,\\xc7\\x12.\\xaf+\\x1b\\\\\\xc9]\\x8c\\x83\\x9c\\xd3;\\xf6\\x19*\\x1d\\xe6\\xc19F\\x08\\xa6\\xa4\\xb9X\\xa6\\x01\\xd4QP\\x00\\xcd\\x001\\xf2\\xae\\xee\\x00\\xf3\\x97?\\x0bM3&\\x87C\\xcb\\xde/\\tqr9S\\xa2p\\xbaJ\\xc3\\\\\\xc1\\x84\\x0b\\xdd)\\xbfB)\\xaab\\x08h8\\ry\\x88\\x04\\x19li\\x189\\x973\\xf2\"\\x80\\xc3\\xa9\\x0c\\x1aTqAo\\xca\\x17\\xf1\\x05z3\\xc85\\xf1\\x17\\x86\\x84\\xab\\x14\\x12\\xbc\\x82K\\x18\\x9eW\\t\\xe6OR\\xdfY\\xfcI\\xe4\\xc0\\xc5r\\xe7\\x8a\\xa2D\\x1e\\xd0r\\xac_N\\x8a\\xcb|S2f\\xa8T\\xee\\x86\\xb8\\xc2m\\xc9\\x97\\xe1]\\xa8\\xfc\\x7f\\\\0\\x88\\xa7\\xbf\\xd9\\xc3\\xda{\\xa3\\x92\\x04MV\\xaa;]\\xe5\\xa0m\\xe8\\xcb\\xaeK+\\xc2\\xe2\\xe0T_\\x8d\\xe6\\x0b@\\xfc\\xf0\\x11\\x98y\\xd0\\xa7k\\xf8\\x01\\x8e3\\x06(\\xd2+ XY\\xcb\\xc4\\xba\\xf8\\xed\\xf5\\xbf\\x8c\\xf3k\\xfd_\\xcf\\x9e\\x16\\x93\\xc2\\x1fO\\xde~\\xbe\\xba\\xfb\\x1d\\'\\xf2\\x91\\xf7\\xf6\\xf5\\xba\\x8c%\\xe5\\xfbp+$m\\xa3@\\xcc\\xfcf\\xb3\\xc3w\\xf7(LB\\xb1\\xf5el\\x9579w\\xdd\\xa6S\\xe2U\\xba\\x04\\xd4\\xcd\\xa6\\x1d\\xc7>5\\x11\\xe5\\x13\\xec\\xbaV\\xe36|,\\xbc\\xb7\\x86!\\x00\\xdb\\xf2U\\xb0\\xbe\\x0f/\\x8b\\x12L\\x8cx\\x89\\x8d\\xff\\'\\xf0\\x7f\\x01\\xa9\\x9a\\xd7%#(\\x00\\x00'\n", + " | padding = '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n", + "\n" + ] + } + ], + "prompt_number": 113 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, I don't know about you, but I can't read this :) Let's use the helpers to help us out.\n", + "\n", + "First we need to create a new Header table that is meaningful for headers received from the server. We set the various sizes to the values we defined in our settings." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "srv_tblhdr = h2.HPackHdrTable(dynamic_table_max_size=max_hdr_tbl_sz, dynamic_table_cap_size=max_hdr_tbl_sz)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 114 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now convert all received headers into their textual representation, and stuff the data frames into a buffer per stream." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# Structure used to store textual representation of the stream headers\n", + "stream_txt = {}\n", + "# Structure used to store data from each stream\n", + "stream_data = {}\n", + "\n", + "# For each frame we previously received\n", + "for frame in stream.frames:\n", + " # If this frame is a header\n", + " if frame.type == h2.H2HeadersFrame.type_id:\n", + " # Convert this header block into its textual representation.\n", + " # For the sake of simplicity of this tutorial, we assume \n", + " # that the header block is not large enough to require a Continuation frame\n", + " stream_txt[frame.stream_id] = srv_tblhdr.gen_txt_repr(frame)\n", + " # If this frame is data\n", + " if frame.type == h2.H2DataFrame.type_id:\n", + " if frame.stream_id not in stream_data:\n", + " stream_data[frame.stream_id] = []\n", + " stream_data[frame.stream_id].append(frame)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 115 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can print the headers from the Favicon response." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "print(stream_txt[3])" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + ":status 200\n", + "vary: Accept-Encoding\n", + "content-encoding: gzip\n", + "content-type: image/x-icon\n", + "date: Thu, 08 Dec 2016 06:23:59 GMT\n", + "expires: Fri, 16 Dec 2016 06:23:59 GMT\n", + "last-modified: Thu, 08 Dec 2016 01:00:57 GMT\n", + "x-content-type-options: nosniff\n", + "server: sffe\n", + "content-length: 1494\n", + "x-xss-protection: 1; mode=block\n", + "cache-control: public, max-age=691200\n", + "age: 472252\n", + "alt-svc: quic=\":443\"; ma=2592000; v=\"35,34\"\n" + ] + } + ], + "prompt_number": 116 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, we received a 200 status code, meaning that we received the favicon. We also can see that the favicon is GZipped. Let's uncompress it and display it in this notebook." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import zlib\n", + "img = zlib.decompress(stream_data[3][0].data, 16+zlib.MAX_WBITS)\n", + "from IPython.core.display import HTML\n", + "HTML(''.format(img.encode('base64')))" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "html": [ + "" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 117, + "text": [ + "" + ] + } + ], + "prompt_number": 117 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now read the frontpage response." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "print(stream_txt[1])" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + ":status 200\n", + "date: Tue, 13 Dec 2016 17:34:51 GMT\n", + "expires: -1\n", + "cache-control: private, max-age=0\n", + "content-type: text/html; charset=ISO-8859-1\n", + "p3p: CP=\"This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\"\n", + "content-encoding: gzip\n", + "server: gws\n", + "content-length: 4420\n", + "x-xss-protection: 1; mode=block\n", + "x-frame-options: SAMEORIGIN\n", + "set-cookie: NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly\n", + "alt-svc: quic=\":443\"; ma=2592000; v=\"35,34\"\n" + ] + } + ], + "prompt_number": 118 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, we received a status code 200, which means that we received a page. Let's \"visualize it\"." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "data = ''\n", + "for frgmt in stream_data[1]:\n", + " data += frgmt.payload.data\n", + "\n", + "HTML(zlib.decompress(data, 16+zlib.MAX_WBITS).decode('UTF-8', 'ignore'))" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "html": [ + "Google

France

 

Recherche avanceOutils linguistiques

© 2016 - Confidentialit - Conditions

" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 119, + "text": [ + "" + ] + } + ], + "prompt_number": 119 + }, + { + "cell_type": "heading", + "level": 2, + "metadata": {}, + "source": [ + "Throwing a query!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now query Google! For this, we will build a new query without the helpers, to explore the low-level parts of the HTTP/2 Scapy module.\n", + "First, we will get the cookie that we were given. For this, we will search for the set-cookie header that we received." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import StringIO\n", + "sio = StringIO.StringIO(stream_txt[1])\n", + "cookie = [val[len('set-cookie: '):].strip() for val in sio if val.startswith('set-cookie: ')]\n", + "print(cookie)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly']\n" + ] + } + ], + "prompt_number": 120 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we build the query by hand. Let's create the Header frame, so that we can later stuff all the header \"lines\" in it. This is a new stream, and we already used the stream ids 1 and 3, so we will use the stream id 5, which is the next available. We will set the \"End Stream\" and \"End Headers\" flags. The End Stream flag means that they are no more frames (except header frames...) after this one in this stream. The End Headers flag means that there are no Continuation frames after this frame. Continuation frames can be used to add more headers than one could fit in previous H2HeaderFrame and Continuation frames...\n", + "\n", + "Our frame will contain little headers and we set very large limits for this tutorial, so we will skip all the checks, but they should be done! The helpers does them, by the way." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "hdrs_frm = h2.H2Frame(flags={'ES', 'EH'}, stream_id=5)/h2.H2HeadersFrame()" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 121 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we have to specify the pseudo headers. These headers are the equivalent of \"GET / HTTP/1.1\\nHost: www.google.fr\" of old.\n", + "For this, we specify that this is a GET query over HTTPS, along with the path, and the \"authority\", which is the new \"Host:\".The GET Method and the HTTPS scheme are part of the static HPack table, according to RFC7541. Let's get the index of these headers and put them into HPackIndexedHdr packets." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "get_hdr_idx = tblhdr.get_idx_by_name_and_value(':method', 'GET')\n", + "get_hdr = h2.HPackIndexedHdr(index = get_hdr_idx)\n", + "get_hdr.show()\n", + "hdrs_frm.payload.hdrs.append(get_hdr)\n", + "\n", + "https_hdr_idx = tblhdr.get_idx_by_name_and_value(':scheme', 'https')\n", + "https_hdr = h2.HPackIndexedHdr(index = https_hdr_idx)\n", + "https_hdr.show()\n", + "hdrs_frm.payload.hdrs.append(https_hdr)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HPack Indexed Header Field ]### \n", + " magic = 1\n", + " index = 2\n", + "\n", + "###[ HPack Indexed Header Field ]### \n", + " magic = 1\n", + " index = 7\n", + "\n" + ] + } + ], + "prompt_number": 122 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the path, we will use \"/search?q=scapy\". The path might be a sensitive value, since it may contain values that we might not want to leak via side-channel attacks (here the query topic). For this reason, we will specify the path using a HPackLitHdrFldWithoutIndexing, which means that we don't want indexing. We also need to set the never_index bit, so that if there are intermediaries between us and the HTTP server, they will not try to compress this header.\n", + "Before setting this header, though, we have to choose whether we want to compress the *string* \"/search?q=scapy\" using Huffman encoding. Let's compress it and compare its wire-length with the uncompressed version." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "z_str = h2.HPackZString('/search?q=scapy')\n", + "unz_str = h2.HPackLiteralString('/search?q=scapy')\n", + "\n", + "print(len(str(z_str)), len(str(unz_str)))" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "(12, 15)\n" + ] + } + ], + "prompt_number": 123 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So the compressed version is smaller. Let's use it, since HTTP/2 is all about performances and compression. \n", + "\n", + "\":path\" is the pseudo-header to define the query path. While we don't want to compress the *value* of the query path, the name can be compressed. As it happens, the \":path\" header name is in the static header table. For this reason, we will search of its index, and then build the header." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "path_hdr_idx = tblhdr.get_idx_by_name(':path')\n", + "path_str = h2.HPackHdrString(data = z_str)\n", + "path_hdr = h2.HPackLitHdrFldWithoutIndexing(\n", + " never_index=1, \n", + " index=path_hdr_idx,\n", + " hdr_value=path_str\n", + ")\n", + "path_hdr.show()\n", + "hdrs_frm.payload.hdrs.append(path_hdr)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", + " magic = 0\n", + " never_index= Never Index\n", + " index = 4\n", + " \\hdr_value \\\n", + " |###[ HPack Header String ]### \n", + " | type = None\n", + " | len = None\n", + " | data = 'HPackZString(/search?q=scapy)'\n", + "\n" + ] + } + ], + "prompt_number": 124 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The final missing pseudo-header is the new \"Host\" header, called \":authority\". \":authority\" is in the static header table, so we *could* use it. As it happens, we can do better because we previously indexed \":authority\" *with the value* \"www.google.fr\". Let's search for the index of this entry in the dynamic table. With luck, the server header table size is large enough so that the value is still inside it." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "host_hdr_idx = tblhdr.get_idx_by_name_and_value(':authority', dn)\n", + "assert(not isinstance(host_hdr_idx, type(None)))\n", + "print(host_hdr_idx)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "66\n" + ] + } + ], + "prompt_number": 125 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, the \":authority www.google.fr\" header is still in the dynamic table. Let's add it to the header list." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "host_hdr = h2.HPackIndexedHdr(index=host_hdr_idx)\n", + "host_hdr.show()\n", + "hdrs_frm.payload.hdrs.append(host_hdr)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HPack Indexed Header Field ]### \n", + " magic = 1\n", + " index = 66\n", + "\n" + ] + } + ], + "prompt_number": 126 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we added all the pseudo-headers, let's add the \"real\" ones. The compression header is in the static table, so we just need to look it up." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "z_hdr_idx = tblhdr.get_idx_by_name_and_value('accept-encoding', 'gzip, deflate')\n", + "z_hdr = h2.HPackIndexedHdr(index = z_hdr_idx)\n", + "z_hdr.show()\n", + "hdrs_frm.payload.hdrs.append(z_hdr)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HPack Indexed Header Field ]### \n", + " magic = 1\n", + " index = 16\n", + "\n" + ] + } + ], + "prompt_number": 127 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also need to create the header for our new cookie. Cookie are sensitive. We don't want it to be indexed and we don't want any intermediate to index it. As such, we will use a HPackLitHdrFldWithoutIndexing and with the never_index bit set, here as well. The name \"cookie\", though, happens to be in the RFC7541 static headers table, so we will use it." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "cookie_hdr_idx = tblhdr.get_idx_by_name('cookie')\n", + "cookie_str = h2.HPackHdrString(data = h2.HPackZString(cookie[0]))\n", + "cookie_hdr = h2.HPackLitHdrFldWithoutIndexing(\n", + " never_index = 1,\n", + " index = cookie_hdr_idx,\n", + " hdr_value = cookie_str\n", + ")\n", + "cookie_hdr.show()\n", + "hdrs_frm.payload.hdrs.append(cookie_hdr)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", + " magic = 0\n", + " never_index= Never Index\n", + " index = 32\n", + " \\hdr_value \\\n", + " |###[ HPack Header String ]### \n", + " | type = None\n", + " | len = None\n", + " | data = 'HPackZString(NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly)'\n", + "\n" + ] + } + ], + "prompt_number": 128 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Also, we need to specify that we read French. Once more, the \"accept-language\" header is in the HPack static table, but \"fr-Fr\" might not be. Let's see if we did index that earlier." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "acceptlang_hdr_idx = tblhdr.get_idx_by_name_and_value('accept-language', 'fr-FR')\n", + "print(acceptlang_hdr_idx)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "65\n" + ] + } + ], + "prompt_number": 129 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Excellent! This is an entry of the dynamic table and we can use it in this session! Let's use it with an HPackIndexedHdr packet." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "acceptlang_hdr = h2.HPackIndexedHdr(index = acceptlang_hdr_idx)\n", + "acceptlang_hdr.show()\n", + "hdrs_frm.payload.hdrs.append(acceptlang_hdr)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HPack Indexed Header Field ]### \n", + " magic = 1\n", + " index = 65\n", + "\n" + ] + } + ], + "prompt_number": 130 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's do the same thing quickly for the other headers." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "accept_hdr_idx = tblhdr.get_idx_by_name_and_value('accept', 'text/html')\n", + "accept_hdr = h2.HPackIndexedHdr(index = accept_hdr_idx)\n", + "accept_hdr.show()\n", + "hdrs_frm.payload.hdrs.append(accept_hdr)\n", + "ua_hdr_idx = tblhdr.get_idx_by_name_and_value('user-agent', 'Scapy HTTP/2 Module')\n", + "ua_hdr = h2.HPackIndexedHdr(index = ua_hdr_idx)\n", + "ua_hdr.show()\n", + "hdrs_frm.payload.hdrs.append(ua_hdr)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HPack Indexed Header Field ]### \n", + " magic = 1\n", + " index = 64\n", + "\n", + "###[ HPack Indexed Header Field ]### \n", + " magic = 1\n", + " index = 63\n", + "\n" + ] + } + ], + "prompt_number": 131 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we forget a piece in our previous queries regarding privacy: I want to add a Do Not Track header (https://tools.ietf.org/html/draft-mayer-do-not-track-00). Let's add this header into every subsequent queries. For this reason, I want to have it indexed. It is worth noting that the \"DNT\" header is not part of the HPack static table (how curious?). Finally, the value of this header is just 1. We might actually save a few bits by NOT compressing this value." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "dnt_name_str = h2.HPackLiteralString('dnt')\n", + "dnt_val_str = h2.HPackLiteralString('1')\n", + "dnt_name = h2.HPackHdrString(data = dnt_name_str)\n", + "dnt_value = h2.HPackHdrString(data = dnt_val_str)\n", + "dnt_hdr = h2.HPackLitHdrFldWithIncrIndexing(\n", + " hdr_name = dnt_name,\n", + " hdr_value = dnt_value\n", + ")\n", + "dnt_hdr.show()\n", + "hdrs_frm.payload.hdrs.append(dnt_hdr)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HPack Literal Header With Incremental Indexing ]### \n", + " magic = 1\n", + " index = 0\n", + " \\hdr_name \\\n", + " |###[ HPack Header String ]### \n", + " | type = None\n", + " | len = None\n", + " | data = 'HPackLiteralString(dnt)'\n", + " \\hdr_value \\\n", + " |###[ HPack Header String ]### \n", + " | type = None\n", + " | len = None\n", + " | data = 'HPackLiteralString(1)'\n", + "\n" + ] + } + ], + "prompt_number": 132 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are not done yet with the DNT header, though. We also need to insert it into the HPack Dynamic table, so that later lookups will find it." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "tblhdr.register(dnt_hdr)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 133 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Phew! We made it! Let's see what we got so far." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "hdrs_frm.show2()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HTTP/2 Frame ]### \n", + " len = 0xc5\n", + " type = HdrsFrm\n", + " flags = set(['End Stream (ES)', 'End Headers (EH)'])\n", + " reserved = 0L\n", + " stream_id = 5L\n", + "###[ HTTP/2 Headers Frame ]### \n", + " \\hdrs \\\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 2\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 7\n", + " |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", + " | magic = 0\n", + " | never_index= Never Index\n", + " | index = 4\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 12\n", + " | | data = 'HPackZString(/search?q=scapy)'\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 66\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 16\n", + " |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", + " | magic = 0\n", + " | never_index= Never Index\n", + " | index = 32\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 165\n", + " | | data = 'HPackZString(NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly)'\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 65\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 64\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 63\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 0\n", + " | \\hdr_name \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Literal\n", + " | | len = 3\n", + " | | data = 'HPackLiteralString(dnt)'\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Literal\n", + " | | len = 1\n", + " | | data = 'HPackLiteralString(1)'\n", + "\n" + ] + } + ], + "prompt_number": 134 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Oh! Just for comparison, we would have got about the same result using the helpers (modulo some safety checks that we did not do here...)." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "tblhdr.parse_txt_hdrs(\n", + " ''':method GET\n", + ":scheme https\n", + ":path /search?q=scapy\n", + ":authority www.google.fr\n", + "accept-encoding: gzip, deflate\n", + "cookie: {}\n", + "accept-language: fr-FR\n", + "accept: text/html\n", + "user-agent: Scapy HTTP/2 Module\n", + "dnt: 1\n", + "'''.format(cookie),\n", + " stream_id=5,\n", + " max_frm_sz=srv_max_frm_sz,\n", + " max_hdr_lst_sz=srv_max_hdr_lst_sz,\n", + " is_sensitive=lambda hdr_name, hdr_val: hdr_name in ['cookie', ':path'],\n", + " should_index=lambda x: x in [\n", + " 'x-requested-with', \n", + " 'user-agent', \n", + " 'accept-language',\n", + " 'host',\n", + " 'accept',\n", + " ':authority',\n", + " 'dnt'\n", + " ]\n", + ").show2()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HTTP/2 Frame Sequence ]### \n", + " \\frames \\\n", + " |###[ HTTP/2 Frame ]### \n", + " | len = 0xdc\n", + " | type = HdrsFrm\n", + " | flags = set(['End Stream (ES)', 'End Headers (EH)'])\n", + " | reserved = 0L\n", + " | stream_id = 5L\n", + " |###[ HTTP/2 Headers Frame ]### \n", + " | \\hdrs \\\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 2\n", + " | |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", + " | | magic = 0\n", + " | | never_index= Never Index\n", + " | | index = 4\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 30\n", + " | | | data = 'HPackZString(/?gfe_rd=cr&ei=2B1IWOeIDujt8weIvIH4BQ)'\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 67\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 7\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 16\n", + " | |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | | magic = 1\n", + " | | index = 17\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 4\n", + " | | | data = 'HPackZString(en-US)'\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 66\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 65\n", + " | |###[ HPack Indexed Header Field ]### \n", + " | | magic = 1\n", + " | | index = 63\n", + " | |###[ HPack Literal Header Without Indexing (or Never Indexing) ]### \n", + " | | magic = 0\n", + " | | never_index= Never Index\n", + " | | index = 32\n", + " | | \\hdr_value \\\n", + " | | |###[ HPack Header String ]### \n", + " | | | type = Compressed\n", + " | | | len = 171\n", + " | | | data = \"HPackZString(['NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly'])\"\n", + "\n" + ] + } + ], + "prompt_number": 135 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now send our query to Google and read the answer!" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "srv_global_window -= len(str(hdrs_frm))\n", + "assert(srv_global_window >= 0)\n", + "ss.send(hdrs_frm)\n", + "\n", + "h2seq = h2.H2Seq()\n", + "\n", + "new_frame = None\n", + "while isinstance(new_frame, type(None)) or 'ES' not in new_frame.flags:\n", + " # As previously, if we receive a ping, we ackownledge it.\n", + " if not isinstance(new_frame, type(None)) and new_frame.stream_id == 0:\n", + " if new_frame.type == h2.H2PingFrame.type_id:\n", + " new_frame.flags.add('A')\n", + " srv_global_window -= len(str(new_frame))\n", + " assert(srv_global_window >= 0)\n", + " ss.send(new_frame)\n", + " \n", + " assert new_frame.type != h2.H2ResetFrame.type_id \\\n", + " and new_frame.type != h2.H2GoAwayFrame.type_id, \\\n", + " \"Error received; something is not right!\"\n", + " \n", + " try:\n", + " new_frame = ss.recv()\n", + " new_frame.show()\n", + " if new_frame.stream_id == 5:\n", + " h2seq.frames.append(new_frame)\n", + " except:\n", + " import time\n", + " time.sleep(1)\n", + " new_frame = None" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "###[ HTTP/2 Frame ]### \n", + " len = 0x8\n", + " type = PingFrm\n", + " flags = set([])\n", + " reserved = 0L\n", + " stream_id = 0L\n", + "###[ HTTP/2 Ping Frame ]### \n", + " opaque = 2\n", + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x8\n", + " type = PingFrm\n", + " flags = set(['ACK (A)'])\n", + " reserved = 0L\n", + " stream_id = 0L\n", + "###[ HTTP/2 Ping Frame ]### \n", + " opaque = 2\n", + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x8\n", + " type = PingFrm\n", + " flags = set(['ACK (A)'])\n", + " reserved = 0L\n", + " stream_id = 0L\n", + "###[ HTTP/2 Ping Frame ]### \n", + " opaque = 2\n", + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x8\n", + " type = PingFrm\n", + " flags = set(['ACK (A)'])\n", + " reserved = 0L\n", + " stream_id = 0L\n", + "###[ HTTP/2 Ping Frame ]### \n", + " opaque = 2\n", + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x21\n", + " type = HdrsFrm\n", + " flags = set(['End Headers (EH)'])\n", + " reserved = 0L\n", + " stream_id = 5L\n", + "###[ HTTP/2 Headers Frame ]### \n", + " \\hdrs \\\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 8\n", + " |###[ HPack Literal Header With Incremental Indexing ]### \n", + " | magic = 1\n", + " | index = 33\n", + " | \\hdr_value \\\n", + " | |###[ HPack Header String ]### \n", + " | | type = Compressed\n", + " | | len = 22\n", + " | | data = 'HPackZString(Tue, 13 Dec 2016 17:36:19 GMT)'\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 70\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 69\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 68\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 83\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 66\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 75\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 64\n", + " |###[ HPack Indexed Header Field ]### \n", + " | magic = 1\n", + " | index = 72\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x1640\n", + " type = DataFrm\n", + " flags = set(['Padded (P)'])\n", + " reserved = 0L\n", + " stream_id = 5L\n", + "###[ HTTP/2 Padded Data Frame ]### \n", + " padlen = 40\n", + " data = '\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xa4;\\t\\x97\\x9bF\\x93\\x7f\\x053/c\\xe9\\r\\x928t!\\r\\xe3u\\x92\\x89\\xe3\\xc4I\\x9c\\xc4\\xd9/\\xdf\\xfay\\xf5\\x1ah\\x04\\x19\\x042\\xb4\\xe6\\xb0F\\xffw\\xf7_lU7\\xa0\\xe6\\x90\\xaf5\\xcf\\xd3\\xa2\\xbb\\xba\\xba\\xeb\\xaej\\xe0\\xf2\\x89\\x9fz\\xecaK\\x95\\x90m\\xe2\\xabK\\xfc\\xabD\\x8cnr/\\xddRGU\\xf9\\r\\x028j\\xc8\\xd8v1\\x1a\\xe5^H7d\\x98f\\xeb\\xd1\\x9f\\x94d^\\xf8\\x07\\xcdw1\\xcb_\\x935U\\x95\\x98$kG\\r2\\x15pQ\\xe2_]n(#\\x8a\\x97&\\x8c&\\xccQ\\x19\\xbdg#\\\\d\\xa9x!\\xc9r\\xca\\x9c\\xbf\\xde\\xfc0\\x98\\xab\\nb\\x1f\\xd0\\xf7\\xbb\\xe8\\xd6Q\\xbf\\x13\\xe0\\x837\\xb0\\xae\\xdaD1\\x8a6\\xb0R>r3\\x92\\xf8Q\\xb2\\x1e\\xad\\xd3t\\x1d\\xd3\\xf5\\xc8\\xb8/\\x7f\\xaer\\x06c$\\xf3W^\\x1a\\xa7\\xd9\\xca0\\xe7\\xfev\\xb8M\\xd6\\x82\\x9cm\\x96n\\x1d\\x95\\xa3\\x01\\xecq\\x94\\xdc(aF\\x83\\x0e\\xd4\\x00\\xe9\\xef<6\\x8a\\xbc\\xb4\\xc2\\x1d\\xa7\\x80\\x0b:T%\\xa3\\xb1\\xa3\\xe6a\\x9a1o\\xc7\\x14\\xe8J\\x00\\x1d\\x8bXL\\xafr\\x8fl\\x1f\\x94\\x81\\xf2\\x07\\x05n\\x01\\x8f\\xa8\\xf2\\x82O\\xbf\\x1c\\x89\\xf1\\xcb\\x9c=@s\\xb6vI\\xa6\\x9d\\xadw9\\xcd\\xf6\\x01P8\\xc8\\xa3\\x0ftaX\\xdb\\xfb\\xe5\\x96\\xf8\\xb8\\x87\\x01K\\xb7\\x0bc{\\xaf<\\x896[X\\x8a$ly\\xe0\\xf3\\xf6!\\x8d\\xd6![\\x98\\xe6\\xf6\\xfeP\\xe0(\\'\\xb9)c\\xe9f1\\xab\\xcfC\\xee\\x0fH\\x1c\\xad\\x93E\\x86S\\x0f\\xc3\\xb5\\x1bj\\xf0\\xc7\\xdf\\xbbi\\xe6\\xd3\\xacZ,O\\xe3\\xc8W\\xce<\\xdb\\x9f\\x05\\xc6R\\xda\\x19,\\x85\\x93\\xca\\xb5\\xf5\\xe56\\xcd#\\x16\\xa5\\xc9\\x82\\xb80i\\xc7\\xe8\\x12q\\x98c\\xa0\\xe0.\\xf2Y\\xb80t\\xfd\\x9b\\xc3\\x7fl\\xa8\\x1f\\x11\\x85\\xc4\\xf1\\x1e\\xa6\\x1b\\xf2\\xd6\\x97\\x1b\\x92\\xad\\xa3d\\xc0w\\xb4\\x18N\\xe8fyK3\\x16y$.\\xf6\\n\\x08\\x0b\\x8a\\x838%l\\x11\\xd3\\x80\\x1d\\x0e\\x04\\x11i\\xf8w\\xbc\\xe7\\x84\\xf9\\xd4K3\\xc2\\xf7\\xb2K\\x80\\x18\\x10,\\x95\\xa8\\xafM\\xe0z\\xb18\\xd3uO\\x86\\x80\\xa1H\\x91\\xc7}\\x7fN\\xcdY\\x03$\\xa8\\x81\\xd8\\xba.\\x8f+\\x97#!\\xdaB\\xc2CPEy\\xdf%\\xb5\\x05\\xa3\\x97)\\xd0\\n\\xa3w\\x8b0\\xf2}\\x9a\\x1c\\x86\\xab\\x87\\xd0\\x97U\\x81s\\xfc\\x9f\\xbd\\xe0\\xa55\\xa6\\x9b\\x83\\x9b\\xfa\\x0f\\x1a\\xf35?\\xba\\xd5\\x86[\\x8d\\x08\\xe8\\x80l\\xa2\\xf8aA\\xb2\\x88\\xc4ZN\\x92|\\x00\\xfa\\x10\\x05KF\\xb6\\x83\\x10X\\x1b#{\\x07b\\xd3\\x190\\xb3gN&Z\\xf9_\\xefs\\xac{\\xb1\\xb9\\x85~ J\\xb4Y\\x17J\\x01\\xb7M\\xee/e\\xf9\\x95:\\x87\\x03\\x0b\\xec\\xa8+2\\xb0,\\x0f\"\\r\\xff\\xe62a3\\x1c\\xba\\xd3\\x86\\xef\\x17\\xc4c\\xd1-\\xc5_\\xb7\\x11(\\x13\\xf5\\xb5!\\x03\\x05\\xde\\x95,6\\x0c\\x0f\\x85\\x17\\xb7\\xc4\\x9c\\xa4\\t\\x05\\x8e\\xfd\\xb5\\xf1\\x15\\xb2@;\\xae\\xe4zm|\\xf7\\xadu8\\x0b\\xd2\\x94\\x956\\xb1\\xd0\\x959\\x1a\\n\\xf6)d\\x7f\\x17\\xc2J\\x83|K<\\nx\\xee2\\xb2=\\x84\\x96\\xbc\\xbfiI\\xc9\\x9d\\xa05I\\xb3\\r\\x89\\x97%\\x8b\\x96\\x15\\xda\\xc3YFs\\x05&\\xfbQ\\xbe\\x8d\\xc9\\xc3\"JP\\xf5\\x0eC\\x10d\\xc1\\'\\x94t\\xb7\\xa1\\x0c\\xc0}\\xf9!\\x85n\\xe3\\x90\\xc6J\\x1ci;\\xfc\\xbb\\x8f\\xa3\\x1c\\x90\\xa0>\\n\\x96\\x85\\x86\\x96\\xc60\\xa6\\xc1X\\x17=\\x1b\\xf7:\\xf1\\x95\\xd0\\xdc\\xb7\\xe9?\\x0ci9\\x05D\\xad\\xe8\\x8a>\\x9c\\x81\\xc9\\x1e\\xceP\\xfa\\t\\xb9\\x05\\xcevJ\\xa9\\x1a\\x07\\xac\\x85\\x90fS\\xbc>\\xc6\\xe3\\xc3\\x19\\xcc(]\\x13\\xcc\\x8a\\xc96\\xa7\\x8b\\xf2G\\xcd\\x94@\\x9bd\\xf7\\xc6]\\x04\\xceV\\x80\\x87R\\xbf\\x07!\\x84f\\x87a\\x92\\xbaY\\x97\\n\\x0cY~r\\xbd\\xc30W`V)e\\xa1|^\\xeeV=n\\x9cz7\\xa5i\\x8cu\\x14\\x97\\x08(+.P\\xdc\\n*\\xc6\\xa0T\\x8a\\x99d<\\x858Q\\xeb\\x1a\\xb3$C\\x94=\\xb3\\xe7U\\x12C\\x17q\\xc6\\\\_;#\\xe0\\xc9\\xeb\\xbb\\xd9\\x00\\x83\\x8eJx\\xe0\\x10rd\\xb1\\x8a\\xb9\\xa8&M\\xfd]\\xbd|\\x03\\xa6\\xb8\\xfa\\xe9\\x8d\\xdfP\\xbay9\\t\\xec\\x94\\xc93\\xc5\\x9a\\x8d@V\\xa3yh6I\\xb6\\x9a.d^\\x8a2\\x02/\\x9e\\xb0\\xc5\\x00\\x97C\\x8f\\x90kC\\xb7\\xa6\\x92n\\x1a\\xfb\\x07\\xbai\\xf5\\x15;(\\x14^\\xa8\\xed&\\xda\\xd0\\xca\\xa1\\x10\\xdd\\'\\xee\\xf2\\xc44\\xdc\\xf8\\xfd \\x87Y0m\\x15\\xdf\\xf9{\\x11\\x9a\\x06f=\\x9a\\xb6\\\\w\\x9ez\\xa7\\xd4\\x1f\\xc7\\xca\\xd5\\xe7:^\\x80\\xfa\\xf9w\\'\\xc1a\\xac\\x05~\\xb3\\xf6\\xcb>:\\x9b\\x19\\x86{8[\\xdd~\\xebv(\\x07\\xd5\\xf1*\\xed\\x833\\x15\\x99X\\x8f\\xb4\\xbc\\xa7R\\xa1\\t\\xcc6Ae\\x95\\t2{\\xb5\\xb1\\\\\\xc9\\xf5X&\\xef\\xa4/*\\x1af\\xb3\\xd9\\xb2\\xe1+%k\\x148~\\xd4\\xfd\\x13\\xe0\\xe3N\\xf0W\\xe1iR\\xea1\\x86\\x9b\\xd6\\xea\\xcdM\\xf4\\xc9\\t\\x8b\\x92\\xae\\xc3\\xd9m\\xfa\\x11V\\xd5\\xf8\\x80\\x7f\\x80\\xb7\\xbf>x\\x12\\x0fx\\xa8\\x93U\\xb9\\x16\\xfb\\n]\\x9e\\x14\\x0bA\\x02\\xbao\\xa8\\xfd\\x18w\\x9c\\x97[\\x18\\x14\\xb9\\x11\\xa0(:\\x84\\xb6\\xf2]-\\x8f\\x1e\\x88\\x0b\\x9b\\xd2\\xa5K\\xbc\\x9bu\\x96B^S\\xf6\\x06Ap\\x0c)\\x90\\x89\\x12\\x8c\\xa1\\xcbFp\\xe0\\xded\\xf5\\x9a\\xba`\\xc6\\xbf\\xf3\\xbf\\x7f\\xe5n-_x\\x8e\\xf9B\\xcd\\x12x\\xa2\\x80>\\x12\\xe75\\x0c\\x7fR\\xe2\\x93\\x18s\\x7f?\\x88\\x01\\x08\\x95\\xf6\\xf7\\xda\\x00\\xa6|\\xbb\\xcdAZ\\x93w\\x97\\x86\\x85\\xcc\\xf8\\x84\\x86\\xa2 *\\xd1p\\xed\\x14\\x7f\\xc6|\\x1b\\xd9o^\\xc9M\\xf4\\xe1[s\\xbdt*\\x95\\xd5\\xd28\\x8e\\xb6y\\x94/\\xdba\\xa0i\\xdau\\xf5\\x13\\xdawk\\xbb\\xfbZ7\\xdfl\\x07\\xaeB^\\xd3\\xe9\\x14fma[\\x1d\\xc8\\xb2\\x9b\\xca\\xb2L\\x03/\\xb4\\x87\\xdf\\xdc\\xca\\x05\\xcc\\xf0Bg\\xf4KP\\xa58\\xb6\\x05\\x1d\\xff\\x1c;<\\x9d;\\t\\xe8\\x90\\xe3\\xc2p\\xf5\\xdd\\xcf\\xebc:;v-\\x1b\\x13\\xb2\\xdc]\\x11)12\\xf4\\x9ak+ \\xbc\\xbd \\xccj8>!\\x1d\\xd3\\x9e4\\xaa\\x0b0\\xe5\\xd9\\xbc\\xde\\'\\xd8?\\x99\\x99\\x9d\\xf8\\x15\\x1e\\xe1\\x1aIW\\x1b\\x8ev\\xc7?y\\x9dc\\xf9R\\xda\\x87o\\xe3\\xd5F\\x06>\\xa3\\xb2%n[2\\x08fr\\xcd|1\\x0b\\xf3\\x15\\x8fk\\xb5\\xb89\\xe6\\x81s\\xf5\\xc3\\xef~\\x13>\\xdf\\xb9\\x01\\xc4\\x9c\\x95\\xc7\\xb2\\xb8\\x91\\xc8/\\xe5\\xc8_$9\\xc8D\\x8bgR\"#f$hVq<\\xf1[y\\xe1q)n\\x06\\xba\"\\x06\\xdc\\xee\\x813\\xd8f\\xcev\\x01\\x94+\\xb4\\x89\\x91\\xfb\\x85<\\xbeQ\\xe0\\x8f_\\xd4\\x16\\xe6D/z\\x1bF?\\xb0*\\xe8\\xd5\\x07\\xcbm\\xe2\\x9aT\\x95^\\x81a\\xf5\\xfb\\xeb*a(\\xcb\\x9c\\x06FQ\\xd5\\xac>\\xec*\\xb5\\x9f\\x8c\\xf1\\xc2\\xc0\\xb7\\xf3j\\x8c\\xee\\xf0\\xb2b\\xf2\\xb7\\r\\xc0\\xf62\\x06\\xf7\\x13\\xc4\\xc3L\\xba\\xc3\\x1do\\x02(\\xbd\\xd3 k&\\xfd\\x85`\\x0c\\xbaQ\\xa4\\xac\\x98\\x0bw\\xb8b\\xaf\\xa2&\\x03L\\xee\\x15\\xf3&%\\xb8\\xee\\xea\\xa7k\\xda\\xe8\\'\\xc3 \\x86\\x01\\xef{\\x85h\\xc34\\x8f!\\x05\\xa8g%\\x9d\\tA\\xbd\\xbc) \\xbd]\\x96\\xc3\\xed6\\x8dxR{\\xc6\\x88\\x9f+]\\x90\\xc5\\x90HPj\\xe3ErQ\\x8c\\xf3\\x8c\\xa4k\\xfc\\x93#E\\xe1\\xd6=\\x18\\xa2;\\xad\\x0f\\xb5\\xa8\\xac*\\xf6C\\x0b\\xd9\\xd4\\xf8\\xee0t\\xe3\\xf5\\xa9d\\xc9\\x03`\\r\\xff4\\x8b@}:5\\x8d\\x8e\\\\PP\\x8b3\\xea\\xa0\\x87\\xe1\\xcd\\xad\\xac\\x0c\\\\\\xae7\\xb7yC\\x99\\xb1O\\xc3~\\xd0\\xabx\\xdb\\xcc\\xb7\\xeb\\x1a\\xc8\\xa1Y3L\\xf1\\xca\\xa9q\\x12PV]\\xc7\\x92\\x8c\\xdb\\xf2\\\\\\x14Y\\x87a\\xd0\\xe2\\xed\\xf6\\'W\\x0e\\x06\\xe1x\\x985R\\xf8e#\\xeav\\xd4ZXQ6L\\x03\\xa3\\xe7k\\xb7\\x81j \\xc8k\\x9f\\xcc\\xb4ce3)\\x01|/\\x12\\xaf*4\\x81\\x1c\\x13L\\xcb\\x18ZX\\xf6\\xe2P\\xbdD\\x1d\\xe0i\\xe3\\x02V\\xf7\\x0eh\\x1fr\\n)q\\x8c\\x9b\\xf6)\\xd3\\x05\\xee\\xe4[\\x88\\xef\\x8d\\xe1\\xa2w\\x95B\\x00k\\x15\\xd6\\xc7\\xa1\\xd39\\xfc\\x11\\xc4U\\xa2}K\\xb1:\\xd9\\xcb:\\xb3Z\\x17/\\xac6\\xb1(\\xc0S\\xe5\\xeal\\xac\\x1e!\\xb8\\xadvF\\xdec\\xb1]\\r\\xe6\\xdb\\x95\\x97\\xb0\\x1a\\xc4T\\xd4\\xe8\\xab\\x84\\x1c\\xabU\\xc1\\x13N\\xb0\\xf0\\t*O\\xda\\xd5Z}\\'\\x9c\\xd5*\\x88+\\xb6\\x1d]P\\xc99\\x9f\\xe2U&!\\x93i\\xfb\\x90\\x99\\'@\\xbb\\x98\\xfd\\xc9\\x08\\xabdb\\xdbv\\x93\\xdbM\\x9b\\xef:I\\xd9\\xe4\\xf1\\xfa\\xea\\xa8\\xddENo4\\x165k\\x99\\x88d\\xe0R\\xdc\\xe79\\x92\\x8e\\x95iQ\\x9d\\xfer\\xebk\\xc3`\\xb3m1w\\xe8\\xc5iNW.K\\xf6\\xcd\\xea\\xfc,\\x88!\\xd6jgn\\xd0\\n\\xb9r\"Yy)\\xc3,+\\x86\\x86\\xad\\xd5R\\xca\\xa4\\x99[\\x9fm\\xf22^,\\x86\\x96\\x859\\x84\\x9cE,K\\xaf\\xca\\r\\x0b\\x0b\\xed\\x7fw;Z\\xe9\\xac\\xb5\\xa3\\xa6h\\xf0\\x94k\\r,\\xacl[\\xc7y\\xc7\\x02\\x1bxrR3\\x84?Z6\\x8eqZ\\xc7\\xbd\\x13\\xbbQ\\x03Ox%\\x0f\\x12\\x13N\\xc1\\x9e\\xd7L\\xae\\xa3>\\xedR\\x94\\xd5_\\x7f\\xbb\\x1a\\xe1\\x8d\\xec\\xec\\xeb\\xd9\\xcd\\xb2\\x9bK\\x1f;\\xf9n\\x06?y\\xcfm\"J\\xb9\\xcb\\xa7\"\\xe2\\xaeK\\t\\xba\\ty\\x196\\xad\\xcf\\x92\\xacO\\x98q\\xdb\\xa8kGP\\xd2\\xd9*w\\'\\x90/\\xae~\\n\\xfdZ\\x0e\\xe5\\xc6P\\xc1|\\xf6\\xb6j\\x07\\xfb\\xb2?\\\\\\xe5\\xf7\\xde\\'\\x95\\xafn\\x91X\\x96\\x86>\\n\\x0b\\x9aZ\\xf5\\xce3\\x9d\\xbc~\\xc09\\xb6\\x9a\\xbc\\x04c\\x00\\x15\\x82\\x14\\xaf<\\xf2\\xe7\\tpuW\\x84\\xaa\\xe2V\\x8ai\\xa2\\xa7ij\\xab\\xbfw\\xbe\\x04\\xce-\\xbb\\x13\\xb4f\\xb2xb+\\x95\\x80\\xbb,\\xee\\x95\\xcf\\xfd`\\xb1U\\x9c\\xaeS\\xd3\\xb4\\xf1ia_I\\xd2AF\\xb7\\x94t\\x1c\\xf6\\x1d=\\xcd\\x17#S\\x06\\x86\\x85\\xcc\\x18\\xcc\\xc7RM(=1\\xc3\\x9f5[<\\x88\\xc7H_\\xb1\\x92\\x8d\\x91p`\\x8e\\'\\xd2JV\\xb5\\xd2t\\xd2\\xbd\\x92\\xc2\\x9f-\\xf1_\\xa0K_C\\xa2\\xfee\\x8b\\xae\\x92o\\x9b\\x19\\xe7\\xd1$y\\xc4\\xd91\\xaeZB\\xae,\\x033\\xdf\\x92\\x8c\\xb6\\x8fa;\\xf3O\\xc0~\\x05$\\xedOb\\x01\\x98\\xdf\\xf4}\\xed\\xdcM^\\xa3}F\\xa7\\xf8$\\x0fi\\xd9\\xb4\\xcf\\x1f\\xd6\\x19\\x85\\xbd\\xd4\\x0e\\xfb\\xaa\\x94\\x07\\xff~\\xbe{\\xe3\\x1eL_V\\x0f\\\\\\xb9\\xb95}\\x98\\xfe\\x11\\x06\\xb5\\xbd0\\x7f\\xf0t\\x14\\x88.\\xa8\\x97L} *\\xe7\\xcd\\xc0\\xcf\\xd2\\xad\\x9f\\xde%\\x83\\rMv\\x8dRG>G\\xc1\\xd3\\xc8v\\x9a\\xeb{x\\xb5\\x9e\\x07\\xf0s\\xaf\\xae0Q=\\x19\\x13q\\x8d\\xecX\\xda\\x15\\xfd>\\xf0\\'\\x03\\xf7\\x0b<\\x00\\xfb\\xf7\\x8d\\xdb|\\xbc\\xd5\\xed\\x9b\\x01\\xb0p\\xa2\\xed\\xb3TJ)\\xfa\\xd9\\xff\\xba\\xa9\\x02\\x90eY\\xa7$$\\x88/w\\x8fO\\xd8\\x8dSQ\\xa2,\\xc9\\x9b\\xa8\\xc1\\xc8\\x02w\\xbd\\xae\\x9dD\\x05\\x06^\\xcb\\xd3!z\\x82W\\xa9\\x063\\x9e\\xac\\xa1\\xed\\xe1{\\x11\\xfb\\x92#F\\xfd\\xa4r\\xdcH\\xb6\\xc6\\xe5\\xa4\\x13\\x8f\\xac\\xec\\xfa\\xb9\\xbc!=\\x9d\\x17N\\xde\\xec\\xc8\\xfd:\\x82\\xbc\\xf0b\\xd6\\xac\\\\\\x8e?\\xc3*\\xc5\\xde\\xfd\\x18t\\xcc\\x03H\\x0c\\x82$2[\\xb8(\\xbb4\\x8b\\x9f\\xee\\xb5\\xcd.\\x8fb\\xd8^u\\xec\\xa8WHK\\xd9\\xb70\\xb96^\\xcb\\xeeZ\\x83\\xe8x\\xc1\\xe0\\xfd \\x0f\\t\\x18\\x02\\xd8gN\\xd1\\xbb\\x19\\xe2\\xa4Y\\xe1\\x8f\\xd2u\\x8d_C\\xa3\\xbf\\x84`\\xee\\xdeDl\\xf0%S6\\xe9\\x87/\\x80\\x17\\x04\\xb12\\xd3\\x10*/\\x979\\x11Q\\xa2d\\xbb\\xabN\\x1d\\xb2\\xa2\\x1e=\\x82\\x15]\\x02x\\xdf\\xea\\x04\\xfc\\xb2\\x0c\\xf8Ly\\xb1\\xea\\xd0D\\x17E\\t?\\xb5m\\xa7_\\xddo\\x0b\\xb42\\x11L\\x91\\xa6\\x05\\xa9\\xa8\\x80\\x1dG\\x0c\\xc7\\x92sy\\x07\\xfb\\x18\\xb8\\x19%7\\x0b\\xfew\\xc0\\x1f&\\xc0\\x8e\\x17A\\xea\\xed\\xf2\\xd2\\xc3\\x17\\x15;r\\xca\\xfd(-\\xe5\\xd6\\xa6\\xb5Te*\\xaaH\\x9e\\xf1\\xf8y\\x9d\\x91\\xb2M\\xce\\xf0\\xea\\xb0\\x80\\x12\\xab\\xdd4 <\\xf8\\xad\\x8cU\\xe7\\xbcv\\xe5\\r\\x0exd]p\\x95@:H6Xg\\xc4\\x8f\\xc0\\x8f\\xf7\\x80\\x13\\xda\\xd9\\xd8\\xb7\\xf5\\x00\\x92\\x9d\\xf1l>\\xa3~\\x7f\\xd915\\xff\\xda\\x99\\xe9\\xd7N,t\\xbe\\x9a&\\xd0hH\\xb2\\x82(\\xf8\\x0f\\xe1\\xd2\\xb4 K7\\xbd\\x02g_ci\\xaf\\xc4\\xfb\\x11\\xc4_\\xb7\\xad\\xcf\\x9b\\xd5r\\x08\\x96>\\xb3\\x8f\\x11\\x1d\\'\\xefr^\\x8d\\xb6\\xe3\\x86@VI{vLq\\xe6\\xc2\\xf1\\xb8\\x1d1\\xe7s%lMf\\x84\\xce\\xbfF\\xc2\\xa7g~B\\xc2\\xa7\\'\\xfe\\xff$\\\\\\xe0\\xfdZ\\twl\\xab\\x8c\\xa5|\\xe4\\x0bE_\\xa1k\\x89\\xde\\x0c&\\xae;\\xe3\\x82\\x93\\x9dF-\\x13<.U\\x99=\\xe4\\xba\\x16\\xafu\\xa4A\\x91\\x06/D3\\xb8?\\xe5?\\x8b\\x1c\\xc3\\xa7\\x01\\xd9\\xc5\\xacp\\xa7\\x93.w\\xdav)\\xe0C?\\xea+9\\x1de=\\xf4EA\\xc6\\xfa\\xf28f\\xf5?;JZ\\xb2$k\\xcc\\xfd\\x8a\\xe4\\xbeFo\\xeeF^\\xbaoc\\xe9L\\xae\\xcb\\xc2d\"W\\xff<\\xe5l\\x9cB\\x15+L:\\xde\\xd6;\\xbe\\xec\\xe7e\\xd1\\x96]\\xf5\\x82]\\xe2\\xe1\\xfez\\xfd\\xfd\\x1dx\\xf9\\xf4n(^\\x1bu\\xf67\\xd7/\\x17Oo>\\xfc\\xf0\\xfb\\xbf~\\x1d\\x84\\xd7\\xde\\xcf\\x0fd\\xfc\\xf3/\\xeb\\xff|\\xfeT\\xbb\\xb9\\xfe\\xfb5\\x8c\\x99\\xfalnj\\xb3\\x89>3\\r\\xcd\\xb0&\\x86\\xad[\\x9a5\\xd3uslic\\xdd\\xb4\\xe7\\xc6\\x04Z\\xcb\\x9c\\xcef\\xd8\\xceu\\xc3\\x84vl\\xccm\\x1b[kl\\xf3\\xfb\\xc9|l`;\\xb7\\xc6\\x087\\x99\\xcc\\xc68ojN\\xa7Sl\\'\\xe0\\xf9\\xb0\\x9d\\xcd\\xa7:\\xb6\\xf3\\xc9\\xa4h\\xc5\\xbd=\\xb7\\xe6\\xa2\\xe5xf\\xe6T7y;\\x9b!\\x9e\\x99\\x058y;3\\xe7\\xbc\\xb5\\'\\xb8>\\xacb\\xcfxkO\\xf8\\xf8T\\xb7Ek[\\x86h\\xf9>g\\xf31\\xc7\\x0f\\xedd\\xca\\xdb\\xd9t\\x8c\\xadm\\xe8\\xfc\\xde\\x1e\\x8f\\xf9z6\\xec\\x98\\xb7\\xf3\\x99\\x18\\x9f\\xdb\\xa2\\xb5\\'\\xd8\\x02\\xf9S\\\\on\\xe8V\\xd9\"\\xfe\\xb9\\xa9s\\xbcs\\xd3\\x98Nxk\\x1a|\\xdc\\x9c\\x1a|\\x1c\\xd8\\xc2\\xc7\\xc7\\xc0Q\\xdeZ\\x9c\\xbfs\\xd87\\xef\\x9f\\xe8\\x13\\x0e?\\x99\\x9a\\xbc\\x9d\\xea\\x86\\xc1[\\xd3\\xd6y;\\x9fr\\xf8\\xd9\\xcc\\xe0\\xf3g6\\x97\\xc7\\x1c\\x11\\x8a\\xd6\\x9a\\x8av\\xcc\\xc7\\xe7S\\x81\\xdf\\xd6u\\xd1\\n:\\xe7\\xb6%\\xf6m[\\xe3\\xe2~,\\xee\\xc7s\\xbe\\x9e=\\xe1|\\x9a\\xdbS[\\x8c\\xcf\\xc6F\\xd1\\xda\\xa2\\x9d >@;\\x9f\\xf2\\xd6\\x9a\\x98\\xbc\\x1d\\xeb\\x86h\\xb9\\xdcm\\xe0\\xd4T\\x9b[\\xe0ml\\xd1\\x9a3\\x8b\\xb7\\x10\\x025\\xa0v:\\x05\\xbd\\x82vf\\xc1\\xba\\xd8\\x8e\\x81O\\xd8\\xce\\'s\\xd1\\xda\\xfc~\\xaeO\\x8av\\xca\\xe1\\xe7\\x13\\xe0\\x07\\xb66\\xf0\\x0bZ\\x1b0\\xf1\\x16\\xf5\\xd5\\xd0MXpn\\xa1\"\\xc3\\x0f\\xd3|\\xaa\\x81i\\x85\\xf8\\x8e\\xf2B\\xd7nr/_<\\xf5l\\xcf6\\xe6\\x81\\xbe2\\xc7O\\x0fKa\\'\\xc3\\x9b\\x1f_9O\\x83\\xec\\xe9\\xf2\\xd0\\xef\\xf5\\x97\\xb2=\\x15\\x00\\xb1\\xe7\\xbc}WB\\xc7\\x91\\xa3\\x97\\xbf\\xd7\\x94]\\xbft\\xaa\\t\\xa4\\xbf\\x0f\\xd2\\xacwK2\\xc5]\\x92\\xf3\\xf3\\xde\\x13\\x82 \\xcf\\x19\\xcb\"\\x17\\xca\\x8e\\xc7\\xc7\\'=\\xd7\\xa9\\xf7\\xf5T\\x1a\\xf9j\\x1f\\x02U\\x9f\\xc0\\x90\\xf0\\x1e\\xbf\\xa6>\\x14\\xa4\\x94\\xed\\xb2Dq\\x1f\\x1f\\xcb}^\\xbfw$\\xfe\\x08\\x08\\xfe\\x96~\\xbeP\\x1d\\xa7\\xf0?\\xe0\\xebxA:\\xdcf)\\x83j1\\xae0l\\xe2\\xf6t\\x05\\xb7[A\\xdcI\\x10Ds\\xfb{\\x96=\\xec\\xab\\xd9\\xbd\\xeb,\\x032I_{b\\xc0\\xe0\\x01\\xd6\\xf1\\xc2\\x9e\\xd7\\xdf\\x1f*\\x04,\\xda\\xd0\\xf6\"\\xbd\\x84\\xde)\\xdf\\x13F\\xfbH\\xfd\\x1b\\x80\\xe9\\xf5\\xab)P/\\xd6\\x16\\xd5<\\xcd\\xd7\\xd6\\xfd=q\\x8e\\x00\\x7fe\\xb14\\xb4\\x8c\\x82\\x9e\\xaa>q\\x80\\xed\\xc0n@\\xfd\\x12s\\x80%\\xb2\\x9fV\\x93<-\\xa8~GK\\xfa6x\\xe7\\xb8Kw\\x08\\xe1\\x18\\x89p\\xf0\\x17\\xd4+>\\xffA X3y\\xdb>\\x8d)\\xa3\\n\\xce:,k~\\xfd\\xfc\\xbcv;\\xbc\\xa5qG\\xd70\\xdeu\\xf7\\x02\\xf3`\\x13y\\xe69DR\\xe9\\xe0\\xc28\\xc8\\xfc\\x00r;Y\"(TU\\x89\\xb4\\xfc\\xf1QU\\x97\\xde\\xe3\\xe3\\xc0x\\x02\\xb4\\xe4\\xfc\\x03\\x8d\\x9ezN#G\\xed?>\\xf6\\x00\\x9c\\xff\\xbe\\x90\\x8d\\xa6\\xe7\\xf7\\xb5\\x81\\xe1\\xc8\\xf01\\x9f\\x00Z\\xeb;5%\\x07P\\xec\\xa5\\x17N\\x01s\\x81\\x89;q`Eu\\xa4^\\xf4\\xd6\\xd0\\xaei\\xb22\\xf5\\xb1\\xda\\xbfP\\x9f\\x11\\xf6\\xb0u\\xa2s\\x8f\\x01$\\xb9P\\xcf=`\\xb1z\\xe1^\\xd0\\x8b\\x00\\xee>\\xdc\\x1fw\\xc2\\xb8\\x1a,G\\xff->3\\x89\\x86\\x8c\\xe6\\x0c\\xf8s~.+|\\x0fWoj\\xa0JT\\xae\\x83{`\\xe4\\x82h\\xebx\\x83/@\\xf75\\x02\\xbc\\xe9\\x97\\x96C*\\x8e>8\\xfb\\xea\\xf7}C\\xc1K\\x90\\xb7d\\x18\\xf9\\xef\\x9c\\xb7\\xd0\\xf9\\xae\\xc0\\xf0\\xc48\\xca\\xe4\\xbd\\xec\\x83Pm\\xea\\xe29:\\xab\\xf7\\xc3\\xed.\\x0f{o\\xdf\\x92w8\\xf0N\\xd6s\\xe2?\\x8f\\x9b\\x06\\xd6\\x9c\\x87\\xeb\\xc3\\x9cC\\x7f\\x08Y^\\xdcca\\x94\\xd7\\xbd\\xa2p2o\\xdb~\\xd2\\xab\\x18\\x07\\x19\\xe2\\rx\\x16XO\\xd5T\\xdf\\x8b\\xd5\\xfe\\xa1\\xda<\\xdc:O\\x0c\\xe9\\xce\\xab\\xb9\\xb1c\\xff3\\xd2\\xeb/\\\\\\xb1+\\x02;*\\xa1\\x14\\x0f\\x96\\x04\\x03|r\\x04\\x95\\xa79O\\xf0\\x8c@\\xf8B\\x02z\\x02*\\x16F\\x01\\xeb\\xa1\\x83\\x03\\xa3?\\x14F\\x01\\x85\\xf7\\xf5-\\xb8\\xbaWQ\\xce(\\x98\\xe4\\xb3\\x9e\\x0f\\xd5\\xfc\\x06zZC=\\xf5\\xfb\\xdf~)>\\x10z\\x054Q\\xa0\\xca\\x03\\xe9\\xf7\\xb5\\x13\\xb8J\\xd29P\\x7fQB1F\\xbc\\x90\\x03V\\xa6)\\xf5\\xf5T\\xe1\\x0f`Z\\xbf\\xc1}\\xc8\\xf9D\\xb2W$}\\x8a\\xf8:\\x8a\\x7f\\xd9\\xf4\\x0f\\xb9%\\xa2W\\xbdj\\xc2]5\\xc5F\\x8e\\xac\\x0e\\x8e\\x11\\x03\\xbc_\\xe1\\xf7\\xafc\\x8a\\x1c\\xd0|\\x1eC4\\n\\xa1\\x8e^\\xae\\x87^\\x18\\xc5>\\xc6\\x84|\\x18\\xd3d\\xcd\\xc2%\\xbd\\xb8\\x10\\x18CG\\x1e~K\\xdf-\\x07\\xc6eOU\\xd4\\x8bp\\xe8\\xc5$\\xcf\\x7f%\\x1bz\\x01\\xf7\\xfd!?\\x82\\xf8\\r\\x1c\\xa7\\xd2<\\xe5U\\n\\xbb\\x0f\\xfb\\x07\\x15\\xcb\\x15\\x08%\\xfe\\x90\\xa7\\xb9\\xc3\"\\x8f\\x06\\xe9\\xd4;\\xd0\\x07\\x1d\\xddUO\\r\\xb7d\\x03\\xbav~K\\xd1\\xda\\x83F\\x8c\\xf3\\t#\\x03\\x18\\xc28\\xb7ha\\xe2K\\x1e4\\xd0iU\\xa0\\x04DPUl\\xd4w\\x9a\\xe7\\xa0\\x04\\x96\\xee[\\xfd]\\x04\\x8a\\x07\\x11\\xd4\\x1b\\xd2{\\xea\\xfd\\xc9\\xf9\\xfb\\xf8(\\xdf\\xf5Td\\x08\\xb8\\x1a\\x00\\xeeW*x\\x03\\xdeV0\\rH\\xbc9j#\\xa8c9\\xf0\\xf8x\\x9bB-\\xa8;\\x8eC\\x9eyoo\\xde=\\xf3\\x1cl\\x16\\xa2\\x01\\xdf\\xb1\\xe0-9\\xa9\\x16#\\xf1\\xe1\\x1b>wS8\\xd7\\x1d5\\xcc\\xb3\\xad\\xaa\\xb8k^\\x9a8*\\x1e\\x97\\xc3?U\\x11\\x95\\x86\\xa8D\\x1cU/;x\\xc5\\xc1\\xef\\xa1\\x06\\x11]xwu\\xe9G\\xb7J\\x04.\\xd9%\\xd9\\xd5%~\\xc3\\x00\\xcb\\x14k\\xac]\\xe3\\xaa\\xfa\\xda\\xecr\\xe4^)\\x97\\xe48T|\\xe0&\\x92\\x83\\xd1\\xe8\\xee\\xae\\nDA6\\x12^\\xffY\\x08\\xce(;g\\xee\\xc6\\x89r/<\\xcf\\xd3]\\xe6Q\\']\\x9f3\\xe2B6\\xa1^\\xf1\\xb8\\x9a_\\x8e\\xc8Gqo\\xc86\\x97\\x90\\xe3m\\x89\\x1a\\xf1\\xc4\\xea\\xd5/\\xd0\\xf5),\\xa8\\x0c%\\x16/\\xdd\\x8cd\\x14s\\xf5\\xea5\\x0c\\x7f\\n\\x05\\x12\\x89\\xef\\xad\\xec\\\\\\x81B\\xbc\\xda\\x90?[\\xc7\\xce\\x0f\\x7f\\x08L\\x86z\\xf5\\xeft\\xf7\\x06 >\\x85\\x0c\\xf2\\n\\x99\\xaa\\xe4.\\x0f\\xb7\\xf2\\x9e\\x12\\xf5\\xea\\xb9\\xc7vP\\x87\\xb3\\xff\\xfd\\x0c\\x16E\\xb1L\\x1c\\xde\\x8f\\x9eq<\\x1b\\xf5\\xea\\x05\\xde~\\n\\x85\\x9fA\\xf1[c\\x10\\x9f\\x9e\\xaaW\\xdf\\xe3H{:73\\xe1\\xa6\\x9a\\x8f)\\xd4\\x8fjG\\x94\\xb0x\\x04m\\xba\\xc5\\t\\xf9\\x08\\xd4p\\x07\\xfc\\xdf\\x01\\x95\\xbb+\\xe5<#\\xefw\\xe9\\x12\\xd7\\xbb\\x1c\\t\\x8d\\x1c\\x81\\x96\\x1eU\\x15S~E(4\\x16\\xe9\\xa5\\xda\\xe2cE\\xa1\\xcaI\\xb5\\xc9\\x08}&\\xf4\\xcb\\xa3A5\\x1at\\x8c\\xd2\\xaa\\x8fH4\\xb4H\\x00\\x03ei\\xf6\\x80$\\x80>\\x08\\xb1\\xa9\\x15\\xe2\\xf1\\xd5\\x8f|\\xba\\xbb\\xafGwe0\\xe86E\\x91/\\x03\\xecAI\\xac\\xb4\\xef\\xca\\'\\xb5\\xda0}x\\x9d\\xbfI\\xb6\\xba\\xbb\\x07w\\xb2\\xa8\\r\\xab\\x9939}ag\\x0b\\xd8\\xd3\\xde^\\x07\\xdbcN\\x9c\\xa8\\xd0!\\xf2\\x01\\xef\\x8fg\\xe1K\\x87Q\\x1d\\xc6\\xfcp\\xbcl\\x19\\xf3\\xcf\\xba2U\\x17\\x0f\\xbd\\xef=\\x07%4l\\xfb\\r\\xc7\\xff\\x15geC+\\x0c}\\xbf\\x176l\\x857\\xd6E\\x04C\\x975*\\x0fK\\x9e\\xeb.\\x86\\x8e;\\xcf?\\x16a[\\x1a\\xa3\\xe4\\xc1\\x94t\\x05\\xfd\\x82}I\\xb3\\x89\\xf7T\\x95l\\xd30]P\\xb5\\xd1\\xce\\x1d\\xc1\\xb8 \\xccA\\xc9\\x8cq\\xbe\\xbf\\x10n>\\x85\\xee\\xdd\\xfdD\\xbdr.\\x8aKV\\xa5\\xdb\\xd3D\\xc0\\xce\\xb3\\xbc\\xe8\\x08\\x06\\xf4\\xf2\\r\\xe6\\x91e\\xafhD\\xb0\\x0c\\x86Tv\\xbd\\xe8_\\x14\\xe77N\\x97\\xc5\\xf4\\xc7\\xa3&\\xa1\\x85\\xd8\\r\\xdc\\xaf&7\\xd2\\x93%Y\\xf5\\xfd8\\xe8*\\xf1\\xc2\\\\2w@\\xc8\\x9e\\xcf\\n\\x16E\\x9bVNT\\xad\\x962\\x87G\\xc5W\\x11\\xed\\x0f\\xa9lj\\x9f]\\xfe\\x90\\xca\\xa9\\x1a\\xd4\\xd4\\xb7\\xe0Jv\\xb0NM\\x1f\\xaeA\\xd4uqp\\xf5\\x94\\xa8\\x0fm9g4\\xaeN\\xf7{\\xb4\\xad\\xe8\\xd3q\\xf2x&f\\xf6\\xc9A\\xe20;\\xc0\\xc9/0\\x08V\\x89\\x91\\x17\\x11\\xe61\\xca\\x10s\\xbc/1#\\x1aQ\\x8a/\\xc3\\xfe\\xcf\\x05s\\x05\\x9e\\\\Z\\x87_\\xd6&\\x83\\xeb&\\x12\\x14WHm:\\xa2\\x9aDCzJ\\x90[\\x06~CfMgiZS[\\x90\\x04\\xd3\\x98\\xc0\\xb2\\r\\x04\\x0f;`\\xc47\\xea\\xc2\"H\\x1e\\x12G\\x18I:5]\\x8d9\\xe9\\x04\\x00k`I\\xa3\\x98Pw\\xd8\\x97,\\x98\\x9c!\\xf4\\x01d\"\\x18\\x96\\xf0\\xaf\\x9e\\xb5\\xf7\\x1f\\xe8\\xd4S\\xfc\\xefm\\xc1\\xe8C\\x0b\\x13\\nOtw\\xd4#\\x16>\\xf0\\xb7\\x80\\xd9;\\xa0\\x9f\\xe8\\x8a\\xe0\\xa8d\\x04s\\x16\\xffBQ\\x8b\\xec\\xc0\\x18\\x99\\x0eha\\xdc\\x8e\\x19\\x8b\\xc5\\xfcI\\x83\\xbe\\xa2\\x00\\'0lmM\\x1e\\xaa\\x18\\xf2:|41\\xa8\\xd7\\xabt \\xae \\xbb\\xfa\\xa4.[\\x86\\x9a\\xbd\\x16\\xfb\\xd5\\xb3h}\\xb7z]Q\\x8e\\xa9|ry\\x88\\x16\\x1el\\xf4\\x1f \\xfc\\xbd\\xed$\\x0c\\x9f\\xdf1G]TX\\x1b\\x00\\xd7\\x06b\\x88a\\xa8\\xc6uA\\x99\\x0e\\xf6\\x9f\\x82R\\x19)\\xd1\\xdd\\x06\\xed\\xe5\\xa5\\xccq\\x99\\xea\\xc9\\xa1\\x9c\\xa8\\x1ee\\x13\\xfb\\xf5N\\x06@\\xe9b\\xa3\\xef\\xea9\\x19\\x19\\xef\\xe9t\\xad<(\\xaf\\x90e\\x17v.\\xe1\\xce&m[\\xad\\xa52\\x83\\xb3j\\xbes\\xa4\\xe4n\\xbb\\xd5\\xec\\x04\\xd4\\x8c\\xb6\\x0b\\xe2@\\xc0\\x96\\x85\\xa2\\x80^\\xdb\\x05\\xa5\\xbf\\x03\\x14\\x07T\\xebu\\xa9\\xa8\\xd6\\x19\\x1c\\xac\\xa0\\xa26\\x9dM\\x1aW jnv5\\xcd\\xb0\\xef\\x1e*\\x85D\\xfb>Y\\xbdIL<*\\xc2F\\x9f\\xd8\\xae\\x9eX\\xe16\\xa3i\\xbc\\x9b\\xc8@\\xef\\xe87\\xc5\\xc3\\xb7\\x13\\x98\\xf0\\xe1\\xfb|*\\x99\\xfa(\\x84\\xd8\\xd58\\x15K\\xc6\\x92Q\\xe8mx&}9\\xf5$\\x8c\\xa1\\xafVY\\xa9\\x1f\\x88\\xfb\\xdf\\xac\\xb2R\\x1f\\xb65\\xbb%\\xf7\\xa4\\x8b\\xee+\\x95\\x95\\xf0\\xa8nGu\\xa1\\xab\\xe6E>=\\xa8;U\\xf1\\xa0s{vY\\xef\\xcb\\'\\x8dQ2Q\\xbd\\x99\\x1d\\xf7\\xda_Hu\\tcb3\\x8a\\xcc\\x1d@\\xbeAE\\xe6\\xc3\\xf7\\xe9\\xc2G\\x9b\\xab3\\x01\\x05\\xc6V\\x89\\xa6\\xa1\\x9ab\\xb9\\xc8\\xefA\\x0b\\x81w#\\xaaK\\x1a(\\x1d\\xb6\\x8d[\\xd1\\x853\\x8c\\xc1lZtL5\\x82\\xbd\\x91,PS\\x08\\x86\\xa7\\xe1\\xba\\x0c(7\\xbe\\x82\\xe2+4\\xa0\\xb2\\x18\\xc2\\x84\\xb0F\\xa1\\xb4kS} \\xd8\\xaeb\\x08*\\xb1HQ\\xf8O~\\x13d\\x12\\xc2%\\xd1\\x804\\x99\\xae\\x14dg+\\xb5\\x9b\\xf7\\xf0st\\x02\\x19&\\xd1Y)\\xcb0F6\\x9b\\x122\\xac\\xda\\x01\\xe4\\xb8\\xa7\\xcb\\x91YT#v\\xd4\\x94\\xee]\\x80\\'\\n\\xa8\\'\\x92\\x1b\\x95\\xc6D\\x8e.\\x86\\xf7\\xcdl^k\\x8a\\xf5er\\x1e\\xda\\xc3kRKM\\xaf*\\xbaD\\x0e\\xdb\\'G\\x83l\\xb7pz\\x99\\x1b\\xcb\\xcc\\x83\\xe4\\xf5\\xc6\\xb3\\xb9\\xb3\\xde\\xa01\\x03z#`o\\x84\\xa5\\x05\\xe8)@V\\x9eC\\xb6).\\xff\\x02\\xde`d^\\x81\\xb3\\xe0H\\x7f\\xb5\\xfc\\xff\\xe8N,\\x7f\\xb3\\xfc\\xbf\\x93=P\\x86\\xb6}~ig8\\xff\\xdf\\xcc<\\xd9\\x8eX0\\x9e\\xce\\xa3\\xea\\xb1y<\\x99\\xb9\\xd9D\\xbe3k\\xd7\\xecV\\xb4\\xdb\\xcfL\\xeeo$5\\xbf+nM,l\\x0eO\\x9b\\x11\\x1a&\\xe0e\\x03B#\\x95\\x10tc\\x1c\\x13R\\x89d\\x1a\\x19\\x0c\\xe3\\xbe\\xb8I9\\xc8k\\x88\\xed\\x08\\xcc\\xdc\\xac\\x00t\\xb0\\xdb\\x1b\\xa3M\\x17\\x84\\xd8#\\x80f\\x13\\xa8a+\"\\xa2o\\xc5&tHML\\x85\\xc1d>\\xde\\xc5;\\xeb\\xf3~\\xbb)6\\x97i\\xf6\\xbcN\\xc7\\xc7m\\x9a\\xb8\\xea\\x0f&\\xd7\\xbax\\xd0R\\x9b7\\x8d\\x9bl\\')\\x8bK6\\x08_\\xd3\\xbf\\xc4>=B\\x9f6\\xcd\\xdaW\\xc3\\x1b\\xec\\xc3W\\xcb\\xb7\\x8f\\xcf\\xc5\\x83o\\x96o7m\\xe5\\xc0\\xd6\\x12\\xed\\x93Ky\\xc1\\xb7_ \\xde\\xed0\\xe4\\xf1S\\xe2\\xae\\x94\\'\\xd5\\xcb|F\\xbf==\\xcd]\\xd5\\xb5C\\xb3\\xa9\\xf6\\xad\\xf1\\x90\\xe4\\xe4\\xe8\\xf6\\x19\\xf2\\x0b\\x08\\xd8\\x0c\\xa7\\x9d\\x00\\xc0\\x9b\\xb63z\\x1cU3\\x06T\\xa6\\x04\\x93\\x81\\xf4@y\\x06\\x16\\x1a\\xb2/2\\xb3aH\\x0f|\\x94-\\xea\\xa0\\xfb\\xddd\\xfc7&\\xd45\\xceT\\xb1B\\xa8\\x9d%(\\x81\\xf7\\x0e\\xd4&\\xb9\\xb8\\xf9}GP\\x98n\\xce\\x96\\x02\\xc8\\x8d\\x81\\xef\\xa2\\x99\\xd2\\x92\\xfaT\\x06\\xdd\\xdc\\xc2~\\xd8d\\x80\\xb6\\x03\\xaf\\x95\\x9d\\xb5\\xcd\\x8c\\x9f$\\xc8\\xb48\\x06Y\\xc1\\xb2h(\\xe4v]\\xf5\\x1a\\xedEMfu\\\\\\xa6\\xc4\\x9a\\xbb_=\\xbdJX\\xe3h\\xf7\\xf8\\xbe\\x7f\\xefv\\xf5\\\\\\xa5Ro\\x1fYwh\\xec\\x9e7\\x8e\\x98\\xf5\\x94\\xd4g\\x8d]\\x9f\\x86\\xe5\\xf0\\x81\\xe0n\\xdf[%\\x96\\xdau\\x1d\\xaaQ\\x9b\\r\\xdb\\xbb!cF\\xc8\\'s\\xec\\xac\\xd7\\x12\\xaf\\x065gVO\\x11\\xda\\xb7\\xc5d\\xe7\\xd8\\xbe?\\x11\\xd5\\x9c\\x88ka\\xde\\x15t\\xa6.:\\xb2\\x19(O\\x0c\\x07F\\x8b8\\xb7V\\xac\\x9c\\x8e\\x89\\x85>\\xff\\xc3\\x8f]\\xac\\t\\xe2\\t3Y>\\x91\\x91\\x0333\\xbeq\\xfb\\xa7\\xa94\\xcd\\x9fh\\x9d\\xc3\\xc6\\xedY\\xaa\\xda\\xd7Ng\\xe70x\\xac\\x1f\\xa8DX\\x8fl\\x9f\\x88\\xbcb\\xcb\\xda\\xbb\\xcc~\\xa0\\xb49\\xaec\\xd8\\n\\xd1\\xa9l0&\\xcc\\xee\\xa3\\n\\x89\\xe2\\x93\\xf8;V\\x83\\xa9\\xa6\\xd8^\\x86\\xf6h v\\xef\\xb2n\\xe3\\xce\\xack\\x83\\xd3\\x93#Q\\xdd\\xddMWf\\xf6\\x85\\xda^\\xe2\\x16\\x7f\\x08\\xc7\\x00\\xfa9v\\x06\\xc7\\xf4\\x04:\\xb3I\\xa3\\xde\\'\\x01\\xff&\\x96t\\'3\\xb4\\xb2|\\xa3\\xaa\\x81\\x9e=:(\\xec\\x0f*\\x07N\\xd8\\xff\\xf8:\\x9a\\xde\\x8e\\xa2\\x90}J\\xf3\\x87\\xc6}&\\xf3pQ6\\xedf\\xed|b\\x99\\xc9Yn7uqQ\\xbdl\\xa8\\xdb5\\xe8\\xbd\\x0e\\x0f\\x9b\\xd1\\x17rM\\xe4\\xc8\\xef_\\x99\\xe5\\x04il\\xb1\\x95Y\\xe2S+3\\x03\\xb9>3\\x08\\t\\x1d\\xb6%\\xc5S\\x14L\\tVj*\\x08\\x04\\xd3$\\xc2>\\x88\\x04\\x85\\xc6\\x842\\xf0P&%\\x1c\\xec\\x98\\xb7\\x84C\\xbe\\x12^\\x9d\\xed\\xcc\\x97s\\xca#z0\\xe1\\xe2\\x11\\xd7z\\xe8\\x8e\\xb4\\x88L\\xc6\\x06\\xb5\\x82\\xa6\\xaa\\xad,\\xcd\\x06\\xd4Q\\xdd\\x9eG\\xf5h\\x93}\\x0f\\x13.4\\xc5\\xee\\xb2T}\\xc8]ej\\xd3;\\xe3\\xd8\\xd1k\\xe5\\xeb\\xb3\\x199)\\xd4k\\xd3\\x87V\\xf5n\\x12\\xd9\\xabR\\xa7\\xe6\\xf6@rzm\\x07\\xf9`14\\x10En\\x12eJY\\xb4\\'\\xd9DY\\xe5 \\x0bc\\x1e^]\"t\\xaad\\r\\x851\\xb1\\xec\\xa5\\x0f7\\xa5@{k@h\\x7f\\xd4#S\\xcb\\xd0y\\xd8\\x10\\x98\\xe11S5\\xe3\\xf6\\xd4v\\xc8\\x88x\\x0e\\xaa\\x07o\\x83C\\x9e;\\xa9\\x98\\x0b\\x0b\\x85\\xf2:&\\x1e\\x904\\xf5\\xa6(//\\xfe\\xd2d\\x12E\\xb5#w\\xee$w\\x9c\\xba\\x9e\\xe5.\\xaf\\xa7jf\\xbb\\x1b\\xb9\\xdf\\x89\\xa0\\xcd\\x08\\x88& d\\x03\\x02\"Y\\x80\\xc5\\xbc\\xe90\\xb3J&hVi\\xa1W\\x1b(H\\x97(:O4I\\xc0\\xf4\\x15\\x84\\xf9\\xa8\\x81\\xa0F8\\xf3\\xec\\xf06\\xa6\\x0f\\x03\\xe7\\xa3\\xff\\xbf\\xa0\\xd9\\xf2\\x8f?\\xc1\\x8c4\\xd9(\\x98\\x06\\xb5\\x81a\\x15\\xa1Zv\\xc1wO\\xb1\\xe8C|{\\x16\\x1f,a\\x02\\xab>\\xe1\\xc7)\\xb6\\xdd\\x14\\xcb;c\\n\\x8cn\\xe4\\x95\\xa8\\xb7~\\x82\\x96]4\\xebC\\xb5\\xb8\\xad\\xaa\\x08z\\xb6\\xae\\xbc\\xd5\\xd0\\xf2y\\x96<\\x9e\\x9e\\xc15\\xff\\x98e\\x18\\x0e\\xcc}\\xe6M8\\x83\\x19RN\\xc3\\x8fX@:\\x89W\\xce\\x98\\x02\\x11]\\xa8\\x11\\xec\\x8a\\xe8\\xec\\x8f\\x89\\x01\\xb0@L\\xae\\xbf\\xc8i5\\xc5\\xc1\\xf2\\xe2\\xf6\\xfe\\xaa\\xd3~h\\xcd\\xae\\xcfG\\xa3\\xa3\\xa3\\xe3\\xd3\\xab\\xd6\\xee\\xe5iFI9\\x17\\xb5e[;\\xfa\\xd6\\xbc.\\x84\\x163|o\\\\\\x93lJ\\xde<\\x83,\\x941\\x88 DOh\\xf5\\xf2\\x03\\xc7P\\x08?_\\xad\\x9c\\xe9\\x1e\\xe0\\xe6\\x95oT\\xceL\\x8e\\x8f\\xe4]\\xe9\\xf0|\\xd8M\\xcc\\xe5\\xcc\\x0bS\\xc3\\x9b\\x1cK\\xd3\\xc3\\x9f \\xc1)2\\x9f$K\\xd3d;\\x12\\xa8\\xf3t\\x1a\\xd5\\x1a\\xddkz^;?\\xc9\\xb5\\xd2e\\xe3h\\xbf\\x91/O\\xa6\\xa9\\x94\\xd5\\xb1[\\xdb\\x93@\\x1b\\xe7*\\x9b\\x91G\\xe7\\x80\\x9eM\\xc8\\xa3]a$Q\\x90F\\x89|P\\x1a\\xfd\\xd7s<\\xe9\\xbfq\\xc9Q>=\\x11*g|\\xa3P\\xf2\\xa3\\x00u4\\xfd:\\x90e\\xe9\\x92\\xab\\x08\\xc7\\xb4/\\xab\\xd4\\x99\\xc5\\x84tLh\\xbbL\\xa6\\x10\\xdb\\x89\\xca\\x9e\\x9d\\x1f\\xf9[\\xa8\\x91\\x9f\\x85\\x16\\xba\\xff\\xfd\\x13\\x04\\x83G\\xfd\\xd1\\x92\\x80?@3\\x85\\xdcn\\xf6cL\\xa8\\xa3u\\xce\\xb4\\x1e\\x1d2\\xdf\\x00\\xb6q\\xa3?\\xcc\\x15T\\xf0c#I1\\x8d\\x89\\xa4\\xb2\\xb1\\x960\\x9b\\x02\\xe8\\x13\\x9e\\x86\\xc15\\x90\\xb5O`\\xa8\\x97Mq\\xb8\\xac[5\\x1a\\xc6\\x904N\\xaf\\x0f\\xfbV\\xdeN4.G\\xe3\\xf4\\xfd\\x89\\x06\\xf7xZ\\xe6\\x97\\x9a1\\x11\\xce\\x8d\\xdf\\xbaa\\xdf\\xfa\\x92\\x8fV\\xd6\\x0cW\\xf1d\\xf7>\\xc0\\xb0)I\\xf1\\x02>B\\xab\\x91\\x05Z\\xbeZ\\xb9p1\\x13\\x8f\\xbeY\\xb9P\\x16\\x9b\\x1a\\x89V\\x9b\\x15W\\x9e\\xcb\\x857\\xd0\\xfav\\xb8\\xfc\\xcd\\x8a\\xb9\\xa0+\\x8d.\\xd9M:\\'\\xe9\\xecQK\\xee6;V#\\x17\\x9d\\xe6o\\xb3\\xed\\xed9i\\xdf\\x80\\x88\\xcd\\xf0o\\t\\x00\\xdf\\x04\\xff\\xce\\xcf\\xd7\\x13a\\x0e\\xfe\\xe2v/\\xe5qL4\\xc34\\x1f\\x17[\\xb9\\x84{\\x97\\x06|\\xb1\\xdc\\xf7\\x1a\\xdc!\\xbb3w\\xd1\\xda:\\xed\\xf7\\xf9\\x86Z,\\xc9\\xbc\\xb8=\\n5q\\xee\\xc3\\xf7}y\\xa1\\x1em\\xfe\\x06\\xfd\\xbdX\\x15.4\\xf8\\x07\\x8a\\xfbI\\xde\\x1d7V\\xa2 0\\xfd`|$\\xab\\x08,K]N\\xb0\\xbe\\xfb4+K\\x98\\xfd,e\\x08\\x0f\\xa7#M\"\\xf7\\x9b\\x87)\\x04Xl\\xdb\\x90\\xe9#\\\\\\xfd\\xfb\\xafH\\x90\\xe9EBAN_\\x1f\\xa5r\\x1el)\\x1c?\\xc1\\x9cS\\xee\\xfeH^\\xe2\\xcc,!Z\\x9e\\xb1e\\xe9e\\x9a\\xe6{\\tx\\xf0\\xf2u\\xe2r%\\xdb\\xf5\\xfa\\x15\\x1d\\x88\\x11\\x0e\\'\\x92\\x92W\\x19\\xf7\\xe9\"\\x89\\x9a\\xabC@\\xac\\x0c\\x17\\x9a\\xe4\\xf1B7\\t\\x1c\\xf7V\\xae\\x0b\\xdbt\\xb0\\x1f\\x80\\x8d\\xd7\\xf5\\x14\\xb4``\\xd8\\xed\\r\\x93\\xe3\\xd9\\x1d\\xd7\\x84\\xe5\\xba<(\\x07`\\xf1k\\xfb\\xdb\\x0c\\x14g.\\xffL\\xaf\\x0b^gp\\x10\\x00\\xcfcU\\xe9/5X\\x8c\\xf3\\xad\\x0b\\n\\x1d\\x1c\\x06@aU\\xfd}\\x86\\t~\\xd7\\x85k:\\xa8\\x04\\x87\\x08.\\xbe\\xd4\\xf0\\x84\\xce \\xaf\\t\\xceMyP\\r\\x80\\x13\\xaa\\xf2\\xef3\\\\\\xca\\xbaTx\\xd3\\x19\\xd4\\x82\\xa3\\xa5\\xac\\xa0\\xc1Uq\\x7f\\xe7\\x92\\x17\\x83\\xe9\\xf4a\\x1d\\x1c\\xf1\\x85\\x9e\\x07>\\x0f\\xf9\\xf3\\xa6\\x00\\xdfX\\x15\\x06\\x19d\\xe1p\\xc3T\\x01x\\xf5\\xee\\x99\\xad\\xd0G]oI9\\x971\\xaa\\xf0\\x93\\xa0\\xc0\\x8b\\x0c|\\xd1\\x14K\\xac\\xecE\\xf9Mq\\x0c\\xfb\\xda\\xc6\\x1e\\x87\\xdc\\x0f\\x13\\xf9\\xa6z\\xb3\\xe9`\\xbd\\x89p\\xbd\\xc9p\\xc5su\\xb4\\xaf\\xad\\x19\\xe6\\x89\\xd0\\xd2\\x8a\\x0cq\\x9c\\x02\\x1c\\xd0SK\\xc9\\x84O\\x0e\\'o\\xc5\\xd0n\\xe69HR\\x9c*>#\\x18\\xa9m\\x80\\x91\\xfe\\xec`\\xa4\\xb7\\x01F\\xe6\\xb3\\x83\\x91\\xd9\\x06\\x18\\xd9\\xcf\\x0eFv\\x1b`\\xe4>;\\x18\\xb9m\\x80\\xb1\\xfb\\xd9\\xc1\\xd8\\xdd\\x06\\x18\\xf9\\xcf\\x0eF~\\x1b`\\x14>;\\x18\\x85m\\x80\\x91L\\x04\\xe1X\\x88\\xeeP\\xaa\\x19\\x9e\\x98\\xd6s\\x04~f\\t\\xf9|G\\xde\\x84\\x81B.\\x80\\x81\\xdddP\\x07\\x08\\xbaN\\xe5w\"w\\xd2(RL\\xc0_\\xdb\\xd0M\\xff\\x0bm\\xe4_\\x8d\\xec\\x01\\xd4\\xf7{D\\xa6=\\xa8/r\\xd8\\xefK2\\tOi\\xec\\xc9t\\x84/m[\\x9a\\x92\\x19\\x8f\\'#\\x13\\xc7\\xc18\\x85\\x8e\\x8a\\xfb\\x1fM\\xe0.E(\\xa8\\xc9\\xc3)\\x94<\\xfa\\xd5M$R9fP\\xa6HvP\\x1f\\xeed\\x94\\xb12m\\xc4Z\\x02\\x1e+\\xb1c@\\xa6\\xe6\\xda\\xf0\\xd8\\xb0\\x87\\x08\\xc5)Z\\x9b\\xf9\\x19[\\x89\\xda\\x14?0a\\x9c\\xe1M\\x99\\xb5h\\xbb\\x83\\x01\\xb1\\x99\\x17M\\xe2\\xe6\\xe9G\\xe7Q@&f\\xd1\\xd1#\\xdb)96p\\x13\\x0ev!]\\xf6&\\xc6\\xaf\\x11?_\\xd8\\xaf\\x11\\xf6\\xe2P\\r\\xa5\\x07\\xe3\\x85\\xe3\\x12\\x7f\\x17\\xe3\\x8db7;\\xbcb\\x82H\\xb5{\\x14{\\xb8\\xe0\\xa7xB\\x8a\\xa5\\xaf\\xc1\\x97\\x18\\xb26\\xf8\\x92\\xcf\\xb4\\xc8\\x9f;\\x11\\x1d\\x87\\x8c\\xa3\\xdb\\x18[\\x80mxf\\xdeCa\\xc6\\xea\\xe0[\\xe8\\xa1\\xa9\\xf8E\\xac>\\x94\\xfe\\x85\\xbf\\x13<\\xd3-\\xd6\\xcf\\xee}\\x13\\xe1\\xfc\\x01\\xb3D\\xcd\\xefB\\x06\\x8f\\xf9Sn#\\x9d\\xdf\\xfav\\xb8\\xc5\\x03\\xf8]\\xdc(\\xfd\\xc8ox\\x03\\x1dJ&\\xf0\\x82D\\x8aY\\xf8\\xeb\\x18C\\xe8\\xf2\\xb8\\xa0\\xea\\x85\\xc3\\x93\\xd6E\\xaa|\\x9f\\xbc8\\x10\\xdd\\x9c\\xabds\\xe7\\xb3\\xe1\\xb4\\x1d\\xf9\\xf3\\xa7\\x17\\xd3c\\x86s\\xe5\\xca\\x8aQ\\xfa\\xddK\\xd9\\x90\\xd8\\xe1r8\\xf1\\xe7\\x934\\xa8\\x92\\x9f\\xf2\\x98b\\x18\\x90K,\\xb6\\xd3\\x0b=\\xab\\xb1:0_\\xf0w\\xd2\\x1f\\x7f|\\xd7\\xfb\\t?\\x93\\xfd\">Fv\\x80\\xb1\\x95;\\x9dd\\xd9\\x18\\x99\\x92\\x13)\\x95d\\xf4\\xb4\\xc1e\\xd3P\\xc8\\xcf\\xf2\\xbc\\x9c\\x97\\x9f\\xb3(\\xc7\\x90\\xc3\\xedH%%\\xc6\\xe7\\xa7\\xdf\\xb4\\x7f\\xef5+}\\xf8\\xd0\\xfb\\xf0\\xe1G\\xe9\\xbb\\xd2\\x02\\xae\\x18\\x83\\xe6\\x8f?z\\xa1\\x87\\x1c\\xd6E\\xea[\\x96T\\x13\\xd0\\x0e\\xff\\xe2\\xbcJL\\xb0\\xfb\\xf3\\x07/\\xbbn\\x8fN\\xbc\\xf4\\xba=\\xaa\\xb2\\xf4\\xba\\xe1$\\xbf\\xc0\\x1f\\x97\\x93\\x98.1\\xc9\\xb8\\xc7\\xa3Q\\x08\\xec\\xfd?\\x00\\x00\\x00\\xff\\xff'\n", + " padding = '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "\n", + "###[ HTTP/2 Frame ]### \n", + " len = 0x5b\n", + " type = DataFrm\n", + " flags = set(['End Stream (ES)', 'Padded (P)'])\n", + " reserved = 0L\n", + " stream_id = 5L\n", + "###[ HTTP/2 Padded Data Frame ]### \n", + " padlen = 80\n", + " data = '\\x03\\x00\\x1d\\x82P[\\x14\\xaa\\x00\\x00'\n", + " padding = '\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n", + "\n" + ] + } + ], + "prompt_number": 137 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's display the answer in human-readable format. We assume, once more for the sake of simplicity that we received very few headers." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "stream_txt = srv_tblhdr.gen_txt_repr(h2seq.frames[0])\n", + "data = ''\n", + "for frgmt in h2seq.frames[1:]:\n", + " data += frgmt.payload.data\n", + "print(stream_txt)\n", + "HTML(zlib.decompress(data, 16+zlib.MAX_WBITS).decode(\"utf-8\", \"ignore\"))" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + ":status 200\n", + "date: Tue, 13 Dec 2016 17:36:19 GMT\n", + "p3p: CP=\"This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\"\n", + "server: gws\n", + "content-length: 4420\n", + "expires: Fri, 16 Dec 2016 06:23:59 GMT\n", + "set-cookie: NID=91=Wt1Jkm3Eretgg-hJ32fkj7kSSOLTc8tfEEIP5F2QTzHqbsXcCFve-QoN1oZvkGEqqqAWklc2wlj97YDkMnGXQUw20iCYMc3FD6X-KVuK1wdFURafcqQMQZ8e-F14YUfn; expires=Wed, 14-Jun-2017 17:34:51 GMT; path=/; domain=.google.fr; HttpOnly\n", + "alt-svc: quic=\":443\"; ma=2592000; v=\"35,34\"\n", + "date: Tue, 13 Dec 2016 17:36:19 GMT\n", + "cache-control: private, max-age=0\n" + ] + }, + { + "html": [ + "scapy - Recherche Google

 
Environ 190 000rsultats

    Scapy - SecDev.org

    www.secdev.org/projects/scapy/
    Scapy is a powerful interactive packet manipulation program. It is able to forge or
    \n", + "decode packets of a wide number of protocols, send them on the wire, capture ...

    Usage — Scapy v2.1.1-dev documentation - SecDev.org

    www.secdev.org/projects/scapy/doc/usage.html
    Scapy's interactive shell is run in a terminal session. Root privileges are needed
    \n", + "to send the packets, so we're using sudo here: $ sudo scapy Welcome to Scapy ...

    Manipulez les paquets rseau avec Scapy - OpenClassrooms

    https://openclassrooms.com/.../manipulez-les-paquets-reseau-avec-scapy
    20 nov. 2013 ... Scapy est un module pour Python permettant de forger, envoyer, rceptionner et
    \n", + "manipuler des paquets rseau. Si le rseau vous intresse et ...

    Scapy — Wikipdia

    https://fr.wikipedia.org/wiki/Scapy
    Scapy est un logiciel libre de manipulation de paquets rseau crit en python. Il
    \n", + "est capable, entre autres, d'intercepter le trafic sur un segment rseau, ...

    Scapy | Les Tutos de Nico

    www.lestutosdenico.com/tutos-de-nico/scapy
    26 avr. 2010 ... Scapy est un outil Open Source crit par Philippe Biondi. Cet utilitaire permet de
    \n", + "manipuler, forger, dcoder, mettre, recevoir les paquets ...

    GitHub - secdev/scapy: Scapy: the python-based interactive packet ...

    https://github.com/secdev/scapy
    scapy - Scapy: the python-based interactive packet manipulation program &
    \n", + "library.

    [PDF] 

    Les Fourberies de Scapy - Zenk - Security - Repository

    https://repo.zenk-security.com/.../Les%20Fourberies%20de%20Scapy.pdf
    Python & Scapy : cration de module ou de programme . . . . . . . . . . . . . . . . . . 12.
    \n", + "3.1. Python & Scapy .... 16 change de paquet de Wireshark vers Scapy .

    Tutorial Scapy, introduction - Le blog de Thierry

    www.chambeyron.fr/index.php/systeme.../scapy/8-scapy-les-bases
    19 sept. 2014 ... Pour connaitre la liste des commandes scapy >>> lsc() arpcachepoison : Poison
    \n", + "target's cache with (your MAC,victim's IP) couple arping : Send ...

    [PDF] 

    Scapy en pratique - Repository Root Me

    repository.root-me.org/.../FR%20-%20Scapy%20en%20pratique.pdf
    17 mai 2008 ... Scapy en pratique. PyCON FR – 17 Mai 2008 - Renaud Lifchitz. 3. Qu'est-ce
    \n", + "que Scapy ? Prsentation gnrale. ○. Interprteur Python ...

    [How To]Utilisation de Scapy | cloud's Blog

    blog.madpowah.org/articles/scapy/index.html
    18 sept. 2008 ... Scapy est un logiciel dvelopp en python qui permet de forger des paquets, de
    \n", + "sniffer et de faire bien d'autres actions bien utiles pour faire du ...

" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 141, + "text": [ + "" + ] + } + ], + "prompt_number": 141 + } + ], + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/scapy/contrib/http2.py b/scapy/contrib/http2.py new file mode 100644 index 000000000..7c632856a --- /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 ## +## ## +## 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 000000000..58cd91aff --- /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) diff --git a/scapy/fields.py b/scapy/fields.py index 4299827b9..7f48092c9 100644 --- a/scapy/fields.py +++ b/scapy/fields.py @@ -7,7 +7,7 @@ Fields: basic data structures that make up parts of packets. """ -import struct,copy,socket +import struct,copy,socket,collections from scapy.config import conf from scapy.volatile import * from scapy.data import * @@ -990,7 +990,94 @@ class FlagsField(BitField): r = "+".join(r) return r - + +MultiFlagsEntry = collections.namedtuple('MultiFlagEntry', ['short', 'long']) + + +class MultiFlagsField(BitField): + __slots__ = FlagsField.__slots__ + ["depends_on"] + + def __init__(self, name, default, size, names, depends_on): + self.names = names + self.depends_on = depends_on + super(MultiFlagsField, self).__init__(name, default, size) + + def any2i(self, pkt, x): + assert isinstance(x, (int, long, set)), 'set expected' + + if pkt is not None: + if isinstance(x, (int, long)): + x = self.m2i(pkt, x) + else: + v = self.depends_on(pkt) + if v is not None: + assert self.names.has_key(v), 'invalid dependency' + these_names = self.names[v] + s = set() + for i in x: + for j in these_names.keys(): + if these_names[j].short == i: + s.add(i) + break + else: + assert False, 'Unknown flag "{}" with this dependency'.format(i) + continue + x = s + return x + + def i2m(self, pkt, x): + v = self.depends_on(pkt) + if v in self.names: + these_names = self.names[v] + else: + these_names = {} + + r = 0 + for flag_set in x: + for i in these_names.keys(): + if these_names[i].short == flag_set: + r |= 1 << i + break + else: + r |= 1 << int(flag_set[len('bit '):]) + return r + + def m2i(self, pkt, x): + v = self.depends_on(pkt) + if v in self.names: + these_names = self.names[v] + else: + these_names = {} + + r = set() + i = 0 + + while x: + if x & 1: + if i in these_names: + r.add(these_names[i].short) + else: + r.add('bit {}'.format(i)) + x >>= 1 + i += 1 + return r + + def i2repr(self, pkt, x): + v = self.depends_on(pkt) + if self.names.has_key(v): + these_names = self.names[v] + else: + these_names = {} + + r = set() + for flag_set in x: + for i in these_names.itervalues(): + if i.short == flag_set: + r.add("{} ({})".format(i.long, i.short)) + break + else: + r.add(flag_set) + return repr(r) class FixedPointField(BitField): diff --git a/test/regression.uts b/test/regression.uts index 65f983cc4..33ae8bc3e 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -6312,3 +6312,188 @@ except: assert(ret) p = ss.recv() assert(p.data == 3) + ++ Tests on MultiFlagsField + += Test calls on MultiFlagsField.any2i +~ multiflagsfield + +import collections +MockPacket = collections.namedtuple('MockPacket', ['type']) + +f = MultiFlagsField('flags', set(), 3, { + 0: { + 0: MultiFlagsEntry('A', 'OptionA'), + 1: MultiFlagsEntry('B', 'OptionB'), + }, + 1: { + 0: MultiFlagsEntry('+', 'Plus'), + 1: MultiFlagsEntry('*', 'Star'), + }, + }, + depends_on=lambda x: x.type +) + +mp = MockPacket(0) +x = f.any2i(mp, set()) +assert(isinstance(x, set)) +assert(len(x) == 0) +x = f.any2i(mp, {'A'}) +assert(isinstance(x, set)) +assert(len(x) == 1) +assert('A' in x) +assert('B' not in x) +assert('+' not in x) +x = f.any2i(mp, {'A', 'B'}) +assert(isinstance(x, set)) +assert(len(x) == 2) +assert('A' in x) +assert('B' in x) +assert('+' not in x) +assert('*' not in x) +x = f.any2i(mp, 3) +assert(isinstance(x, set)) +assert(len(x) == 2) +assert('A' in x) +assert('B' in x) +assert('+' not in x) +assert('*' not in x) +x = f.any2i(mp, 7) +assert(isinstance(x, set)) +assert(len(x) == 3) +assert('A' in x) +assert('B' in x) +assert('bit 2' in x) +assert('+' not in x) +assert('*' not in x) +mp = MockPacket(1) +x = f.any2i(mp, {'+', '*'}) +assert(isinstance(x, set)) +assert(len(x) == 2) +assert('+' in x) +assert('*' in x) +assert('A' not in x) +assert('B' not in x) +try: + x = f.any2i(mp, {'A'}) + ret = False +except AssertionError: + ret = True + +assert(ret) +#Following test demonstrate a non-sensical yet acceptable usage :( +x = f.any2i(None, {'Toto'}) +assert('Toto' in x) + += Test calls on MultiFlagsField.i2m +~ multiflagsfield + +import collections +MockPacket = collections.namedtuple('MockPacket', ['type']) + +f = MultiFlagsField('flags', set(), 3, { + 0: { + 0: MultiFlagsEntry('A', 'OptionA'), + 1: MultiFlagsEntry('B', 'OptionB'), + }, + 1: { + 0: MultiFlagsEntry('+', 'Plus'), + 1: MultiFlagsEntry('*', 'Star'), + }, + }, + depends_on=lambda x: x.type +) + +mp = MockPacket(0) +x = f.i2m(mp, set()) +assert(isinstance(x, (int, long))) +assert(x == 0) +x = f.i2m(mp, {'A'}) +assert(isinstance(x, (int, long))) +assert(x == 1) +x = f.i2m(mp, {'A', 'B'}) +assert(isinstance(x, (int, long))) +assert(x == 3) +x = f.i2m(mp, {'A', 'B', 'bit 2'}) +assert(isinstance(x, (int, long))) +assert(x == 7) +try: + x = f.i2m(mp, {'+'}) + ret = False +except: + ret = True + +assert(ret) + += Test calls on MultiFlagsField.m2i +~ multiflagsfield + +import collections +MockPacket = collections.namedtuple('MockPacket', ['type']) + +f = MultiFlagsField('flags', set(), 3, { + 0: { + 0: MultiFlagsEntry('A', 'OptionA'), + 1: MultiFlagsEntry('B', 'OptionB'), + }, + 1: { + 0: MultiFlagsEntry('+', 'Plus'), + 1: MultiFlagsEntry('*', 'Star'), + }, + }, + depends_on=lambda x: x.type +) + +mp = MockPacket(0) +x = f.m2i(mp, 2) +assert(isinstance(x, set)) +assert(len(x) == 1) +assert('B' in x) +assert('A' not in x) +assert('*' not in x) + +x = f.m2i(mp, 7) +assert(isinstance(x, set)) +assert('B' in x) +assert('A' in x) +assert('bit 2' in x) +assert('*' not in x) +assert('+' not in x) +x = f.m2i(mp, 0) +assert(len(x) == 0) +mp = MockPacket(1) +x = f.m2i(mp, 2) +assert(isinstance(x, set)) +assert(len(x) == 1) +assert('*' in x) +assert('+' not in x) +assert('B' not in x) + += Test calls on MultiFlagsField.i2repr +~ multiflagsfield + +import collections, re +MockPacket = collections.namedtuple('MockPacket', ['type']) + +f = MultiFlagsField('flags', set(), 3, { + 0: { + 0: MultiFlagsEntry('A', 'OptionA'), + 1: MultiFlagsEntry('B', 'OptionB'), + }, + 1: { + 0: MultiFlagsEntry('+', 'Plus'), + 1: MultiFlagsEntry('*', 'Star'), + }, + }, + depends_on=lambda x: x.type +) + +mp = MockPacket(0) +x = f.i2repr(mp, {'A', 'B'}) +assert(re.match(r'^.*OptionA \(A\).*$', x) is not None) +assert(re.match(r'^.*OptionB \(B\).*$', x) is not None) +mp = MockPacket(1) +x = f.i2repr(mp, {'*', '+', 'bit 2'}) +assert(re.match(r'^.*Star \(\*\).*$', x) is not None) +assert(re.match(r'^.*Plus \(\+\).*$', x) is not None) +assert(re.match(r'^.*bit 2.*$', x) is not None)