summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Landau <hlandau@openssl.org>2023-04-18 19:30:56 +0100
committerHugo Landau <hlandau@openssl.org>2023-05-12 14:47:13 +0100
commited835673ae5d99cac39d0bef6677597a68d1e248 (patch)
treedae346fd5828d6a76e322238ccdba6a28360f43a
parentacc6fde0d44d22c7fa4578c967aee69c3fbcf350 (diff)
downloadopenssl-new-ed835673ae5d99cac39d0bef6677597a68d1e248.tar.gz
QUIC MSST: Tests
Reviewed-by: Matt Caswell <matt@openssl.org> Reviewed-by: Tomas Mraz <tomas@openssl.org> (Merged from https://github.com/openssl/openssl/pull/20765)
-rw-r--r--test/build.info6
-rw-r--r--test/quic_multistream_test.c1203
-rw-r--r--test/recipes/70-test_quic_multistream.t21
3 files changed, 1229 insertions, 1 deletions
diff --git a/test/build.info b/test/build.info
index 996825c075..4f1d19e516 100644
--- a/test/build.info
+++ b/test/build.info
@@ -340,6 +340,10 @@ IF[{- !$disabled{tests} -}]
INCLUDE[quic_client_test]=../include ../apps/include
DEPEND[quic_client_test]=../libcrypto.a ../libssl.a libtestutil.a
+ SOURCE[quic_multistream_test]=quic_multistream_test.c
+ INCLUDE[quic_multistream_test]=../include ../apps/include
+ DEPEND[quic_multistream_test]=../libcrypto.a ../libssl.a libtestutil.a
+
SOURCE[asynctest]=asynctest.c
INCLUDE[asynctest]=../include ../apps/include
DEPEND[asynctest]=../libcrypto
@@ -1095,7 +1099,7 @@ ENDIF
PROGRAMS{noinst}=quic_wire_test quic_ackm_test quic_record_test
PROGRAMS{noinst}=quic_fc_test quic_stream_test quic_cfq_test quic_txpim_test
PROGRAMS{noinst}=quic_fifd_test quic_txp_test quic_tserver_test
- PROGRAMS{noinst}=quic_client_test quic_cc_test
+ PROGRAMS{noinst}=quic_client_test quic_cc_test quic_multistream_test
ENDIF
SOURCE[quic_ackm_test]=quic_ackm_test.c cc_dummy.c
diff --git a/test/quic_multistream_test.c b/test/quic_multistream_test.c
new file mode 100644
index 0000000000..1b15c1d16e
--- /dev/null
+++ b/test/quic_multistream_test.c
@@ -0,0 +1,1203 @@
+/*
+ * Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+#include <openssl/ssl.h>
+#include <openssl/quic.h>
+#include <openssl/bio.h>
+#include <openssl/lhash.h>
+#include "internal/quic_tserver.h"
+#include "testutil.h"
+
+static const char *certfile, *keyfile;
+
+typedef struct stream_info {
+ const char *name;
+ SSL *c_stream;
+ uint64_t s_stream_id;
+} STREAM_INFO;
+
+DEFINE_LHASH_OF_EX(STREAM_INFO);
+
+struct helper {
+ int s_fd;
+ BIO *s_net_bio, *s_net_bio_own;
+ BIO_ADDR *s_net_bio_addr;
+ QUIC_TSERVER *s;
+ LHASH_OF(STREAM_INFO) *s_streams;
+
+ int c_fd;
+ BIO *c_net_bio, *c_net_bio_own;
+ SSL_CTX *c_ctx;
+ SSL *c_conn;
+ LHASH_OF(STREAM_INFO) *c_streams;
+
+ OSSL_TIME start_time;
+ int init, blocking;
+};
+
+struct script_op {
+ uint32_t op;
+ const void *arg0;
+ size_t arg1;
+ int (*check_func)(struct helper *h, const struct script_op *op);
+ const char *stream_name;
+ uint64_t arg2;
+};
+
+#define OPK_END 0
+#define OPK_CHECK 1
+#define OPK_C_SET_ALPN 2
+#define OPK_C_CONNECT_WAIT 3
+#define OPK_C_WRITE 4
+#define OPK_S_WRITE 5
+#define OPK_C_READ_EXPECT 6
+#define OPK_S_READ_EXPECT 7
+#define OPK_C_EXPECT_FIN 8
+#define OPK_S_EXPECT_FIN 9
+#define OPK_C_CONCLUDE 10
+#define OPK_S_CONCLUDE 11
+#define OPK_C_DETACH 12
+#define OPK_C_ATTACH 13
+#define OPK_C_NEW_STREAM 14
+#define OPK_S_NEW_STREAM 15
+#define OPK_C_ACCEPT_STREAM 16
+#define OPK_C_ACCEPT_STREAM_NONE 17
+#define OPK_C_FREE_STREAM 18
+#define OPK_C_SET_DEFAULT_STREAM_MODE 19
+#define OPK_C_SET_INCOMING_STREAM_REJECT_POLICY 20
+#define OPK_C_SHUTDOWN 21
+#define OPK_C_EXPECT_CONN_CLOSE_INFO 22
+#define OPK_S_EXPECT_CONN_CLOSE_INFO 23
+#define OPK_S_BIND_STREAM_ID 24
+#define OPK_C_WAIT_FOR_DATA 25
+#define OPK_C_WRITE_FAIL 26
+#define OPK_S_WRITE_FAIL 27
+#define OPK_C_READ_FAIL 28
+#define OPK_C_STREAM_RESET 29
+
+#define EXPECT_CONN_CLOSE_APP (1U << 0)
+#define EXPECT_CONN_CLOSE_REMOTE (1U << 1)
+
+#define C_BIDI_ID(ordinal) \
+ (((ordinal) << 2) | QUIC_STREAM_INITIATOR_CLIENT | QUIC_STREAM_DIR_BIDI)
+#define S_BIDI_ID(ordinal) \
+ (((ordinal) << 2) | QUIC_STREAM_INITIATOR_SERVER | QUIC_STREAM_DIR_BIDI)
+#define C_UNI_ID(ordinal) \
+ (((ordinal) << 2) | QUIC_STREAM_INITIATOR_CLIENT | QUIC_STREAM_DIR_UNI)
+#define S_UNI_ID(ordinal) \
+ (((ordinal) << 2) | QUIC_STREAM_INITIATOR_SERVER | QUIC_STREAM_DIR_UNI)
+
+#define OP_END \
+ {OPK_END}
+#define OP_CHECK(func, arg2) \
+ {OPK_CHECK, NULL, 0, (func), NULL, (arg2)},
+#define OP_C_SET_ALPN(alpn) \
+ {OPK_C_SET_ALPN, (alpn), 0, NULL, NULL},
+#define OP_C_CONNECT_WAIT() \
+ {OPK_C_CONNECT_WAIT, NULL, 0, NULL, NULL},
+#define OP_C_WRITE(stream_name, buf, buf_len) \
+ {OPK_C_WRITE, (buf), (buf_len), NULL, #stream_name},
+#define OP_S_WRITE(stream_name, buf, buf_len) \
+ {OPK_S_WRITE, (buf), (buf_len), NULL, #stream_name},
+#define OP_C_READ_EXPECT(stream_name, buf, buf_len) \
+ {OPK_C_READ_EXPECT, (buf), (buf_len), NULL, #stream_name},
+#define OP_S_READ_EXPECT(stream_name, buf, buf_len) \
+ {OPK_S_READ_EXPECT, (buf), (buf_len), NULL, #stream_name},
+#define OP_C_EXPECT_FIN(stream_name) \
+ {OPK_C_EXPECT_FIN, NULL, 0, NULL, #stream_name},
+#define OP_S_EXPECT_FIN(stream_name) \
+ {OPK_S_EXPECT_FIN, NULL, 0, NULL, #stream_name},
+#define OP_C_CONCLUDE(stream_name) \
+ {OPK_C_CONCLUDE, NULL, 0, NULL, #stream_name},
+#define OP_S_CONCLUDE(stream_name) \
+ {OPK_S_CONCLUDE, NULL, 0, NULL, #stream_name},
+#define OP_C_DETACH(stream_name) \
+ {OPK_C_DETACH, NULL, 0, NULL, #stream_name},
+#define OP_C_ATTACH(stream_name) \
+ {OPK_C_ATTACH, NULL, 0, NULL, #stream_name},
+#define OP_C_NEW_STREAM_BIDI(stream_name, expect_id) \
+ {OPK_C_NEW_STREAM, NULL, 0, NULL, #stream_name, (expect_id)},
+#define OP_C_NEW_STREAM_UNI(stream_name, expect_id) \
+ {OPK_C_NEW_STREAM, NULL, 1, NULL, #stream_name, (expect_id)},
+#define OP_S_NEW_STREAM_BIDI(stream_name, expect_id) \
+ {OPK_S_NEW_STREAM, NULL, 0, NULL, #stream_name, (expect_id)},
+#define OP_S_NEW_STREAM_UNI(stream_name, expect_id) \
+ {OPK_S_NEW_STREAM, NULL, 1, NULL, #stream_name, (expect_id)},
+#define OP_C_ACCEPT_STREAM(stream_name) \
+ {OPK_C_ACCEPT_STREAM, NULL, 0, NULL, #stream_name},
+#define OP_C_ACCEPT_STREAM_NONE() \
+ {OPK_C_ACCEPT_STREAM_NONE, NULL, 0, NULL, NULL},
+#define OP_C_FREE_STREAM(stream_name) \
+ {OPK_C_FREE_STREAM, NULL, 0, NULL, #stream_name},
+#define OP_C_SET_DEFAULT_STREAM_MODE(mode) \
+ {OPK_C_SET_DEFAULT_STREAM_MODE, NULL, (mode), NULL, NULL},
+#define OP_C_SET_INCOMING_STREAM_REJECT_POLICY(policy) \
+ {OPK_C_SET_INCOMING_STREAM_REJECT_POLICY, NULL, (policy), NULL, NULL},
+#define OP_C_SHUTDOWN() \
+ {OPK_C_SHUTDOWN, NULL, 0, NULL, NULL},
+#define OP_C_EXPECT_CONN_CLOSE_INFO(ec, app, remote) \
+ {OPK_C_EXPECT_CONN_CLOSE_INFO, NULL, \
+ ((app) ? EXPECT_CONN_CLOSE_APP : 0) | \
+ ((remote) ? EXPECT_CONN_CLOSE_REMOTE : 0), \
+ NULL, NULL, (ec)},
+#define OP_S_EXPECT_CONN_CLOSE_INFO(ec, app, remote) \
+ {OPK_S_EXPECT_CONN_CLOSE_INFO, NULL, \
+ ((app) ? EXPECT_CONN_CLOSE_APP : 0) | \
+ ((remote) ? EXPECT_CONN_CLOSE_REMOTE : 0), \
+ NULL, NULL, (ec)},
+#define OP_S_BIND_STREAM_ID(stream_name, stream_id) \
+ {OPK_S_BIND_STREAM_ID, NULL, 0, NULL, #stream_name, (stream_id)},
+#define OP_C_WAIT_FOR_DATA(stream_name) \
+ {OPK_C_WAIT_FOR_DATA, NULL, 0, NULL, #stream_name},
+#define OP_C_WRITE_FAIL(stream_name) \
+ {OPK_C_WRITE_FAIL, NULL, 0, NULL, #stream_name},
+#define OP_S_WRITE_FAIL(stream_name) \
+ {OPK_S_WRITE_FAIL, NULL, 0, NULL, #stream_name},
+#define OP_C_READ_FAIL(stream_name) \
+ {OPK_C_READ_FAIL, NULL, 0, NULL, #stream_name},
+#define OP_C_STREAM_RESET(stream_name, aec) \
+ {OPK_C_STREAM_RESET, NULL, 0, NULL, #stream_name, (aec)},
+
+static int check_rejected(struct helper *h, const struct script_op *op)
+{
+ uint64_t stream_id = op->arg2;
+
+ if (!TEST_true(ossl_quic_tserver_stream_has_peer_stop_sending(h->s, stream_id, NULL))
+ || !TEST_true(ossl_quic_tserver_stream_has_peer_reset_stream(h->s, stream_id, NULL)))
+ return 0;
+
+ return 1;
+}
+
+static int check_stream_reset(struct helper *h, const struct script_op *op)
+{
+ uint64_t stream_id = op->arg2, aec = 0;
+
+ return TEST_true(ossl_quic_tserver_stream_has_peer_reset_stream(h->s, stream_id,
+ &aec))
+ && TEST_uint64_t_eq(aec, 42);
+}
+
+static int check_stream_stopped(struct helper *h, const struct script_op *op)
+{
+ uint64_t stream_id = op->arg2;
+
+ return TEST_true(ossl_quic_tserver_stream_has_peer_stop_sending(h->s, stream_id,
+ NULL));
+}
+
+static unsigned long stream_info_hash(const STREAM_INFO *info)
+{
+ return OPENSSL_LH_strhash(info->name);
+}
+
+static int stream_info_cmp(const STREAM_INFO *a, const STREAM_INFO *b)
+{
+ return strcmp(a->name, b->name);
+}
+
+static void cleanup_stream(STREAM_INFO *info)
+{
+ SSL_free(info->c_stream);
+ OPENSSL_free(info);
+}
+
+static void helper_cleanup_streams(LHASH_OF(STREAM_INFO) **lh)
+{
+ if (*lh == NULL)
+ return;
+
+ lh_STREAM_INFO_doall(*lh, cleanup_stream);
+ lh_STREAM_INFO_free(*lh);
+ *lh = NULL;
+}
+
+static void helper_cleanup(struct helper *h)
+{
+ helper_cleanup_streams(&h->s_streams);
+ helper_cleanup_streams(&h->c_streams);
+
+ SSL_free(h->c_conn);
+ h->c_conn = NULL;
+
+ ossl_quic_tserver_free(h->s);
+ h->s = NULL;
+
+ BIO_free(h->s_net_bio_own);
+ h->s_net_bio_own = NULL;
+
+ BIO_free(h->c_net_bio_own);
+ h->c_net_bio_own = NULL;
+
+ if (h->s_fd >= 0) {
+ BIO_closesocket(h->s_fd);
+ h->s_fd = -1;
+ }
+
+ if (h->c_fd >= 0) {
+ BIO_closesocket(h->c_fd);
+ h->c_fd = -1;
+ }
+
+ BIO_ADDR_free(h->s_net_bio_addr);
+ h->s_net_bio_addr = NULL;
+
+ SSL_CTX_free(h->c_ctx);
+ h->c_ctx = NULL;
+}
+
+static int helper_init(struct helper *h)
+{
+ short port = 8186;
+ struct in_addr ina = {0};
+ QUIC_TSERVER_ARGS s_args = {0};
+
+ memset(h, 0, sizeof(*h));
+ h->c_fd = -1;
+ h->s_fd = -1;
+
+ if (!TEST_ptr(h->s_streams = lh_STREAM_INFO_new(stream_info_hash,
+ stream_info_cmp)))
+ goto err;
+
+ if (!TEST_ptr(h->c_streams = lh_STREAM_INFO_new(stream_info_hash,
+ stream_info_cmp)))
+ goto err;
+
+ ina.s_addr = htonl(0x7f000001UL);
+
+ h->s_fd = BIO_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0);
+ if (!TEST_int_ge(h->s_fd, 0))
+ goto err;
+
+ if (!TEST_true(BIO_socket_nbio(h->s_fd, 1)))
+ goto err;
+
+ if (!TEST_ptr(h->s_net_bio_addr = BIO_ADDR_new()))
+ goto err;
+
+ if (!TEST_true(BIO_ADDR_rawmake(h->s_net_bio_addr, AF_INET, &ina, sizeof(ina),
+ htons(port))))
+ goto err;
+
+ if (!TEST_true(BIO_bind(h->s_fd, h->s_net_bio_addr, 0)))
+ goto err;
+
+ if (!TEST_int_gt(BIO_ADDR_rawport(h->s_net_bio_addr), 0))
+ goto err;
+
+ if (!TEST_ptr(h->s_net_bio = h->s_net_bio_own = BIO_new_dgram(h->s_fd, 0)))
+ goto err;
+
+ if (!BIO_up_ref(h->s_net_bio))
+ goto err;
+
+ s_args.net_rbio = h->s_net_bio;
+ s_args.net_wbio = h->s_net_bio;
+ //s_args.now_cb = ...;
+
+ if (!TEST_ptr(h->s = ossl_quic_tserver_new(&s_args, certfile, keyfile)))
+ goto err;
+
+ h->s_net_bio_own = NULL;
+
+ h->c_fd = BIO_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0);
+ if (!TEST_int_ge(h->c_fd, 0))
+ goto err;
+
+ if (!TEST_true(BIO_socket_nbio(h->c_fd, 1)))
+ goto err;
+
+ if (!TEST_ptr(h->c_net_bio = h->c_net_bio_own = BIO_new_dgram(h->c_fd, 0)))
+ goto err;
+
+ if (!TEST_true(BIO_dgram_set_peer(h->c_net_bio, h->s_net_bio_addr)))
+ goto err;
+
+
+ if (!TEST_ptr(h->c_ctx = SSL_CTX_new(OSSL_QUIC_client_method())))
+ goto err;
+
+ if (!TEST_ptr(h->c_conn = SSL_new(h->c_ctx)))
+ goto err;
+
+ /* Takes ownership of our reference to the BIO. */
+ SSL_set0_rbio(h->c_conn, h->c_net_bio);
+ h->c_net_bio_own = NULL;
+
+ if (!TEST_true(BIO_up_ref(h->c_net_bio)))
+ goto err;
+
+ SSL_set0_wbio(h->c_conn, h->c_net_bio);
+
+ if (!TEST_true(SSL_set_blocking_mode(h->c_conn, 0)))
+ goto err;
+
+ h->start_time = ossl_time_now();
+ h->init = 1;
+ return 1;
+
+err:
+ helper_cleanup(h);
+ return 0;
+}
+
+static STREAM_INFO *get_stream_info(LHASH_OF(STREAM_INFO) *lh,
+ const char *stream_name)
+{
+ STREAM_INFO key, *info;
+
+ if (!TEST_ptr(stream_name))
+ return NULL;
+
+ if (!strcmp(stream_name, "DEFAULT"))
+ return NULL;
+
+ key.name = stream_name;
+ info = lh_STREAM_INFO_retrieve(lh, &key);
+ if (info == NULL) {
+ info = OPENSSL_zalloc(sizeof(*info));
+ if (info == NULL)
+ return NULL;
+
+ info->name = stream_name;
+ info->s_stream_id = UINT64_MAX;
+ lh_STREAM_INFO_insert(lh, info);
+ }
+
+ return info;
+}
+
+static int helper_set_c_stream(struct helper *h, const char *stream_name,
+ SSL *c_stream)
+{
+ STREAM_INFO *info = get_stream_info(h->c_streams, stream_name);
+
+ if (info == NULL)
+ return 0;
+
+ info->c_stream = c_stream;
+ info->s_stream_id = UINT64_MAX;
+ return 1;
+}
+
+static SSL *helper_get_c_stream(struct helper *h, const char *stream_name)
+{
+ STREAM_INFO *info;
+
+ if (!strcmp(stream_name, "DEFAULT"))
+ return h->c_conn;
+
+ info = get_stream_info(h->c_streams, stream_name);
+ if (info == NULL)
+ return NULL;
+
+ return info->c_stream;
+}
+
+static int
+helper_set_s_stream(struct helper *h, const char *stream_name,
+ uint64_t s_stream_id)
+{
+ STREAM_INFO *info;
+
+ if (!strcmp(stream_name, "DEFAULT"))
+ return 0;
+
+ info = get_stream_info(h->s_streams, stream_name);
+ if (info == NULL)
+ return 0;
+
+ info->c_stream = NULL;
+ info->s_stream_id = s_stream_id;
+ return 1;
+}
+
+static uint64_t helper_get_s_stream(struct helper *h, const char *stream_name)
+{
+ STREAM_INFO *info;
+
+ if (!strcmp(stream_name, "DEFAULT"))
+ return UINT64_MAX;
+
+ info = get_stream_info(h->s_streams, stream_name);
+ if (info == NULL)
+ return UINT64_MAX;
+
+ return info->s_stream_id;
+}
+
+static int is_want(SSL *s, int ret)
+{
+ int ec = SSL_get_error(s, ret);
+
+ return ec == SSL_ERROR_WANT_READ || ec == SSL_ERROR_WANT_WRITE;
+}
+
+static int run_script(const struct script_op *script)
+{
+ size_t op_idx = 0;
+ int testresult = 0;
+ int no_advance = 0;
+ const struct script_op *op = NULL;
+ struct helper h;
+ unsigned char *tmp_buf = NULL;
+ int connect_started = 0;
+
+ if (!TEST_true(helper_init(&h)))
+ goto out;
+
+#define SPIN_AGAIN() do { no_advance = 1; continue; } while (0)
+
+ for (;; no_advance ? (void)(no_advance = 0) : (void)++op_idx) {
+ SSL *c_tgt = h.c_conn;
+ uint64_t s_stream_id = UINT64_MAX;
+
+ op = &script[op_idx];
+
+ if (op->stream_name != NULL) {
+ c_tgt = helper_get_c_stream(&h, op->stream_name);
+ s_stream_id = helper_get_s_stream(&h, op->stream_name);
+ }
+
+ ossl_quic_tserver_tick(h.s);
+ if (connect_started)
+ SSL_tick(h.c_conn);
+
+ switch (op->op) {
+ case OPK_END:
+ testresult = 1;
+ goto out;
+
+ case OPK_CHECK:
+ if (!TEST_true(op->check_func(&h, op)))
+ goto out;
+
+ break;
+
+ case OPK_C_SET_ALPN:
+ {
+ const char *alpn = op->arg0;
+ size_t alpn_len = strlen(alpn);
+
+ if (!TEST_size_t_le(alpn_len, UINT8_MAX)
+ || !TEST_ptr(tmp_buf = (unsigned char *)OPENSSL_malloc(alpn_len + 1)))
+ goto out;
+
+ memcpy(tmp_buf + 1, alpn, alpn_len);
+ tmp_buf[0] = (unsigned char)alpn_len;
+
+ /* 0 is the success case for SSL_set_alpn_protos(). */
+ if (!TEST_false(SSL_set_alpn_protos(h.c_conn, tmp_buf,
+ alpn_len + 1)))
+ goto out;
+
+ OPENSSL_free(tmp_buf);
+ tmp_buf = NULL;
+ }
+ break;
+
+ case OPK_C_CONNECT_WAIT:
+ {
+ int ret;
+
+ connect_started = 1;
+
+ ret = SSL_connect(h.c_conn);
+ if (!TEST_true(ret == 1
+ || (!h.blocking && is_want(h.c_conn, ret))))
+ goto out;
+
+ if (!h.blocking && ret != 1)
+ SPIN_AGAIN();
+ }
+ break;
+
+ case OPK_C_WRITE:
+ {
+ size_t bytes_written = 0;
+
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ if (!TEST_true(SSL_write_ex(c_tgt, op->arg0, op->arg1,
+ &bytes_written))
+ || !TEST_size_t_eq(bytes_written, op->arg1))
+ goto out;
+ }
+ break;
+
+ case OPK_S_WRITE:
+ {
+ size_t bytes_written = 0;
+
+ if (!TEST_uint64_t_ne(s_stream_id, UINT64_MAX))
+ goto out;
+
+ if (!TEST_true(ossl_quic_tserver_write(h.s, s_stream_id,
+ op->arg0, op->arg1,
+ &bytes_written))
+ || !TEST_size_t_eq(bytes_written, op->arg1))
+ goto out;
+ }
+ break;
+
+ case OPK_C_CONCLUDE:
+ {
+ if (!TEST_true(SSL_stream_conclude(c_tgt, 0)))
+ goto out;
+ }
+ break;
+
+ case OPK_S_CONCLUDE:
+ {
+ if (!TEST_uint64_t_ne(s_stream_id, UINT64_MAX))
+ goto out;
+
+ ossl_quic_tserver_conclude(h.s, s_stream_id);
+ }
+ break;
+
+ case OPK_C_WAIT_FOR_DATA:
+ {
+ char buf[1];
+ size_t bytes_read = 0;
+
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ if (!SSL_peek_ex(c_tgt, buf, sizeof(buf), &bytes_read)
+ || bytes_read == 0)
+ SPIN_AGAIN();
+ }
+ break;
+
+ case OPK_C_READ_EXPECT:
+ {
+ size_t bytes_read = 0;
+
+ if (!TEST_ptr(tmp_buf = OPENSSL_malloc(op->arg1)))
+ goto out;
+
+ if (!TEST_true(SSL_read_ex(c_tgt, tmp_buf, op->arg1,
+ &bytes_read))
+ || !TEST_size_t_eq(bytes_read, op->arg1))
+ goto out;
+
+ if (!TEST_mem_eq(tmp_buf, op->arg1, op->arg0, op->arg1))
+ goto out;
+
+ OPENSSL_free(tmp_buf);
+ tmp_buf = NULL;
+ }
+ break;
+
+ case OPK_S_READ_EXPECT:
+ {
+ size_t bytes_read = 0;
+
+ if (!TEST_uint64_t_ne(s_stream_id, UINT64_MAX))
+ goto out;
+
+ if (op->arg1 > 0
+ && !TEST_ptr(tmp_buf = OPENSSL_malloc(op->arg1)))
+ goto out;
+
+ if (!TEST_true(ossl_quic_tserver_read(h.s, s_stream_id,
+ tmp_buf, op->arg1,
+ &bytes_read))
+ || !TEST_size_t_eq(bytes_read, op->arg1))
+ goto out;
+
+ if (op->arg1 > 0
+ && !TEST_mem_eq(tmp_buf, op->arg1, op->arg0, op->arg1))
+ goto out;
+
+ OPENSSL_free(tmp_buf);
+ tmp_buf = NULL;
+ }
+ break;
+
+ case OPK_C_EXPECT_FIN:
+ {
+ char buf[1];
+ size_t bytes_read = 0;
+
+ if (!TEST_false(SSL_read_ex(c_tgt, buf, sizeof(buf),
+ &bytes_read))
+ || !TEST_size_t_eq(bytes_read, 0)
+ || !TEST_int_eq(SSL_get_error(c_tgt, 0),
+ SSL_ERROR_ZERO_RETURN))
+ goto out;
+ }
+ break;
+
+ case OPK_S_EXPECT_FIN:
+ {
+ if (!TEST_uint64_t_ne(s_stream_id, UINT64_MAX)
+ || !TEST_true(ossl_quic_tserver_has_read_ended(h.s,
+ s_stream_id)))
+ goto out;
+ }
+ break;
+
+ case OPK_C_DETACH:
+ {
+ SSL *c_stream;
+
+ if (!TEST_ptr_null(c_tgt))
+ goto out; /* don't overwrite existing stream with same name */
+
+ if (!TEST_ptr(c_stream = SSL_detach_stream(h.c_conn)))
+ goto out;
+
+ if (!TEST_true(helper_set_c_stream(&h, op->stream_name, c_stream)))
+ goto out;
+ }
+ break;
+
+ case OPK_C_ATTACH:
+ {
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ if (!TEST_true(SSL_attach_stream(h.c_conn, c_tgt)))
+ goto out;
+
+ if (!TEST_true(helper_set_c_stream(&h, op->stream_name, NULL)))
+ goto out;
+ }
+ break;
+
+ case OPK_C_NEW_STREAM:
+ {
+ SSL *c_stream;
+ uint64_t flags = 0;
+
+ if (!TEST_ptr_null(c_tgt))
+ goto out; /* don't overwrite existing stream with same name */
+
+ if (op->arg1 != 0)
+ flags |= SSL_STREAM_FLAG_UNI;
+
+ if (!TEST_ptr(c_stream = SSL_new_stream(h.c_conn, flags)))
+ goto out;
+
+ if (op->arg2 != UINT64_MAX
+ && !TEST_uint64_t_eq(SSL_get_stream_id(c_stream),
+ op->arg2))
+ goto out;
+
+ if (!TEST_true(helper_set_c_stream(&h, op->stream_name, c_stream)))
+ goto out;
+ }
+ break;
+
+ case OPK_S_NEW_STREAM:
+ {
+ uint64_t stream_id = UINT64_MAX;
+
+ if (!TEST_uint64_t_eq(s_stream_id, UINT64_MAX))
+ goto out; /* don't overwrite existing stream with same name */
+
+ if (!TEST_true(ossl_quic_tserver_stream_new(h.s,
+ op->arg1 > 0,
+ &stream_id)))
+ goto out;
+
+ if (op->arg2 != UINT64_MAX
+ && !TEST_uint64_t_eq(stream_id, op->arg2))
+ goto out;
+
+ if (!TEST_true(helper_set_s_stream(&h, op->stream_name,
+ stream_id)))
+ goto out;
+ }
+ break;
+
+ case OPK_C_ACCEPT_STREAM:
+ {
+ SSL *c_stream;
+
+ if (!TEST_ptr_null(c_tgt))
+ goto out; /* don't overwrite existing stream with same name */
+
+ if (!TEST_ptr(c_stream = SSL_accept_stream(h.c_conn, 0)))
+ goto out;
+
+ if (!TEST_true(helper_set_c_stream(&h, op->stream_name,
+ c_stream)))
+ goto out;
+ }
+ break;
+
+ case OPK_C_ACCEPT_STREAM_NONE:
+ {
+ SSL *c_stream;
+
+ if (!TEST_ptr_null(c_stream = SSL_accept_stream(h.c_conn, 0))) {
+ SSL_free(c_stream);
+ goto out;
+ }
+ }
+ break;
+
+ case OPK_C_FREE_STREAM:
+ {
+ if (!TEST_ptr(c_tgt)
+ || !TEST_true(!SSL_is_connection(c_tgt)))
+ goto out;
+
+ if (!TEST_true(helper_set_c_stream(&h, op->stream_name, NULL)))
+ goto out;
+
+ SSL_free(c_tgt);
+ c_tgt = NULL;
+ }
+ break;
+
+ case OPK_C_SET_DEFAULT_STREAM_MODE:
+ {
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ if (!TEST_true(SSL_set_default_stream_mode(c_tgt, op->arg1)))
+ goto out;
+ }
+ break;
+
+ case OPK_C_SET_INCOMING_STREAM_REJECT_POLICY:
+ {
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ if (!TEST_true(SSL_set_incoming_stream_reject_policy(c_tgt,
+ op->arg1, 0)))
+ goto out;
+ }
+ break;
+
+ case OPK_C_SHUTDOWN:
+ {
+ int ret;
+
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ ret = SSL_shutdown_ex(c_tgt, 0, NULL, 0);
+ if (!TEST_int_ge(ret, 0))
+ goto out;
+
+ }
+ break;
+
+ case OPK_C_EXPECT_CONN_CLOSE_INFO:
+ {
+ SSL_CONN_CLOSE_INFO cc_info = {0};
+ int expect_app = (op->arg1 & EXPECT_CONN_CLOSE_APP) != 0;
+ int expect_remote = (op->arg1 & EXPECT_CONN_CLOSE_REMOTE) != 0;
+ uint64_t error_code = op->arg2;
+
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ if (!TEST_true(SSL_get_conn_close_info(c_tgt, &cc_info,
+ sizeof(cc_info))))
+ goto out;
+
+ if (!TEST_int_eq(expect_app, !cc_info.is_transport)
+ || !TEST_int_eq(expect_remote, !cc_info.is_local)
+ || !TEST_uint64_t_eq(error_code, cc_info.error_code))
+ goto out;
+ }
+ break;
+
+ case OPK_S_EXPECT_CONN_CLOSE_INFO:
+ {
+ const QUIC_TERMINATE_CAUSE *tc;
+ int expect_app = (op->arg1 & EXPECT_CONN_CLOSE_APP) != 0;
+ int expect_remote = (op->arg1 & EXPECT_CONN_CLOSE_REMOTE) != 0;
+ uint64_t error_code = op->arg2;
+
+ if (!TEST_true(ossl_quic_tserver_is_term_any(h.s)))
+ goto out;
+
+ if (!TEST_ptr(tc = ossl_quic_tserver_get_terminate_cause(h.s)))
+ goto out;
+
+ if (!TEST_uint64_t_eq(error_code, tc->error_code)
+ || !TEST_int_eq(expect_app, tc->app)
+ || !TEST_int_eq(expect_remote, tc->remote))
+ goto out;
+ }
+ break;
+
+ case OPK_S_BIND_STREAM_ID:
+ {
+ if (!TEST_uint64_t_eq(s_stream_id, UINT64_MAX))
+ goto out;
+
+ if (!TEST_true(helper_set_s_stream(&h, op->stream_name, op->arg2)))
+ goto out;
+ }
+ break;
+
+ case OPK_C_WRITE_FAIL:
+ {
+ uint64_t bytes_written = 0;
+
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ if (!TEST_false(SSL_write_ex(c_tgt, "apple", 5, &bytes_written)))
+ goto out;
+ }
+ break;
+
+ case OPK_S_WRITE_FAIL:
+ {
+ size_t bytes_written = 0;
+
+ if (!TEST_uint64_t_ne(s_stream_id, UINT64_MAX))
+ goto out;
+
+ if (!TEST_false(ossl_quic_tserver_write(h.s, s_stream_id,
+ (const unsigned char *)"apple", 5,
+ &bytes_written)))
+ goto out;
+ }
+ break;
+
+ case OPK_C_READ_FAIL:
+ {
+ uint64_t bytes_read = 0;
+ char buf[1];
+
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ if (!TEST_false(SSL_read_ex(c_tgt, buf, sizeof(buf), &bytes_read)))
+ goto out;
+ }
+ break;
+
+ case OPK_C_STREAM_RESET:
+ {
+ SSL_STREAM_RESET_ARGS args = {0};
+
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ args.quic_error_code = op->arg2;
+
+ if (!TEST_true(SSL_stream_reset(c_tgt, &args, sizeof(args))))
+ goto out;
+ }
+ break;
+
+ default:
+ TEST_error("unknown op");
+ goto out;
+ }
+ }
+
+out:
+ if (!testresult)
+ TEST_error("failed at script op %zu\n", op_idx + 1);
+
+ OPENSSL_free(tmp_buf);
+ helper_cleanup(&h);
+ return testresult;
+}
+
+/* 1. Simple single-stream test */
+static const struct script_op script_1[] = {
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+ OP_C_WRITE (DEFAULT, "apple", 5)
+ OP_C_CONCLUDE (DEFAULT)
+ OP_S_BIND_STREAM_ID (a, C_BIDI_ID(0))
+ OP_S_READ_EXPECT (a, "apple", 5)
+ OP_S_EXPECT_FIN (a)
+ OP_S_WRITE (a, "orange", 6)
+ OP_S_CONCLUDE (a)
+ OP_C_READ_EXPECT (DEFAULT, "orange", 6)
+ OP_C_EXPECT_FIN (DEFAULT)
+ OP_END
+};
+
+/* 2. Multi-stream test */
+static const struct script_op script_2[] = {
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+ OP_C_SET_INCOMING_STREAM_REJECT_POLICY(SSL_INCOMING_STREAM_REJECT_POLICY_ACCEPT)
+ OP_C_WRITE (DEFAULT, "apple", 5)
+ OP_S_BIND_STREAM_ID (a, C_BIDI_ID(0))
+ OP_S_READ_EXPECT (a, "apple", 5)
+ OP_S_WRITE (a, "orange", 6)
+ OP_C_READ_EXPECT (DEFAULT, "orange", 6)
+
+ OP_C_NEW_STREAM_BIDI (b, C_BIDI_ID(1))
+ OP_C_WRITE (b, "flamingo", 8)
+ OP_C_CONCLUDE (b)
+ OP_S_BIND_STREAM_ID (b, C_BIDI_ID(1))
+ OP_S_READ_EXPECT (b, "flamingo", 8)
+ OP_S_EXPECT_FIN (b)
+ OP_S_WRITE (b, "gargoyle", 8)
+ OP_S_CONCLUDE (b)
+ OP_C_READ_EXPECT (b, "gargoyle", 8)
+ OP_C_EXPECT_FIN (b)
+
+ OP_C_NEW_STREAM_UNI (c, C_UNI_ID(0))
+ OP_C_WRITE (c, "elephant", 8)
+ OP_C_CONCLUDE (c)
+ OP_S_BIND_STREAM_ID (c, C_UNI_ID(0))
+ OP_S_READ_EXPECT (c, "elephant", 8)
+ OP_S_EXPECT_FIN (c)
+ OP_S_WRITE_FAIL (c)
+
+ OP_C_ACCEPT_STREAM_NONE ()
+
+ OP_S_NEW_STREAM_BIDI (d, S_BIDI_ID(0))
+ OP_S_WRITE (d, "frog", 4)
+ OP_S_CONCLUDE (d)
+
+ OP_C_ACCEPT_STREAM (d)
+ OP_C_ACCEPT_STREAM_NONE ()
+ OP_C_READ_EXPECT (d, "frog", 4)
+ OP_C_EXPECT_FIN (d)
+
+ OP_S_NEW_STREAM_BIDI (e, S_BIDI_ID(1))
+ OP_S_WRITE (e, "mixture", 7)
+ OP_S_CONCLUDE (e)
+
+ OP_C_ACCEPT_STREAM (e)
+ OP_C_READ_EXPECT (e, "mixture", 7)
+ OP_C_EXPECT_FIN (e)
+ OP_C_WRITE (e, "ramble", 6)
+ OP_S_READ_EXPECT (e, "ramble", 6)
+ OP_C_CONCLUDE (e)
+ OP_S_EXPECT_FIN (e)
+
+ OP_S_NEW_STREAM_UNI (f, S_UNI_ID(0))
+ OP_S_WRITE (f, "yonder", 6)
+ OP_S_CONCLUDE (f)
+
+ OP_C_ACCEPT_STREAM (f)
+ OP_C_ACCEPT_STREAM_NONE ()
+ OP_C_READ_EXPECT (f, "yonder", 6)
+ OP_C_EXPECT_FIN (f)
+ OP_C_WRITE_FAIL (f)
+
+ OP_C_SET_INCOMING_STREAM_REJECT_POLICY(SSL_INCOMING_STREAM_REJECT_POLICY_REJECT)
+ OP_S_NEW_STREAM_BIDI (g, S_BIDI_ID(2))
+ OP_S_WRITE (g, "unseen", 6)
+ OP_S_CONCLUDE (g)
+
+ OP_C_ACCEPT_STREAM_NONE ()
+
+ OP_C_SET_INCOMING_STREAM_REJECT_POLICY(SSL_INCOMING_STREAM_REJECT_POLICY_AUTO)
+ OP_S_NEW_STREAM_BIDI (h, S_BIDI_ID(3))
+ OP_S_WRITE (h, "UNSEEN", 6)
+ OP_S_CONCLUDE (h)
+
+ OP_C_ACCEPT_STREAM_NONE ()
+
+ /*
+ * Streams g, h should have been rejected, so server should have got
+ * STOP_SENDING/RESET_STREAM.
+ */
+ OP_CHECK (check_rejected, S_BIDI_ID(2))
+ OP_CHECK (check_rejected, S_BIDI_ID(3))
+
+ OP_END
+};
+
+/* 3. Default stream detach/reattach test */
+static const struct script_op script_3[] = {
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+
+ OP_C_WRITE (DEFAULT, "apple", 5)
+ OP_C_DETACH (a) /* DEFAULT becomes stream 'a' */
+ OP_C_WRITE_FAIL (DEFAULT)
+
+ OP_C_WRITE (a, "by", 2)
+
+ OP_S_BIND_STREAM_ID (a, C_BIDI_ID(0))
+ OP_S_READ_EXPECT (a, "appleby", 7)
+
+ OP_S_WRITE (a, "hello", 5)
+ OP_C_READ_EXPECT (a, "hello", 5)
+
+ OP_C_WRITE_FAIL (DEFAULT)
+ OP_C_ATTACH (a)
+ OP_C_WRITE (DEFAULT, "is here", 7)
+ OP_S_READ_EXPECT (a, "is here", 7)
+
+ OP_C_DETACH (a)
+ OP_C_CONCLUDE (a)
+ OP_S_EXPECT_FIN (a)
+
+ OP_END
+};
+
+/* 4. Default stream mode test */
+static const struct script_op script_4[] = {
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+
+ OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+ OP_C_WRITE_FAIL (DEFAULT)
+
+ OP_S_NEW_STREAM_BIDI (a, S_BIDI_ID(0))
+ OP_S_WRITE (a, "apple", 5)
+
+ OP_C_READ_FAIL (DEFAULT)
+
+ OP_C_ACCEPT_STREAM (a)
+ OP_C_READ_EXPECT (a, "apple", 5)
+
+ OP_C_ATTACH (a)
+ OP_C_WRITE (DEFAULT, "orange", 6)
+ OP_S_READ_EXPECT (a, "orange", 6)
+
+ OP_END
+};
+
+/* 5. Test stream reset functionality */
+static const struct script_op script_5[] = {
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+
+ OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+ OP_C_NEW_STREAM_BIDI (a, C_BIDI_ID(0))
+
+ OP_C_WRITE (a, "apple", 5)
+ OP_C_STREAM_RESET (a, 42)
+
+ OP_S_BIND_STREAM_ID (a, C_BIDI_ID(0))
+ OP_S_READ_EXPECT (a, "apple", 5)
+ OP_CHECK (check_stream_reset, C_BIDI_ID(0))
+
+ OP_END
+};
+
+/* 6. Test STOP_SENDING functionality */
+static const struct script_op script_6[] = {
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+
+ OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+ OP_S_NEW_STREAM_BIDI (a, S_BIDI_ID(0))
+ OP_S_WRITE (a, "apple", 5)
+
+ OP_C_ACCEPT_STREAM (a)
+ OP_C_FREE_STREAM (a)
+ OP_C_ACCEPT_STREAM_NONE ()
+
+ OP_CHECK (check_stream_stopped, S_BIDI_ID(0))
+
+ OP_END
+};
+
+/* 7. Unidirectional default stream mode test (client sends first) */
+static const struct script_op script_7[] = {
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+
+ OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_AUTO_UNI)
+ OP_C_WRITE (DEFAULT, "apple", 5)
+
+ OP_S_BIND_STREAM_ID (a, C_UNI_ID(0))
+ OP_S_READ_EXPECT (a, "apple", 5)
+ OP_S_WRITE_FAIL (a)
+
+ OP_END
+};
+
+/* 8. Unidirectional default stream mode test (server sends first) */
+static const struct script_op script_8[] = {
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+
+ OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_AUTO_UNI)
+ OP_S_NEW_STREAM_UNI (a, S_UNI_ID(0))
+ OP_S_WRITE (a, "apple", 5)
+ OP_C_READ_EXPECT (DEFAULT, "apple", 5)
+ OP_C_WRITE_FAIL (DEFAULT)
+
+ OP_END
+};
+
+/* 9. Unidirectional default stream mode test (server sends first on bidi) */
+static const struct script_op script_9[] = {
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+
+ OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_AUTO_UNI)
+ OP_S_NEW_STREAM_BIDI (a, S_BIDI_ID(0))
+ OP_S_WRITE (a, "apple", 5)
+ OP_C_READ_EXPECT (DEFAULT, "apple", 5)
+ OP_C_WRITE (DEFAULT, "orange", 6)
+ OP_S_READ_EXPECT (a, "orange", 6)
+
+ OP_END
+};
+
+/* 10. Shutdown */
+static const struct script_op script_10[] = {
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+
+ OP_C_WRITE (DEFAULT, "apple", 5)
+ OP_S_BIND_STREAM_ID (a, C_BIDI_ID(0))
+ OP_S_READ_EXPECT (a, "apple", 5)
+
+ OP_C_SHUTDOWN ()
+ OP_C_EXPECT_CONN_CLOSE_INFO(0, 1, 0)
+ OP_S_EXPECT_CONN_CLOSE_INFO(0, 1, 1)
+
+ OP_END
+};
+
+static const struct script_op *const scripts[] = {
+ script_1,
+ script_2,
+ script_3,
+ script_4,
+ script_5,
+ script_6,
+ script_7,
+ script_8,
+ script_9,
+ script_10,
+};
+
+static int test_script(int idx)
+{
+ TEST_info("Running script %d", idx + 1);
+ return run_script(scripts[idx]);
+}
+
+OPT_TEST_DECLARE_USAGE("certfile privkeyfile\n")
+
+int setup_tests(void)
+{
+ if (!test_skip_common_options()) {
+ TEST_error("Error parsing test options\n");
+ return 0;
+ }
+
+ if (!TEST_ptr(certfile = test_get_argument(0))
+ || !TEST_ptr(keyfile = test_get_argument(1)))
+ return 0;
+
+ ADD_ALL_TESTS(test_script, OSSL_NELEM(scripts));
+ return 1;
+}
diff --git a/test/recipes/70-test_quic_multistream.t b/test/recipes/70-test_quic_multistream.t
new file mode 100644
index 0000000000..e7bdf03d74
--- /dev/null
+++ b/test/recipes/70-test_quic_multistream.t
@@ -0,0 +1,21 @@
+#! /usr/bin/env perl
+# Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License"). You may not use
+# this file except in compliance with the License. You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use OpenSSL::Test qw/:DEFAULT srctop_file/;
+use OpenSSL::Test::Utils;
+
+setup("test_quic_multistream");
+
+plan skip_all => "QUIC protocol is not supported by this OpenSSL build"
+ if disabled('quic');
+
+plan tests => 1;
+
+ok(run(test(["quic_multistream_test",
+ srctop_file("test", "certs", "servercert.pem"),
+ srctop_file("test", "certs", "serverkey.pem")])));