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