1a74702c6SGeorge Wang/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc.  See LICENSE. */
20adf085aSDmitri Tikhonov/*
30adf085aSDmitri Tikhonov * md5_client.c -- This client sends one or more files to MD5 QUIC server
40adf085aSDmitri Tikhonov *                 for MD5 sum calculation.
50adf085aSDmitri Tikhonov */
60adf085aSDmitri Tikhonov
70adf085aSDmitri Tikhonov#include <assert.h>
80adf085aSDmitri Tikhonov#include <errno.h>
90adf085aSDmitri Tikhonov#include <inttypes.h>
100adf085aSDmitri Tikhonov#include <stdio.h>
110adf085aSDmitri Tikhonov#include <stdlib.h>
120adf085aSDmitri Tikhonov#include <string.h>
130adf085aSDmitri Tikhonov#include <sys/queue.h>
140adf085aSDmitri Tikhonov#include <sys/types.h>
150adf085aSDmitri Tikhonov#include <sys/stat.h>
16fb3e20e0SDmitri Tikhonov
17fb3e20e0SDmitri Tikhonov#ifndef WIN32
18fb3e20e0SDmitri Tikhonov#include <unistd.h>
190adf085aSDmitri Tikhonov#include <fcntl.h>
20fb3e20e0SDmitri Tikhonov#else
21fb3e20e0SDmitri Tikhonov#include "vc_compat.h"
22fb3e20e0SDmitri Tikhonov#include "getopt.h"
23fb3e20e0SDmitri Tikhonov#endif
240adf085aSDmitri Tikhonov
250adf085aSDmitri Tikhonov#include <event2/event.h>
260adf085aSDmitri Tikhonov#include <openssl/md5.h>
270adf085aSDmitri Tikhonov
280adf085aSDmitri Tikhonov#include "lsquic.h"
290adf085aSDmitri Tikhonov#include "test_common.h"
300adf085aSDmitri Tikhonov#include "prog.h"
310adf085aSDmitri Tikhonov
320adf085aSDmitri Tikhonov#include "../src/liblsquic/lsquic_logger.h"
330adf085aSDmitri Tikhonov#include "../src/liblsquic/lsquic_int_types.h"
340adf085aSDmitri Tikhonov#include "../src/liblsquic/lsquic_varint.h"
350adf085aSDmitri Tikhonov#include "../src/liblsquic/lsquic_hq.h"
360adf085aSDmitri Tikhonov#include "../src/liblsquic/lsquic_sfcw.h"
370adf085aSDmitri Tikhonov#include "../src/liblsquic/lsquic_hash.h"
380adf085aSDmitri Tikhonov#include "../src/liblsquic/lsquic_stream.h"
390adf085aSDmitri Tikhonov
400adf085aSDmitri Tikhonov/* Set to non-zero value to test out what happens when reset is sent */
410adf085aSDmitri Tikhonov#define RESET_AFTER_N_WRITES 0
420adf085aSDmitri Tikhonov
430adf085aSDmitri Tikhonovstatic int g_write_file = 1;
440adf085aSDmitri Tikhonov
450adf085aSDmitri Tikhonov#define LOCAL_BUF_SIZE 0x100
460adf085aSDmitri Tikhonov
470adf085aSDmitri Tikhonovstatic struct {
480adf085aSDmitri Tikhonov    unsigned    stream_id;  /* If set, reset this stream ID */
490adf085aSDmitri Tikhonov    off_t       offset;     /* Reset it after writing this many bytes */
500adf085aSDmitri Tikhonov} g_reset_stream;
510adf085aSDmitri Tikhonov
520adf085aSDmitri Tikhonovstruct file {
530adf085aSDmitri Tikhonov    LIST_ENTRY(file)        next_file;
540adf085aSDmitri Tikhonov    const char             *filename;
550adf085aSDmitri Tikhonov    struct lsquic_reader    reader;
560adf085aSDmitri Tikhonov    int                     fd;
570adf085aSDmitri Tikhonov    unsigned                priority;
580adf085aSDmitri Tikhonov    enum {
590adf085aSDmitri Tikhonov        FILE_RESET  = (1 << 0),
600adf085aSDmitri Tikhonov    }                       file_flags;
610adf085aSDmitri Tikhonov    size_t                  md5_off;
620adf085aSDmitri Tikhonov    char                    md5str[MD5_DIGEST_LENGTH * 2];
630adf085aSDmitri Tikhonov};
640adf085aSDmitri Tikhonov
650adf085aSDmitri Tikhonovstruct lsquic_conn_ctx;
660adf085aSDmitri Tikhonov
670adf085aSDmitri Tikhonovstruct client_ctx {
680adf085aSDmitri Tikhonov    struct lsquic_conn_ctx  *conn_h;
690adf085aSDmitri Tikhonov    LIST_HEAD(, file)            files;
700adf085aSDmitri Tikhonov    unsigned                     n_files;
710adf085aSDmitri Tikhonov    struct file                 *cur_file;
720adf085aSDmitri Tikhonov    lsquic_engine_t             *engine;
730adf085aSDmitri Tikhonov    struct service_port         *sport;
740adf085aSDmitri Tikhonov    struct prog                 *prog;
750adf085aSDmitri Tikhonov};
760adf085aSDmitri Tikhonov
770adf085aSDmitri Tikhonovstruct lsquic_conn_ctx {
780adf085aSDmitri Tikhonov    lsquic_conn_t       *conn;
790adf085aSDmitri Tikhonov    struct client_ctx   *client_ctx;
800adf085aSDmitri Tikhonov};
810adf085aSDmitri Tikhonov
820adf085aSDmitri Tikhonov
830adf085aSDmitri Tikhonovstatic lsquic_conn_ctx_t *
840adf085aSDmitri Tikhonovclient_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
850adf085aSDmitri Tikhonov{
860adf085aSDmitri Tikhonov    struct client_ctx *client_ctx = stream_if_ctx;
870adf085aSDmitri Tikhonov    lsquic_conn_ctx_t *conn_h = malloc(sizeof(*conn_h));
880adf085aSDmitri Tikhonov    conn_h->conn = conn;
890adf085aSDmitri Tikhonov    conn_h->client_ctx = client_ctx;
900adf085aSDmitri Tikhonov    client_ctx->conn_h = conn_h;
910adf085aSDmitri Tikhonov    assert(client_ctx->n_files > 0);
920adf085aSDmitri Tikhonov    unsigned n = client_ctx->n_files;
930adf085aSDmitri Tikhonov    while (n--)
940adf085aSDmitri Tikhonov        lsquic_conn_make_stream(conn);
950adf085aSDmitri Tikhonov    print_conn_info(conn);
960adf085aSDmitri Tikhonov    return conn_h;
970adf085aSDmitri Tikhonov}
980adf085aSDmitri Tikhonov
990adf085aSDmitri Tikhonov
1000adf085aSDmitri Tikhonovstatic void
1010adf085aSDmitri Tikhonovclient_on_goaway_received (lsquic_conn_t *conn)
1020adf085aSDmitri Tikhonov{
1030adf085aSDmitri Tikhonov    LSQ_NOTICE("GOAWAY received");
1040adf085aSDmitri Tikhonov}
1050adf085aSDmitri Tikhonov
1060adf085aSDmitri Tikhonov
1070adf085aSDmitri Tikhonovstatic void
1080adf085aSDmitri Tikhonovclient_on_conn_closed (lsquic_conn_t *conn)
1090adf085aSDmitri Tikhonov{
1100adf085aSDmitri Tikhonov    lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn);
1110adf085aSDmitri Tikhonov    LSQ_NOTICE("Connection closed");
1120adf085aSDmitri Tikhonov    prog_stop(conn_h->client_ctx->prog);
1130adf085aSDmitri Tikhonov    free(conn_h);
1140adf085aSDmitri Tikhonov}
1150adf085aSDmitri Tikhonov
1160adf085aSDmitri Tikhonov
1170adf085aSDmitri Tikhonovstruct lsquic_stream_ctx {
1180adf085aSDmitri Tikhonov    lsquic_stream_t     *stream;
1190adf085aSDmitri Tikhonov    struct client_ctx   *client_ctx;
1200adf085aSDmitri Tikhonov    struct file         *file;
1210adf085aSDmitri Tikhonov    struct event        *read_stdin_ev;
1220adf085aSDmitri Tikhonov    struct {
1230adf085aSDmitri Tikhonov        int         initialized;
1240adf085aSDmitri Tikhonov        size_t      size,
1250adf085aSDmitri Tikhonov                    off;
1260adf085aSDmitri Tikhonov    }                    small;
1270adf085aSDmitri Tikhonov};
1280adf085aSDmitri Tikhonov
1290adf085aSDmitri Tikhonov
1300adf085aSDmitri Tikhonovstatic lsquic_stream_ctx_t *
1310adf085aSDmitri Tikhonovclient_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
1320adf085aSDmitri Tikhonov{
1330adf085aSDmitri Tikhonov    struct client_ctx *const client_ctx = stream_if_ctx;
1340adf085aSDmitri Tikhonov    if (!stream)
1350adf085aSDmitri Tikhonov    {
1360adf085aSDmitri Tikhonov        assert(client_ctx->n_files > 0);
1370adf085aSDmitri Tikhonov        LSQ_NOTICE("%s: got null stream: no more streams possible; # files: %u",
1380adf085aSDmitri Tikhonov                                                 __func__, client_ctx->n_files);
1390adf085aSDmitri Tikhonov        --client_ctx->n_files;
1400adf085aSDmitri Tikhonov        if (0 == client_ctx->n_files)
1410adf085aSDmitri Tikhonov        {
1420adf085aSDmitri Tikhonov            LSQ_DEBUG("closing connection");
1430adf085aSDmitri Tikhonov            lsquic_conn_close(client_ctx->conn_h->conn);
1440adf085aSDmitri Tikhonov        }
1450adf085aSDmitri Tikhonov        return NULL;
1460adf085aSDmitri Tikhonov    }
1470adf085aSDmitri Tikhonov    lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h));
1480adf085aSDmitri Tikhonov    st_h->stream = stream;
1490adf085aSDmitri Tikhonov    st_h->client_ctx = stream_if_ctx;
1500adf085aSDmitri Tikhonov    if (LIST_EMPTY(&st_h->client_ctx->files))
1510adf085aSDmitri Tikhonov    {
1520adf085aSDmitri Tikhonov        /* XXX: perhaps we should not be able to write immediately: there may
1530adf085aSDmitri Tikhonov         * be internal memory constraints...
1540adf085aSDmitri Tikhonov         */
1550adf085aSDmitri Tikhonov        lsquic_stream_write(stream, "client request", 14);
1560adf085aSDmitri Tikhonov        (void) lsquic_stream_flush(stream);
1570adf085aSDmitri Tikhonov        lsquic_stream_wantwrite(stream, 0);
1580adf085aSDmitri Tikhonov        lsquic_stream_wantread(stream, 1);
1590adf085aSDmitri Tikhonov    }
1600adf085aSDmitri Tikhonov    else
1610adf085aSDmitri Tikhonov    {
1620adf085aSDmitri Tikhonov        st_h->file = LIST_FIRST(&st_h->client_ctx->files);
1630adf085aSDmitri Tikhonov        if (g_write_file)
1640adf085aSDmitri Tikhonov        {
1650adf085aSDmitri Tikhonov            st_h->file->fd = -1;
1660adf085aSDmitri Tikhonov            st_h->file->reader.lsqr_read = test_reader_read;
1670adf085aSDmitri Tikhonov            st_h->file->reader.lsqr_size = test_reader_size;
1680adf085aSDmitri Tikhonov            st_h->file->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->file->filename);
1690adf085aSDmitri Tikhonov            if (!st_h->file->reader.lsqr_ctx)
1700adf085aSDmitri Tikhonov                exit(1);
1710adf085aSDmitri Tikhonov        }
1720adf085aSDmitri Tikhonov        else
1730adf085aSDmitri Tikhonov        {
1740adf085aSDmitri Tikhonov            st_h->file->fd = open(st_h->file->filename, O_RDONLY);
1750adf085aSDmitri Tikhonov            if (st_h->file->fd < 0)
1760adf085aSDmitri Tikhonov            {
1770adf085aSDmitri Tikhonov                LSQ_ERROR("could not open %s for reading: %s",
1780adf085aSDmitri Tikhonov                          st_h->file->filename, strerror(errno));
1790adf085aSDmitri Tikhonov                exit(1);
1800adf085aSDmitri Tikhonov            }
1810adf085aSDmitri Tikhonov        }
1820adf085aSDmitri Tikhonov        LIST_REMOVE(st_h->file, next_file);
1830adf085aSDmitri Tikhonov        lsquic_stream_set_priority(stream, st_h->file->priority);
1840adf085aSDmitri Tikhonov        lsquic_stream_wantwrite(stream, 1);
1850adf085aSDmitri Tikhonov    }
1860adf085aSDmitri Tikhonov    return st_h;
1870adf085aSDmitri Tikhonov}
1880adf085aSDmitri Tikhonov
1890adf085aSDmitri Tikhonov
1900adf085aSDmitri Tikhonovstatic size_t
1910adf085aSDmitri Tikhonovbuf_reader_size (void *reader_ctx)
1920adf085aSDmitri Tikhonov{
1930adf085aSDmitri Tikhonov    lsquic_stream_ctx_t *const st_h = reader_ctx;
1940adf085aSDmitri Tikhonov    struct stat st;
1950adf085aSDmitri Tikhonov    off_t off;
1960adf085aSDmitri Tikhonov
1970adf085aSDmitri Tikhonov    if (st_h->small.initialized)
1980adf085aSDmitri Tikhonov        goto initialized;
1990adf085aSDmitri Tikhonov
2000adf085aSDmitri Tikhonov    if (0 != fstat(st_h->file->fd, &st))
2010adf085aSDmitri Tikhonov    {
2020adf085aSDmitri Tikhonov        LSQ_ERROR("fstat failed: %s", strerror(errno));
2030adf085aSDmitri Tikhonov        goto err;
2040adf085aSDmitri Tikhonov    }
2050adf085aSDmitri Tikhonov
2060adf085aSDmitri Tikhonov    off = lseek(st_h->file->fd, 0, SEEK_CUR);
2070adf085aSDmitri Tikhonov    if (off == (off_t) -1)
2080adf085aSDmitri Tikhonov    {
2090adf085aSDmitri Tikhonov        LSQ_ERROR("lseek failed: %s", strerror(errno));
2100adf085aSDmitri Tikhonov        goto err;
2110adf085aSDmitri Tikhonov    }
2120adf085aSDmitri Tikhonov
2130adf085aSDmitri Tikhonov    if (st.st_size < off)
2140adf085aSDmitri Tikhonov    {
2150adf085aSDmitri Tikhonov        LSQ_ERROR("size mismatch");
2160adf085aSDmitri Tikhonov        goto err;
2170adf085aSDmitri Tikhonov    }
2180adf085aSDmitri Tikhonov
2190adf085aSDmitri Tikhonov    st_h->small.initialized = 1;
2200adf085aSDmitri Tikhonov    st_h->small.off = off;
2210adf085aSDmitri Tikhonov    st_h->small.size = st.st_size;
2220adf085aSDmitri Tikhonov
2230adf085aSDmitri Tikhonov  initialized:
2240adf085aSDmitri Tikhonov    if (st_h->small.size - st_h->small.off > LOCAL_BUF_SIZE)
2250adf085aSDmitri Tikhonov        return LOCAL_BUF_SIZE;
2260adf085aSDmitri Tikhonov    else
2270adf085aSDmitri Tikhonov        return st_h->small.size - st_h->small.off;
2280adf085aSDmitri Tikhonov
2290adf085aSDmitri Tikhonov  err:
2300adf085aSDmitri Tikhonov    close(st_h->file->fd);
2310adf085aSDmitri Tikhonov    st_h->file->fd = 0;
2320adf085aSDmitri Tikhonov    return 0;
2330adf085aSDmitri Tikhonov}
2340adf085aSDmitri Tikhonov
2350adf085aSDmitri Tikhonov
2360adf085aSDmitri Tikhonovstatic size_t
2370adf085aSDmitri Tikhonovbuf_reader_read (void *reader_ctx, void *buf, size_t count)
2380adf085aSDmitri Tikhonov{
2390adf085aSDmitri Tikhonov    lsquic_stream_ctx_t *const st_h = reader_ctx;
2400adf085aSDmitri Tikhonov    ssize_t nr;
2410adf085aSDmitri Tikhonov    unsigned char local_buf[LOCAL_BUF_SIZE];
2420adf085aSDmitri Tikhonov
2430adf085aSDmitri Tikhonov    assert(st_h->small.initialized);
2440adf085aSDmitri Tikhonov
2450adf085aSDmitri Tikhonov    if (count > sizeof(local_buf))
2460adf085aSDmitri Tikhonov        count = sizeof(local_buf);
2470adf085aSDmitri Tikhonov
2480adf085aSDmitri Tikhonov    nr = read(st_h->file->fd, local_buf, count);
2490adf085aSDmitri Tikhonov    if (nr < 0)
2500adf085aSDmitri Tikhonov    {
2510adf085aSDmitri Tikhonov        LSQ_ERROR("read: %s", strerror(errno));
2520adf085aSDmitri Tikhonov        close(st_h->file->fd);
2530adf085aSDmitri Tikhonov        st_h->file->fd = 0;
2540adf085aSDmitri Tikhonov        return 0;
2550adf085aSDmitri Tikhonov    }
2560adf085aSDmitri Tikhonov
2570adf085aSDmitri Tikhonov    memcpy(buf, local_buf, nr);
2580adf085aSDmitri Tikhonov    st_h->small.off += nr;
2590adf085aSDmitri Tikhonov    return nr;
2600adf085aSDmitri Tikhonov}
2610adf085aSDmitri Tikhonov
2620adf085aSDmitri Tikhonov
2630adf085aSDmitri Tikhonovstatic void
2640adf085aSDmitri Tikhonovclient_file_on_write_buf (lsquic_stream_ctx_t *st_h)
2650adf085aSDmitri Tikhonov{
2660adf085aSDmitri Tikhonov    ssize_t nw;
2670adf085aSDmitri Tikhonov    struct lsquic_reader reader = {
2680adf085aSDmitri Tikhonov        .lsqr_read = buf_reader_read,
2690adf085aSDmitri Tikhonov        .lsqr_size = buf_reader_size,
2700adf085aSDmitri Tikhonov        .lsqr_ctx  = st_h,
2710adf085aSDmitri Tikhonov    };
2720adf085aSDmitri Tikhonov
2730adf085aSDmitri Tikhonov    if (g_reset_stream.stream_id == lsquic_stream_id(st_h->stream) &&
2740adf085aSDmitri Tikhonov        lseek(st_h->file->fd, 0, SEEK_CUR) >= g_reset_stream.offset)
2750adf085aSDmitri Tikhonov    {
27699a1ad0fSDmitri Tikhonov        /* Note: this is an internal function */
27799a1ad0fSDmitri Tikhonov        lsquic_stream_maybe_reset(st_h->stream,
27899a1ad0fSDmitri Tikhonov                0x01 /* QUIC_INTERNAL_ERROR */, 1);
2790adf085aSDmitri Tikhonov        g_reset_stream.stream_id = 0;   /* Reset only once */
2800adf085aSDmitri Tikhonov    }
2810adf085aSDmitri Tikhonov
2820adf085aSDmitri Tikhonov    nw = lsquic_stream_writef(st_h->stream, &reader);
2830adf085aSDmitri Tikhonov    if (-1 == nw)
2840adf085aSDmitri Tikhonov    {
2850adf085aSDmitri Tikhonov        if (ECONNRESET == errno)
2860adf085aSDmitri Tikhonov            st_h->file->file_flags |= FILE_RESET;
2870adf085aSDmitri Tikhonov        LSQ_WARN("lsquic_stream_read: %s", strerror(errno));
2880adf085aSDmitri Tikhonov        lsquic_stream_close(st_h->stream);
2890adf085aSDmitri Tikhonov        return;
2900adf085aSDmitri Tikhonov    }
2910adf085aSDmitri Tikhonov
2920adf085aSDmitri Tikhonov#if RESET_AFTER_N_WRITES
2930adf085aSDmitri Tikhonov    static int write_count = 0;
2940adf085aSDmitri Tikhonov    if (write_count++ > RESET_AFTER_N_WRITES)
2950adf085aSDmitri Tikhonov        lsquic_stream_reset(st_h->stream, 0);
2960adf085aSDmitri Tikhonov#endif
2970adf085aSDmitri Tikhonov
2980adf085aSDmitri Tikhonov    if (0 == nw)
2990adf085aSDmitri Tikhonov    {
3000adf085aSDmitri Tikhonov        (void) close(st_h->file->fd);
3010adf085aSDmitri Tikhonov        if (0 == lsquic_stream_shutdown(st_h->stream, 1))
3020adf085aSDmitri Tikhonov            lsquic_stream_wantread(st_h->stream, 1);
3030adf085aSDmitri Tikhonov        else
3040adf085aSDmitri Tikhonov        {
3050adf085aSDmitri Tikhonov            if (ECONNRESET == errno)
3060adf085aSDmitri Tikhonov                st_h->file->file_flags |= FILE_RESET;
3070adf085aSDmitri Tikhonov            LSQ_WARN("lsquic_stream_shutdown: %s", strerror(errno));
3080adf085aSDmitri Tikhonov            lsquic_stream_close(st_h->stream);
3090adf085aSDmitri Tikhonov        }
3100adf085aSDmitri Tikhonov    }
3110adf085aSDmitri Tikhonov}
3120adf085aSDmitri Tikhonov
3130adf085aSDmitri Tikhonov
3140adf085aSDmitri Tikhonovstatic void
3150adf085aSDmitri Tikhonovclient_file_on_write_efficient (lsquic_stream_t *stream,
3160adf085aSDmitri Tikhonov                                                lsquic_stream_ctx_t *st_h)
3170adf085aSDmitri Tikhonov{
3180adf085aSDmitri Tikhonov    ssize_t nw;
3190adf085aSDmitri Tikhonov
3200adf085aSDmitri Tikhonov    nw = lsquic_stream_writef(stream, &st_h->file->reader);
3210adf085aSDmitri Tikhonov    if (nw < 0)
3220adf085aSDmitri Tikhonov    {
3230adf085aSDmitri Tikhonov        LSQ_ERROR("write error: %s", strerror(errno));
3240adf085aSDmitri Tikhonov        exit(1);
3250adf085aSDmitri Tikhonov    }
3260adf085aSDmitri Tikhonov    if (nw == 0)
3270adf085aSDmitri Tikhonov    {
3280adf085aSDmitri Tikhonov        destroy_lsquic_reader_ctx(st_h->file->reader.lsqr_ctx);
3290adf085aSDmitri Tikhonov        st_h->file->reader.lsqr_ctx = NULL;
3300adf085aSDmitri Tikhonov        if (0 == lsquic_stream_shutdown(st_h->stream, 1))
3310adf085aSDmitri Tikhonov            lsquic_stream_wantread(st_h->stream, 1);
3320adf085aSDmitri Tikhonov        else
3330adf085aSDmitri Tikhonov        {
3340adf085aSDmitri Tikhonov            if (ECONNRESET == errno)
3350adf085aSDmitri Tikhonov                st_h->file->file_flags |= FILE_RESET;
3360adf085aSDmitri Tikhonov            LSQ_WARN("lsquic_stream_shutdown: %s", strerror(errno));
3370adf085aSDmitri Tikhonov            lsquic_stream_close(st_h->stream);
3380adf085aSDmitri Tikhonov        }
3390adf085aSDmitri Tikhonov    }
3400adf085aSDmitri Tikhonov}
3410adf085aSDmitri Tikhonov
3420adf085aSDmitri Tikhonov
3430adf085aSDmitri Tikhonovstatic void
3440adf085aSDmitri Tikhonovclient_file_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
3450adf085aSDmitri Tikhonov{
3460adf085aSDmitri Tikhonov    if (g_write_file)
3470adf085aSDmitri Tikhonov        client_file_on_write_efficient(stream, st_h);
3480adf085aSDmitri Tikhonov    else
3490adf085aSDmitri Tikhonov        client_file_on_write_buf(st_h);
3500adf085aSDmitri Tikhonov}
3510adf085aSDmitri Tikhonov
3520adf085aSDmitri Tikhonov
3530adf085aSDmitri Tikhonovstatic void
3540adf085aSDmitri Tikhonovclient_file_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
3550adf085aSDmitri Tikhonov{
3560adf085aSDmitri Tikhonov    char buf;
3570adf085aSDmitri Tikhonov    /* We expect to read in 32-character MD5 string */
3580adf085aSDmitri Tikhonov    size_t ntoread = sizeof(st_h->file->md5str) - st_h->file->md5_off;
3590adf085aSDmitri Tikhonov    if (0 == ntoread)
3600adf085aSDmitri Tikhonov    {
3610adf085aSDmitri Tikhonov        lsquic_stream_wantread(stream, 0);
3620adf085aSDmitri Tikhonov        /* XXX What about an error (due to RST_STREAM) here: how are we to
3630adf085aSDmitri Tikhonov         *     handle it?
3640adf085aSDmitri Tikhonov         */
3650adf085aSDmitri Tikhonov        /* Expect a FIN */
3660adf085aSDmitri Tikhonov        if (0 == lsquic_stream_read(stream, &buf, sizeof(buf)))
3670adf085aSDmitri Tikhonov        {
3680adf085aSDmitri Tikhonov            LSQ_NOTICE("%.*s  %s", (int) sizeof(st_h->file->md5str),
3690adf085aSDmitri Tikhonov                                                    st_h->file->md5str,
3700adf085aSDmitri Tikhonov                                                    st_h->file->filename);
3710adf085aSDmitri Tikhonov            fflush(stdout);
3720adf085aSDmitri Tikhonov            LSQ_DEBUG("# of files: %d", st_h->client_ctx->n_files);
3730adf085aSDmitri Tikhonov            lsquic_stream_shutdown(stream, 0);
3740adf085aSDmitri Tikhonov        }
3750adf085aSDmitri Tikhonov        else
3760adf085aSDmitri Tikhonov            LSQ_ERROR("expected FIN from stream!");
3770adf085aSDmitri Tikhonov    }
3780adf085aSDmitri Tikhonov    else
3790adf085aSDmitri Tikhonov    {
3800adf085aSDmitri Tikhonov        ssize_t nr = lsquic_stream_read(stream,
3810adf085aSDmitri Tikhonov            st_h->file->md5str + st_h->file->md5_off, ntoread);
3820adf085aSDmitri Tikhonov        if (-1 == nr)
3830adf085aSDmitri Tikhonov        {
3840adf085aSDmitri Tikhonov            if (ECONNRESET == errno)
3850adf085aSDmitri Tikhonov                st_h->file->file_flags |= FILE_RESET;
3860adf085aSDmitri Tikhonov            LSQ_WARN("lsquic_stream_read: %s", strerror(errno));
3870adf085aSDmitri Tikhonov            lsquic_stream_close(stream);
3880adf085aSDmitri Tikhonov            return;
3890adf085aSDmitri Tikhonov        }
3900adf085aSDmitri Tikhonov        else
3910adf085aSDmitri Tikhonov            st_h->file->md5_off += nr;
3920adf085aSDmitri Tikhonov    }
3930adf085aSDmitri Tikhonov}
3940adf085aSDmitri Tikhonov
3950adf085aSDmitri Tikhonov
3960adf085aSDmitri Tikhonovstatic void
3970adf085aSDmitri Tikhonovclient_file_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
3980adf085aSDmitri Tikhonov{
3990adf085aSDmitri Tikhonov    --st_h->client_ctx->n_files;
4000adf085aSDmitri Tikhonov    LSQ_NOTICE("%s called for stream %"PRIu64", # files: %u", __func__,
4010adf085aSDmitri Tikhonov                        lsquic_stream_id(stream), st_h->client_ctx->n_files);
4020adf085aSDmitri Tikhonov    if (0 == st_h->client_ctx->n_files)
4030adf085aSDmitri Tikhonov        lsquic_conn_close(st_h->client_ctx->conn_h->conn);
4040adf085aSDmitri Tikhonov    if (!(st_h->file->file_flags & FILE_RESET) && 0 == RESET_AFTER_N_WRITES)
4050adf085aSDmitri Tikhonov        assert(st_h->file->md5_off == sizeof(st_h->file->md5str));
4060adf085aSDmitri Tikhonov    if (st_h->file->reader.lsqr_ctx)
4070adf085aSDmitri Tikhonov    {
4080adf085aSDmitri Tikhonov        destroy_lsquic_reader_ctx(st_h->file->reader.lsqr_ctx);
4090adf085aSDmitri Tikhonov        st_h->file->reader.lsqr_ctx = NULL;
4100adf085aSDmitri Tikhonov    }
4110adf085aSDmitri Tikhonov    if (st_h->file->fd >= 0)
4120adf085aSDmitri Tikhonov        (void) close(st_h->file->fd);
4130adf085aSDmitri Tikhonov    free(st_h->file);
4140adf085aSDmitri Tikhonov    free(st_h);
4150adf085aSDmitri Tikhonov}
4160adf085aSDmitri Tikhonov
4170adf085aSDmitri Tikhonov
4180adf085aSDmitri Tikhonovconst struct lsquic_stream_if client_file_stream_if = {
4190adf085aSDmitri Tikhonov    .on_new_conn            = client_on_new_conn,
4200adf085aSDmitri Tikhonov    .on_goaway_received     = client_on_goaway_received,
4210adf085aSDmitri Tikhonov    .on_conn_closed         = client_on_conn_closed,
4220adf085aSDmitri Tikhonov    .on_new_stream          = client_on_new_stream,
4230adf085aSDmitri Tikhonov    .on_read                = client_file_on_read,
4240adf085aSDmitri Tikhonov    .on_write               = client_file_on_write,
4250adf085aSDmitri Tikhonov    .on_close               = client_file_on_close,
4260adf085aSDmitri Tikhonov};
4270adf085aSDmitri Tikhonov
4280adf085aSDmitri Tikhonov
4290adf085aSDmitri Tikhonovstatic void
4300adf085aSDmitri Tikhonovusage (const char *prog)
4310adf085aSDmitri Tikhonov{
4320adf085aSDmitri Tikhonov    const char *const slash = strrchr(prog, '/');
4330adf085aSDmitri Tikhonov    if (slash)
4340adf085aSDmitri Tikhonov        prog = slash + 1;
4350adf085aSDmitri Tikhonov    printf(
4360adf085aSDmitri Tikhonov"Usage: %s [opts]\n"
4370adf085aSDmitri Tikhonov"\n"
4380adf085aSDmitri Tikhonov"Options:\n"
4390adf085aSDmitri Tikhonov"   -f FILE     File to send to the server -- must be specified at least\n"
4400adf085aSDmitri Tikhonov"                 once.\n"
4410adf085aSDmitri Tikhonov"   -b          Use buffering API for sending files over rather than\n"
4420adf085aSDmitri Tikhonov"                 the efficient version.\n"
4430adf085aSDmitri Tikhonov"   -p PRIORITY Applicatble to previous file specified with -f\n"
4440adf085aSDmitri Tikhonov"   -r STREAM_ID:OFFSET\n"
4450adf085aSDmitri Tikhonov"               Reset stream STREAM_ID after sending more that OFFSET bytes.\n"
4460adf085aSDmitri Tikhonov            , prog);
4470adf085aSDmitri Tikhonov}
4480adf085aSDmitri Tikhonov
4490adf085aSDmitri Tikhonov
4500adf085aSDmitri Tikhonovint
4510adf085aSDmitri Tikhonovmain (int argc, char **argv)
4520adf085aSDmitri Tikhonov{
4530adf085aSDmitri Tikhonov    int opt, s;
4540adf085aSDmitri Tikhonov    struct sport_head sports;
4550adf085aSDmitri Tikhonov    struct prog prog;
4560adf085aSDmitri Tikhonov    struct client_ctx client_ctx;
4570adf085aSDmitri Tikhonov    struct file *file;
4580adf085aSDmitri Tikhonov
4590adf085aSDmitri Tikhonov    file = NULL;
4600adf085aSDmitri Tikhonov    memset(&client_ctx, 0, sizeof(client_ctx));
4610adf085aSDmitri Tikhonov    client_ctx.prog = &prog;
4620adf085aSDmitri Tikhonov
4630adf085aSDmitri Tikhonov    TAILQ_INIT(&sports);
4640adf085aSDmitri Tikhonov    prog_init(&prog, 0, &sports, &client_file_stream_if, &client_ctx);
4654429f8eaSDmitri Tikhonov    prog.prog_api.ea_alpn = "md5";
4660adf085aSDmitri Tikhonov
4670adf085aSDmitri Tikhonov    while (-1 != (opt = getopt(argc, argv, PROG_OPTS "bhr:f:p:")))
4680adf085aSDmitri Tikhonov    {
4690adf085aSDmitri Tikhonov        switch (opt) {
4700adf085aSDmitri Tikhonov        case 'p':
4710adf085aSDmitri Tikhonov            if (file)
4720adf085aSDmitri Tikhonov                file->priority = atoi(optarg);
4730adf085aSDmitri Tikhonov            else
4740adf085aSDmitri Tikhonov            {
4750adf085aSDmitri Tikhonov                fprintf(stderr, "No file to apply priority to\n");
4760adf085aSDmitri Tikhonov                exit(1);
4770adf085aSDmitri Tikhonov            }
4780adf085aSDmitri Tikhonov            break;
4790adf085aSDmitri Tikhonov        case 'b':
4800adf085aSDmitri Tikhonov            g_write_file = 0;
4810adf085aSDmitri Tikhonov            break;
4820adf085aSDmitri Tikhonov        case 'f':
4830adf085aSDmitri Tikhonov            file = calloc(1, sizeof(*file));
4840adf085aSDmitri Tikhonov            LIST_INSERT_HEAD(&client_ctx.files, file, next_file);
4850adf085aSDmitri Tikhonov            ++client_ctx.n_files;
4860adf085aSDmitri Tikhonov            file->filename = optarg;
4870adf085aSDmitri Tikhonov            break;
4880adf085aSDmitri Tikhonov        case 'r':
4890adf085aSDmitri Tikhonov            g_reset_stream.stream_id = atoi(optarg);
4900adf085aSDmitri Tikhonov            g_reset_stream.offset = atoi(strchr(optarg, ':') + 1);
4910adf085aSDmitri Tikhonov            break;
4920adf085aSDmitri Tikhonov        case 'h':
4930adf085aSDmitri Tikhonov            usage(argv[0]);
4940adf085aSDmitri Tikhonov            prog_print_common_options(&prog, stdout);
4950adf085aSDmitri Tikhonov            exit(0);
4960adf085aSDmitri Tikhonov        default:
4970adf085aSDmitri Tikhonov            if (0 != prog_set_opt(&prog, opt, optarg))
4980adf085aSDmitri Tikhonov                exit(1);
4990adf085aSDmitri Tikhonov        }
5000adf085aSDmitri Tikhonov    }
5010adf085aSDmitri Tikhonov
5020adf085aSDmitri Tikhonov    if (LIST_EMPTY(&client_ctx.files))
5030adf085aSDmitri Tikhonov    {
5040adf085aSDmitri Tikhonov        fprintf(stderr, "please specify one of more files using -f\n");
5050adf085aSDmitri Tikhonov        exit(1);
5060adf085aSDmitri Tikhonov    }
5070adf085aSDmitri Tikhonov
5080adf085aSDmitri Tikhonov    if (0 != prog_prep(&prog))
5090adf085aSDmitri Tikhonov    {
5100adf085aSDmitri Tikhonov        LSQ_ERROR("could not prep");
5110adf085aSDmitri Tikhonov        exit(EXIT_FAILURE);
5120adf085aSDmitri Tikhonov    }
5130adf085aSDmitri Tikhonov    client_ctx.sport = TAILQ_FIRST(&sports);
5140adf085aSDmitri Tikhonov
5150adf085aSDmitri Tikhonov    if (0 != prog_connect(&prog, NULL, 0))
5160adf085aSDmitri Tikhonov    {
5170adf085aSDmitri Tikhonov        LSQ_ERROR("could not connect");
5180adf085aSDmitri Tikhonov        exit(EXIT_FAILURE);
5190adf085aSDmitri Tikhonov    }
5200adf085aSDmitri Tikhonov
5210adf085aSDmitri Tikhonov    LSQ_DEBUG("entering event loop");
5220adf085aSDmitri Tikhonov
5230adf085aSDmitri Tikhonov    s = prog_run(&prog);
5240adf085aSDmitri Tikhonov    prog_cleanup(&prog);
5250adf085aSDmitri Tikhonov
5260adf085aSDmitri Tikhonov    exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE);
5270adf085aSDmitri Tikhonov}
528