static int done;
+/**
+ * @brief variable to record output basename path
+ */
+static char *dlpath = NULL;
+
+/**
+ * @brief struct to hold user data for async http3 writes
+ */
+struct stream_user_data {
+ char *outpath;
+ FILE *fp;
+};
+
static void make_nv(nghttp3_nv *nv, const char *name, const char *value)
{
nv->name = (uint8_t *)name;
static int on_recv_data(nghttp3_conn *h3conn, int64_t stream_id,
const uint8_t *data, size_t datalen,
- void *conn_user_data, void *stream_user_data)
+ void *conn_user_data, void *user_data)
{
+ FILE *outfp;
size_t wr;
+ struct stream_user_data *sdata = OSSL_DEMO_H3_STREAM_get_user_data((const OSSL_DEMO_H3_STREAM *)user_data);
- /* HTTP response body data - write it to stdout. */
+ if (dlpath == NULL) {
+ outfp = stdout;
+ } else {
+ if (sdata->fp == NULL) {
+ sdata->fp = fopen(sdata->outpath, "w+");
+ }
+ if (sdata->fp == NULL) {
+ fprintf(stderr, "Failed to open %s for writing\n", sdata->outpath);
+ return 1;
+ }
+ outfp = sdata->fp;
+ }
+ /* HTTP response body data - write it. */
while (datalen > 0) {
- wr = fwrite(data, 1, datalen, stdout);
- if (ferror(stdout))
+ fprintf(stderr, "writing %lu bytes to %s\n", datalen, sdata->outpath);
+ wr = fwrite(data, 1, datalen, outfp);
+ if (ferror(outfp))
return 1;
data += wr;
datalen -= wr;
}
-
return 0;
}
static int on_end_stream(nghttp3_conn *h3conn, int64_t stream_id,
- void *conn_user_data, void *stream_user_data)
+ void *conn_user_data, void *user_data)
{
/* HTTP transaction is done - set done flag so that we stop looping. */
done = 1;
return 0;
}
-static int try_conn(OSSL_DEMO_H3_CONN *conn, const char *bare_hostname)
+static int try_conn(OSSL_DEMO_H3_CONN *conn, const char *bare_hostname, const char *path)
{
nghttp3_nv nva[16];
size_t num_nv = 0;
+ struct stream_user_data *sdata;
+ size_t needed_size;
+ int pathsize;
/* Build HTTP headers. */
make_nv(&nva[num_nv++], ":method", "GET");
make_nv(&nva[num_nv++], ":scheme", "https");
make_nv(&nva[num_nv++], ":authority", bare_hostname);
- make_nv(&nva[num_nv++], ":path", "/");
+ make_nv(&nva[num_nv++], ":path", path);
make_nv(&nva[num_nv++], "user-agent", "OpenSSL-Demo/nghttp3");
+ needed_size = sizeof(struct stream_user_data);
+ pathsize = snprintf(NULL, 0, "%s/%s", dlpath, path);
+ if (pathsize < 0) {
+ fprintf(stderr, "Unable to format path string\n");
+ return 0;
+ }
+ needed_size += pathsize;
+
+ sdata = malloc(needed_size + 1);
+ if (sdata == NULL)
+ return 0;
+ sdata->outpath = (char *)(sdata + 1);
+ sprintf(sdata->outpath, "%s/%s", dlpath, path);
+ sdata->fp = NULL;
+ fprintf(stderr, "Requesting %s\n", sdata->outpath);
/* Submit request. */
- if (!OSSL_DEMO_H3_CONN_submit_request(conn, nva, num_nv, NULL, NULL)) {
- ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL,
- "cannot submit HTTP/3 request");
+ if (!OSSL_DEMO_H3_CONN_submit_request(conn, nva, num_nv, NULL, sdata)) {
+ fprintf(stderr, "Cannot submit HTTP/3 request\n");
return 0;
}
/* Wait for request to complete. */
done = 0;
- while (!done)
+ while (!done) {
if (!OSSL_DEMO_H3_CONN_handle_events(conn)) {
- ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL,
- "cannot handle events");
+ fprintf(stderr, "Cannot handle events\n");
return 0;
}
+ }
+ if (sdata->fp != NULL) {
+ fclose(sdata->fp);
+ fprintf(stderr, "Closing local FILE pointer for %s\n", sdata->outpath);
+ }
+ free(sdata);
return 1;
}
char *hostname, *service;
BIO_ADDRINFO *bai = NULL;
const BIO_ADDRINFO *bai_walk;
+ FILE *req_fp = NULL;
+ size_t req_count = 0;
+ char **req_array = NULL;
+ size_t i;
+ char buffer[PATH_MAX];
/* Check arguments. */
if (argc < 2) {
- fprintf(stderr, "usage: %s <host:port>\n", argv[0]);
+ fprintf(stderr, "usage: %s <host:port> [requestfile.txt <download_dir>]\n", argv[0]);
goto err;
}
+ /*
+ * If we have more than two arguments, then we are accepting both a request file
+ * which is a newline separated list of paths to request from the server
+ * as well as a download location relative to the current working directory
+ */
+ if (argc >= 4) {
+ dlpath = argv[3];
+ fprintf(stderr, "setting download path to %s, and reading %s\n", dlpath, argv[2]);
+
+ /*
+ * Read in our request file, one path per line
+ */
+ req_fp = fopen(argv[2], "r");
+ if (req_fp == NULL) {
+ fprintf(stderr, "Unable to open request file\n");
+ goto err;
+ }
+ while (!feof(req_fp)) {
+ req_count++;
+ req_array = realloc(req_array, sizeof(char *) * req_count);
+ req_array[req_count - 1] = NULL;
+ if (fscanf(req_fp, "%s", buffer) != 1) {
+ req_count--;
+ if (feof(req_fp))
+ break;
+ fprintf(stderr, "Failed to read request file at index %lu\n", req_count);
+ fclose(req_fp);
+ goto err;
+ }
+ req_array[req_count - 1] = calloc(1, strlen(buffer) + 1);
+ memcpy(req_array[req_count - 1], buffer, strlen(buffer));
+ }
+ fclose(req_fp);
+ }
+
addr = argv[1];
hostname = NULL;
if ((ctx = SSL_CTX_new(OSSL_QUIC_client_method())) == NULL)
goto err;
- SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
if (SSL_CTX_set_default_verify_paths(ctx) == 0)
goto err;
conn = OSSL_DEMO_H3_CONN_new_for_addr(ctx, bai_walk, hostname,
&callbacks, NULL, NULL);
if (conn != NULL) {
- if (try_conn(conn, addr) == 0) {
- /*
- * Failure, try the next address.
- */
- OSSL_DEMO_H3_CONN_free(conn);
- conn = NULL;
- ret = 1;
- } else {
- /*
- * Success, done.
- */
- ret = 0;
- break;
+ for (i = 0; i < req_count; i++) {
+ if (try_conn(conn, addr, req_array[i]) == 0) {
+ /*
+ * Failure, bail out.
+ */
+ OSSL_DEMO_H3_CONN_free(conn);
+ conn = NULL;
+ ret = 1;
+ goto err;
+ }
}
+ fprintf(stderr, "all requests complete\n");
+ ret = 0;
}
}
err:
+ for (i = 0; i < req_count; i++)
+ free(req_array[i]);
+ free(req_array);
+
if (ret != 0)
ERR_print_errors_fp(stderr);
return NULL;
}
-static OSSL_DEMO_H3_STREAM *h3_conn_accept_stream(OSSL_DEMO_H3_CONN *conn, SSL *qstream)
-{
- OSSL_DEMO_H3_STREAM *s;
-
- if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL)
- return NULL;
-
- s->id = SSL_get_stream_id(qstream);
- s->s = qstream;
- lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s);
- return s;
-}
-
static void h3_conn_remove_stream(OSSL_DEMO_H3_CONN *conn, OSSL_DEMO_H3_STREAM *s)
{
if (s == NULL)
return conn->qconn;
}
+typedef struct ossl_demo_h3_poll_list {
+ SSL_POLL_ITEM *poll_list;
+ OSSL_DEMO_H3_STREAM **h3_streams;
+ OSSL_DEMO_H3_CONN *conn;
+ size_t poll_count;
+ size_t idx;
+} OSSL_DEMO_H3_POLL_LIST;
+
+static void h3_conn_collect_streams(OSSL_DEMO_H3_STREAM *s, void *list)
+{
+ OSSL_DEMO_H3_POLL_LIST *pollist = list;
+
+ pollist->poll_list[pollist->idx].desc = SSL_as_poll_descriptor(s->s);
+ pollist->poll_list[pollist->idx].revents = 0;
+ pollist->poll_list[pollist->idx].events = SSL_POLL_EVENT_R;
+ pollist->h3_streams[pollist->idx] = s;
+ pollist->idx++;
+}
+
/* Pumps received data to the HTTP/3 stack for a single stream. */
static void h3_conn_pump_stream(OSSL_DEMO_H3_STREAM *s, void *conn_)
{
nghttp3_vec vecs[8] = { 0 };
OSSL_DEMO_H3_STREAM key, *s;
SSL *snew;
+ OSSL_DEMO_H3_POLL_LIST *pollist = NULL;
+ size_t poll_num;
+ struct timeval poll_timeout;
+ size_t result_count;
if (conn == NULL)
return 0;
for (;;) {
if ((snew = SSL_accept_stream(conn->qconn, SSL_ACCEPT_STREAM_NO_BLOCK)) == NULL)
break;
-
- /*
- * Each new incoming stream gets wrapped into an OSSL_DEMO_H3_STREAM object and
- * added into our stream ID map.
- */
- if (h3_conn_accept_stream(conn, snew) == NULL) {
- SSL_free(snew);
- return 0;
- }
}
/* 2. Pump outgoing data from HTTP/3 engine to QUIC. */
}
}
- /* 3. Pump incoming data from QUIC to HTTP/3 engine. */
+ /* 3. Build a list of streams to poll on */
conn->pump_res = 1; /* cleared in below call if an error occurs */
- lh_OSSL_DEMO_H3_STREAM_doall_arg(conn->streams, h3_conn_pump_stream, conn);
+ poll_num = lh_OSSL_DEMO_H3_STREAM_num_items(conn->streams);
+ pollist = OPENSSL_malloc(sizeof(OSSL_DEMO_H3_POLL_LIST) + (sizeof(SSL_POLL_ITEM) * poll_num) + (sizeof(OSSL_DEMO_H3_STREAM *) * poll_num));
+ pollist->poll_count = poll_num;
+ pollist->poll_list = (SSL_POLL_ITEM *)(pollist + 1);
+ pollist->h3_streams = (OSSL_DEMO_H3_STREAM **)(pollist->poll_list + poll_num);
+ pollist->conn = conn;
+ pollist->idx = 0;
+ lh_OSSL_DEMO_H3_STREAM_doall_arg(conn->streams, h3_conn_collect_streams, pollist);
+ poll_timeout.tv_sec = 0;
+ poll_timeout.tv_usec = 0;
+ result_count = 0;
+
+ /* 4. poll the list built above, looking for streams that are ready to read */
+ if (!SSL_poll(pollist->poll_list, pollist->idx, sizeof(SSL_POLL_ITEM),
+ &poll_timeout, 0, &result_count)) {
+ fprintf(stderr, "Failed to poll\n");
+ goto end;
+ }
+
+ /* 5. Pump incoming data from QUIC to HTTP/3 engine. */
+ for (i = 0; result_count != 0; i++) {
+ if (pollist->poll_list[i].revents == SSL_POLL_EVENT_R) {
+ result_count--;
+ h3_conn_pump_stream(pollist->h3_streams[i], pollist->conn);
+ }
+ }
+end:
+ OPENSSL_free(pollist);
if (!conn->pump_res)
return 0;