http_client.c revision b329a00e
1/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc.  See LICENSE. */
2/*
3 * http_client.c -- A simple HTTP/QUIC client
4 */
5
6#ifndef WIN32
7#include <arpa/inet.h>
8#include <netinet/in.h>
9#else
10#include <Windows.h>
11#include <WinSock2.h>
12#include <io.h>
13#include <stdlib.h>
14#include <getopt.h>
15#define STDOUT_FILENO 1
16#define random rand
17#pragma warning(disable:4996) //POSIX name deprecated
18#endif
19#include <assert.h>
20#include <errno.h>
21#include <inttypes.h>
22#include <stddef.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/queue.h>
27#ifndef WIN32
28#include <unistd.h>
29#include <sys/types.h>
30#include <dirent.h>
31#include <limits.h>
32#endif
33#include <sys/stat.h>
34#include <fcntl.h>
35#include <event2/event.h>
36#include <math.h>
37
38#include <openssl/bio.h>
39#include <openssl/pem.h>
40#include <openssl/x509.h>
41
42#include "lsquic.h"
43#include "test_common.h"
44#include "prog.h"
45
46#include "../src/liblsquic/lsquic_logger.h"
47#include "../src/liblsquic/lsquic_int_types.h"
48#include "../src/liblsquic/lsquic_util.h"
49/* include directly for reset_stream testing */
50#include "../src/liblsquic/lsquic_varint.h"
51#include "../src/liblsquic/lsquic_hq.h"
52#include "../src/liblsquic/lsquic_sfcw.h"
53#include "../src/liblsquic/lsquic_hash.h"
54#include "../src/liblsquic/lsquic_stream.h"
55/* include directly for retire_cid testing */
56#include "../src/liblsquic/lsquic_conn.h"
57#include "lsxpack_header.h"
58
59#define MIN(a, b) ((a) < (b) ? (a) : (b))
60
61/* This is used to exercise generating and sending of priority frames */
62static int randomly_reprioritize_streams;
63
64static int s_display_cert_chain;
65
66/* If this file descriptor is open, the client will accept server push and
67 * dump the contents here.  See -u flag.
68 */
69static int promise_fd = -1;
70
71/* Set to true value to use header bypass.  This means that the use code
72 * creates header set via callbacks and then fetches it by calling
73 * lsquic_stream_get_hset() when the first "on_read" event is called.
74 */
75static int g_header_bypass;
76
77static int s_discard_response;
78
79struct sample_stats
80{
81    unsigned        n;
82    unsigned long   min, max;
83    unsigned long   sum;        /* To calculate mean */
84    unsigned long   sum_X2;     /* To calculate stddev */
85};
86
87static struct sample_stats  s_stat_to_conn,     /* Time to connect */
88                            s_stat_ttfb,
89                            s_stat_req;     /* From TTFB to EOS */
90static unsigned s_stat_conns_ok, s_stat_conns_failed;
91static unsigned long s_stat_downloaded_bytes;
92
93static void
94update_sample_stats (struct sample_stats *stats, unsigned long val)
95{
96    LSQ_DEBUG("%s: %p: %lu", __func__, stats, val);
97    if (stats->n)
98    {
99        if (val < stats->min)
100            stats->min = val;
101        else if (val > stats->max)
102            stats->max = val;
103    }
104    else
105    {
106        stats->min = val;
107        stats->max = val;
108    }
109    stats->sum += val;
110    stats->sum_X2 += val * val;
111    ++stats->n;
112}
113
114
115static void
116calc_sample_stats (const struct sample_stats *stats,
117        long double *mean_p, long double *stddev_p)
118{
119    unsigned long mean, tmp;
120
121    if (stats->n)
122    {
123        mean = stats->sum / stats->n;
124        *mean_p = (long double) mean;
125        if (stats->n > 1)
126        {
127            tmp = stats->sum_X2 - stats->n * mean * mean;
128            tmp /= stats->n - 1;
129            *stddev_p = sqrtl((long double) tmp);
130        }
131        else
132            *stddev_p = 0;
133    }
134    else
135    {
136        *mean_p = 0;
137        *stddev_p = 0;
138    }
139}
140
141
142#ifdef WIN32
143static char *
144strndup(const char *s, size_t n)
145{
146    char *copy;
147
148    copy = malloc(n + 1);
149    if (copy)
150    {
151        memcpy(copy, s, n);
152        copy[n] = '\0';
153    }
154
155    return copy;
156}
157#endif
158
159struct lsquic_conn_ctx;
160
161struct path_elem {
162    TAILQ_ENTRY(path_elem)      next_pe;
163    const char                 *path;
164};
165
166struct http_client_ctx {
167    TAILQ_HEAD(, lsquic_conn_ctx)
168                                 conn_ctxs;
169    const char                  *hostname;
170    const char                  *method;
171    const char                  *payload;
172    char                         payload_size[20];
173
174    /* hcc_path_elems holds a list of paths which are to be requested from
175     * the server.  Each new request gets the next path from the list (the
176     * iterator is stored in hcc_cur_pe); when the end is reached, the
177     * iterator wraps around.
178     */
179    TAILQ_HEAD(, path_elem)      hcc_path_elems;
180    struct path_elem            *hcc_cur_pe;
181
182    unsigned                     hcc_total_n_reqs;
183    unsigned                     hcc_reqs_per_conn;
184    unsigned                     hcc_concurrency;
185    unsigned                     hcc_cc_reqs_per_conn;
186    unsigned                     hcc_n_open_conns;
187    unsigned                     hcc_reset_after_nbytes;
188    unsigned                     hcc_retire_cid_after_nbytes;
189
190    char                        *hcc_sess_resume_file_name;
191
192    enum {
193        HCC_SKIP_SESS_RESUME    = (1 << 0),
194        HCC_SEEN_FIN            = (1 << 1),
195        HCC_ABORT_ON_INCOMPLETE = (1 << 2),
196    }                            hcc_flags;
197    struct prog                 *prog;
198    const char                  *qif_file;
199    FILE                        *qif_fh;
200};
201
202struct lsquic_conn_ctx {
203    TAILQ_ENTRY(lsquic_conn_ctx) next_ch;
204    lsquic_conn_t       *conn;
205    struct http_client_ctx   *client_ctx;
206    lsquic_time_t        ch_created;
207    unsigned             ch_n_reqs;    /* This number gets decremented as streams are closed and
208                                        * incremented as push promises are accepted.
209                                        */
210    unsigned             ch_n_cc_streams;   /* This number is incremented as streams are opened
211                                             * and decremented as streams are closed. It should
212                                             * never exceed hcc_cc_reqs_per_conn in client_ctx.
213                                             */
214    enum {
215        CH_SESSION_RESUME_SAVED   = 1 << 0,
216    }                    ch_flags;
217};
218
219
220struct hset_elem
221{
222    STAILQ_ENTRY(hset_elem)     next;
223    size_t                      nalloc;
224    struct lsxpack_header       xhdr;
225};
226
227
228STAILQ_HEAD(hset, hset_elem);
229
230static void
231hset_dump (const struct hset *, FILE *);
232static void
233hset_destroy (void *hset);
234static void
235display_cert_chain (lsquic_conn_t *);
236
237
238static void
239create_connections (struct http_client_ctx *client_ctx)
240{
241    size_t len;
242    FILE *file;
243    unsigned char sess_resume[0x2000];
244
245    if (0 == (client_ctx->hcc_flags & HCC_SKIP_SESS_RESUME)
246                                    && client_ctx->hcc_sess_resume_file_name)
247    {
248        file = fopen(client_ctx->hcc_sess_resume_file_name, "rb");
249        if (!file)
250        {
251            LSQ_DEBUG("cannot open %s for reading: %s",
252                        client_ctx->hcc_sess_resume_file_name, strerror(errno));
253            goto no_file;
254        }
255        len = fread(sess_resume, 1, sizeof(sess_resume), file);
256        if (0 == len && !feof(file))
257            LSQ_WARN("error reading %s: %s",
258                        client_ctx->hcc_sess_resume_file_name, strerror(errno));
259        fclose(file);
260        LSQ_INFO("create connection sess_resume %zu bytes", len);
261    }
262    else no_file:
263        len = 0;
264
265    while (client_ctx->hcc_n_open_conns < client_ctx->hcc_concurrency &&
266           client_ctx->hcc_total_n_reqs > 0)
267        if (0 != prog_connect(client_ctx->prog, len ? sess_resume : NULL, len))
268        {
269            LSQ_ERROR("connection failed");
270            exit(EXIT_FAILURE);
271        }
272}
273
274
275static void
276create_streams (struct http_client_ctx *client_ctx, lsquic_conn_ctx_t *conn_h)
277{
278    while (conn_h->ch_n_reqs - conn_h->ch_n_cc_streams &&
279            conn_h->ch_n_cc_streams < client_ctx->hcc_cc_reqs_per_conn)
280    {
281        lsquic_conn_make_stream(conn_h->conn);
282        conn_h->ch_n_cc_streams++;
283    }
284}
285
286
287static lsquic_conn_ctx_t *
288http_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
289{
290    struct http_client_ctx *client_ctx = stream_if_ctx;
291    lsquic_conn_ctx_t *conn_h = calloc(1, sizeof(*conn_h));
292    conn_h->conn = conn;
293    conn_h->client_ctx = client_ctx;
294    conn_h->ch_n_reqs = MIN(client_ctx->hcc_total_n_reqs,
295                                                client_ctx->hcc_reqs_per_conn);
296    client_ctx->hcc_total_n_reqs -= conn_h->ch_n_reqs;
297    TAILQ_INSERT_TAIL(&client_ctx->conn_ctxs, conn_h, next_ch);
298    ++conn_h->client_ctx->hcc_n_open_conns;
299    if (!TAILQ_EMPTY(&client_ctx->hcc_path_elems))
300        create_streams(client_ctx, conn_h);
301    conn_h->ch_created = lsquic_time_now();
302    return conn_h;
303}
304
305
306struct create_another_conn_or_stop_ctx
307{
308    struct event            *event;
309    struct http_client_ctx  *client_ctx;
310};
311
312
313static void
314create_another_conn_or_stop (evutil_socket_t sock, short events, void *ctx)
315{
316    struct create_another_conn_or_stop_ctx *const cacos = ctx;
317    struct http_client_ctx *const client_ctx = cacos->client_ctx;
318
319    event_del(cacos->event);
320    event_free(cacos->event);
321    free(cacos);
322
323    create_connections(client_ctx);
324    if (0 == client_ctx->hcc_n_open_conns)
325    {
326        LSQ_INFO("All connections are closed: stop engine");
327        prog_stop(client_ctx->prog);
328    }
329}
330
331
332static void
333http_client_on_conn_closed (lsquic_conn_t *conn)
334{
335    lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn);
336    struct create_another_conn_or_stop_ctx *cacos;
337    enum LSQUIC_CONN_STATUS status;
338    struct event_base *eb;
339    char errmsg[80];
340
341    status = lsquic_conn_status(conn, errmsg, sizeof(errmsg));
342    LSQ_INFO("Connection closed.  Status: %d.  Message: %s", status,
343        errmsg[0] ? errmsg : "<not set>");
344    if (conn_h->client_ctx->hcc_flags & HCC_ABORT_ON_INCOMPLETE)
345    {
346        if (!(conn_h->client_ctx->hcc_flags & HCC_SEEN_FIN))
347            abort();
348    }
349    TAILQ_REMOVE(&conn_h->client_ctx->conn_ctxs, conn_h, next_ch);
350    --conn_h->client_ctx->hcc_n_open_conns;
351
352    cacos = calloc(1, sizeof(*cacos));
353    if (!cacos)
354    {
355        LSQ_ERROR("cannot allocate cacos");
356        exit(1);
357    }
358    eb = prog_eb(conn_h->client_ctx->prog);
359    cacos->client_ctx = conn_h->client_ctx;
360    cacos->event = event_new(eb, -1, 0, create_another_conn_or_stop, cacos);
361    if (!cacos->event)
362    {
363        LSQ_ERROR("cannot allocate event");
364        exit(1);
365    }
366    if (0 != event_add(cacos->event, NULL))
367    {
368        LSQ_ERROR("cannot add cacos event");
369        exit(1);
370    }
371    event_active(cacos->event, 0, 0);
372
373    free(conn_h);
374}
375
376
377static int
378hsk_status_ok (enum lsquic_hsk_status status)
379{
380    return status == LSQ_HSK_OK || status == LSQ_HSK_RESUMED_OK;
381}
382
383
384static void
385http_client_on_hsk_done (lsquic_conn_t *conn, enum lsquic_hsk_status status)
386{
387    lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn);
388    struct http_client_ctx *client_ctx = conn_h->client_ctx;
389
390    if (hsk_status_ok(status))
391        LSQ_INFO("handshake success %s",
392                    status == LSQ_HSK_RESUMED_OK ? "(session resumed)" : "");
393    else if (status == LSQ_HSK_FAIL)
394        LSQ_INFO("handshake failed");
395    else if (status == LSQ_HSK_RESUMED_FAIL)
396    {
397        LSQ_INFO("handshake failed because of session resumption, will retry "
398                                                                "without it");
399        client_ctx->hcc_flags |= HCC_SKIP_SESS_RESUME;
400        ++client_ctx->hcc_concurrency;
401        ++client_ctx->hcc_total_n_reqs;
402    }
403    else
404        assert(0);
405
406    if (hsk_status_ok(status) && s_display_cert_chain)
407        display_cert_chain(conn);
408
409    if (hsk_status_ok(status))
410    {
411        conn_h = lsquic_conn_get_ctx(conn);
412        ++s_stat_conns_ok;
413        update_sample_stats(&s_stat_to_conn,
414                                    lsquic_time_now() - conn_h->ch_created);
415        if (TAILQ_EMPTY(&client_ctx->hcc_path_elems))
416        {
417            LSQ_INFO("no paths mode: close connection");
418            lsquic_conn_close(conn_h->conn);
419        }
420    }
421    else
422        ++s_stat_conns_failed;
423}
424
425
426static void
427http_client_on_sess_resume_info (lsquic_conn_t *conn, const unsigned char *buf,
428                                                                size_t bufsz)
429{
430    lsquic_conn_ctx_t *const conn_h = lsquic_conn_get_ctx(conn);
431    struct http_client_ctx *const client_ctx = conn_h->client_ctx;
432    FILE *file;
433    size_t nw;
434
435    assert(client_ctx->hcc_sess_resume_file_name);
436
437    /* Our client is rather limited: only one file and only one ticket per
438     * connection can be saved.
439     */
440    if (conn_h->ch_flags & CH_SESSION_RESUME_SAVED)
441    {
442        LSQ_DEBUG("session resumption information already saved for this "
443                                                                "connection");
444        return;
445    }
446
447    file = fopen(client_ctx->hcc_sess_resume_file_name, "wb");
448    if (!file)
449    {
450        LSQ_WARN("cannot open %s for writing: %s",
451            client_ctx->hcc_sess_resume_file_name, strerror(errno));
452        return;
453    }
454
455    nw = fwrite(buf, 1, bufsz, file);
456    if (nw == bufsz)
457    {
458        LSQ_DEBUG("wrote %zd bytes of session resumption information to %s",
459            nw, client_ctx->hcc_sess_resume_file_name);
460        conn_h->ch_flags |= CH_SESSION_RESUME_SAVED;
461    }
462    else
463        LSQ_WARN("error: fwrite(%s) returns %zd instead of %zd: %s",
464            client_ctx->hcc_sess_resume_file_name, nw, bufsz, strerror(errno));
465
466    fclose(file);
467}
468
469
470struct lsquic_stream_ctx {
471    lsquic_stream_t     *stream;
472    struct http_client_ctx   *client_ctx;
473    const char          *path;
474    enum {
475        HEADERS_SENT    = (1 << 0),
476        PROCESSED_HEADERS = 1 << 1,
477    }                    sh_flags;
478    lsquic_time_t        sh_created;
479    lsquic_time_t        sh_ttfb;
480    unsigned             count;
481    struct lsquic_reader reader;
482};
483
484
485static lsquic_stream_ctx_t *
486http_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
487{
488    const int pushed = lsquic_stream_is_pushed(stream);
489
490    if (pushed)
491    {
492        LSQ_INFO("not accepting server push");
493        lsquic_stream_refuse_push(stream);
494        return NULL;
495    }
496
497    lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h));
498    st_h->stream = stream;
499    st_h->client_ctx = stream_if_ctx;
500    st_h->sh_created = lsquic_time_now();
501    if (st_h->client_ctx->hcc_cur_pe)
502    {
503        st_h->client_ctx->hcc_cur_pe = TAILQ_NEXT(
504                                        st_h->client_ctx->hcc_cur_pe, next_pe);
505        if (!st_h->client_ctx->hcc_cur_pe)  /* Wrap around */
506            st_h->client_ctx->hcc_cur_pe =
507                                TAILQ_FIRST(&st_h->client_ctx->hcc_path_elems);
508    }
509    else
510        st_h->client_ctx->hcc_cur_pe = TAILQ_FIRST(
511                                            &st_h->client_ctx->hcc_path_elems);
512    st_h->path = st_h->client_ctx->hcc_cur_pe->path;
513    if (st_h->client_ctx->payload)
514    {
515        st_h->reader.lsqr_read = test_reader_read;
516        st_h->reader.lsqr_size = test_reader_size;
517        st_h->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->client_ctx->payload);
518        if (!st_h->reader.lsqr_ctx)
519            exit(1);
520    }
521    else
522        st_h->reader.lsqr_ctx = NULL;
523    LSQ_INFO("created new stream, path: %s", st_h->path);
524    lsquic_stream_wantwrite(stream, 1);
525    if (randomly_reprioritize_streams)
526        lsquic_stream_set_priority(stream, 1 + (random() & 0xFF));
527
528    return st_h;
529}
530
531
532static void
533send_headers (lsquic_stream_ctx_t *st_h)
534{
535    const char *hostname = st_h->client_ctx->hostname;
536    struct header_buf hbuf;
537    if (!hostname)
538        hostname = st_h->client_ctx->prog->prog_hostname;
539    hbuf.off = 0;
540    struct lsxpack_header headers_arr[7];
541#define V(v) (v), strlen(v)
542    header_set_ptr(&headers_arr[0], &hbuf, V(":method"), V(st_h->client_ctx->method));
543    header_set_ptr(&headers_arr[1], &hbuf, V(":scheme"), V("https"));
544    header_set_ptr(&headers_arr[2], &hbuf, V(":path"), V(st_h->path));
545    header_set_ptr(&headers_arr[3], &hbuf, V(":authority"), V(hostname));
546    header_set_ptr(&headers_arr[4], &hbuf, V("user-agent"), V(st_h->client_ctx->prog->prog_settings.es_ua));
547    /* The following headers only gets sent if there is request payload: */
548    header_set_ptr(&headers_arr[5], &hbuf, V("content-type"), V("application/octet-stream"));
549    header_set_ptr(&headers_arr[6], &hbuf, V("content-length"), V( st_h->client_ctx->payload_size));
550    lsquic_http_headers_t headers = {
551        .count = sizeof(headers_arr) / sizeof(headers_arr[0]),
552        .headers = headers_arr,
553    };
554    if (!st_h->client_ctx->payload)
555        headers.count -= 2;
556    if (0 != lsquic_stream_send_headers(st_h->stream, &headers,
557                                    st_h->client_ctx->payload == NULL))
558    {
559        LSQ_ERROR("cannot send headers: %s", strerror(errno));
560        exit(1);
561    }
562}
563
564
565/* This is here to exercise lsquic_conn_get_server_cert_chain() API */
566static void
567display_cert_chain (lsquic_conn_t *conn)
568{
569    STACK_OF(X509) *chain;
570    X509_NAME *name;
571    X509 *cert;
572    unsigned i;
573    char buf[100];
574
575    chain = lsquic_conn_get_server_cert_chain(conn);
576    if (!chain)
577    {
578        LSQ_WARN("could not get server certificate chain");
579        return;
580    }
581
582    for (i = 0; i < sk_X509_num(chain); ++i)
583    {
584        cert = sk_X509_value(chain, i);
585        name = X509_get_subject_name(cert);
586        LSQ_INFO("cert #%u: name: %s", i,
587                            X509_NAME_oneline(name, buf, sizeof(buf)));
588        X509_free(cert);
589    }
590
591    sk_X509_free(chain);
592}
593
594
595static void
596http_client_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
597{
598    ssize_t nw;
599
600    if (st_h->sh_flags & HEADERS_SENT)
601    {
602        if (st_h->client_ctx->payload && test_reader_size(st_h->reader.lsqr_ctx) > 0)
603        {
604            nw = lsquic_stream_writef(stream, &st_h->reader);
605            if (nw < 0)
606            {
607                LSQ_ERROR("write error: %s", strerror(errno));
608                exit(1);
609            }
610            if (test_reader_size(st_h->reader.lsqr_ctx) > 0)
611            {
612                lsquic_stream_wantwrite(stream, 1);
613            }
614            else
615            {
616                lsquic_stream_shutdown(stream, 1);
617                lsquic_stream_wantread(stream, 1);
618            }
619        }
620        else
621        {
622            lsquic_stream_shutdown(stream, 1);
623            lsquic_stream_wantread(stream, 1);
624        }
625    }
626    else
627    {
628        st_h->sh_flags |= HEADERS_SENT;
629        send_headers(st_h);
630    }
631}
632
633
634static size_t
635discard (void *ctx, const unsigned char *buf, size_t sz, int fin)
636{
637    return sz;
638}
639
640
641static void
642http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
643{
644    struct http_client_ctx *const client_ctx = st_h->client_ctx;
645    struct hset *hset;
646    ssize_t nread;
647    unsigned old_prio, new_prio;
648    unsigned char buf[0x200];
649    unsigned nreads = 0;
650#ifdef WIN32
651	srand(GetTickCount());
652#endif
653
654    do
655    {
656        if (g_header_bypass && !(st_h->sh_flags & PROCESSED_HEADERS))
657        {
658            hset = lsquic_stream_get_hset(stream);
659            if (!hset)
660            {
661                LSQ_ERROR("could not get header set from stream");
662                exit(2);
663            }
664            st_h->sh_ttfb = lsquic_time_now();
665            update_sample_stats(&s_stat_ttfb, st_h->sh_ttfb - st_h->sh_created);
666            if (s_discard_response)
667                LSQ_DEBUG("discard response: do not dump headers");
668            else
669                hset_dump(hset, stdout);
670            hset_destroy(hset);
671            st_h->sh_flags |= PROCESSED_HEADERS;
672        }
673        else if (nread = (s_discard_response
674                            ? lsquic_stream_readf(stream, discard, NULL)
675                            : lsquic_stream_read(stream, buf, sizeof(buf))),
676                    nread > 0)
677        {
678            s_stat_downloaded_bytes += nread;
679            /* test stream_reset after some number of read bytes */
680            if (client_ctx->hcc_reset_after_nbytes &&
681                s_stat_downloaded_bytes > client_ctx->hcc_reset_after_nbytes)
682            {
683                lsquic_stream_reset(stream, 0x1);
684                break;
685            }
686            /* test retire_cid after some number of read bytes */
687            if (client_ctx->hcc_retire_cid_after_nbytes &&
688                s_stat_downloaded_bytes > client_ctx->hcc_retire_cid_after_nbytes)
689            {
690                lsquic_conn_retire_cid(lsquic_stream_conn(stream));
691                client_ctx->hcc_retire_cid_after_nbytes = 0;
692                break;
693            }
694            if (!g_header_bypass && !(st_h->sh_flags & PROCESSED_HEADERS))
695            {
696                /* First read is assumed to be the first byte */
697                st_h->sh_ttfb = lsquic_time_now();
698                update_sample_stats(&s_stat_ttfb,
699                                    st_h->sh_ttfb - st_h->sh_created);
700                st_h->sh_flags |= PROCESSED_HEADERS;
701            }
702            if (!s_discard_response)
703                fwrite(buf, 1, nread, stdout);
704            if (randomly_reprioritize_streams && (st_h->count++ & 0x3F) == 0)
705            {
706                old_prio = lsquic_stream_priority(stream);
707                new_prio = 1 + (random() & 0xFF);
708#ifndef NDEBUG
709                const int s =
710#endif
711                lsquic_stream_set_priority(stream, new_prio);
712                assert(s == 0);
713                LSQ_DEBUG("changed stream %"PRIu64" priority from %u to %u",
714                                lsquic_stream_id(stream), old_prio, new_prio);
715            }
716        }
717        else if (0 == nread)
718        {
719            update_sample_stats(&s_stat_req, lsquic_time_now() - st_h->sh_ttfb);
720            client_ctx->hcc_flags |= HCC_SEEN_FIN;
721            lsquic_stream_shutdown(stream, 0);
722            break;
723        }
724        else if (client_ctx->prog->prog_settings.es_rw_once && EWOULDBLOCK == errno)
725        {
726            LSQ_NOTICE("emptied the buffer in 'once' mode");
727            break;
728        }
729        else if (lsquic_stream_is_rejected(stream))
730        {
731            LSQ_NOTICE("stream was rejected");
732            lsquic_stream_close(stream);
733            break;
734        }
735        else
736        {
737            LSQ_ERROR("could not read: %s", strerror(errno));
738            exit(2);
739        }
740    }
741    while (client_ctx->prog->prog_settings.es_rw_once
742            && nreads++ < 3 /* Emulate just a few reads */);
743}
744
745
746static void
747http_client_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
748{
749    const int pushed = lsquic_stream_is_pushed(stream);
750    if (pushed)
751    {
752        assert(NULL == st_h);
753        return;
754    }
755
756    LSQ_INFO("%s called", __func__);
757    struct http_client_ctx *const client_ctx = st_h->client_ctx;
758    lsquic_conn_t *conn = lsquic_stream_conn(stream);
759    lsquic_conn_ctx_t *conn_h;
760    TAILQ_FOREACH(conn_h, &client_ctx->conn_ctxs, next_ch)
761        if (conn_h->conn == conn)
762            break;
763    assert(conn_h);
764    --conn_h->ch_n_reqs;
765    --conn_h->ch_n_cc_streams;
766    if (0 == conn_h->ch_n_reqs)
767    {
768        LSQ_INFO("all requests completed, closing connection");
769        lsquic_conn_close(conn_h->conn);
770    }
771    else
772    {
773        LSQ_INFO("%u active stream, %u request remain, creating %u new stream",
774            conn_h->ch_n_cc_streams,
775            conn_h->ch_n_reqs - conn_h->ch_n_cc_streams,
776            MIN((conn_h->ch_n_reqs - conn_h->ch_n_cc_streams),
777                (client_ctx->hcc_cc_reqs_per_conn - conn_h->ch_n_cc_streams)));
778        create_streams(client_ctx, conn_h);
779    }
780    if (st_h->reader.lsqr_ctx)
781        destroy_lsquic_reader_ctx(st_h->reader.lsqr_ctx);
782    free(st_h);
783}
784
785
786static struct lsquic_stream_if http_client_if = {
787    .on_new_conn            = http_client_on_new_conn,
788    .on_conn_closed         = http_client_on_conn_closed,
789    .on_new_stream          = http_client_on_new_stream,
790    .on_read                = http_client_on_read,
791    .on_write               = http_client_on_write,
792    .on_close               = http_client_on_close,
793    .on_hsk_done            = http_client_on_hsk_done,
794};
795
796
797static void
798usage (const char *prog)
799{
800    const char *const slash = strrchr(prog, '/');
801    if (slash)
802        prog = slash + 1;
803    printf(
804"Usage: %s [opts]\n"
805"\n"
806"Options:\n"
807"   -p PATH     Path to request.  May be specified more than once.  If no\n"
808"                 path is specified, the connection is closed as soon as\n"
809"                 handshake succeeds.\n"
810"   -n CONNS    Number of concurrent connections.  Defaults to 1.\n"
811"   -r NREQS    Total number of requests to send.  Defaults to 1.\n"
812"   -R MAXREQS  Maximum number of requests per single connection.  Some\n"
813"                 connections will have fewer requests than this.\n"
814"   -w CONCUR   Number of concurrent requests per single connection.\n"
815"                 Defaults to 1.\n"
816"   -M METHOD   Method.  Defaults to GET.\n"
817"   -P PAYLOAD  Name of the file that contains payload to be used in the\n"
818"                 request.  This adds two more headers to the request:\n"
819"                 content-type: application/octet-stream and\n"
820"                 content-length\n"
821"   -K          Discard server response\n"
822"   -I          Abort on incomplete reponse from server\n"
823"   -4          Prefer IPv4 when resolving hostname\n"
824"   -6          Prefer IPv6 when resolving hostname\n"
825"   -0 FILE     Provide RTT info file (reading or writing)\n"
826#ifndef WIN32
827"   -C DIR      Certificate store.  If specified, server certificate will\n"
828"                 be verified.\n"
829#endif
830"   -a          Display server certificate chain after successful handshake.\n"
831"   -b N_BYTES  Send RESET_STREAM frame after the client has read n bytes.\n"
832"   -t          Print stats to stdout.\n"
833"   -T FILE     Print stats to FILE.  If FILE is -, print stats to stdout.\n"
834"   -q FILE     QIF mode: issue requests from the QIF file and validate\n"
835"                 server responses.\n"
836"   -e TOKEN    Hexadecimal string representing resume token.\n"
837            , prog);
838}
839
840
841#ifndef WIN32
842static X509_STORE *store;
843
844/* Windows does not have regex... */
845static int
846ends_in_pem (const char *s)
847{
848    int len;
849
850    len = strlen(s);
851
852    return len >= 4
853        && 0 == strcasecmp(s + len - 4, ".pem");
854}
855
856
857static X509 *
858file2cert (const char *path)
859{
860    X509 *cert = NULL;
861    BIO *in;
862
863    in = BIO_new(BIO_s_file());
864    if (!in)
865        goto end;
866
867    if (BIO_read_filename(in, path) <= 0)
868        goto end;
869
870    cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
871
872  end:
873    BIO_free(in);
874    return cert;
875}
876
877
878static int
879init_x509_cert_store (const char *path)
880{
881    struct dirent *ent;
882    X509 *cert;
883    DIR *dir;
884    char file_path[NAME_MAX];
885    int ret;
886
887    dir = opendir(path);
888    if (!dir)
889    {
890        LSQ_WARN("Cannot open directory `%s': %s", path, strerror(errno));
891        return -1;
892    }
893
894    store = X509_STORE_new();
895
896    while ((ent = readdir(dir)))
897    {
898        if (ends_in_pem(ent->d_name))
899        {
900            ret = snprintf(file_path, sizeof(file_path), "%s/%s",
901                                                            path, ent->d_name);
902            if (ret < 0)
903            {
904                LSQ_WARN("file_path formatting error %s", strerror(errno));
905                continue;
906            }
907            else if ((unsigned)ret >= sizeof(file_path))
908            {
909                LSQ_WARN("file_path was truncated %s", strerror(errno));
910                continue;
911            }
912            cert = file2cert(file_path);
913            if (cert)
914            {
915                if (1 != X509_STORE_add_cert(store, cert))
916                    LSQ_WARN("could not add cert from %s", file_path);
917            }
918            else
919                LSQ_WARN("could not read cert from %s", file_path);
920        }
921    }
922    (void) closedir(dir);
923    return 0;
924}
925
926
927static int
928verify_server_cert (void *ctx, STACK_OF(X509) *chain)
929{
930    X509_STORE_CTX store_ctx;
931    X509 *cert;
932    int ver;
933
934    if (!store)
935    {
936        if (0 != init_x509_cert_store(ctx))
937            return -1;
938    }
939
940    cert = sk_X509_shift(chain);
941    X509_STORE_CTX_init(&store_ctx, store, cert, chain);
942
943    ver = X509_verify_cert(&store_ctx);
944
945    X509_STORE_CTX_cleanup(&store_ctx);
946
947    if (ver != 1)
948        LSQ_WARN("could not verify server certificate");
949
950    return ver == 1 ? 0 : -1;
951}
952#endif
953
954
955static void *
956hset_create (void *hsi_ctx, lsquic_stream_t *stream, int is_push_promise)
957{
958    struct hset *hset;
959
960    if ((hset = malloc(sizeof(*hset))))
961    {
962        STAILQ_INIT(hset);
963        return hset;
964    }
965    else
966        return NULL;
967}
968
969
970static struct lsxpack_header *
971hset_prepare_decode (void *hset_p, struct lsxpack_header *xhdr,
972                                                        size_t req_space)
973{
974    struct hset *const hset = hset_p;
975    struct hset_elem *el;
976    char *buf;
977
978    if (0 == req_space)
979        req_space = 0x100;
980
981    if (req_space > LSXPACK_MAX_STRLEN)
982    {
983        LSQ_WARN("requested space for header is too large: %zd bytes",
984                                                                    req_space);
985        return NULL;
986    }
987
988    if (!xhdr)
989    {
990        buf = malloc(req_space);
991        if (!buf)
992        {
993            LSQ_WARN("cannot allocate buf of %zd bytes", req_space);
994            return NULL;
995        }
996        el = malloc(sizeof(*el));
997        if (!el)
998        {
999            LSQ_WARN("cannot allocate hset_elem");
1000            free(buf);
1001            return NULL;
1002        }
1003        STAILQ_INSERT_TAIL(hset, el, next);
1004        lsxpack_header_prepare_decode(&el->xhdr, buf, 0, req_space);
1005        el->nalloc = req_space;
1006    }
1007    else
1008    {
1009        el = (struct hset_elem *) ((char *) xhdr
1010                                        - offsetof(struct hset_elem, xhdr));
1011        if (req_space <= el->nalloc)
1012        {
1013            LSQ_ERROR("requested space is smaller than already allocated");
1014            return NULL;
1015        }
1016        if (req_space < el->nalloc * 2)
1017            req_space = el->nalloc * 2;
1018        buf = realloc(el->xhdr.buf, req_space);
1019        if (!buf)
1020        {
1021            LSQ_WARN("cannot reallocate hset buf");
1022            return NULL;
1023        }
1024        el->xhdr.buf = buf;
1025        el->xhdr.val_len = req_space;
1026        el->nalloc = req_space;
1027    }
1028
1029    return &el->xhdr;
1030}
1031
1032
1033static int
1034hset_add_header (void *hset_p, struct lsxpack_header *xhdr)
1035{
1036    unsigned name_len, value_len;
1037    /* Not much to do: the header value are in xhdr */
1038
1039    if (xhdr)
1040    {
1041        name_len = xhdr->name_len;
1042        value_len = xhdr->val_len;
1043        s_stat_downloaded_bytes += name_len + value_len + 4;    /* ": \r\n" */
1044    }
1045    else
1046        s_stat_downloaded_bytes += 2;   /* \r\n "*/
1047
1048    return 0;
1049}
1050
1051
1052static void
1053hset_destroy (void *hset_p)
1054{
1055    struct hset *hset = hset_p;
1056    struct hset_elem *el, *next;
1057
1058    for (el = STAILQ_FIRST(hset); el; el = next)
1059    {
1060        next = STAILQ_NEXT(el, next);
1061        free(el->xhdr.buf);
1062        free(el);
1063    }
1064    free(hset);
1065}
1066
1067
1068static void
1069hset_dump (const struct hset *hset, FILE *out)
1070{
1071    const struct hset_elem *el;
1072
1073    STAILQ_FOREACH(el, hset, next)
1074        if (el->xhdr.flags & (LSXPACK_HPACK_VAL_MATCHED|LSXPACK_QPACK_IDX))
1075            fprintf(out, "%.*s (%s static table idx %u): %.*s\n",
1076                (int) el->xhdr.name_len, lsxpack_header_get_name(&el->xhdr),
1077                el->xhdr.flags & LSXPACK_HPACK_VAL_MATCHED ? "hpack" : "qpack",
1078                el->xhdr.flags & LSXPACK_HPACK_VAL_MATCHED ? el->xhdr.hpack_index
1079                                                    : el->xhdr.qpack_index,
1080                (int) el->xhdr.val_len, lsxpack_header_get_value(&el->xhdr));
1081        else
1082            fprintf(out, "%.*s: %.*s\n",
1083                (int) el->xhdr.name_len, lsxpack_header_get_name(&el->xhdr),
1084                (int) el->xhdr.val_len, lsxpack_header_get_value(&el->xhdr));
1085
1086    fprintf(out, "\n");
1087    fflush(out);
1088}
1089
1090
1091/* These are basic and for illustration purposes only.  You will want to
1092 * do your own verification by doing something similar to what is done
1093 * in src/liblsquic/lsquic_http1x_if.c
1094 */
1095static const struct lsquic_hset_if header_bypass_api =
1096{
1097    .hsi_create_header_set  = hset_create,
1098    .hsi_prepare_decode     = hset_prepare_decode,
1099    .hsi_process_header     = hset_add_header,
1100    .hsi_discard_header_set = hset_destroy,
1101};
1102
1103
1104static void
1105display_stat (FILE *out, const struct sample_stats *stats, const char *name)
1106{
1107    long double mean, stddev;
1108
1109    calc_sample_stats(stats, &mean, &stddev);
1110    fprintf(out, "%s: n: %u; min: %.2Lf ms; max: %.2Lf ms; mean: %.2Lf ms; "
1111        "sd: %.2Lf ms\n", name, stats->n, (long double) stats->min / 1000,
1112        (long double) stats->max / 1000, mean / 1000, stddev / 1000);
1113}
1114
1115
1116static lsquic_conn_ctx_t *
1117qif_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
1118{
1119    lsquic_conn_make_stream(conn);
1120    return stream_if_ctx;
1121}
1122
1123
1124static void
1125qif_client_on_conn_closed (lsquic_conn_t *conn)
1126{
1127    struct http_client_ctx *client_ctx = (void *) lsquic_conn_get_ctx(conn);
1128    LSQ_INFO("connection is closed: stop engine");
1129    prog_stop(client_ctx->prog);
1130}
1131
1132
1133struct qif_stream_ctx
1134{
1135    int                         reqno;
1136    struct lsquic_http_headers  headers;
1137    char                       *qif_str;
1138    size_t                      qif_sz;
1139    size_t                      qif_off;
1140    char                       *resp_str;   /* qif_sz allocated */
1141    size_t                      resp_off;   /* Read so far */
1142    enum {
1143        QSC_HEADERS_SENT = 1 << 0,
1144        QSC_GOT_HEADERS  = 1 << 1,
1145    }                           flags;
1146};
1147
1148#define MAX(a, b) ((a) > (b) ? (a) : (b))
1149
1150lsquic_stream_ctx_t *
1151qif_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
1152{
1153    struct http_client_ctx *const client_ctx = stream_if_ctx;
1154    FILE *const fh = client_ctx->qif_fh;
1155    struct qif_stream_ctx *ctx;
1156    struct lsxpack_header *header;
1157    static int reqno;
1158    size_t nalloc;
1159    char *end, *tab, *line;
1160    char line_buf[0x1000];
1161
1162    ctx = calloc(1, sizeof(*ctx));
1163    if (!ctx)
1164    {
1165        perror("calloc");
1166        exit(1);
1167    }
1168    ctx->reqno = reqno++;
1169
1170    nalloc = 0;
1171    while ((line = fgets(line_buf, sizeof(line_buf), fh)))
1172    {
1173        end = strchr(line, '\n');
1174        if (!end)
1175        {
1176            fprintf(stderr, "no newline\n");
1177            exit(1);
1178        }
1179
1180        if (end == line)
1181            break;
1182
1183        if (*line == '#')
1184            continue;
1185
1186        tab = strchr(line, '\t');
1187        if (!tab)
1188        {
1189            fprintf(stderr, "no TAB\n");
1190            exit(1);
1191        }
1192
1193        if (nalloc + (end + 1 - line) > ctx->qif_sz)
1194        {
1195            if (nalloc)
1196                nalloc = MAX(nalloc * 2, nalloc + (end + 1 - line));
1197            else
1198                nalloc = end + 1 - line;
1199            ctx->qif_str = realloc(ctx->qif_str, nalloc);
1200            if (!ctx->qif_str)
1201            {
1202                perror("realloc");
1203                exit(1);
1204            }
1205        }
1206        memcpy(ctx->qif_str + ctx->qif_sz, line, end + 1 - line);
1207
1208        ctx->headers.headers = realloc(ctx->headers.headers,
1209                sizeof(ctx->headers.headers[0]) * (ctx->headers.count + 1));
1210        if (!ctx->headers.headers)
1211        {
1212            perror("realloc");
1213            exit(1);
1214        }
1215        header = &ctx->headers.headers[ctx->headers.count++];
1216        lsxpack_header_set_offset2(header, ctx->qif_str + ctx->qif_sz, 0,
1217                                    tab - line, tab - line + 1, end - tab - 1);
1218        ctx->qif_sz += end + 1 - line;
1219    }
1220
1221    lsquic_stream_wantwrite(stream, 1);
1222
1223    if (!line)
1224    {
1225        LSQ_DEBUG("Input QIF file ends; close file handle");
1226        fclose(client_ctx->qif_fh);
1227        client_ctx->qif_fh = NULL;
1228    }
1229
1230    return (void *) ctx;
1231}
1232
1233
1234static void
1235qif_client_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *h)
1236{
1237    struct qif_stream_ctx *const ctx = (void *) h;
1238    size_t towrite;
1239    ssize_t nw;
1240
1241    if (ctx->flags & QSC_HEADERS_SENT)
1242    {
1243        towrite = ctx->qif_sz - ctx->qif_off;
1244        nw = lsquic_stream_write(stream, ctx->qif_str + ctx->qif_off, towrite);
1245        if (nw >= 0)
1246        {
1247            LSQ_DEBUG("wrote %zd bytes to stream", nw);
1248            ctx->qif_off += nw;
1249            if (ctx->qif_off == (size_t) nw)
1250            {
1251                lsquic_stream_shutdown(stream, 1);
1252                lsquic_stream_wantread(stream, 1);
1253                LSQ_DEBUG("finished writing request %d", ctx->reqno);
1254            }
1255        }
1256        else
1257        {
1258            LSQ_ERROR("cannot write to stream: %s", strerror(errno));
1259            lsquic_stream_wantwrite(stream, 0);
1260            lsquic_conn_abort(lsquic_stream_conn(stream));
1261        }
1262    }
1263    else
1264    {
1265        if (0 == lsquic_stream_send_headers(stream, &ctx->headers, 0))
1266        {
1267            ctx->flags |= QSC_HEADERS_SENT;
1268            LSQ_DEBUG("sent headers");
1269        }
1270        else
1271        {
1272            LSQ_ERROR("cannot send headers: %s", strerror(errno));
1273            lsquic_stream_wantwrite(stream, 0);
1274            lsquic_conn_abort(lsquic_stream_conn(stream));
1275        }
1276    }
1277}
1278
1279
1280static void
1281qif_client_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *h)
1282{
1283    struct qif_stream_ctx *const ctx = (void *) h;
1284    struct hset *hset;
1285    ssize_t nr;
1286    unsigned char buf[1];
1287
1288    LSQ_DEBUG("reading response to request %d", ctx->reqno);
1289
1290    if (!(ctx->flags & QSC_GOT_HEADERS))
1291    {
1292        hset = lsquic_stream_get_hset(stream);
1293        if (!hset)
1294        {
1295            LSQ_ERROR("could not get header set from stream");
1296            exit(2);
1297        }
1298        LSQ_DEBUG("got header set for response %d", ctx->reqno);
1299        hset_dump(hset, stdout);
1300        hset_destroy(hset);
1301        ctx->flags |= QSC_GOT_HEADERS;
1302    }
1303    else
1304    {
1305        if (!ctx->resp_str)
1306        {
1307            ctx->resp_str = malloc(ctx->qif_sz);
1308            if (!ctx->resp_str)
1309            {
1310                perror("malloc");
1311                exit(1);
1312            }
1313        }
1314        if (ctx->resp_off < ctx->qif_sz)
1315        {
1316            nr = lsquic_stream_read(stream, ctx->resp_str + ctx->resp_off,
1317                                        ctx->qif_sz - ctx->resp_off);
1318            if (nr > 0)
1319            {
1320                ctx->resp_off += nr;
1321                LSQ_DEBUG("read %zd bytes of reponse %d", nr, ctx->reqno);
1322            }
1323            else if (nr == 0)
1324            {
1325                LSQ_INFO("response %d too short", ctx->reqno);
1326                LSQ_WARN("response %d FAIL", ctx->reqno);
1327                lsquic_stream_shutdown(stream, 0);
1328            }
1329            else
1330            {
1331                LSQ_ERROR("error reading from stream");
1332                lsquic_stream_wantread(stream, 0);
1333                lsquic_conn_abort(lsquic_stream_conn(stream));
1334            }
1335        }
1336        else
1337        {
1338            /* Collect EOF */
1339            nr = lsquic_stream_read(stream, buf, sizeof(buf));
1340            if (nr == 0)
1341            {
1342                if (0 == memcmp(ctx->qif_str, ctx->resp_str, ctx->qif_sz))
1343                    LSQ_INFO("response %d OK", ctx->reqno);
1344                else
1345                    LSQ_WARN("response %d FAIL", ctx->reqno);
1346                lsquic_stream_shutdown(stream, 0);
1347            }
1348            else if (nr > 0)
1349            {
1350                LSQ_INFO("response %d too long", ctx->reqno);
1351                LSQ_WARN("response %d FAIL", ctx->reqno);
1352                lsquic_stream_shutdown(stream, 0);
1353            }
1354            else
1355            {
1356                LSQ_ERROR("error reading from stream");
1357                lsquic_stream_shutdown(stream, 0);
1358                lsquic_conn_abort(lsquic_stream_conn(stream));
1359            }
1360        }
1361    }
1362}
1363
1364
1365static void
1366qif_client_on_close (struct lsquic_stream *stream, lsquic_stream_ctx_t *h)
1367{
1368    struct lsquic_conn *conn = lsquic_stream_conn(stream);
1369    struct http_client_ctx *client_ctx = (void *) lsquic_conn_get_ctx(conn);
1370    struct qif_stream_ctx *const ctx = (void *) h;
1371    free(ctx->qif_str);
1372    free(ctx->resp_str);
1373    free(ctx->headers.headers);
1374    free(ctx);
1375    if (client_ctx->qif_fh)
1376        lsquic_conn_make_stream(conn);
1377    else
1378        lsquic_conn_close(conn);
1379}
1380
1381
1382const struct lsquic_stream_if qif_client_if = {
1383    .on_new_conn            = qif_client_on_new_conn,
1384    .on_conn_closed         = qif_client_on_conn_closed,
1385    .on_new_stream          = qif_client_on_new_stream,
1386    .on_read                = qif_client_on_read,
1387    .on_write               = qif_client_on_write,
1388    .on_close               = qif_client_on_close,
1389};
1390
1391
1392int
1393main (int argc, char **argv)
1394{
1395    int opt, s, was_empty;
1396    lsquic_time_t start_time;
1397    FILE *stats_fh = NULL;
1398    long double elapsed;
1399    struct http_client_ctx client_ctx;
1400    struct stat st;
1401    struct path_elem *pe;
1402    struct sport_head sports;
1403    struct prog prog;
1404    const char *token = NULL;
1405
1406    TAILQ_INIT(&sports);
1407    memset(&client_ctx, 0, sizeof(client_ctx));
1408    TAILQ_INIT(&client_ctx.hcc_path_elems);
1409    TAILQ_INIT(&client_ctx.conn_ctxs);
1410    client_ctx.method = "GET";
1411    client_ctx.hcc_concurrency = 1;
1412    client_ctx.hcc_cc_reqs_per_conn = 1;
1413    client_ctx.hcc_reqs_per_conn = 1;
1414    client_ctx.hcc_total_n_reqs = 1;
1415    client_ctx.hcc_reset_after_nbytes = 0;
1416    client_ctx.hcc_retire_cid_after_nbytes = 0;
1417    client_ctx.prog = &prog;
1418
1419    prog_init(&prog, LSENG_HTTP, &sports, &http_client_if, &client_ctx);
1420
1421    while (-1 != (opt = getopt(argc, argv, PROG_OPTS
1422                                    "46Br:R:IKu:EP:M:n:w:H:p:0:q:e:hatT:b:d:"
1423#ifndef WIN32
1424                                                                      "C:"
1425#endif
1426                                                                            )))
1427    {
1428        switch (opt) {
1429        case 'a':
1430            ++s_display_cert_chain;
1431            break;
1432        case '4':
1433        case '6':
1434            prog.prog_ipver = opt - '0';
1435            break;
1436        case 'B':
1437            g_header_bypass = 1;
1438            prog.prog_api.ea_hsi_if = &header_bypass_api;
1439            prog.prog_api.ea_hsi_ctx = NULL;
1440            break;
1441        case 'I':
1442            client_ctx.hcc_flags |= HCC_ABORT_ON_INCOMPLETE;
1443            break;
1444        case 'K':
1445            ++s_discard_response;
1446            break;
1447        case 'u':   /* Accept p<U>sh promise */
1448            promise_fd = open(optarg, O_WRONLY|O_CREAT|O_TRUNC, 0644);
1449            if (promise_fd < 0)
1450            {
1451                perror("open");
1452                exit(1);
1453            }
1454            prog.prog_settings.es_support_push = 1;     /* Pokes into prog */
1455            break;
1456        case 'E':   /* E: randomly reprioritize str<E>ams.  Now, that's
1457                     * pretty random. :)
1458                     */
1459            randomly_reprioritize_streams = 1;
1460            break;
1461        case 'n':
1462            client_ctx.hcc_concurrency = atoi(optarg);
1463            break;
1464        case 'w':
1465            client_ctx.hcc_cc_reqs_per_conn = atoi(optarg);
1466            break;
1467        case 'P':
1468            client_ctx.payload = optarg;
1469            if (0 != stat(optarg, &st))
1470            {
1471                perror("stat");
1472                exit(2);
1473            }
1474            sprintf(client_ctx.payload_size, "%jd", (intmax_t) st.st_size);
1475            break;
1476        case 'M':
1477            client_ctx.method = optarg;
1478            break;
1479        case 'r':
1480            client_ctx.hcc_total_n_reqs = atoi(optarg);
1481            break;
1482        case 'R':
1483            client_ctx.hcc_reqs_per_conn = atoi(optarg);
1484            break;
1485        case 'H':
1486            client_ctx.hostname = optarg;
1487            prog.prog_hostname = optarg;            /* Pokes into prog */
1488            break;
1489        case 'p':
1490            pe = calloc(1, sizeof(*pe));
1491            pe->path = optarg;
1492            TAILQ_INSERT_TAIL(&client_ctx.hcc_path_elems, pe, next_pe);
1493            break;
1494        case 'h':
1495            usage(argv[0]);
1496            prog_print_common_options(&prog, stdout);
1497            exit(0);
1498        case 'q':
1499            client_ctx.qif_file = optarg;
1500            break;
1501        case 'e':
1502            if (TAILQ_EMPTY(&sports))
1503                token = optarg;
1504            else
1505                sport_set_token(TAILQ_LAST(&sports, sport_head), optarg);
1506            break;
1507#ifndef WIN32
1508        case 'C':
1509            prog.prog_api.ea_verify_cert = verify_server_cert;
1510            prog.prog_api.ea_verify_ctx = optarg;
1511            break;
1512#endif
1513        case 't':
1514            stats_fh = stdout;
1515            break;
1516        case 'T':
1517            if (0 == strcmp(optarg, "-"))
1518                stats_fh = stdout;
1519            else
1520            {
1521                stats_fh = fopen(optarg, "w");
1522                if (!stats_fh)
1523                {
1524                    perror("fopen");
1525                    exit(1);
1526                }
1527            }
1528            break;
1529        case 'b':
1530            client_ctx.hcc_reset_after_nbytes = atoi(optarg);
1531            break;
1532        case 'd':
1533            client_ctx.hcc_retire_cid_after_nbytes = atoi(optarg);
1534            break;
1535        case '0':
1536            http_client_if.on_sess_resume_info = http_client_on_sess_resume_info;
1537            client_ctx.hcc_sess_resume_file_name = optarg;
1538            break;
1539        default:
1540            if (0 != prog_set_opt(&prog, opt, optarg))
1541                exit(1);
1542        }
1543    }
1544
1545#if LSQUIC_CONN_STATS
1546    prog.prog_api.ea_stats_fh = stats_fh;
1547#endif
1548    prog.prog_settings.es_ua = LITESPEED_ID;
1549
1550    if (client_ctx.qif_file)
1551    {
1552        client_ctx.qif_fh = fopen(client_ctx.qif_file, "r");
1553        if (!client_ctx.qif_fh)
1554        {
1555            fprintf(stderr, "Cannot open %s for reading: %s\n",
1556                                    client_ctx.qif_file, strerror(errno));
1557            exit(1);
1558        }
1559        LSQ_NOTICE("opened QIF file %s for reading\n", client_ctx.qif_file);
1560        prog.prog_api.ea_stream_if = &qif_client_if;
1561        g_header_bypass = 1;
1562        prog.prog_api.ea_hsi_if = &header_bypass_api;
1563        prog.prog_api.ea_hsi_ctx = NULL;
1564    }
1565    else if (TAILQ_EMPTY(&client_ctx.hcc_path_elems))
1566    {
1567        fprintf(stderr, "Specify at least one path using -p option\n");
1568        exit(1);
1569    }
1570
1571    start_time = lsquic_time_now();
1572    was_empty = TAILQ_EMPTY(&sports);
1573    if (0 != prog_prep(&prog))
1574    {
1575        LSQ_ERROR("could not prep");
1576        exit(EXIT_FAILURE);
1577    }
1578    if (!(client_ctx.hostname || prog.prog_hostname))
1579    {
1580        fprintf(stderr, "Specify hostname (used for SNI and :authority) via "
1581            "-H option\n");
1582        exit(EXIT_FAILURE);
1583    }
1584    if (was_empty && token)
1585        sport_set_token(TAILQ_LAST(&sports, sport_head), token);
1586
1587    if (client_ctx.qif_file)
1588    {
1589        if (0 != prog_connect(&prog, NULL, 0))
1590        {
1591            LSQ_ERROR("connection failed");
1592            exit(EXIT_FAILURE);
1593        }
1594    }
1595    else
1596        create_connections(&client_ctx);
1597
1598    LSQ_DEBUG("entering event loop");
1599
1600    s = prog_run(&prog);
1601
1602    if (stats_fh)
1603    {
1604        elapsed = (long double) (lsquic_time_now() - start_time) / 1000000;
1605        fprintf(stats_fh, "overall statistics as calculated by %s:\n", argv[0]);
1606        display_stat(stats_fh, &s_stat_to_conn, "time for connect");
1607        display_stat(stats_fh, &s_stat_req, "time for request");
1608        display_stat(stats_fh, &s_stat_ttfb, "time to 1st byte");
1609        fprintf(stats_fh, "downloaded %lu application bytes in %.3Lf seconds\n",
1610            s_stat_downloaded_bytes, elapsed);
1611        fprintf(stats_fh, "%.2Lf reqs/sec; %.0Lf bytes/sec\n",
1612            (long double) s_stat_req.n / elapsed,
1613            (long double) s_stat_downloaded_bytes / elapsed);
1614        fprintf(stats_fh, "read handler count %lu\n", prog.prog_read_count);
1615    }
1616
1617    prog_cleanup(&prog);
1618    if (promise_fd >= 0)
1619        (void) close(promise_fd);
1620
1621    while ((pe = TAILQ_FIRST(&client_ctx.hcc_path_elems)))
1622    {
1623        TAILQ_REMOVE(&client_ctx.hcc_path_elems, pe, next_pe);
1624        free(pe);
1625    }
1626
1627    if (client_ctx.qif_fh)
1628        (void) fclose(client_ctx.qif_fh);
1629
1630    exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE);
1631}
1632