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