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