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