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