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