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