From ed835673ae5d99cac39d0bef6677597a68d1e248 Mon Sep 17 00:00:00 2001 From: Hugo Landau Date: Tue, 18 Apr 2023 19:30:56 +0100 Subject: QUIC MSST: Tests Reviewed-by: Matt Caswell Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/20765) --- test/build.info | 6 +- test/quic_multistream_test.c | 1203 +++++++++++++++++++++++++++++++ test/recipes/70-test_quic_multistream.t | 21 + 3 files changed, 1229 insertions(+), 1 deletion(-) create mode 100644 test/quic_multistream_test.c create mode 100644 test/recipes/70-test_quic_multistream.t 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 +#include +#include +#include +#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")]))); -- cgit v1.2.1