http_client.c revision fb3e20e0
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_zero_rtt_file_name;
191
192    enum {
193        HCC_SKIP_0RTT           = (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_ZERO_RTT_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 zero_rtt[0x2000];
244
245    if (0 == (client_ctx->hcc_flags & HCC_SKIP_0RTT)
246                                    && client_ctx->hcc_zero_rtt_file_name)
247    {
248        file = fopen(client_ctx->hcc_zero_rtt_file_name, "rb");
249        if (!file)
250        {
251            LSQ_DEBUG("cannot open %s for reading: %s",
252                        client_ctx->hcc_zero_rtt_file_name, strerror(errno));
253            goto no_file;
254        }
255        len = fread(zero_rtt, 1, sizeof(zero_rtt), file);
256        if (0 == len && !feof(file))
257            LSQ_WARN("error reading %s: %s",
258                        client_ctx->hcc_zero_rtt_file_name, strerror(errno));
259        fclose(file);
260        LSQ_INFO("create connection zero_rtt %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 ? zero_rtt : 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_0RTT_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_0RTT_OK ? "with 0-RTT" : "");
393    else if (status == LSQ_HSK_FAIL)
394        LSQ_INFO("handshake failed");
395    else if (status == LSQ_HSK_0RTT_FAIL)
396    {
397        LSQ_INFO("handshake failed because of 0-RTT, will retry without it");
398        client_ctx->hcc_flags |= HCC_SKIP_0RTT;
399        ++client_ctx->hcc_concurrency;
400        ++client_ctx->hcc_total_n_reqs;
401    }
402    else
403        assert(0);
404
405    if (hsk_status_ok(status) && s_display_cert_chain)
406        display_cert_chain(conn);
407
408    if (hsk_status_ok(status))
409    {
410        conn_h = lsquic_conn_get_ctx(conn);
411        ++s_stat_conns_ok;
412        update_sample_stats(&s_stat_to_conn,
413                                    lsquic_time_now() - conn_h->ch_created);
414        if (TAILQ_EMPTY(&client_ctx->hcc_path_elems))
415        {
416            LSQ_INFO("no paths mode: close connection");
417            lsquic_conn_close(conn_h->conn);
418        }
419    }
420    else
421        ++s_stat_conns_failed;
422}
423
424
425static void
426http_client_on_zero_rtt_info (lsquic_conn_t *conn, const unsigned char *buf,
427                                                                size_t bufsz)
428{
429    lsquic_conn_ctx_t *const conn_h = lsquic_conn_get_ctx(conn);
430    struct http_client_ctx *const client_ctx = conn_h->client_ctx;
431    FILE *file;
432    size_t nw;
433
434    assert(client_ctx->hcc_zero_rtt_file_name);
435
436    /* Our client is rather limited: only one file and only one ticket per
437     * connection can be saved.
438     */
439    if (conn_h->ch_flags & CH_ZERO_RTT_SAVED)
440    {
441        LSQ_DEBUG("zero-rtt already saved for this connection");
442        return;
443    }
444
445    file = fopen(client_ctx->hcc_zero_rtt_file_name, "wb");
446    if (!file)
447    {
448        LSQ_WARN("cannot open %s for writing: %s",
449            client_ctx->hcc_zero_rtt_file_name, strerror(errno));
450        return;
451    }
452
453    nw = fwrite(buf, 1, bufsz, file);
454    if (nw == bufsz)
455    {
456        LSQ_DEBUG("wrote %zd bytes of zero-rtt information to %s",
457            nw, client_ctx->hcc_zero_rtt_file_name);
458        conn_h->ch_flags |= CH_ZERO_RTT_SAVED;
459    }
460    else
461        LSQ_WARN("error: fwrite(%s) returns %zd instead of %zd: %s",
462            client_ctx->hcc_zero_rtt_file_name, nw, bufsz, strerror(errno));
463
464    fclose(file);
465}
466
467
468struct lsquic_stream_ctx {
469    lsquic_stream_t     *stream;
470    struct http_client_ctx   *client_ctx;
471    const char          *path;
472    enum {
473        HEADERS_SENT    = (1 << 0),
474        PROCESSED_HEADERS = 1 << 1,
475    }                    sh_flags;
476    lsquic_time_t        sh_created;
477    lsquic_time_t        sh_ttfb;
478    unsigned             count;
479    struct lsquic_reader reader;
480};
481
482
483static lsquic_stream_ctx_t *
484http_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
485{
486    const int pushed = lsquic_stream_is_pushed(stream);
487
488    if (pushed)
489    {
490        LSQ_INFO("not accepting server push");
491        lsquic_stream_refuse_push(stream);
492        return NULL;
493    }
494
495    lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h));
496    st_h->stream = stream;
497    st_h->client_ctx = stream_if_ctx;
498    st_h->sh_created = lsquic_time_now();
499    if (st_h->client_ctx->hcc_cur_pe)
500    {
501        st_h->client_ctx->hcc_cur_pe = TAILQ_NEXT(
502                                        st_h->client_ctx->hcc_cur_pe, next_pe);
503        if (!st_h->client_ctx->hcc_cur_pe)  /* Wrap around */
504            st_h->client_ctx->hcc_cur_pe =
505                                TAILQ_FIRST(&st_h->client_ctx->hcc_path_elems);
506    }
507    else
508        st_h->client_ctx->hcc_cur_pe = TAILQ_FIRST(
509                                            &st_h->client_ctx->hcc_path_elems);
510    st_h->path = st_h->client_ctx->hcc_cur_pe->path;
511    if (st_h->client_ctx->payload)
512    {
513        st_h->reader.lsqr_read = test_reader_read;
514        st_h->reader.lsqr_size = test_reader_size;
515        st_h->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->client_ctx->payload);
516        if (!st_h->reader.lsqr_ctx)
517            exit(1);
518    }
519    else
520        st_h->reader.lsqr_ctx = NULL;
521    LSQ_INFO("created new stream, path: %s", st_h->path);
522    lsquic_stream_wantwrite(stream, 1);
523    if (randomly_reprioritize_streams)
524        lsquic_stream_set_priority(stream, 1 + (random() & 0xFF));
525
526    return st_h;
527}
528
529
530static void
531send_headers (lsquic_stream_ctx_t *st_h)
532{
533    const char *hostname = st_h->client_ctx->hostname;
534    struct header_buf hbuf;
535    if (!hostname)
536        hostname = st_h->client_ctx->prog->prog_hostname;
537    hbuf.off = 0;
538    struct lsxpack_header headers_arr[7];
539#define V(v) (v), strlen(v)
540    header_set_ptr(&headers_arr[0], &hbuf, V(":method"), V(st_h->client_ctx->method));
541    header_set_ptr(&headers_arr[1], &hbuf, V(":scheme"), V("https"));
542    header_set_ptr(&headers_arr[2], &hbuf, V(":path"), V(st_h->path));
543    header_set_ptr(&headers_arr[3], &hbuf, V(":authority"), V(hostname));
544    header_set_ptr(&headers_arr[4], &hbuf, V("user-agent"), V(st_h->client_ctx->prog->prog_settings.es_ua));
545    /* The following headers only gets sent if there is request payload: */
546    header_set_ptr(&headers_arr[5], &hbuf, V("content-type"), V("application/octet-stream"));
547    header_set_ptr(&headers_arr[6], &hbuf, V("content-length"), V( st_h->client_ctx->payload_size));
548    lsquic_http_headers_t headers = {
549        .count = sizeof(headers_arr) / sizeof(headers_arr[0]),
550        .headers = headers_arr,
551    };
552    if (!st_h->client_ctx->payload)
553        headers.count -= 2;
554    if (0 != lsquic_stream_send_headers(st_h->stream, &headers,
555                                    st_h->client_ctx->payload == NULL))
556    {
557        LSQ_ERROR("cannot send headers: %s", strerror(errno));
558        exit(1);
559    }
560}
561
562
563/* This is here to exercise lsquic_conn_get_server_cert_chain() API */
564static void
565display_cert_chain (lsquic_conn_t *conn)
566{
567    STACK_OF(X509) *chain;
568    X509_NAME *name;
569    X509 *cert;
570    unsigned i;
571    char buf[100];
572
573    chain = lsquic_conn_get_server_cert_chain(conn);
574    if (!chain)
575    {
576        LSQ_WARN("could not get server certificate chain");
577        return;
578    }
579
580    for (i = 0; i < sk_X509_num(chain); ++i)
581    {
582        cert = sk_X509_value(chain, i);
583        name = X509_get_subject_name(cert);
584        LSQ_INFO("cert #%u: name: %s", i,
585                            X509_NAME_oneline(name, buf, sizeof(buf)));
586        X509_free(cert);
587    }
588
589    sk_X509_free(chain);
590}
591
592
593static void
594http_client_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
595{
596    ssize_t nw;
597
598    if (st_h->sh_flags & HEADERS_SENT)
599    {
600        if (st_h->client_ctx->payload && test_reader_size(st_h->reader.lsqr_ctx) > 0)
601        {
602            nw = lsquic_stream_writef(stream, &st_h->reader);
603            if (nw < 0)
604            {
605                LSQ_ERROR("write error: %s", strerror(errno));
606                exit(1);
607            }
608            if (test_reader_size(st_h->reader.lsqr_ctx) > 0)
609            {
610                lsquic_stream_wantwrite(stream, 1);
611            }
612            else
613            {
614                lsquic_stream_shutdown(stream, 1);
615                lsquic_stream_wantread(stream, 1);
616            }
617        }
618        else
619        {
620            lsquic_stream_shutdown(stream, 1);
621            lsquic_stream_wantread(stream, 1);
622        }
623    }
624    else
625    {
626        st_h->sh_flags |= HEADERS_SENT;
627        send_headers(st_h);
628    }
629}
630
631
632static size_t
633discard (void *ctx, const unsigned char *buf, size_t sz, int fin)
634{
635    return sz;
636}
637
638
639static void
640http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
641{
642    struct http_client_ctx *const client_ctx = st_h->client_ctx;
643    struct hset *hset;
644    ssize_t nread;
645    unsigned old_prio, new_prio;
646    unsigned char buf[0x200];
647    unsigned nreads = 0;
648#ifdef WIN32
649	srand(GetTickCount());
650#endif
651
652    do
653    {
654        if (g_header_bypass && !(st_h->sh_flags & PROCESSED_HEADERS))
655        {
656            hset = lsquic_stream_get_hset(stream);
657            if (!hset)
658            {
659                LSQ_ERROR("could not get header set from stream");
660                exit(2);
661            }
662            st_h->sh_ttfb = lsquic_time_now();
663            update_sample_stats(&s_stat_ttfb, st_h->sh_ttfb - st_h->sh_created);
664            if (s_discard_response)
665                LSQ_DEBUG("discard response: do not dump headers");
666            else
667                hset_dump(hset, stdout);
668            hset_destroy(hset);
669            st_h->sh_flags |= PROCESSED_HEADERS;
670        }
671        else if (nread = (s_discard_response
672                            ? lsquic_stream_readf(stream, discard, NULL)
673                            : lsquic_stream_read(stream, buf, sizeof(buf))),
674                    nread > 0)
675        {
676            s_stat_downloaded_bytes += nread;
677            /* test stream_reset after some number of read bytes */
678            if (client_ctx->hcc_reset_after_nbytes &&
679                s_stat_downloaded_bytes > client_ctx->hcc_reset_after_nbytes)
680            {
681                lsquic_stream_reset(stream, 0x1);
682                break;
683            }
684            /* test retire_cid after some number of read bytes */
685            if (client_ctx->hcc_retire_cid_after_nbytes &&
686                s_stat_downloaded_bytes > client_ctx->hcc_retire_cid_after_nbytes)
687            {
688                lsquic_conn_retire_cid(lsquic_stream_conn(stream));
689                client_ctx->hcc_retire_cid_after_nbytes = 0;
690                break;
691            }
692            if (!g_header_bypass && !(st_h->sh_flags & PROCESSED_HEADERS))
693            {
694                /* First read is assumed to be the first byte */
695                st_h->sh_ttfb = lsquic_time_now();
696                update_sample_stats(&s_stat_ttfb,
697                                    st_h->sh_ttfb - st_h->sh_created);
698                st_h->sh_flags |= PROCESSED_HEADERS;
699            }
700            if (!s_discard_response)
701                fwrite(buf, 1, nread, stdout);
702            if (randomly_reprioritize_streams && (st_h->count++ & 0x3F) == 0)
703            {
704                old_prio = lsquic_stream_priority(stream);
705                new_prio = 1 + (random() & 0xFF);
706#ifndef NDEBUG
707                const int s =
708#endif
709                lsquic_stream_set_priority(stream, new_prio);
710                assert(s == 0);
711                LSQ_DEBUG("changed stream %"PRIu64" priority from %u to %u",
712                                lsquic_stream_id(stream), old_prio, new_prio);
713            }
714        }
715        else if (0 == nread)
716        {
717            update_sample_stats(&s_stat_req, lsquic_time_now() - st_h->sh_ttfb);
718            client_ctx->hcc_flags |= HCC_SEEN_FIN;
719            lsquic_stream_shutdown(stream, 0);
720            break;
721        }
722        else if (client_ctx->prog->prog_settings.es_rw_once && EWOULDBLOCK == errno)
723        {
724            LSQ_NOTICE("emptied the buffer in 'once' mode");
725            break;
726        }
727        else if (lsquic_stream_is_rejected(stream))
728        {
729            LSQ_NOTICE("stream was rejected");
730            lsquic_stream_close(stream);
731            break;
732        }
733        else
734        {
735            LSQ_ERROR("could not read: %s", strerror(errno));
736            exit(2);
737        }
738    }
739    while (client_ctx->prog->prog_settings.es_rw_once
740            && nreads++ < 3 /* Emulate just a few reads */);
741}
742
743
744static void
745http_client_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
746{
747    const int pushed = lsquic_stream_is_pushed(stream);
748    if (pushed)
749    {
750        assert(NULL == st_h);
751        return;
752    }
753
754    LSQ_INFO("%s called", __func__);
755    struct http_client_ctx *const client_ctx = st_h->client_ctx;
756    lsquic_conn_t *conn = lsquic_stream_conn(stream);
757    lsquic_conn_ctx_t *conn_h;
758    TAILQ_FOREACH(conn_h, &client_ctx->conn_ctxs, next_ch)
759        if (conn_h->conn == conn)
760            break;
761    assert(conn_h);
762    --conn_h->ch_n_reqs;
763    --conn_h->ch_n_cc_streams;
764    if (0 == conn_h->ch_n_reqs)
765    {
766        LSQ_INFO("all requests completed, closing connection");
767        lsquic_conn_close(conn_h->conn);
768    }
769    else
770    {
771        LSQ_INFO("%u active stream, %u request remain, creating %u new stream",
772            conn_h->ch_n_cc_streams,
773            conn_h->ch_n_reqs - conn_h->ch_n_cc_streams,
774            MIN((conn_h->ch_n_reqs - conn_h->ch_n_cc_streams),
775                (client_ctx->hcc_cc_reqs_per_conn - conn_h->ch_n_cc_streams)));
776        create_streams(client_ctx, conn_h);
777    }
778    if (st_h->reader.lsqr_ctx)
779        destroy_lsquic_reader_ctx(st_h->reader.lsqr_ctx);
780    free(st_h);
781}
782
783
784static struct lsquic_stream_if http_client_if = {
785    .on_new_conn            = http_client_on_new_conn,
786    .on_conn_closed         = http_client_on_conn_closed,
787    .on_new_stream          = http_client_on_new_stream,
788    .on_read                = http_client_on_read,
789    .on_write               = http_client_on_write,
790    .on_close               = http_client_on_close,
791    .on_hsk_done            = http_client_on_hsk_done,
792};
793
794
795static void
796usage (const char *prog)
797{
798    const char *const slash = strrchr(prog, '/');
799    if (slash)
800        prog = slash + 1;
801    printf(
802"Usage: %s [opts]\n"
803"\n"
804"Options:\n"
805"   -p PATH     Path to request.  May be specified more than once.  If no\n"
806"                 path is specified, the connection is closed as soon as\n"
807"                 handshake succeeds.\n"
808"   -n CONNS    Number of concurrent connections.  Defaults to 1.\n"
809"   -r NREQS    Total number of requests to send.  Defaults to 1.\n"
810"   -R MAXREQS  Maximum number of requests per single connection.  Some\n"
811"                 connections will have fewer requests than this.\n"
812"   -w CONCUR   Number of concurrent requests per single connection.\n"
813"                 Defaults to 1.\n"
814"   -M METHOD   Method.  Defaults to GET.\n"
815"   -P PAYLOAD  Name of the file that contains payload to be used in the\n"
816"                 request.  This adds two more headers to the request:\n"
817"                 content-type: application/octet-stream and\n"
818"                 content-length\n"
819"   -K          Discard server response\n"
820"   -I          Abort on incomplete reponse from server\n"
821"   -4          Prefer IPv4 when resolving hostname\n"
822"   -6          Prefer IPv6 when resolving hostname\n"
823"   -0 FILE     Provide RTT info file (reading or writing)\n"
824#ifndef WIN32
825"   -C DIR      Certificate store.  If specified, server certificate will\n"
826"                 be verified.\n"
827#endif
828"   -a          Display server certificate chain after successful handshake.\n"
829"   -b N_BYTES  Send RESET_STREAM frame after the client has read n bytes.\n"
830"   -t          Print stats to stdout.\n"
831"   -T FILE     Print stats to FILE.  If FILE is -, print stats to stdout.\n"
832"   -q FILE     QIF mode: issue requests from the QIF file and validate\n"
833"                 server responses.\n"
834"   -e TOKEN    Hexadecimal string representing resume token.\n"
835            , prog);
836}
837
838
839#ifndef WIN32
840static X509_STORE *store;
841
842/* Windows does not have regex... */
843static int
844ends_in_pem (const char *s)
845{
846    int len;
847
848    len = strlen(s);
849
850    return len >= 4
851        && 0 == strcasecmp(s + len - 4, ".pem");
852}
853
854
855static X509 *
856file2cert (const char *path)
857{
858    X509 *cert = NULL;
859    BIO *in;
860
861    in = BIO_new(BIO_s_file());
862    if (!in)
863        goto end;
864
865    if (BIO_read_filename(in, path) <= 0)
866        goto end;
867
868    cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
869
870  end:
871    BIO_free(in);
872    return cert;
873}
874
875
876static int
877init_x509_cert_store (const char *path)
878{
879    struct dirent *ent;
880    X509 *cert;
881    DIR *dir;
882    char file_path[NAME_MAX];
883    int ret;
884
885    dir = opendir(path);
886    if (!dir)
887    {
888        LSQ_WARN("Cannot open directory `%s': %s", path, strerror(errno));
889        return -1;
890    }
891
892    store = X509_STORE_new();
893
894    while ((ent = readdir(dir)))
895    {
896        if (ends_in_pem(ent->d_name))
897        {
898            ret = snprintf(file_path, sizeof(file_path), "%s/%s",
899                                                            path, ent->d_name);
900            if (ret < 0)
901            {
902                LSQ_WARN("file_path formatting error %s", strerror(errno));
903                continue;
904            }
905            else if ((unsigned)ret >= sizeof(file_path))
906            {
907                LSQ_WARN("file_path was truncated %s", strerror(errno));
908                continue;
909            }
910            cert = file2cert(file_path);
911            if (cert)
912            {
913                if (1 != X509_STORE_add_cert(store, cert))
914                    LSQ_WARN("could not add cert from %s", file_path);
915            }
916            else
917                LSQ_WARN("could not read cert from %s", file_path);
918        }
919    }
920    (void) closedir(dir);
921    return 0;
922}
923
924
925static int
926verify_server_cert (void *ctx, STACK_OF(X509) *chain)
927{
928    X509_STORE_CTX store_ctx;
929    X509 *cert;
930    int ver;
931
932    if (!store)
933    {
934        if (0 != init_x509_cert_store(ctx))
935            return -1;
936    }
937
938    cert = sk_X509_shift(chain);
939    X509_STORE_CTX_init(&store_ctx, store, cert, chain);
940
941    ver = X509_verify_cert(&store_ctx);
942
943    X509_STORE_CTX_cleanup(&store_ctx);
944
945    if (ver != 1)
946        LSQ_WARN("could not verify server certificate");
947
948    return ver == 1 ? 0 : -1;
949}
950#endif
951
952
953static void *
954hset_create (void *hsi_ctx, lsquic_stream_t *stream, int is_push_promise)
955{
956    struct hset *hset;
957
958    if (s_discard_response)
959        return (void *) 1;
960    else 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    if (!s_discard_response)
1059    {
1060        for (el = STAILQ_FIRST(hset); el; el = next)
1061        {
1062            next = STAILQ_NEXT(el, next);
1063            free(el->xhdr.buf);
1064            free(el);
1065        }
1066        free(hset);
1067    }
1068}
1069
1070
1071static void
1072hset_dump (const struct hset *hset, FILE *out)
1073{
1074    const struct hset_elem *el;
1075
1076    STAILQ_FOREACH(el, hset, next)
1077        if (el->xhdr.flags & (LSXPACK_HPACK_VAL_MATCHED|LSXPACK_QPACK_IDX))
1078            fprintf(out, "%.*s (%s static table idx %u): %.*s\n",
1079                (int) el->xhdr.name_len, lsxpack_header_get_name(&el->xhdr),
1080                el->xhdr.flags & LSXPACK_HPACK_VAL_MATCHED ? "hpack" : "qpack",
1081                el->xhdr.flags & LSXPACK_HPACK_VAL_MATCHED ? el->xhdr.hpack_index
1082                                                    : el->xhdr.qpack_index,
1083                (int) el->xhdr.val_len, lsxpack_header_get_value(&el->xhdr));
1084        else
1085            fprintf(out, "%.*s: %.*s\n",
1086                (int) el->xhdr.name_len, lsxpack_header_get_name(&el->xhdr),
1087                (int) el->xhdr.val_len, lsxpack_header_get_value(&el->xhdr));
1088
1089    fprintf(out, "\n");
1090    fflush(out);
1091}
1092
1093
1094/* These are basic and for illustration purposes only.  You will want to
1095 * do your own verification by doing something similar to what is done
1096 * in src/liblsquic/lsquic_http1x_if.c
1097 */
1098static const struct lsquic_hset_if header_bypass_api =
1099{
1100    .hsi_create_header_set  = hset_create,
1101    .hsi_prepare_decode     = hset_prepare_decode,
1102    .hsi_process_header     = hset_add_header,
1103    .hsi_discard_header_set = hset_destroy,
1104};
1105
1106
1107static void
1108display_stat (FILE *out, const struct sample_stats *stats, const char *name)
1109{
1110    long double mean, stddev;
1111
1112    calc_sample_stats(stats, &mean, &stddev);
1113    fprintf(out, "%s: n: %u; min: %.2Lf ms; max: %.2Lf ms; mean: %.2Lf ms; "
1114        "sd: %.2Lf ms\n", name, stats->n, (long double) stats->min / 1000,
1115        (long double) stats->max / 1000, mean / 1000, stddev / 1000);
1116}
1117
1118
1119static lsquic_conn_ctx_t *
1120qif_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
1121{
1122    lsquic_conn_make_stream(conn);
1123    return stream_if_ctx;
1124}
1125
1126
1127static void
1128qif_client_on_conn_closed (lsquic_conn_t *conn)
1129{
1130    struct http_client_ctx *client_ctx = (void *) lsquic_conn_get_ctx(conn);
1131    LSQ_INFO("connection is closed: stop engine");
1132    prog_stop(client_ctx->prog);
1133}
1134
1135
1136struct qif_stream_ctx
1137{
1138    int                         reqno;
1139    struct lsquic_http_headers  headers;
1140    char                       *qif_str;
1141    size_t                      qif_sz;
1142    size_t                      qif_off;
1143    char                       *resp_str;   /* qif_sz allocated */
1144    size_t                      resp_off;   /* Read so far */
1145    enum {
1146        QSC_HEADERS_SENT = 1 << 0,
1147        QSC_GOT_HEADERS  = 1 << 1,
1148    }                           flags;
1149};
1150
1151#define MAX(a, b) ((a) > (b) ? (a) : (b))
1152
1153lsquic_stream_ctx_t *
1154qif_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
1155{
1156    struct http_client_ctx *const client_ctx = stream_if_ctx;
1157    FILE *const fh = client_ctx->qif_fh;
1158    struct qif_stream_ctx *ctx;
1159    struct lsxpack_header *header;
1160    static int reqno;
1161    size_t nalloc;
1162    char *end, *tab, *line;
1163    char line_buf[0x1000];
1164
1165    ctx = calloc(1, sizeof(*ctx));
1166    if (!ctx)
1167    {
1168        perror("calloc");
1169        exit(1);
1170    }
1171    ctx->reqno = reqno++;
1172
1173    nalloc = 0;
1174    while ((line = fgets(line_buf, sizeof(line_buf), fh)))
1175    {
1176        end = strchr(line, '\n');
1177        if (!end)
1178        {
1179            fprintf(stderr, "no newline\n");
1180            exit(1);
1181        }
1182
1183        if (end == line)
1184            break;
1185
1186        if (*line == '#')
1187            continue;
1188
1189        tab = strchr(line, '\t');
1190        if (!tab)
1191        {
1192            fprintf(stderr, "no TAB\n");
1193            exit(1);
1194        }
1195
1196        if (nalloc + (end + 1 - line) > ctx->qif_sz)
1197        {
1198            if (nalloc)
1199                nalloc = MAX(nalloc * 2, nalloc + (end + 1 - line));
1200            else
1201                nalloc = end + 1 - line;
1202            ctx->qif_str = realloc(ctx->qif_str, nalloc);
1203            if (!ctx->qif_str)
1204            {
1205                perror("realloc");
1206                exit(1);
1207            }
1208        }
1209        memcpy(ctx->qif_str + ctx->qif_sz, line, end + 1 - line);
1210
1211        ctx->headers.headers = realloc(ctx->headers.headers,
1212                sizeof(ctx->headers.headers[0]) * (ctx->headers.count + 1));
1213        if (!ctx->headers.headers)
1214        {
1215            perror("realloc");
1216            exit(1);
1217        }
1218        header = &ctx->headers.headers[ctx->headers.count++];
1219        lsxpack_header_set_offset2(header, ctx->qif_str + ctx->qif_sz, 0,
1220                                    tab - line, tab - line + 1, end - tab - 1);
1221        ctx->qif_sz += end + 1 - line;
1222    }
1223
1224    lsquic_stream_wantwrite(stream, 1);
1225
1226    if (!line)
1227    {
1228        LSQ_DEBUG("Input QIF file ends; close file handle");
1229        fclose(client_ctx->qif_fh);
1230        client_ctx->qif_fh = NULL;
1231    }
1232
1233    return (void *) ctx;
1234}
1235
1236
1237static void
1238qif_client_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *h)
1239{
1240    struct qif_stream_ctx *const ctx = (void *) h;
1241    size_t towrite;
1242    ssize_t nw;
1243
1244    if (ctx->flags & QSC_HEADERS_SENT)
1245    {
1246        towrite = ctx->qif_sz - ctx->qif_off;
1247        nw = lsquic_stream_write(stream, ctx->qif_str + ctx->qif_off, towrite);
1248        if (nw >= 0)
1249        {
1250            LSQ_DEBUG("wrote %zd bytes to stream", nw);
1251            ctx->qif_off += nw;
1252            if (ctx->qif_off == (size_t) nw)
1253            {
1254                lsquic_stream_shutdown(stream, 1);
1255                lsquic_stream_wantread(stream, 1);
1256                LSQ_DEBUG("finished writing request %d", ctx->reqno);
1257            }
1258        }
1259        else
1260        {
1261            LSQ_ERROR("cannot write to stream: %s", strerror(errno));
1262            lsquic_stream_wantwrite(stream, 0);
1263            lsquic_conn_abort(lsquic_stream_conn(stream));
1264        }
1265    }
1266    else
1267    {
1268        if (0 == lsquic_stream_send_headers(stream, &ctx->headers, 0))
1269        {
1270            ctx->flags |= QSC_HEADERS_SENT;
1271            LSQ_DEBUG("sent headers");
1272        }
1273        else
1274        {
1275            LSQ_ERROR("cannot send headers: %s", strerror(errno));
1276            lsquic_stream_wantwrite(stream, 0);
1277            lsquic_conn_abort(lsquic_stream_conn(stream));
1278        }
1279    }
1280}
1281
1282
1283static void
1284qif_client_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *h)
1285{
1286    struct qif_stream_ctx *const ctx = (void *) h;
1287    struct hset *hset;
1288    ssize_t nr;
1289    unsigned char buf[1];
1290
1291    LSQ_DEBUG("reading response to request %d", ctx->reqno);
1292
1293    if (!(ctx->flags & QSC_GOT_HEADERS))
1294    {
1295        hset = lsquic_stream_get_hset(stream);
1296        if (!hset)
1297        {
1298            LSQ_ERROR("could not get header set from stream");
1299            exit(2);
1300        }
1301        LSQ_DEBUG("got header set for response %d", ctx->reqno);
1302        hset_dump(hset, stdout);
1303        hset_destroy(hset);
1304        ctx->flags |= QSC_GOT_HEADERS;
1305    }
1306    else
1307    {
1308        if (!ctx->resp_str)
1309        {
1310            ctx->resp_str = malloc(ctx->qif_sz);
1311            if (!ctx->resp_str)
1312            {
1313                perror("malloc");
1314                exit(1);
1315            }
1316        }
1317        if (ctx->resp_off < ctx->qif_sz)
1318        {
1319            nr = lsquic_stream_read(stream, ctx->resp_str + ctx->resp_off,
1320                                        ctx->qif_sz - ctx->resp_off);
1321            if (nr > 0)
1322            {
1323                ctx->resp_off += nr;
1324                LSQ_DEBUG("read %zd bytes of reponse %d", nr, ctx->reqno);
1325            }
1326            else if (nr == 0)
1327            {
1328                LSQ_INFO("response %d too short", ctx->reqno);
1329                LSQ_WARN("response %d FAIL", ctx->reqno);
1330                lsquic_stream_shutdown(stream, 0);
1331            }
1332            else
1333            {
1334                LSQ_ERROR("error reading from stream");
1335                lsquic_stream_wantread(stream, 0);
1336                lsquic_conn_abort(lsquic_stream_conn(stream));
1337            }
1338        }
1339        else
1340        {
1341            /* Collect EOF */
1342            nr = lsquic_stream_read(stream, buf, sizeof(buf));
1343            if (nr == 0)
1344            {
1345                if (0 == memcmp(ctx->qif_str, ctx->resp_str, ctx->qif_sz))
1346                    LSQ_INFO("response %d OK", ctx->reqno);
1347                else
1348                    LSQ_WARN("response %d FAIL", ctx->reqno);
1349                lsquic_stream_shutdown(stream, 0);
1350            }
1351            else if (nr > 0)
1352            {
1353                LSQ_INFO("response %d too long", ctx->reqno);
1354                LSQ_WARN("response %d FAIL", ctx->reqno);
1355                lsquic_stream_shutdown(stream, 0);
1356            }
1357            else
1358            {
1359                LSQ_ERROR("error reading from stream");
1360                lsquic_stream_shutdown(stream, 0);
1361                lsquic_conn_abort(lsquic_stream_conn(stream));
1362            }
1363        }
1364    }
1365}
1366
1367
1368static void
1369qif_client_on_close (struct lsquic_stream *stream, lsquic_stream_ctx_t *h)
1370{
1371    struct lsquic_conn *conn = lsquic_stream_conn(stream);
1372    struct http_client_ctx *client_ctx = (void *) lsquic_conn_get_ctx(conn);
1373    struct qif_stream_ctx *const ctx = (void *) h;
1374    free(ctx->qif_str);
1375    free(ctx->resp_str);
1376    free(ctx->headers.headers);
1377    free(ctx);
1378    if (client_ctx->qif_fh)
1379        lsquic_conn_make_stream(conn);
1380    else
1381        lsquic_conn_close(conn);
1382}
1383
1384
1385const struct lsquic_stream_if qif_client_if = {
1386    .on_new_conn            = qif_client_on_new_conn,
1387    .on_conn_closed         = qif_client_on_conn_closed,
1388    .on_new_stream          = qif_client_on_new_stream,
1389    .on_read                = qif_client_on_read,
1390    .on_write               = qif_client_on_write,
1391    .on_close               = qif_client_on_close,
1392};
1393
1394
1395int
1396main (int argc, char **argv)
1397{
1398    int opt, s, was_empty;
1399    lsquic_time_t start_time;
1400    FILE *stats_fh = NULL;
1401    long double elapsed;
1402    struct http_client_ctx client_ctx;
1403    struct stat st;
1404    struct path_elem *pe;
1405    struct sport_head sports;
1406    struct prog prog;
1407    const char *token = NULL;
1408
1409    TAILQ_INIT(&sports);
1410    memset(&client_ctx, 0, sizeof(client_ctx));
1411    TAILQ_INIT(&client_ctx.hcc_path_elems);
1412    TAILQ_INIT(&client_ctx.conn_ctxs);
1413    client_ctx.method = "GET";
1414    client_ctx.hcc_concurrency = 1;
1415    client_ctx.hcc_cc_reqs_per_conn = 1;
1416    client_ctx.hcc_reqs_per_conn = 1;
1417    client_ctx.hcc_total_n_reqs = 1;
1418    client_ctx.hcc_reset_after_nbytes = 0;
1419    client_ctx.hcc_retire_cid_after_nbytes = 0;
1420    client_ctx.prog = &prog;
1421
1422    prog_init(&prog, LSENG_HTTP, &sports, &http_client_if, &client_ctx);
1423
1424    while (-1 != (opt = getopt(argc, argv, PROG_OPTS
1425                                    "46Br:R:IKu:EP:M:n:w:H:p:0:q:e:hatT:b:d:"
1426#ifndef WIN32
1427                                                                      "C:"
1428#endif
1429                                                                            )))
1430    {
1431        switch (opt) {
1432        case 'a':
1433            ++s_display_cert_chain;
1434            break;
1435        case '4':
1436        case '6':
1437            prog.prog_ipver = opt - '0';
1438            break;
1439        case 'B':
1440            g_header_bypass = 1;
1441            prog.prog_api.ea_hsi_if = &header_bypass_api;
1442            prog.prog_api.ea_hsi_ctx = NULL;
1443            break;
1444        case 'I':
1445            client_ctx.hcc_flags |= HCC_ABORT_ON_INCOMPLETE;
1446            break;
1447        case 'K':
1448            ++s_discard_response;
1449            break;
1450        case 'u':   /* Accept p<U>sh promise */
1451            promise_fd = open(optarg, O_WRONLY|O_CREAT|O_TRUNC, 0644);
1452            if (promise_fd < 0)
1453            {
1454                perror("open");
1455                exit(1);
1456            }
1457            prog.prog_settings.es_support_push = 1;     /* Pokes into prog */
1458            break;
1459        case 'E':   /* E: randomly reprioritize str<E>ams.  Now, that's
1460                     * pretty random. :)
1461                     */
1462            randomly_reprioritize_streams = 1;
1463            break;
1464        case 'n':
1465            client_ctx.hcc_concurrency = atoi(optarg);
1466            break;
1467        case 'w':
1468            client_ctx.hcc_cc_reqs_per_conn = atoi(optarg);
1469            break;
1470        case 'P':
1471            client_ctx.payload = optarg;
1472            if (0 != stat(optarg, &st))
1473            {
1474                perror("stat");
1475                exit(2);
1476            }
1477            sprintf(client_ctx.payload_size, "%jd", (intmax_t) st.st_size);
1478            break;
1479        case 'M':
1480            client_ctx.method = optarg;
1481            break;
1482        case 'r':
1483            client_ctx.hcc_total_n_reqs = atoi(optarg);
1484            break;
1485        case 'R':
1486            client_ctx.hcc_reqs_per_conn = atoi(optarg);
1487            break;
1488        case 'H':
1489            client_ctx.hostname = optarg;
1490            prog.prog_hostname = optarg;            /* Pokes into prog */
1491            break;
1492        case 'p':
1493            pe = calloc(1, sizeof(*pe));
1494            pe->path = optarg;
1495            TAILQ_INSERT_TAIL(&client_ctx.hcc_path_elems, pe, next_pe);
1496            break;
1497        case 'h':
1498            usage(argv[0]);
1499            prog_print_common_options(&prog, stdout);
1500            exit(0);
1501        case 'q':
1502            client_ctx.qif_file = optarg;
1503            break;
1504        case 'e':
1505            if (TAILQ_EMPTY(&sports))
1506                token = optarg;
1507            else
1508                sport_set_token(TAILQ_LAST(&sports, sport_head), optarg);
1509            break;
1510#ifndef WIN32
1511        case 'C':
1512            prog.prog_api.ea_verify_cert = verify_server_cert;
1513            prog.prog_api.ea_verify_ctx = optarg;
1514            break;
1515#endif
1516        case 't':
1517            stats_fh = stdout;
1518            break;
1519        case 'T':
1520            if (0 == strcmp(optarg, "-"))
1521                stats_fh = stdout;
1522            else
1523            {
1524                stats_fh = fopen(optarg, "w");
1525                if (!stats_fh)
1526                {
1527                    perror("fopen");
1528                    exit(1);
1529                }
1530            }
1531            break;
1532        case 'b':
1533            client_ctx.hcc_reset_after_nbytes = atoi(optarg);
1534            break;
1535        case 'd':
1536            client_ctx.hcc_retire_cid_after_nbytes = atoi(optarg);
1537            break;
1538        case '0':
1539            http_client_if.on_zero_rtt_info = http_client_on_zero_rtt_info;
1540            client_ctx.hcc_zero_rtt_file_name = optarg;
1541            break;
1542        default:
1543            if (0 != prog_set_opt(&prog, opt, optarg))
1544                exit(1);
1545        }
1546    }
1547
1548#if LSQUIC_CONN_STATS
1549    prog.prog_api.ea_stats_fh = stats_fh;
1550#endif
1551    prog.prog_settings.es_ua = LITESPEED_ID;
1552
1553    if (client_ctx.qif_file)
1554    {
1555        client_ctx.qif_fh = fopen(client_ctx.qif_file, "r");
1556        if (!client_ctx.qif_fh)
1557        {
1558            fprintf(stderr, "Cannot open %s for reading: %s\n",
1559                                    client_ctx.qif_file, strerror(errno));
1560            exit(1);
1561        }
1562        LSQ_NOTICE("opened QIF file %s for reading\n", client_ctx.qif_file);
1563        prog.prog_api.ea_stream_if = &qif_client_if;
1564        g_header_bypass = 1;
1565        prog.prog_api.ea_hsi_if = &header_bypass_api;
1566        prog.prog_api.ea_hsi_ctx = NULL;
1567    }
1568    else if (TAILQ_EMPTY(&client_ctx.hcc_path_elems))
1569    {
1570        fprintf(stderr, "Specify at least one path using -p option\n");
1571        exit(1);
1572    }
1573
1574    start_time = lsquic_time_now();
1575    was_empty = TAILQ_EMPTY(&sports);
1576    if (0 != prog_prep(&prog))
1577    {
1578        LSQ_ERROR("could not prep");
1579        exit(EXIT_FAILURE);
1580    }
1581    if (!(client_ctx.hostname || prog.prog_hostname))
1582    {
1583        fprintf(stderr, "Specify hostname (used for SNI and :authority) via "
1584            "-H option\n");
1585        exit(EXIT_FAILURE);
1586    }
1587    if (was_empty && token)
1588        sport_set_token(TAILQ_LAST(&sports, sport_head), token);
1589
1590    if (client_ctx.qif_file)
1591    {
1592        if (0 != prog_connect(&prog, NULL, 0))
1593        {
1594            LSQ_ERROR("connection failed");
1595            exit(EXIT_FAILURE);
1596        }
1597    }
1598    else
1599        create_connections(&client_ctx);
1600
1601    LSQ_DEBUG("entering event loop");
1602
1603    s = prog_run(&prog);
1604
1605    if (stats_fh)
1606    {
1607        elapsed = (long double) (lsquic_time_now() - start_time) / 1000000;
1608        fprintf(stats_fh, "overall statistics as calculated by %s:\n", argv[0]);
1609        display_stat(stats_fh, &s_stat_to_conn, "time for connect");
1610        display_stat(stats_fh, &s_stat_req, "time for request");
1611        display_stat(stats_fh, &s_stat_ttfb, "time to 1st byte");
1612        fprintf(stats_fh, "downloaded %lu application bytes in %.3Lf seconds\n",
1613            s_stat_downloaded_bytes, elapsed);
1614        fprintf(stats_fh, "%.2Lf reqs/sec; %.0Lf bytes/sec\n",
1615            (long double) s_stat_req.n / elapsed,
1616            (long double) s_stat_downloaded_bytes / elapsed);
1617        fprintf(stats_fh, "read handler count %lu\n", prog.prog_read_count);
1618    }
1619
1620    prog_cleanup(&prog);
1621    if (promise_fd >= 0)
1622        (void) close(promise_fd);
1623
1624    while ((pe = TAILQ_FIRST(&client_ctx.hcc_path_elems)))
1625    {
1626        TAILQ_REMOVE(&client_ctx.hcc_path_elems, pe, next_pe);
1627        free(pe);
1628    }
1629
1630    if (client_ctx.qif_fh)
1631        (void) fclose(client_ctx.qif_fh);
1632
1633    exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE);
1634}
1635