1/*
2 * Read QIF and encode it using HPACK.  Use for benchmarking.
3 *
4 * Based on ls-qpack's interop-encode.
5 *
6 * How it works: read in QIF into a list of header set objects and encode
7 * the list a number of times.
8 *
9 * QIF Format:
10 * https://github.com/quicwg/base-drafts/wiki/QPACK-Offline-Interop
11 */
12
13#define _GNU_SOURCE /* for memmem */
14#include <assert.h>
15
16#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__)
17#include <sys/endian.h>
18#define bswap_16 bswap16
19#define bswap_32 bswap32
20#define bswap_64 bswap64
21#elif defined(__APPLE__)
22#include <libkern/OSByteOrder.h>
23#define bswap_16 OSSwapInt16
24#define bswap_32 OSSwapInt32
25#define bswap_64 OSSwapInt64
26#elif defined(WIN32)
27#error Not supported on Windows
28#else
29#include <byteswap.h>
30#endif
31
32#include <errno.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <unistd.h>
37#include <sys/types.h>
38#include <sys/stat.h>
39#include <fcntl.h>
40#include <inttypes.h>
41#include <sys/mman.h>
42
43#include "lshpack.h"
44
45static int s_verbose;
46
47#define TABLE_SIZE 4096
48
49static void
50usage (const char *name)
51{
52    fprintf(stderr,
53"Usage: %s [options] [-i input] [-o output]\n"
54"\n"
55"Options:\n"
56"   -i FILE     Input file.\n"
57"   -o FILE     Output file.  If not spepcified or set to `-', the output\n"
58"                 is written to stdout.\n"
59"   -K          Discard output: encoded output is discarded.\n"
60"   -H          Do not use the history heuristic.\n"
61"   -n NUMBER   Number of times to iterate over the header set list.\n"
62"   -t NUMBER   Dynamic table size.  Defaults to %u.\n"
63"   -v          Verbose: print various messages to stderr.\n"
64"\n"
65"   -h          Print this help screen and exit\n"
66    , name, TABLE_SIZE);
67}
68
69
70struct header
71{
72    const char    *name;
73    const char    *val;
74    size_t         name_len;
75    size_t         val_len;
76};
77
78
79#define MAX_HEADERS 32
80
81struct header_set
82{
83    STAILQ_ENTRY(header_set)    next;
84    unsigned                    n_headers;
85    struct header               headers[MAX_HEADERS];
86};
87
88
89static inline void
90lsxpack_header_set_ptr(lsxpack_header_t *hdr,
91                       const char *name, size_t name_len,
92                       const char *val, size_t val_len)
93{
94    static char buf[65536];
95    memcpy(buf, name, name_len);
96    memcpy(&buf[name_len], val, val_len);
97    lsxpack_header_set_offset2(hdr, buf, 0, name_len, name_len, val_len);
98}
99
100
101int
102main (int argc, char **argv)
103{
104    FILE *out = stdout;
105    int opt, qif_fd = -1;
106    int discard = 0, use_history = 1;
107    unsigned n_iters = 1, n, i;
108    unsigned dyn_table_size     = TABLE_SIZE;
109    STAILQ_HEAD(, header_set) header_sets = STAILQ_HEAD_INITIALIZER(header_sets);
110    struct stat st;
111    const unsigned char *qif = NULL, *p, *tab, *nl, *nlnl;
112    unsigned char *s;
113    struct header *header;
114    struct header_set *hset;
115    struct lshpack_enc encoder;
116    unsigned char buf[0x2000];
117
118    while (-1 != (opt = getopt(argc, argv, "Hi:o:Kn:t:vh")))
119    {
120        switch (opt)
121        {
122        case 'n':
123            n_iters = atoi(optarg);
124            break;
125        case 'i':
126            qif_fd = open(optarg, O_RDONLY);
127            if (qif_fd < 0)
128            {
129                perror("open");
130                exit(EXIT_FAILURE);
131            }
132            if (0 != fstat(qif_fd, &st))
133            {
134                perror("fstat");
135                exit(EXIT_FAILURE);
136            }
137            qif = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, qif_fd, 0);
138            if (!qif)
139            {
140                perror("mmap");
141                exit(EXIT_FAILURE);
142            }
143            break;
144        case 'o':
145            if (0 != strcmp(optarg, "-"))
146            {
147                out = fopen(optarg, "wb");
148                if (!out)
149                {
150                    fprintf(stderr, "cannot open `%s' for writing: %s\n",
151                                                optarg, strerror(errno));
152                    exit(EXIT_FAILURE);
153                }
154            }
155            break;
156        case 'K':
157            discard = 1;
158            break;
159        case 'H':
160            use_history = 0;
161            break;
162        case 't':
163            dyn_table_size = atoi(optarg);
164            break;
165        case 'h':
166            usage(argv[0]);
167            exit(EXIT_SUCCESS);
168        case 'v':
169            ++s_verbose;
170            break;
171        default:
172            exit(EXIT_FAILURE);
173        }
174    }
175
176    if (!qif)
177    {
178        fprintf(stderr, "Please specify input QIF file using -i flag\n");
179        exit(EXIT_FAILURE);
180    }
181
182    const unsigned char *const begin = qif;
183    const unsigned char *const end = begin + st.st_size;
184    while (qif + 2 < end)
185    {
186        nlnl = memmem(qif, end - qif, "\n\n", 2);
187        if (!nlnl)
188            nlnl = end;
189        hset = calloc(1, sizeof(*hset));
190        if (!hset)
191        {
192            perror("malloc");
193            exit(EXIT_FAILURE);
194        }
195        STAILQ_INSERT_TAIL(&header_sets, hset, next);
196        p = qif;
197        while (p < nlnl)
198        {
199            if (hset->n_headers >= MAX_HEADERS)
200            {
201                fprintf(stderr, "max headers > 32, off: %u",
202                                            (unsigned) (p - begin));
203                exit(EXIT_FAILURE);
204            }
205            tab = memmem(p, nlnl - p, "\t", 1);
206            if (!tab)
207            {
208                fprintf(stderr, "tab not found, off: %u",
209                                            (unsigned) (p - begin));
210                exit(EXIT_FAILURE);
211            }
212            nl = memmem(tab + 1, nlnl - tab - 1, "\n", 1);
213            if (!nl)
214                nl = nlnl;
215            hset->headers[ hset->n_headers ] = (struct header) {
216                .name = (const char *) p,
217                .val = (const char *) tab + 1,
218                .name_len =  tab - p,
219                .val_len = nl - tab - 1,
220            };
221            ++hset->n_headers;
222            p = nl + 1;
223        }
224        qif = nlnl + 2;
225    }
226
227    lsxpack_header_t hdr;
228    for (n = 0; n < n_iters; ++n)
229    {
230        if (0 != lshpack_enc_init(&encoder))
231        {
232            perror("lshpack_enc_init");
233            exit(EXIT_FAILURE);
234        }
235        (void) lshpack_enc_use_hist(&encoder, use_history);
236
237        STAILQ_FOREACH(hset, &header_sets, next)
238        {
239            for (i = 0; i < hset->n_headers; ++i)
240            {
241                header = &hset->headers[i];
242                lsxpack_header_set_ptr(&hdr, header->name, header->name_len,
243                                       header->val, header->val_len);
244                s = lshpack_enc_encode(&encoder, buf, buf + sizeof(buf), &hdr);
245                if (s <= buf)
246                {
247                    fprintf(stderr, "cannot encode\n");
248                    exit(EXIT_FAILURE);
249                }
250                if (!discard)
251                    (void) fwrite(buf, 1, s - buf, out);
252            }
253        }
254
255        lshpack_enc_set_max_capacity(&encoder, dyn_table_size);
256        lshpack_enc_cleanup(&encoder);
257    }
258
259    munmap((void *) begin, st.st_size);
260    if (qif_fd >= 0)
261        close(qif_fd);
262    exit(EXIT_SUCCESS);
263}
264