1#!/usr/bin/env python3
2#
3# This script reads input headers from json file given in the
4# command-line (each file must be written in the format described in
5# https://github.com/Jxck/hpack-test-case but we require only
6# 'headers' data). Then it encodes input header set and write the
7# encoded header block in the same format. The output files are
8# created under 'out' directory in the current directory. It must
9# exist, otherwise the script will fail. The output filename is the
10# same as the input filename.
11#
12import sys, base64, json, os.path, os, argparse, errno
13from binascii import b2a_hex
14import nghttp2
15
16def testsuite(testdata, filename, outdir, table_size, deflate_table_size,
17              simulate_table_size_change):
18    res = {
19        'description': '''\
20Encoded by nghttp2. The basic encoding strategy is described in \
21http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html \
22We use huffman encoding only if it produces strictly shorter byte string than \
23original. We make some headers not indexing at all, but this does not always \
24result in less bits on the wire.'''
25    }
26    cases = []
27    deflater = nghttp2.HDDeflater(deflate_table_size)
28
29    if table_size != nghttp2.DEFAULT_HEADER_TABLE_SIZE:
30        deflater.change_table_size(table_size)
31
32    num_item = len(testdata['cases'])
33
34    change_points = {}
35    if simulate_table_size_change and num_item > 1:
36        change_points[num_item * 2 // 3] = table_size * 2 // 3
37        change_points[num_item // 3] = table_size // 3
38
39    for casenum, item  in enumerate(testdata['cases']):
40        outitem = {
41            'seqno': casenum,
42            'headers': item['headers']
43        }
44
45        if casenum in change_points:
46            new_table_size = change_points[casenum]
47            deflater.change_table_size(new_table_size)
48            outitem['header_table_size'] = new_table_size
49
50        casenum += 1
51        hdrs = [(list(x.keys())[0].encode('utf-8'),
52                 list(x.values())[0].encode('utf-8')) \
53                for x in item['headers']]
54        outitem['wire'] = b2a_hex(deflater.deflate(hdrs)).decode('utf-8')
55        cases.append(outitem)
56
57    if cases and table_size != nghttp2.DEFAULT_HEADER_TABLE_SIZE:
58        cases[0]['header_table_size'] = table_size
59
60    res['cases'] = cases
61    jsonstr = json.dumps(res, indent=2)
62    with open(os.path.join(outdir, filename), 'w') as f:
63        f.write(jsonstr)
64
65if __name__ == '__main__':
66    ap = argparse.ArgumentParser(description='HPACK test case generator')
67    ap.add_argument('-d', '--dir', help='output directory', default='out')
68    ap.add_argument('-s', '--table-size', help='max header table size',
69                    type=int, default=nghttp2.DEFAULT_HEADER_TABLE_SIZE)
70    ap.add_argument('-S', '--deflate-table-size',
71                    help='max header table size for deflater',
72                    type=int, default=nghttp2.DEFLATE_MAX_HEADER_TABLE_SIZE)
73    ap.add_argument('-c', '--simulate-table-size-change',
74                    help='simulate table size change scenario',
75                    action='store_true')
76
77    ap.add_argument('file', nargs='*', help='input file')
78    args = ap.parse_args()
79    try:
80        os.mkdir(args.dir)
81    except OSError as e:
82        if e.errno != errno.EEXIST:
83            raise e
84    for filename in args.file:
85        sys.stderr.write('{}\n'.format(filename))
86        with open(filename) as f:
87            input = f.read()
88        testsuite(json.loads(input), os.path.basename(filename),
89                  args.dir, args.table_size, args.deflate_table_size,
90                  args.simulate_table_size_change)
91