From: Hugo Landau Date: Mon, 5 Feb 2024 17:48:49 +0000 (+0000) Subject: QUIC RADIX: Add RADIX test framework implementation X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cc00d9857cd39905f2c03971a63354d19885e3ba;p=thirdparty%2Fopenssl.git QUIC RADIX: Add RADIX test framework implementation Reviewed-by: Neil Horman Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/23487) --- diff --git a/test/radix/main.c b/test/radix/main.c new file mode 100644 index 00000000000..55f2dc000b1 --- /dev/null +++ b/test/radix/main.c @@ -0,0 +1,61 @@ +/* + * Copyright 2024 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 + */ + +OPT_TEST_DECLARE_USAGE("cert_file key_file\n") + +/* + * A RADIX test suite binding must define: + * + * static SCRIPT_INFO *const scripts[]; + * + * int bindings_process_init(size_t node_idx, size_t process_idx); + * void bindings_process_finish(int testresult); + * int bindings_adjust_terp_config(TERP_CONFIG *cfg); + * + */ +static int test_script(int idx) +{ + SCRIPT_INFO *script_info = scripts[idx]; + int testresult; + TERP_CONFIG cfg = {0}; + + if (!TEST_true(bindings_process_init(0, 0))) + return 0; + + cfg.debug_bio = bio_err; + + if (!TEST_true(bindings_adjust_terp_config(&cfg))) + return 0; + + testresult = TERP_run(script_info, &cfg); + + if (!bindings_process_finish(testresult)) + testresult = 0; + + return testresult; +} + +int setup_tests(void) +{ + if (!test_skip_common_options()) { + TEST_error("Error parsing test options\n"); + return 0; + } + + cert_file = test_get_argument(0); + if (cert_file == NULL) + cert_file = "test/certs/servercert.pem"; + + key_file = test_get_argument(1); + if (key_file == NULL) + key_file = "test/certs/serverkey.pem"; + + ADD_ALL_TESTS(test_script, OSSL_NELEM(scripts)); + return 1; +} diff --git a/test/radix/quic_bindings.c b/test/radix/quic_bindings.c new file mode 100644 index 00000000000..d738d8ceaf2 --- /dev/null +++ b/test/radix/quic_bindings.c @@ -0,0 +1,768 @@ +/* + * Copyright 2024 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 "internal/quic_engine.h" +#include "internal/quic_channel.h" +#include "internal/quic_ssl.h" +#include "internal/quic_error.h" + +/* + * RADIX 6D QUIC Test Framework + * ============================================================================= + * + * The radix test framework is a six-dimension script-driven facility to support + * execution of + * + * multi-stream + * multi-client + * multi-server + * multi-thread + * multi-process + * multi-node + * + * test vignettes for QUIC. Unlike the older multistream test framework, it does + * not assume a single client and a single server. Examples of vignettes + * designed to be supported by the radix test framework in future include: + * + * single client <-> single server + * multiple clients <-> single server + * single client <-> multiple servers + * multiple clients <-> multiple servers + * + * 'Multi-process' and 'multi-node' means there has been some consideration + * given to support of multi-process and multi-node testing in the future, + * though this is not currently supported. + */ + +/* + * An object is something associated with a name in the process-level state. The + * process-level state primarily revolves around a global dictionary of SSL + * objects. + */ +typedef struct radix_obj_st { + char *name; /* owned, zero-terminated */ + SSL *ssl; /* owns one reference */ + unsigned int registered : 1; /* in LHASH? */ + unsigned int active : 1; /* tick? */ +} RADIX_OBJ; + +DEFINE_LHASH_OF_EX(RADIX_OBJ); + +/* Process-level state (i.e. "globals" in the normal sense of the word) */ +typedef struct radix_process_st { + size_t node_idx; + size_t process_idx; + size_t next_thread_idx; + STACK_OF(RADIX_THREAD) *threads; + + /* Process-global state. */ + CRYPTO_MUTEX *gm; /* global mutex */ + LHASH_OF(RADIX_OBJ) *objs; /* protected by gm */ + OSSL_TIME time_slip; /* protected by gm */ + + int done_join_all_threads; + + /* + * Valid if done_join_all threads. Logical AND of all child worker results. + */ + int thread_composite_testresult; +} RADIX_PROCESS; + +#define NUM_SLOTS 4 + +/* Thread-level state within a process */ +typedef struct radix_thread_st { + RADIX_PROCESS *rp; + CRYPTO_THREAD *t; + unsigned char *tmp_buf; + size_t tmp_buf_offset; + size_t thread_idx; /* 0=main thread */ + RADIX_OBJ *slot[NUM_SLOTS]; + SSL *ssl[NUM_SLOTS]; + + /* child thread spawn arguments */ + SCRIPT_INFO *child_script_info; + BIO *debug_bio; + + /* m protects all of the below values */ + CRYPTO_MUTEX *m; + int done; + int testresult; /* valid if done */ + + uint64_t scratch0; +} RADIX_THREAD; + +DEFINE_STACK_OF(RADIX_THREAD) + +/* ssl reference is transferred. name is copied and is required. */ +static RADIX_OBJ *RADIX_OBJ_new(const char *name, SSL *ssl) +{ + RADIX_OBJ *obj; + + if (!TEST_ptr(name) || !TEST_ptr(ssl)) + return NULL; + + if (!TEST_ptr(obj = OPENSSL_zalloc(sizeof(*obj)))) + return NULL; + + obj->name = OPENSSL_strdup(name); + obj->ssl = ssl; + return obj; +} + +static void RADIX_OBJ_free(RADIX_OBJ *obj) +{ + if (obj == NULL) + return; + + assert(!obj->registered); + + SSL_free(obj->ssl); + OPENSSL_free(obj->name); + OPENSSL_free(obj); +} + +static unsigned long RADIX_OBJ_hash(const RADIX_OBJ *obj) +{ + return OPENSSL_LH_strhash(obj->name); +} + +static int RADIX_OBJ_cmp(const RADIX_OBJ *a, const RADIX_OBJ *b) +{ + return strcmp(a->name, b->name); +} + +static int RADIX_PROCESS_init(RADIX_PROCESS *rp, size_t node_idx, size_t process_idx) +{ + if (!TEST_ptr(rp->gm = ossl_crypto_mutex_new())) + goto err; + + if (!TEST_ptr(rp->objs = lh_RADIX_OBJ_new(RADIX_OBJ_hash, RADIX_OBJ_cmp))) + goto err; + + if (!TEST_ptr(rp->threads = sk_RADIX_THREAD_new(NULL))) + goto err; + + rp->node_idx = node_idx; + rp->process_idx = process_idx; + rp->done_join_all_threads = 0; + rp->next_thread_idx = 0; + return 1; + +err: + lh_RADIX_OBJ_free(rp->objs); + rp->objs = NULL; + ossl_crypto_mutex_free(&rp->gm); + return 0; +} + +static const char *stream_state_to_str(int state) +{ + switch (state) { + case SSL_STREAM_STATE_NONE: + return "none"; + case SSL_STREAM_STATE_OK: + return "OK"; + case SSL_STREAM_STATE_WRONG_DIR: + return "wrong dir"; + case SSL_STREAM_STATE_FINISHED: + return "finished"; + case SSL_STREAM_STATE_RESET_LOCAL: + return "reset-local"; + case SSL_STREAM_STATE_RESET_REMOTE: + return "reset-remote"; + case SSL_STREAM_STATE_CONN_CLOSED: + return "conn-closed"; + default: + return "?"; + } +} + +static void report_ssl_state(BIO *bio, const char *pfx, int is_write, + int state, uint64_t ec) +{ + const char *state_s = stream_state_to_str(state); + + BIO_printf(bio, "%s%-15s%s(%d)", pfx, is_write ? "Write state: " : "Read state: ", + state_s, state); + if (ec != UINT64_MAX) + BIO_printf(bio, ", %llu", (unsigned long long)ec); + BIO_printf(bio, "\n"); +} + +static void report_ssl(SSL *ssl, BIO *bio, const char *pfx) +{ + const char *type = "SSL"; + int is_quic = SSL_is_quic(ssl), is_conn = 0, is_listener = 0; + SSL_CONN_CLOSE_INFO cc_info = {0}; + const char *e_str, *f_str; + + if (is_quic) { + is_conn = SSL_is_connection(ssl); + is_listener = SSL_is_listener(ssl); + + if (is_listener) + type = "QLSO"; + else if (is_conn) + type = "QCSO"; + else + type = "QSSO"; + } + + BIO_printf(bio, "%sType: %s\n", pfx, type); + + if (is_quic && is_conn + && SSL_get_conn_close_info(ssl, &cc_info, sizeof(cc_info))) { + + e_str = ossl_quic_err_to_string(cc_info.error_code); + f_str = ossl_quic_frame_type_to_string(cc_info.frame_type); + + if (e_str == NULL) + e_str = "?"; + if (f_str == NULL) + f_str = "?"; + + BIO_printf(bio, "%sConnection is closed: %s(%llu)/%s(%llu), " + "%s, %s, reason: \"%s\"\n", + pfx, + e_str, + (unsigned long long)cc_info.error_code, + f_str, + (unsigned long long)cc_info.frame_type, + (cc_info.flags & SSL_CONN_CLOSE_FLAG_LOCAL) != 0 + ? "local" : "remote", + (cc_info.flags & SSL_CONN_CLOSE_FLAG_TRANSPORT) != 0 + ? "transport" : "app", + cc_info.reason != NULL ? cc_info.reason : "-"); + } + + if (is_quic && !is_listener) { + uint64_t stream_id = SSL_get_stream_id(ssl), rec, wec; + int rstate, wstate; + + if (stream_id != UINT64_MAX) + BIO_printf(bio, "%sStream ID: %llu\n", pfx, + (unsigned long long)stream_id); + + rstate = SSL_get_stream_read_state(ssl); + wstate = SSL_get_stream_write_state(ssl); + + if (SSL_get_stream_read_error_code(ssl, &rec) != 1) + rec = UINT64_MAX; + + if (SSL_get_stream_write_error_code(ssl, &wec) != 1) + wec = UINT64_MAX; + + report_ssl_state(bio, pfx, 0, rstate, rec); + report_ssl_state(bio, pfx, 1, wstate, wec); + } +} + +static void report_obj(RADIX_OBJ *obj, void *arg) +{ + BIO *bio = arg; + SSL *ssl = obj->ssl; + + BIO_printf(bio, " - %-16s @ %p\n", obj->name, (void *)obj->ssl); + ERR_set_mark(); + report_ssl(ssl, bio, " "); + ERR_pop_to_mark(); +} + +static void RADIX_THREAD_report_state(RADIX_THREAD *rt, BIO *bio) +{ + size_t i; + + BIO_printf(bio, " Slots:\n"); + for (i = 0; i < NUM_SLOTS; ++i) + if (rt->slot[i] == NULL) + BIO_printf(bio, " %3zu) \n", i); + else + BIO_printf(bio, " %3zu) '%s' (SSL: %p)\n", i, + rt->slot[i]->name, + (void *)rt->ssl[i]); +} + +static void RADIX_PROCESS_report_state(RADIX_PROCESS *rp, BIO *bio, + int verbose) +{ + BIO_printf(bio, "Final process state for node %zu, process %zu:\n", + rp->node_idx, rp->process_idx); + + BIO_printf(bio, " Threads (incl. main): %zu\n", + rp->next_thread_idx); + BIO_printf(bio, " Time slip: %zu ms\n", + ossl_time2ms(rp->time_slip)); + + BIO_printf(bio, " Objects:\n"); + lh_RADIX_OBJ_doall_arg(rp->objs, report_obj, bio); + + if (verbose) + RADIX_THREAD_report_state(sk_RADIX_THREAD_value(rp->threads, 0), + bio_err); + + BIO_printf(bio, "\n===========================================" + "===========================\n"); +} + +static void RADIX_PROCESS_report_thread_results(RADIX_PROCESS *rp, BIO *bio) +{ + size_t i; + RADIX_THREAD *rt; + char *p; + long l; + char pfx_buf[64]; + int rt_testresult; + + for (i = 1; i < (size_t)sk_RADIX_THREAD_num(rp->threads); ++i) { + rt = sk_RADIX_THREAD_value(rp->threads, i); + + ossl_crypto_mutex_lock(rt->m); + assert(rt->done); + rt_testresult = rt->testresult; + ossl_crypto_mutex_unlock(rt->m); + + BIO_printf(bio, "\n====(n%zu/p%zu/t%zu)============================" + "===========================\n" + "Result for child thread with index %zu:\n", + rp->node_idx, rp->process_idx, rt->thread_idx, rt->thread_idx); + + BIO_snprintf(pfx_buf, sizeof(pfx_buf), "# -T-%2zu:\t# ", rt->thread_idx); + BIO_set_prefix(bio_err, pfx_buf); + + l = BIO_get_mem_data(rt->debug_bio, &p); + BIO_write(bio, p, l); + BIO_printf(bio, "\n"); + BIO_set_prefix(bio_err, "# "); + BIO_printf(bio, "==> Child thread with index %zu exited with %d\n", + rt->thread_idx, rt_testresult); + if (!rt_testresult) + RADIX_THREAD_report_state(rt, bio); + } + + BIO_printf(bio, "\n===========================================" + "===========================\n"); +} + +static int RADIX_THREAD_join(RADIX_THREAD *rt); + +static int RADIX_PROCESS_join_all_threads(RADIX_PROCESS *rp, int *testresult) +{ + int ok = 1; + size_t i; + RADIX_THREAD *rt; + int composite_testresult = 1; + + if (rp->done_join_all_threads) { + *testresult = rp->thread_composite_testresult; + return 1; + } + + for (i = 1; i < (size_t)sk_RADIX_THREAD_num(rp->threads); ++i) { + rt = sk_RADIX_THREAD_value(rp->threads, i); + + BIO_printf(bio_err, "==> Joining thread %zu\n", i); + + if (!TEST_true(RADIX_THREAD_join(rt))) + ok = 0; + + if (!rt->testresult) + composite_testresult = 0; + } + + rp->thread_composite_testresult = composite_testresult; + *testresult = composite_testresult; + rp->done_join_all_threads = 1; + + RADIX_PROCESS_report_thread_results(rp, bio_err); + return ok; +} + +static void cleanup_one(RADIX_OBJ *obj) +{ + obj->registered = 0; + RADIX_OBJ_free(obj); +} + +static void RADIX_THREAD_free(RADIX_THREAD *rt); + +static void RADIX_PROCESS_cleanup(RADIX_PROCESS *rp) +{ + size_t i; + + assert(rp->done_join_all_threads); + + for (i = 0; i < (size_t)sk_RADIX_THREAD_num(rp->threads); ++i) + RADIX_THREAD_free(sk_RADIX_THREAD_value(rp->threads, i)); + + sk_RADIX_THREAD_free(rp->threads); + rp->threads = NULL; + + lh_RADIX_OBJ_doall(rp->objs, cleanup_one); + lh_RADIX_OBJ_free(rp->objs); + rp->objs = NULL; + + ossl_crypto_mutex_free(&rp->gm); +} + +static RADIX_OBJ *RADIX_PROCESS_get_obj(RADIX_PROCESS *rp, const char *name) +{ + RADIX_OBJ key; + + key.name = (char *)name; + return lh_RADIX_OBJ_retrieve(rp->objs, &key); +} + +static int RADIX_PROCESS_set_obj(RADIX_PROCESS *rp, + const char *name, RADIX_OBJ *obj) +{ + RADIX_OBJ *existing; + + if (obj != NULL && !TEST_false(obj->registered)) + return 0; + + existing = RADIX_PROCESS_get_obj(rp, name); + if (existing != NULL && obj != existing) { + if (!TEST_true(existing->registered)) + return 0; + + lh_RADIX_OBJ_delete(rp->objs, existing); + existing->registered = 0; + RADIX_OBJ_free(existing); + } + + if (obj != NULL) { + lh_RADIX_OBJ_insert(rp->objs, obj); + obj->registered = 1; + } + + return 1; +} + +static int RADIX_PROCESS_set_ssl(RADIX_PROCESS *rp, const char *name, SSL *ssl) +{ + RADIX_OBJ *obj; + + if (!TEST_ptr(obj = RADIX_OBJ_new(name, ssl))) + return 0; + + if (!TEST_true(RADIX_PROCESS_set_obj(rp, name, obj))) { + RADIX_OBJ_free(obj); + return 0; + } + + return 1; +} + +static SSL *RADIX_PROCESS_get_ssl(RADIX_PROCESS *rp, const char *name) +{ + RADIX_OBJ *obj = RADIX_PROCESS_get_obj(rp, name); + + if (obj == NULL) + return NULL; + + return obj->ssl; +} + +static RADIX_THREAD *RADIX_THREAD_new(RADIX_PROCESS *rp) +{ + RADIX_THREAD *rt; + + if (!TEST_ptr(rp) + || !TEST_ptr(rt = OPENSSL_zalloc(sizeof(*rt)))) + return 0; + + rt->rp = rp; + + if (!TEST_ptr(rt->m = ossl_crypto_mutex_new())) { + OPENSSL_free(rt); + return 0; + } + + if (!TEST_true(sk_RADIX_THREAD_push(rp->threads, rt))) { + OPENSSL_free(rt); + return 0; + } + + rt->thread_idx = rp->next_thread_idx++; + assert(rt->thread_idx + 1 == (size_t)sk_RADIX_THREAD_num(rp->threads)); + return rt; +} + +static void RADIX_THREAD_free(RADIX_THREAD *rt) +{ + if (rt == NULL) + return; + + assert(rt->t == NULL); + BIO_free_all(rt->debug_bio); + OPENSSL_free(rt->tmp_buf); + ossl_crypto_mutex_free(&rt->m); + OPENSSL_free(rt); +} + +static int RADIX_THREAD_join(RADIX_THREAD *rt) +{ + CRYPTO_THREAD_RETVAL rv; + + if (rt->t != NULL) + ossl_crypto_thread_native_join(rt->t, &rv); + + ossl_crypto_thread_native_clean(rt->t); + rt->t = NULL; + + if (!TEST_true(rt->done)) + return 0; + + return 1; +} + +static RADIX_PROCESS radix_process; +static CRYPTO_THREAD_LOCAL radix_thread; + +static void radix_thread_cleanup_tl(void *p) +{ + /* Should already have been cleaned up. */ + if (!TEST_ptr_null(p)) + abort(); +} + +static RADIX_THREAD *radix_get_thread(void) +{ + return CRYPTO_THREAD_get_local(&radix_thread); +} + +static int radix_thread_init(RADIX_THREAD *rt) +{ + if (!TEST_ptr(rt) + || !TEST_ptr_null(CRYPTO_THREAD_get_local(&radix_thread))) + return 0; + + if (!TEST_true(CRYPTO_THREAD_set_local(&radix_thread, rt))) + return 0; + + set_override_bio_out(rt->debug_bio); + set_override_bio_err(rt->debug_bio); + return 1; +} + +static void radix_thread_cleanup(void) +{ + RADIX_THREAD *rt = radix_get_thread(); + + if (!TEST_ptr(rt)) + return; + + if (!TEST_true(CRYPTO_THREAD_set_local(&radix_thread, NULL))) + return; +} + +static int bindings_process_init(size_t node_idx, size_t process_idx) +{ + RADIX_THREAD *rt; + + if (!TEST_true(RADIX_PROCESS_init(&radix_process, node_idx, process_idx))) + return 0; + + if (!TEST_true(CRYPTO_THREAD_init_local(&radix_thread, + radix_thread_cleanup_tl))) + return 0; + + if (!TEST_ptr(rt = RADIX_THREAD_new(&radix_process))) + return 0; + + /* Allocate structures for main thread. */ + return radix_thread_init(rt); +} + +static int bindings_process_finish(int testresult_main) +{ + int testresult, testresult_child; + + if (!TEST_true(RADIX_PROCESS_join_all_threads(&radix_process, + &testresult_child))) + return 0; + + testresult = testresult_main && testresult_child; + RADIX_PROCESS_report_state(&radix_process, bio_err, + /*verbose=*/!testresult); + radix_thread_cleanup(); /* cleanup main thread */ + RADIX_PROCESS_cleanup(&radix_process); + + if (testresult) + BIO_printf(bio_err, "==> OK\n\n"); + else + BIO_printf(bio_err, "==> ERROR (main=%d, children=%d)\n\n", + testresult_main, testresult_child); + + return testresult; +} + +#define RP() (&radix_process) +#define RT() (radix_get_thread()) + +static OSSL_TIME get_time(void *arg) +{ + OSSL_TIME time_slip; + + ossl_crypto_mutex_lock(RP()->gm); + time_slip = RP()->time_slip; + ossl_crypto_mutex_unlock(RP()->gm); + + return ossl_time_add(ossl_time_now(), time_slip); +} + +ossl_unused static void radix_skip_time(OSSL_TIME t) +{ + ossl_crypto_mutex_lock(RP()->gm); + RP()->time_slip = ossl_time_add(RP()->time_slip, t); + ossl_crypto_mutex_unlock(RP()->gm); +} + +static void per_op_tick_obj(RADIX_OBJ *obj) +{ + if (obj->active) + SSL_handle_events(obj->ssl); +} + +static int do_per_op(TERP *terp, void *arg) +{ + lh_RADIX_OBJ_doall(RP()->objs, per_op_tick_obj); + return 1; +} + +static int bindings_adjust_terp_config(TERP_CONFIG *cfg) +{ + cfg->now_cb = get_time; + cfg->per_op_cb = do_per_op; + return 1; +} + +static int expect_slot_ssl(FUNC_CTX *fctx, size_t idx, SSL **p_ssl) +{ + if (!TEST_size_t_lt(idx, NUM_SLOTS) + || !TEST_ptr(*p_ssl = RT()->ssl[idx])) + return 0; + + return 1; +} + +#define REQUIRE_SSL_N(idx, ssl) \ + do { \ + if (!TEST_true(expect_slot_ssl(fctx, (idx), &(ssl)))) \ + goto err; \ + } while (0) +#define REQUIRE_SSL(ssl) REQUIRE_SSL_N(0, (ssl)) + +#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) + +static int RADIX_THREAD_worker_run(RADIX_THREAD *rt) +{ + int ok = 0; + TERP_CONFIG cfg = {0}; + + cfg.debug_bio = rt->debug_bio; + if (!TEST_true(bindings_adjust_terp_config(&cfg))) + goto err; + + if (!TERP_run(rt->child_script_info, &cfg)) + goto err; + + ok = 1; +err: + return ok; +} + +static unsigned int RADIX_THREAD_worker_main(void *p) +{ + int testresult = 0; + RADIX_THREAD *rt = p; + + if (!TEST_true(radix_thread_init(rt))) + return 0; + + /* Wait until thread-specific init is done (e.g. setting rt->t) */ + ossl_crypto_mutex_lock(rt->m); + ossl_crypto_mutex_unlock(rt->m); + + testresult = RADIX_THREAD_worker_run(rt); + + ossl_crypto_mutex_lock(rt->m); + rt->testresult = testresult; + rt->done = 1; + ossl_crypto_mutex_unlock(rt->m); + + radix_thread_cleanup(); + return 1; +} + +static void radix_activate_obj(RADIX_OBJ *obj) +{ + if (obj != NULL) + obj->active = 1; +} + +static void radix_activate_slot(size_t idx) +{ + if (idx >= NUM_SLOTS) + return; + + radix_activate_obj(RT()->slot[idx]); +} + +DEF_FUNC(hf_spawn_thread) +{ + int ok = 0; + RADIX_THREAD *child_rt = NULL; + SCRIPT_INFO *script_info = NULL; + + F_POP(script_info); + if (!TEST_ptr(script_info)) + goto err; + +#if !defined(OPENSSL_THREADS) + TEST_skip("threading not supported, skipping"); + F_SKIP_REST(); +#else + if (!TEST_ptr(child_rt = RADIX_THREAD_new(&radix_process))) + return 0; + + if (!TEST_ptr(child_rt->debug_bio = BIO_new(BIO_s_mem()))) + goto err; + + ossl_crypto_mutex_lock(child_rt->m); + + child_rt->child_script_info = script_info; + if (!TEST_ptr(child_rt->t = ossl_crypto_thread_native_start(RADIX_THREAD_worker_main, + child_rt, 1))) { + ossl_crypto_mutex_unlock(child_rt->m); + goto err; + } + + ossl_crypto_mutex_unlock(child_rt->m); + ok = 1; +#endif +err: + if (!ok) + RADIX_THREAD_free(child_rt); + + return ok; +} + +#define OP_SPAWN_THREAD(script_name) \ + (OP_PUSH_P(SCRIPT(script_name)), OP_FUNC(hf_spawn_thread)) diff --git a/test/radix/quic_ops.c b/test/radix/quic_ops.c new file mode 100644 index 00000000000..f3dedc13ac4 --- /dev/null +++ b/test/radix/quic_ops.c @@ -0,0 +1,1044 @@ +#include + +static const unsigned char alpn_ossltest[] = { + /* "\x08ossltest" (hex for EBCDIC resilience) */ + 0x08, 0x6f, 0x73, 0x73, 0x6c, 0x74, 0x65, 0x73, 0x74 +}; + +DEF_FUNC(hf_unbind) +{ + int ok = 0; + const char *name; + + F_POP(name); + RADIX_PROCESS_set_obj(RP(), name, NULL); + + ok = 1; +err: + return ok; +} + +static int ssl_ctx_select_alpn(SSL *ssl, + const unsigned char **out, unsigned char *out_len, + const unsigned char *in, unsigned int in_len, + void *arg) +{ + if (SSL_select_next_proto((unsigned char **)out, out_len, + alpn_ossltest, sizeof(alpn_ossltest), in, in_len) + != OPENSSL_NPN_NEGOTIATED) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + return SSL_TLSEXT_ERR_OK; +} + +static int ssl_ctx_configure(SSL_CTX *ctx, int is_server) +{ + if (!TEST_true(ossl_quic_set_diag_title(ctx, "quic_radix_test"))) + return 0; + + if (!is_server) + return 1; + + if (!TEST_int_eq(SSL_CTX_use_certificate_file(ctx, cert_file, + SSL_FILETYPE_PEM), 1) + || !TEST_int_eq(SSL_CTX_use_PrivateKey_file(ctx, key_file, + SSL_FILETYPE_PEM), 1)) + return 0; + + SSL_CTX_set_alpn_select_cb(ctx, ssl_ctx_select_alpn, NULL); + return 1; +} + +static int ssl_create_bound_socket(uint16_t listen_port, + int *p_fd, uint16_t *p_result_port) +{ + int ok = 0; + int fd = -1; + BIO_ADDR *addr = NULL; + union BIO_sock_info_u info; + struct in_addr ina = { htonl(INADDR_LOOPBACK) }; + + fd = BIO_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0); + if (!TEST_int_ge(fd, 0)) + goto err; + + if (!TEST_true(BIO_socket_nbio(fd, 1))) + goto err; + + if (!TEST_ptr(addr = BIO_ADDR_new())) + goto err; + + if (!TEST_true(BIO_ADDR_rawmake(addr, AF_INET, + &ina, sizeof(ina), 0))) + goto err; + + if (!TEST_true(BIO_bind(fd, addr, 0))) + goto err; + + info.addr = addr; + if (!TEST_true(BIO_sock_info(fd, BIO_SOCK_INFO_ADDRESS, &info))) + goto err; + + if (!TEST_int_gt(BIO_ADDR_rawport(addr), 0)) + goto err; + + ok = 1; +err: + if (!ok && fd >= 0) + BIO_closesocket(fd); + else if (ok) { + *p_fd = fd; + if (p_result_port != NULL) + *p_result_port = BIO_ADDR_rawport(addr); + } + BIO_ADDR_free(addr); + return ok; +} + +static int ssl_attach_bio_dgram(SSL *ssl, + uint16_t local_port, uint16_t *actual_port) +{ + int s_fd = -1; + BIO *bio; + + if (!TEST_true(ssl_create_bound_socket(local_port, &s_fd, actual_port))) + return 0; + + if (!TEST_ptr(bio = BIO_new_dgram(s_fd, BIO_CLOSE))) { + BIO_closesocket(s_fd); + return 0; + } + + SSL_set0_rbio(ssl, bio); + if (!TEST_true(BIO_up_ref(bio))) + return 0; + + SSL_set0_wbio(ssl, bio); + + return 1; +} + +DEF_FUNC(hf_new_ssl) +{ + int ok = 0; + const char *name; + SSL_CTX *ctx = NULL; + const SSL_METHOD *method; + SSL *ssl; + uint64_t flags; + int is_server; + + F_POP2(name, flags); + + is_server = (flags != 0); + method = is_server ? OSSL_QUIC_server_method() : OSSL_QUIC_client_method(); + if (!TEST_ptr(ctx = SSL_CTX_new(method))) + goto err; + + if (!TEST_true(ssl_ctx_configure(ctx, is_server))) + goto err; + + if (is_server) { + if (!TEST_ptr(ssl = SSL_new_listener(ctx, 0))) + goto err; + } else { + if (!TEST_ptr(ssl = SSL_new(ctx))) + goto err; + } + + if (!TEST_true(ssl_attach_bio_dgram(ssl, 0, NULL))) + goto err; + + if (!TEST_true(RADIX_PROCESS_set_ssl(RP(), name, ssl))) { + SSL_free(ssl); + goto err; + } + + ok = 1; +err: + /* SSL object will hold ref, we don't need it */ + SSL_CTX_free(ctx); + return ok; +} + +DEF_FUNC(hf_listen) +{ + int ok = 0, r; + SSL *ssl; + + REQUIRE_SSL(ssl); + + r = SSL_listen(ssl); + if (!TEST_true(r)) + goto err; + + radix_activate_slot(0); + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_new_stream) +{ + int ok = 0; + const char *conn_name, *stream_name; + SSL *conn, *stream; + uint64_t flags, do_accept; + + F_POP2(flags, do_accept); + F_POP2(conn_name, stream_name); + + if (!TEST_ptr_null(RADIX_PROCESS_get_obj(RP(), stream_name))) + goto err; + + if (!TEST_ptr(conn = RADIX_PROCESS_get_ssl(RP(), conn_name))) + goto err; + + if (do_accept) { + stream = SSL_accept_stream(conn, flags); + + if (stream == NULL) + F_SPIN_AGAIN(); + } else { + stream = SSL_new_stream(conn, flags); + } + + if (!TEST_ptr(stream)) + goto err; + + /* TODO(QUIC RADIX): Implement wait behaviour */ + + if (stream != NULL + && !TEST_true(RADIX_PROCESS_set_ssl(RP(), stream_name, stream))) { + SSL_free(stream); + goto err; + } + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_accept_conn) +{ + int ok = 0; + const char *conn_name; + uint64_t flags; + SSL *listener, *conn; + + F_POP2(conn_name, flags); + REQUIRE_SSL(listener); + + if (!TEST_ptr_null(RADIX_PROCESS_get_obj(RP(), conn_name))) + goto err; + + conn = SSL_accept_connection(listener, flags); + if (conn == NULL) + F_SPIN_AGAIN(); + + if (!TEST_true(RADIX_PROCESS_set_ssl(RP(), conn_name, conn))) { + SSL_free(conn); + goto err; + } + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_accept_conn_none) +{ + int ok = 0; + SSL *listener, *conn; + + REQUIRE_SSL(listener); + + conn = SSL_accept_connection(listener, 0); + if (!TEST_ptr_null(conn)) { + SSL_free(conn); + goto err; + } + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_accept_stream_none) +{ + int ok = 0; + const char *conn_name; + uint64_t flags; + SSL *conn, *stream; + + F_POP2(conn_name, flags); + + if (!TEST_ptr(conn = RADIX_PROCESS_get_ssl(RP(), conn_name))) + goto err; + + stream = SSL_accept_stream(conn, flags); + if (!TEST_ptr_null(stream)) { + SSL_free(stream); + goto err; + } + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_pop_err) +{ + ERR_pop(); + + return 1; +} + +DEF_FUNC(hf_stream_reset) +{ + int ok = 0; + const char *name; + SSL_STREAM_RESET_ARGS args = {0}; + SSL *ssl; + + F_POP2(name, args.quic_error_code); + REQUIRE_SSL(ssl); + + if (!TEST_true(SSL_stream_reset(ssl, &args, sizeof(args)))) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_set_default_stream_mode) +{ + int ok = 0; + uint64_t mode; + SSL *ssl; + + F_POP(mode); + REQUIRE_SSL(ssl); + + if (!TEST_true(SSL_set_default_stream_mode(ssl, mode))) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_set_incoming_stream_policy) +{ + int ok = 0; + uint64_t policy, error_code; + SSL *ssl; + + F_POP(error_code); + F_POP(policy); + REQUIRE_SSL(ssl); + + if (!TEST_true(SSL_set_incoming_stream_policy(ssl, policy, error_code))) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_shutdown_wait) +{ + int ok = 0, ret; + uint64_t flags; + SSL *ssl; + SSL_SHUTDOWN_EX_ARGS args = {0}; + QUIC_CHANNEL *ch; + + F_POP(args.quic_reason); + F_POP(args.quic_error_code); + F_POP(flags); + REQUIRE_SSL(ssl); + + ch = ossl_quic_conn_get_channel(ssl); + ossl_quic_engine_set_inhibit_tick(ossl_quic_channel_get0_engine(ch), 0); + + ret = SSL_shutdown_ex(ssl, flags, &args, sizeof(args)); + if (!TEST_int_ge(ret, 0)) + goto err; + + if (ret == 0) + F_SPIN_AGAIN(); + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_conclude) +{ + int ok = 0; + SSL *ssl; + + REQUIRE_SSL(ssl); + + if (!TEST_true(SSL_stream_conclude(ssl, 0))) + goto err; + + ok = 1; +err: + return ok; +} + +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 check_consistent_want(SSL *s, int ret) +{ + int ec = SSL_get_error(s, ret); + int w = SSL_want(s); + + int ok = TEST_true( + (ec == SSL_ERROR_NONE && w == SSL_NOTHING) + || (ec == SSL_ERROR_ZERO_RETURN && w == SSL_NOTHING) + || (ec == SSL_ERROR_SSL && w == SSL_NOTHING) + || (ec == SSL_ERROR_SYSCALL && w == SSL_NOTHING) + || (ec == SSL_ERROR_WANT_READ && w == SSL_READING) + || (ec == SSL_ERROR_WANT_WRITE && w == SSL_WRITING) + || (ec == SSL_ERROR_WANT_CLIENT_HELLO_CB && w == SSL_CLIENT_HELLO_CB) + || (ec == SSL_ERROR_WANT_X509_LOOKUP && w == SSL_X509_LOOKUP) + || (ec == SSL_ERROR_WANT_RETRY_VERIFY && w == SSL_RETRY_VERIFY) + ); + + if (!ok) + TEST_error("got error=%d, want=%d", ec, w); + + return ok; +} + +DEF_FUNC(hf_write) +{ + int ok = 0, r; + SSL *ssl; + const void *buf; + size_t buf_len, bytes_written = 0; + + F_POP2(buf, buf_len); + REQUIRE_SSL(ssl); + + r = SSL_write_ex(ssl, buf, buf_len, &bytes_written); + if (!TEST_true(r) + || !check_consistent_want(ssl, r) + || !TEST_size_t_eq(bytes_written, buf_len)) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_write_ex2) +{ + int ok = 0, r; + SSL *ssl; + const void *buf; + size_t buf_len, bytes_written = 0; + uint64_t flags; + + F_POP(flags); + F_POP2(buf, buf_len); + REQUIRE_SSL(ssl); + + r = SSL_write_ex2(ssl, buf, buf_len, flags, &bytes_written); + if (!TEST_true(r) + || !check_consistent_want(ssl, r) + || !TEST_size_t_eq(bytes_written, buf_len)) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_write_fail) +{ + int ok = 0, ret; + SSL *ssl; + size_t bytes_written = 0; + + REQUIRE_SSL(ssl); + + ret = SSL_write_ex(ssl, "apple", 5, &bytes_written); + if (!TEST_false(ret) + || !TEST_true(check_consistent_want(ssl, ret)) + || !TEST_size_t_eq(bytes_written, 0)) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_read_expect) +{ + int ok = 0, r; + SSL *ssl; + const void *buf; + uint64_t buf_len, bytes_read = 0; + + F_POP2(buf, buf_len); + REQUIRE_SSL(ssl); + + if (buf_len > 0 && RT()->tmp_buf == NULL + && !TEST_ptr(RT()->tmp_buf = OPENSSL_malloc(buf_len))) + goto err; + + r = SSL_read_ex(ssl, RT()->tmp_buf + RT()->tmp_buf_offset, + buf_len - RT()->tmp_buf_offset, + &bytes_read); + if (!TEST_true(check_consistent_want(ssl, r))) + goto err; + + if (!r) + F_SPIN_AGAIN(); + + if (bytes_read + RT()->tmp_buf_offset != buf_len) { + RT()->tmp_buf_offset += bytes_read; + F_SPIN_AGAIN(); + } + + if (buf_len > 0 + && !TEST_mem_eq(RT()->tmp_buf, buf_len, buf, buf_len)) + goto err; + + OPENSSL_free(RT()->tmp_buf); + RT()->tmp_buf = NULL; + RT()->tmp_buf_offset = 0; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_read_fail) +{ + int ok = 0, r; + SSL *ssl; + char buf[1] = {0}; + size_t bytes_read = 0; + uint64_t do_wait; + + F_POP(do_wait); + REQUIRE_SSL(ssl); + + r = SSL_read_ex(ssl, buf, sizeof(buf), &bytes_read); + if (!TEST_false(r) + || !TEST_true(check_consistent_want(ssl, r)) + || !TEST_size_t_eq(bytes_read, 0)) + goto err; + + if (do_wait && is_want(ssl, 0)) + F_SPIN_AGAIN(); + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_connect_wait) +{ + int ok = 0, ret; + SSL *ssl; + + REQUIRE_SSL(ssl); + + /* if not started */ + if (RT()->scratch0 == 0) { + if (!TEST_true(SSL_set_blocking_mode(ssl, 0))) + return 0; + + SSL_CONN_CLOSE_INFO cc_info = {0}; + if (!TEST_false(SSL_get_conn_close_info(ssl, &cc_info, sizeof(cc_info)))) + goto err; + + /* 0 is the success case for SSL_set_alpn_protos(). */ + if (!TEST_false(SSL_set_alpn_protos(ssl, alpn_ossltest, + sizeof(alpn_ossltest)))) + goto err; + } + + RT()->scratch0 = 1; /* connect started */ + ret = SSL_connect(ssl); + radix_activate_slot(0); + if (!TEST_true(check_consistent_want(ssl, ret))) + goto err; + + if (ret != 1) { + if (1 /* TODO */ && is_want(ssl, ret)) + F_SPIN_AGAIN(); + + if (!TEST_int_eq(ret, 1)) + goto err; + } + + ok = 1; +err: + RT()->scratch0 = 0; + return ok; +} + +DEF_FUNC(hf_detach) +{ + int ok = 0; + const char *conn_name, *stream_name; + SSL *conn, *stream; + + F_POP2(conn_name, stream_name); + if (!TEST_ptr(conn = RADIX_PROCESS_get_ssl(RP(), conn_name))) + goto err; + + if (!TEST_ptr(stream = ossl_quic_detach_stream(conn))) + goto err; + + if (!TEST_true(RADIX_PROCESS_set_ssl(RP(), stream_name, stream))) { + SSL_free(stream); + goto err; + } + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_attach) +{ + int ok = 0; + const char *conn_name, *stream_name; + SSL *conn, *stream; + + F_POP2(conn_name, stream_name); + + if (!TEST_ptr(conn = RADIX_PROCESS_get_ssl(RP(), conn_name))) + goto err; + + if (!TEST_ptr(stream = RADIX_PROCESS_get_ssl(RP(), stream_name))) + goto err; + + if (!TEST_true(ossl_quic_attach_stream(conn, stream))) + goto err; + + if (!TEST_true(RADIX_PROCESS_set_ssl(RP(), stream_name, NULL))) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_expect_fin) +{ + int ok = 0, ret; + SSL *ssl; + char buf[1]; + size_t bytes_read = 0; + + REQUIRE_SSL(ssl); + + ret = SSL_read_ex(ssl, buf, sizeof(buf), &bytes_read); + if (!TEST_true(check_consistent_want(ssl, ret)) + || !TEST_false(ret) + || !TEST_size_t_eq(bytes_read, 0)) + goto err; + + if (is_want(ssl, 0)) + F_SPIN_AGAIN(); + + if (!TEST_int_eq(SSL_get_error(ssl, 0), + SSL_ERROR_ZERO_RETURN)) + goto err; + + if (!TEST_int_eq(SSL_want(ssl), SSL_NOTHING)) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_expect_conn_close_info) +{ + int ok = 0; + SSL *ssl; + SSL_CONN_CLOSE_INFO cc_info = {0}; + uint64_t error_code, expect_app, expect_remote; + + F_POP(error_code); + F_POP2(expect_app, expect_remote); + REQUIRE_SSL(ssl); + + /* TODO BLOCKING */ + + if (!SSL_get_conn_close_info(ssl, &cc_info, sizeof(cc_info))) + F_SPIN_AGAIN(); + + if (!TEST_int_eq(expect_app, + (cc_info.flags & SSL_CONN_CLOSE_FLAG_TRANSPORT) == 0) + || !TEST_int_eq(expect_remote, + (cc_info.flags & SSL_CONN_CLOSE_FLAG_LOCAL) == 0) + || !TEST_uint64_t_eq(error_code, cc_info.error_code)) { + TEST_info("connection close reason: %s", cc_info.reason); + goto err; + } + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_wait_for_data) +{ + int ok = 0; + SSL *ssl; + char buf[1]; + size_t bytes_read = 0; + + REQUIRE_SSL(ssl); + + if (!SSL_peek_ex(ssl, buf, sizeof(buf), &bytes_read) + || bytes_read == 0) + F_SPIN_AGAIN(); + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_expect_err) +{ + int ok = 0; + uint64_t lib, reason; + + F_POP2(lib, reason); + if (!TEST_size_t_eq((size_t)ERR_GET_LIB(ERR_peek_last_error()), lib) + || !TEST_size_t_eq((size_t)ERR_GET_REASON(ERR_peek_last_error()), reason)) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_expect_ssl_err) +{ + int ok = 0; + uint64_t expected; + SSL *ssl; + + F_POP(expected); + REQUIRE_SSL(ssl); + + if (!TEST_size_t_eq((size_t)SSL_get_error(ssl, 0), expected) + || !TEST_int_eq(SSL_want(ssl), SSL_NOTHING)) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_expect_stream_id) +{ + int ok = 0; + SSL *ssl; + uint64_t expected, actual; + + F_POP(expected); + REQUIRE_SSL(ssl); + + actual = SSL_get_stream_id(ssl); + if (!TEST_uint64_t_eq(actual, expected)) + goto err; + + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_select_ssl) +{ + int ok = 0; + uint64_t slot; + const char *name; + RADIX_OBJ *obj; + + F_POP2(slot, name); + if (!TEST_ptr(obj = RADIX_PROCESS_get_obj(RP(), name))) + goto err; + + if (!TEST_uint64_t_lt(slot, NUM_SLOTS)) + goto err; + + RT()->slot[slot] = obj; + RT()->ssl[slot] = obj->ssl; + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_clear_slot) +{ + int ok = 0; + uint64_t slot; + + F_POP(slot); + if (!TEST_uint64_t_lt(slot, NUM_SLOTS)) + goto err; + + RT()->slot[slot] = NULL; + RT()->ssl[slot] = NULL; + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_skip_time) +{ + int ok = 0; + uint64_t ms; + + F_POP(ms); + + radix_skip_time(ossl_ms2time(ms)); + ok = 1; +err: + return ok; +} + +DEF_FUNC(hf_set_peer_addr_from) +{ + int ok = 0; + SSL *dst_ssl, *src_ssl; + BIO *dst_bio, *src_bio; + int src_fd = -1; + union BIO_sock_info_u src_info; + BIO_ADDR *src_addr = NULL; + + REQUIRE_SSL_N(0, dst_ssl); + REQUIRE_SSL_N(1, src_ssl); + dst_bio = SSL_get_rbio(dst_ssl); + src_bio = SSL_get_rbio(src_ssl); + if (!TEST_ptr(dst_bio) || !TEST_ptr(src_bio)) + goto err; + + if (!TEST_ptr(src_addr = BIO_ADDR_new())) + goto err; + + if (!TEST_true(BIO_get_fd(src_bio, &src_fd)) + || !TEST_int_ge(src_fd, 0)) + goto err; + + src_info.addr = src_addr; + if (!TEST_true(BIO_sock_info(src_fd, BIO_SOCK_INFO_ADDRESS, &src_info)) + || !TEST_int_ge(ntohs(BIO_ADDR_rawport(src_addr)), 0)) + goto err; + + /* + * Could use SSL_set_initial_peer_addr here, but set it on the + * BIO_s_datagram instead and make sure we pick it up automatically. + */ + if (!TEST_true(BIO_dgram_set_peer(dst_bio, src_addr))) + goto err; + + ok = 1; +err: + BIO_ADDR_free(src_addr); + return ok; +} + +#define OP_UNBIND(name) \ + (OP_PUSH_PZ(#name), \ + OP_FUNC(hf_unbind)) + +#define OP_SELECT_SSL(slot, name) \ + (OP_PUSH_U64(slot), \ + OP_PUSH_PZ(#name), \ + OP_FUNC(hf_select_ssl)) + +#define OP_CLEAR_SLOT(slot) \ + (OP_PUSH_U64(slot), \ + OP_FUNC(hf_clear_slot)) + +#define OP_CONNECT_WAIT(name) \ + (OP_SELECT_SSL(0, name), \ + OP_FUNC(hf_connect_wait)) + +#define OP_LISTEN(name) \ + (OP_SELECT_SSL(0, name), \ + OP_FUNC(hf_listen)) + +#define OP_NEW_SSL_C(name) \ + (OP_PUSH_PZ(#name), \ + OP_PUSH_U64(0), \ + OP_FUNC(hf_new_ssl)) + +#define OP_NEW_SSL_L(name) \ + (OP_PUSH_PZ(#name), \ + OP_PUSH_U64(1), \ + OP_FUNC(hf_new_ssl)) + +#define OP_NEW_SSL_L_LISTEN(name) \ + (OP_NEW_SSL_L(name), \ + OP_LISTEN(name)) + +#define OP_SET_PEER_ADDR_FROM(dst_name, src_name) \ + (OP_SELECT_SSL(0, dst_name), \ + OP_SELECT_SSL(1, src_name), \ + OP_FUNC(hf_set_peer_addr_from)) + +#define OP_SIMPLE_PAIR_CONN() \ + (OP_NEW_SSL_L_LISTEN(L), \ + OP_NEW_SSL_C(C), \ + OP_SET_PEER_ADDR_FROM(C, L), \ + OP_CONNECT_WAIT(C)) + +#define OP_NEW_STREAM(conn_name, stream_name, flags) \ + (OP_SELECT_SSL(0, conn_name), \ + OP_PUSH_PZ(#stream_name), \ + OP_PUSH_U64(flags), \ + OP_PUSH_U64(0), \ + OP_FUNC(hf_new_stream)) + +#define OP_ACCEPT_STREAM_WAIT(conn_name, stream_name, flags) \ + (OP_SELECT_SSL(0, conn_name), \ + OP_PUSH_PZ(#stream_name), \ + OP_PUSH_U64(flags), \ + OP_PUSH_U64(1), \ + OP_FUNC(hf_new_stream)) + +#define OP_ACCEPT_STREAM_NONE(conn_name) \ + (OP_SELECT_SSL(0, conn_name), \ + OP_FUNC(hf_accept_stream_none)) + +#define OP_ACCEPT_CONN_WAIT(listener_name, conn_name, flags) \ + (OP_SELECT_SSL(0, listener_name), \ + OP_PUSH_PZ(#conn_name), \ + OP_PUSH_U64(flags), \ + OP_FUNC(hf_accept_conn)) + +#define OP_ACCEPT_CONN_NONE(listener_name) \ + (OP_SELECT_SSL(0, listener_name), \ + OP_FUNC(hf_accept_conn_none)) + +#define OP_WRITE(name, buf, buf_len) \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_BUFP(buf, buf_len), \ + OP_FUNC(hf_write)) + +#define OP_WRITE_B(name, buf) \ + OP_WRITE(name, (buf), sizeof(buf)) + +#define OP_WRITE_EX2(name, buf, buf_len, flags) \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_BUFP(buf, buf_len), \ + OP_PUSH_U64(flags), \ + OP_FUNC(hf_write_ex2)) + +#define OP_WRITE_FAIL(name) \ + (OP_SELECT_SSL(0, name), \ + OP_FUNC(hf_write_fail)) + +#define OP_CONCLUDE(name) \ + (OP_SELECT_SSL(0, name), \ + OP_FUNC(hf_conclude)) + +#define OP_READ_EXPECT(name, buf, buf_len) \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_BUFP(buf, buf_len), \ + OP_FUNC(hf_read_expect)) + +#define OP_READ_EXPECT_B(name, buf) \ + OP_READ_EXPECT(name, (buf), sizeof(buf)) + +#define OP_READ_FAIL() \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_U64(0), \ + OP_FUNC(hf_read_fail)) + +#define OP_READ_FAIL_WAIT(name) \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_U64(1), \ + OP_FUNC(hf_read_fail) + +#define OP_POP_ERR() \ + OP_FUNC(hf_pop_err) + +#define OP_SET_DEFAULT_STREAM_MODE(name, mode) \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_U64(mode), \ + OP_FUNC(hf_set_default_stream_mode)) + +#define OP_SET_INCOMING_STREAM_POLICY(name, policy, error_code) \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_U64(policy), \ + OP_PUSH_U64(error_code), \ + OP_FUNC(hf_set_incoming_stream_policy)) + +#define OP_STREAM_RESET(name, error_code) \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_U64(flags), \ + OP_PUSH_U64(error_code), \ + OP_FUNC(hf_stream_reset)) \ + +#define OP_SHUTDOWN_WAIT(name, flags, error_code, reason) \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_U64(flags), \ + OP_PUSH_U64(error_code), \ + OP_PUSH_PZ(reason), \ + OP_FUNC(hf_shutdown_wait)) + +#define OP_DETACH(conn_name, stream_name) \ + (OP_SELECT_SSL(0, conn_name), \ + OP_PUSH_PZ(#stream_name), \ + OP_FUNC(hf_detach)) + +#define OP_ATTACH(conn_name, stream_name) \ + (OP_SELECT_SSL(0, conn_name), \ + OP_PUSH_PZ(stream_name), \ + OP_FUNC(hf_attach)) + +#define OP_EXPECT_FIN(name) \ + (OP_SELECT_SSL(0, name), \ + OP_FUNC(hf_expect_fin)) + +#define OP_EXPECT_CONN_CLOSE_INFO(name, error_code, expect_app, expect_remote) \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_U64(expect_app), \ + OP_PUSH_U64(expect_remote), \ + OP_PUSH_U64(error_code), \ + OP_FUNC(hf_expect_conn_close_info)) + +#define OP_WAIT_FOR_DATA(name) \ + (OP_SELECT_SSL(0, name), \ + OP_FUNC(hf_wait_for_data)) + +#define OP_EXPECT_ERR(lib, reason) \ + (OP_PUSH_U64(lib), \ + OP_PUSH_U64(reason), \ + OP_FUNC(hf_expect_err)) + +#define OP_EXPECT_SSL_ERR(name, expected) \ + (OP_SELECT_SSL(0, name), \ + OP_PUSH_U64(expected), \ + OP_FUNC(hf_expect_ssl_err)) + +#define OP_EXPECT_STREAM_ID(expected) \ + (OP_PUSH_U64(expected), \ + OP_FUNC(hf_expect_stream_id)) + +#define OP_SKIP_TIME(ms) \ + (OP_PUSH_U64(ms), \ + OP_FUNC(hf_skip_time)) diff --git a/test/radix/quic_radix.c b/test/radix/quic_radix.c new file mode 100644 index 00000000000..2715f26d951 --- /dev/null +++ b/test/radix/quic_radix.c @@ -0,0 +1,13 @@ +/* + * Copyright 2024 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 "terp.c" +#include "quic_bindings.c" +#include "quic_ops.c" +#include "quic_tests.c" +#include "main.c" diff --git a/test/radix/quic_tests.c b/test/radix/quic_tests.c new file mode 100644 index 00000000000..3398c9d4d5e --- /dev/null +++ b/test/radix/quic_tests.c @@ -0,0 +1,33 @@ +/* + * Copyright 2024 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 + */ + +/* + * Test Scripts + * ============================================================================ + */ + +DEF_SCRIPT(simple_conn, "simple connection to server") +{ + OP_SIMPLE_PAIR_CONN(); + OP_WRITE_B(C, "apple"); + + OP_ACCEPT_CONN_WAIT(L, La, 0); + OP_ACCEPT_CONN_NONE(L); + + OP_WRITE_B(La, "orange"); + OP_READ_EXPECT_B(C, "orange"); +} + +/* + * List of Test Scripts + * ============================================================================ + */ +static SCRIPT_INFO *const scripts[] = { + USE(simple_conn) +}; diff --git a/test/radix/terp.c b/test/radix/terp.c new file mode 100644 index 00000000000..06aa0aa5159 --- /dev/null +++ b/test/radix/terp.c @@ -0,0 +1,882 @@ +/* + * Copyright 2024 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 +#include "../testutil.h" +#include "internal/numbers.h" /* UINT64_C */ +#include "internal/time.h" /* OSSL_TIME */ + +static const char *cert_file, *key_file; + +/* + * TERP - Test Executive Script Interpreter + * ======================================== + */ +typedef struct gen_ctx_st GEN_CTX; + +typedef void (*script_gen_t)(GEN_CTX *ctx); + +typedef struct script_info_st { + /* name: A symbolic name, like simple_conn. */ + const char *name; + /* desc: A short, one-line description. */ + const char *desc; + const char *file; + int line; + /* gen_func: The script generation function. */ + script_gen_t gen_func; +} SCRIPT_INFO; + +struct gen_ctx_st { + SCRIPT_INFO *script_info; + const char *cur_file; + int error, cur_line; + const char *first_error_msg, *first_error_file; + int first_error_line; + + uint8_t *build_buf_beg, *build_buf_cur, *build_buf_end; +}; + +static int GEN_CTX_init(GEN_CTX *ctx, SCRIPT_INFO *script_info) +{ + ctx->script_info = script_info; + ctx->error = 0; + ctx->cur_file = NULL; + ctx->cur_line = 0; + ctx->first_error_msg = NULL; + ctx->first_error_line = 0; + ctx->build_buf_beg = NULL; + ctx->build_buf_cur = NULL; + ctx->build_buf_end = NULL; + return 1; +} + +static void GEN_CTX_cleanup(GEN_CTX *ctx) +{ + OPENSSL_free(ctx->build_buf_beg); + ctx->build_buf_beg = ctx->build_buf_cur = ctx->build_buf_end = NULL; +} + +typedef struct terp_st TERP; + +#define F_RET_SPIN_AGAIN 2 +#define F_RET_SKIP_REST 3 + +#define F_SPIN_AGAIN() \ + do { \ + ok = F_RET_SPIN_AGAIN; \ + fctx->spin_again = 1; \ + goto err; \ + } while (0) + +#define F_SKIP_REST() \ + do { \ + ok = F_RET_SKIP_REST; \ + fctx->skip_rest = 1; \ + goto err; \ + } while (0) + +typedef struct func_ctx_st { + TERP *terp; + + /* + * Set to 1 inside a user function if the function should spin again. + * Cleared automatically after the user function returns. + */ + int spin_again; + + /* + * Immediately exit script successfully. Useful for skipping. + */ + int skip_rest; +} FUNC_CTX; + +static ossl_inline int TERP_stk_pop(TERP *terp, + void *buf, size_t buf_len); + +#define TERP_STK_PUSH(terp, v) \ + do { \ + if (!TEST_true(TERP_stk_push((terp), &(v), sizeof(v)))) \ + goto err; \ + } while (0) + +#define TERP_STK_POP(terp, v) \ + do { \ + if (!TEST_true(TERP_stk_pop((terp), &(v), sizeof(v)))) \ + goto err; \ + } while (0) + +#define TERP_STK_POP2(terp, a, b) \ + do { \ + TERP_STK_POP((terp), (b)); \ + TERP_STK_POP((terp), (a)); \ + } while (0) + +#define F_PUSH(v) TERP_STK_PUSH(fctx->terp, (v)) +#define F_POP(v) TERP_STK_POP (fctx->terp, (v)) +#define F_POP2(a, b) TERP_STK_POP2(fctx->terp, (a), (b)) + +typedef int (*helper_func_t)(FUNC_CTX *fctx); + +#define DEF_FUNC(name) ossl_unused static int name(FUNC_CTX *fctx) + +#define DEF_SCRIPT(name, desc) \ + static void script_gen_##name(GEN_CTX *ctx); \ + static SCRIPT_INFO script_info_##name = { \ + #name, desc, __FILE__, __LINE__, \ + script_gen_##name \ + }; \ + static void script_gen_##name(GEN_CTX *ctx) + +enum { + OPK_INVALID, + OPK_END, + OPK_PUSH_P, + /* + * This is exactly like PUSH_P, but the script dumper knows the pointer + * points to a static NUL-terminated string and can therefore print it. + */ + OPK_PUSH_PZ, + OPK_PUSH_U64, + /* + * Could use OPK_PUSH_U64 for this but it's annoying to have to avoid using + * size_t in case it is a different size. + */ + OPK_PUSH_SIZE, + OPK_FUNC, + OPK_LABEL +}; + +static void *openc_alloc_space(GEN_CTX *ctx, size_t num_bytes); + +#define DEF_ENCODER(name, type) \ + static void name(GEN_CTX *ctx, type v) \ + { \ + void *dst = openc_alloc_space(ctx, sizeof(v)); \ + if (dst == NULL) \ + return; \ + \ + memcpy(dst, &v, sizeof(v)); \ + } + +DEF_ENCODER(openc_u64, uint64_t) +DEF_ENCODER(openc_size, size_t) +DEF_ENCODER(openc_p, void *) +DEF_ENCODER(openc_fp, helper_func_t) +#define openc_opcode openc_u64 + +static void opgen_END(GEN_CTX *ctx) +{ + openc_opcode(ctx, OPK_END); +} + +static ossl_unused void opgen_PUSH_P(GEN_CTX *ctx, void *p) +{ + openc_opcode(ctx, OPK_PUSH_P); + openc_p(ctx, p); +} + +static void opgen_PUSH_PZ(GEN_CTX *ctx, void *p) +{ + openc_opcode(ctx, OPK_PUSH_PZ); + openc_p(ctx, p); +} + +static void opgen_PUSH_U64(GEN_CTX *ctx, uint64_t v) +{ + openc_opcode(ctx, OPK_PUSH_U64); + openc_u64(ctx, v); +} + +ossl_unused static void opgen_PUSH_SIZE(GEN_CTX *ctx, size_t v) +{ + openc_opcode(ctx, OPK_PUSH_SIZE); + openc_size(ctx, v); +} + +ossl_unused static void opgen_FUNC(GEN_CTX *ctx, helper_func_t f, + const char *f_name) +{ + openc_opcode(ctx, OPK_FUNC); + openc_fp(ctx, f); + openc_p(ctx, (void *)f_name); +} + +ossl_unused static void opgen_LABEL(GEN_CTX *ctx, const char *name) +{ + openc_opcode(ctx, OPK_LABEL); + openc_p(ctx, (void *)name); +} + +static void opgen_set_line(GEN_CTX *ctx, const char *file, int line) +{ + ctx->cur_file = file; + ctx->cur_line = line; +} + +static ossl_unused void opgen_fail(GEN_CTX *ctx, const char *msg) +{ + if (!ctx->error) { + ctx->first_error_file = ctx->cur_file; + ctx->first_error_line = ctx->cur_line; + ctx->first_error_msg = msg; + } + + ctx->error = 1; +} + +#define OPGEN(n) (opgen_set_line(ctx, __FILE__, __LINE__), \ + opgen_##n) +#define OP_END() OPGEN(END) (ctx) +#define OP_PUSH_P(v) OPGEN(PUSH_P) (ctx, (v)) +#define OP_PUSH_PZ(v) OPGEN(PUSH_PZ) (ctx, (v)) +#define OP_PUSH_U64(v) OPGEN(PUSH_U64) (ctx, (v)) +#define OP_PUSH_SIZE(v) OPGEN(PUSH_SIZE) (ctx, (v)) +#define OP_PUSH_BUFP(p, l) (OP_PUSH_P(p), OP_PUSH_SIZE(l)) +#define OP_PUSH_BUF(v) OP_PUSH_BUFP(&(v), sizeof(v)) +#define OP_PUSH_LREF(v) OPGEN(PUSH_LREF)(ctx, (lref)) +#define OP_FUNC(f) OPGEN(FUNC) (ctx, (f), #f) +#define OP_LABEL(name) OPGEN(LABEL) (ctx, (name)) +#define GEN_FAIL(msg) OPGEN(fail) (ctx, (msg)) + +static void *openc_alloc_space(GEN_CTX *ctx, size_t num_bytes) +{ + void *p; + size_t cur_spare, old_size, new_size, off; + + cur_spare = ctx->build_buf_end - ctx->build_buf_cur; + if (cur_spare < num_bytes) { + off = ctx->build_buf_cur - ctx->build_buf_beg; + old_size = ctx->build_buf_end - ctx->build_buf_beg; + new_size = (old_size == 0) ? 1024 : old_size * 2; + p = OPENSSL_realloc(ctx->build_buf_beg, new_size); + if (!TEST_ptr(p)) + return NULL; + + ctx->build_buf_beg = p; + ctx->build_buf_cur = ctx->build_buf_beg + off; + ctx->build_buf_end = ctx->build_buf_beg + new_size; + } + + p = ctx->build_buf_cur; + ctx->build_buf_cur += num_bytes; + return p; +} + +/* + * Script Interpreter + * ============================================================================ + */ +typedef struct gen_script_st { + const uint8_t *buf; + size_t buf_len; +} GEN_SCRIPT; + +static int GEN_CTX_finish(GEN_CTX *ctx, GEN_SCRIPT *script) +{ + script->buf = ctx->build_buf_beg; + script->buf_len = ctx->build_buf_cur - ctx->build_buf_beg; + ctx->build_buf_beg = ctx->build_buf_cur = ctx->build_buf_end = NULL; + return 1; +} + +static void GEN_SCRIPT_cleanup(GEN_SCRIPT *script) +{ + OPENSSL_free((char *)script->buf); + + script->buf = NULL; + script->buf_len = 0; +} + +static int GEN_SCRIPT_init(GEN_SCRIPT *gen_script, SCRIPT_INFO *script_info) +{ + int ok = 0; + GEN_CTX gctx; + + if (!TEST_true(GEN_CTX_init(&gctx, script_info))) + return 0; + + script_info->gen_func(&gctx); + opgen_END(&gctx); + + if (!TEST_false(gctx.error)) + goto err; + + if (!TEST_true(GEN_CTX_finish(&gctx, gen_script))) + goto err; + + ok = 1; +err: + if (!ok) { + if (gctx.error) + TEST_error("script generation failed: %s (at %s:%d)", + gctx.first_error_msg, + gctx.first_error_file, + gctx.first_error_line); + + GEN_CTX_cleanup(&gctx); + } + return ok; +} + +typedef struct srdr_st { + const uint8_t *beg, *cur, *end, *save_cur; +} SRDR; + +static void SRDR_init(SRDR *rdr, const uint8_t *buf, size_t buf_len) +{ + rdr->beg = rdr->cur = buf; + rdr->end = rdr->beg + buf_len; + rdr->save_cur = NULL; +} + +static ossl_inline int SRDR_get_operand(SRDR *srdr, void *buf, size_t buf_len) +{ + if (!TEST_size_t_ge(srdr->end - srdr->cur, buf_len)) + return 0; /* malformed script */ + + memcpy(buf, srdr->cur, buf_len); + srdr->cur += buf_len; + return 1; +} + +static ossl_inline void SRDR_save(SRDR *srdr) +{ + srdr->save_cur = srdr->cur; +} + +static ossl_inline void SRDR_restore(SRDR *srdr) +{ + srdr->cur = srdr->save_cur; +} + +#define GET_OPERAND(srdr, v) \ + do { \ + if (!TEST_true(SRDR_get_operand(srdr, &(v), sizeof(v)))) \ + goto err; \ + } while (0) + + +static void print_opc(BIO *bio, size_t op_num, size_t offset, const char *name) +{ + if (op_num != SIZE_MAX) + BIO_printf(bio, "%3zu- %4zx>\t%-8s \t", op_num, + offset, name); + else + BIO_printf(bio, " %4zx>\t%-8s \t", + offset, name); +} + +static int SRDR_print_one(SRDR *srdr, BIO *bio, size_t i, int *was_end) +{ + int ok = 0; + const uint8_t *opc_start; + uint64_t opc; + + if (was_end != NULL) + *was_end = 0; + + opc_start = srdr->cur; + GET_OPERAND(srdr, opc); + +#define PRINT_OPC(name) print_opc(bio, i, (size_t)(opc_start - srdr->beg), #name) + + switch (opc) { + case OPK_END: + PRINT_OPC(END); + opc_start = srdr->cur; + if (was_end != NULL) + *was_end = 1; + break; + case OPK_PUSH_P: + { + void *v; + + GET_OPERAND(srdr, v); + PRINT_OPC(PUSH_P); + BIO_printf(bio, "%20p", v); + } + break; + case OPK_PUSH_PZ: + { + void *v; + + GET_OPERAND(srdr, v); + PRINT_OPC(PUSH_P); + if (v != NULL && strlen((const char *)v) == 1) + BIO_printf(bio, "%20p (%s)", v, (const char *)v); + else + BIO_printf(bio, "%20p (\"%s\")", v, (const char *)v); + } + break; + case OPK_PUSH_U64: + { + uint64_t v; + + GET_OPERAND(srdr, v); + PRINT_OPC(PUSH_U64); + BIO_printf(bio, "%#20llx (%lld)", + (unsigned long long)v, (unsigned long long)v); + } + break; + case OPK_PUSH_SIZE: + { + size_t v; + + GET_OPERAND(srdr, v); + PRINT_OPC(PUSH_SIZE); + BIO_printf(bio, "%#20llx (%lld)", + (unsigned long long)v, (unsigned long long)v); + } + break; + case OPK_FUNC: + { + helper_func_t v; + void *f_name, *x; + + GET_OPERAND(srdr, v); + GET_OPERAND(srdr, f_name); + + PRINT_OPC(FUNC); + memcpy(&x, &v, sizeof(x) < sizeof(v) ? sizeof(x) : sizeof(v)); + BIO_printf(bio, "%s", (const char *)f_name); + } + break; + case OPK_LABEL: + { + void *l_name; + + GET_OPERAND(srdr, l_name); + + BIO_printf(bio, "\n%s:\n", (const char *)l_name); + PRINT_OPC(LABEL); + } + break; + default: + TEST_error("unsupported opcode while printing: %llu", + (unsigned long long)opc); + goto err; + } + + ok = 1; +err: + return ok; +} + +static int GEN_SCRIPT_print(GEN_SCRIPT *gen_script, BIO *bio, + const SCRIPT_INFO *script_info) +{ + int ok = 0; + size_t i; + SRDR srdr_v, *srdr = &srdr_v; + int was_end = 0; + + SRDR_init(srdr, gen_script->buf, gen_script->buf_len); + + if (script_info != NULL) { + BIO_printf(bio, "\nGenerated script for '%s':\n", + script_info->name); + BIO_printf(bio, "\n--GENERATED-------------------------------------" + "----------------------\n"); + BIO_printf(bio, " # NAME:\n # %s\n", + script_info->name); + BIO_printf(bio, " # SOURCE:\n # %s:%d\n", + script_info->file, script_info->line); + BIO_printf(bio, " # DESCRIPTION:\n # %s\n", script_info->desc); + } + + for (i = 0; !was_end; ++i) { + BIO_printf(bio, "\n"); + + if (!TEST_true(SRDR_print_one(srdr, bio, i, &was_end))) + goto err; + } + + if (script_info != NULL) { + const unsigned char *opc_start = srdr->cur; + + BIO_printf(bio, "\n"); + PRINT_OPC(+++); + BIO_printf(bio, "\n------------------------------------------------" + "----------------------\n\n"); + } + + ok = 1; +err: + return ok; +} + +static void SCRIPT_INFO_print(SCRIPT_INFO *script_info, BIO *bio, int error, + const char *msg) +{ + if (error) + TEST_error("%s: script '%s' (%s)", + msg, script_info->name, script_info->desc); + else + TEST_info("%s: script '%s' (%s)", + msg, script_info->name, script_info->desc); +} + +typedef struct terp_config_st { + BIO *debug_bio; + + OSSL_TIME (*now_cb)(void *arg); + void *now_cb_arg; + + int (*per_op_cb)(TERP *terp, void *arg); + void *per_op_cb_arg; + + OSSL_TIME max_execution_time; /* duration */ +} TERP_CONFIG; + +#define TERP_DEFAULT_MAX_EXECUTION_TIME (ossl_ms2time(3000)) + +struct terp_st { + TERP_CONFIG cfg; + const SCRIPT_INFO *script_info; + const GEN_SCRIPT *gen_script; + SRDR srdr; + uint8_t *stk_beg, *stk_cur, *stk_end, *stk_save_cur; + FUNC_CTX fctx; + uint64_t ops_executed; + int log_execute; + OSSL_TIME start_time, deadline_time; +}; + +static int TERP_init(TERP *terp, + const TERP_CONFIG *cfg, + const SCRIPT_INFO *script_info, + const GEN_SCRIPT *gen_script) +{ + if (!TEST_true(cfg->now_cb != NULL)) + return 0; + + terp->cfg = *cfg; + terp->script_info = script_info; + terp->gen_script = gen_script; + terp->fctx.terp = terp; + terp->fctx.spin_again = 0; + terp->fctx.skip_rest = 0; + terp->stk_beg = NULL; + terp->stk_cur = NULL; + terp->stk_end = NULL; + terp->stk_save_cur = NULL; + terp->ops_executed = 0; + terp->log_execute = 1; + + if (ossl_time_is_zero(terp->cfg.max_execution_time)) + terp->cfg.max_execution_time = TERP_DEFAULT_MAX_EXECUTION_TIME; + + return 1; +} + +static void TERP_cleanup(TERP *terp) +{ + if (terp->script_info == NULL) + return; + + OPENSSL_free(terp->stk_beg); + terp->stk_beg = terp->stk_cur = terp->stk_end = NULL; + terp->script_info = NULL; +} + +static int TERP_stk_ensure_capacity(TERP *terp, size_t spare) +{ + uint8_t *p; + size_t old_size, new_size, off; + + old_size = terp->stk_end - terp->stk_beg; + if (old_size >= spare) + return 1; + + off = terp->stk_end - terp->stk_cur; + new_size = old_size != 0 ? old_size * 2 : 256; + p = OPENSSL_realloc(terp->stk_beg, new_size); + if (!TEST_ptr(p)) + return 0; + + terp->stk_beg = p; + terp->stk_end = terp->stk_beg + new_size; + terp->stk_cur = terp->stk_end - off; + return 1; +} + +static ossl_inline int TERP_stk_push(TERP *terp, + const void *buf, size_t buf_len) +{ + if (!TEST_true(TERP_stk_ensure_capacity(terp, buf_len))) + return 0; + + terp->stk_cur -= buf_len; + memcpy(terp->stk_cur, buf, buf_len); + return 1; +} + +static ossl_inline int TERP_stk_pop(TERP *terp, + void *buf, size_t buf_len) +{ + if (!TEST_size_t_ge(terp->stk_end - terp->stk_cur, buf_len)) { + asm("int3"); + return 0; + } + + memcpy(buf, terp->stk_cur, buf_len); + terp->stk_cur += buf_len; + return 1; +} + +static void TERP_print_stack(TERP *terp, BIO *bio, const char *header) +{ + test_output_memory(header, terp->stk_cur, terp->stk_end - terp->stk_cur); + BIO_printf(bio, " (%zu bytes)\n", terp->stk_end - terp->stk_cur); + BIO_printf(bio, "\n"); +} + +#define TERP_GET_OPERAND(v) GET_OPERAND(&terp->srdr, (v)) + +#define TERP_SPIN_AGAIN() \ + do { \ + SRDR_restore(&terp->srdr); \ + terp->stk_cur = terp->stk_save_cur; \ + ++spin_count; \ + goto spin_again; \ + } while (0) + +static OSSL_TIME TERP_now(TERP *terp) +{ + return terp->cfg.now_cb(terp->cfg.now_cb_arg); +} + +static void TERP_log_spin(TERP *terp, size_t spin_count) +{ + if (spin_count > 0) + BIO_printf(terp->cfg.debug_bio, " \t\t(span %zu times)\n", + spin_count); +} + +static int TERP_execute(TERP *terp) +{ + int ok = 0; + uint64_t opc; + size_t op_num = SIZE_MAX; + int in_debug_output = 0; + size_t spin_count = 0; + BIO *debug_bio = terp->cfg.debug_bio; + + SRDR_init(&terp->srdr, terp->gen_script->buf, terp->gen_script->buf_len); + + terp->start_time = TERP_now(terp); + terp->deadline_time = ossl_time_add(terp->start_time, + terp->cfg.max_execution_time); + + for (;;) { + if (terp->log_execute) { + SRDR srdr_copy = terp->srdr; + + if (!in_debug_output) { + BIO_printf(debug_bio, "\n--EXECUTION-----------------------------" + "------------------------------\n"); + in_debug_output = 1; + } + + TERP_log_spin(terp, spin_count); + if (!TEST_true(SRDR_print_one(&srdr_copy, debug_bio, SIZE_MAX, NULL))) + goto err; + + BIO_printf(debug_bio, "\n"); + } + + TERP_GET_OPERAND(opc); + ++op_num; + SRDR_save(&terp->srdr); + terp->stk_save_cur = terp->stk_cur; + spin_count = 0; + + ++terp->ops_executed; + +spin_again: + if (ossl_time_compare(TERP_now(terp), terp->deadline_time) >= 0) { + TEST_error("timed out while executing op %zu", op_num); + if (terp->log_execute) + TERP_log_spin(terp, spin_count); + goto err; + } + + if (terp->cfg.per_op_cb != NULL) + if (!TEST_true(terp->cfg.per_op_cb(terp, terp->cfg.per_op_cb_arg))) { + TEST_error("pre-operation processing failed at op %zu", op_num); + if (terp->log_execute) + TERP_log_spin(terp, spin_count); + goto err; + } + + switch (opc) { + case OPK_END: + goto stop; + case OPK_PUSH_P: + case OPK_PUSH_PZ: + { + void *v; + + TERP_GET_OPERAND(v); + TERP_STK_PUSH(terp, v); + } + break; + case OPK_PUSH_U64: + { + uint64_t v; + + TERP_GET_OPERAND(v); + TERP_STK_PUSH(terp, v); + } + break; + case OPK_PUSH_SIZE: + { + uint64_t v; + + TERP_GET_OPERAND(v); + TERP_STK_PUSH(terp, v); + } + break; + case OPK_LABEL: + { + const char *l_name; + + TERP_GET_OPERAND(l_name); + /* no-op */ + } + break; + case OPK_FUNC: + { + helper_func_t v; + const void *f_name; + int ret; + + TERP_GET_OPERAND(v); + TERP_GET_OPERAND(f_name); + + if (!TEST_true(v != NULL)) + goto err; + + ret = v(&terp->fctx); + + if (terp->fctx.skip_rest) { + if (!TEST_int_eq(ret, F_RET_SKIP_REST)) + goto err; + + if (terp->log_execute) + BIO_printf(terp->cfg.debug_bio, " \t\t(skipping)\n"); + + terp->fctx.skip_rest = 0; + goto stop; + } else if (terp->fctx.spin_again) { + if (!TEST_int_eq(ret, F_RET_SPIN_AGAIN)) + goto err; + + terp->fctx.spin_again = 0; + TERP_SPIN_AGAIN(); + } else { + if (!TEST_false(terp->fctx.spin_again)) + goto err; + + if (ret != 1) { + TEST_error("op %zu (FUNC %s) failed with return value %d", + op_num, (const char *)f_name, ret); + goto err; + } + } + } + break; + default: + TEST_error("unknown opcode: %llu", (unsigned long long)opc); + goto err; + } + } + +stop: + ok = 1; +err: + if (in_debug_output) + BIO_printf(debug_bio, "----------------------------------------" + "------------------------------\n"); + + if (!ok) { + TEST_error("FAILED while executing script: %s at op %zu, error stack:", + terp->script_info->name, op_num); + ERR_print_errors(terp->cfg.debug_bio); + BIO_printf(debug_bio, "\n"); + } else if (ERR_peek_last_error() != 0) { + TEST_info("WARNING: errors on error stack despite success:"); + ERR_print_errors(terp->cfg.debug_bio); + BIO_printf(debug_bio, "\n"); + } + + return ok; +} + +static int TERP_run(SCRIPT_INFO *script_info, TERP_CONFIG *cfg) +{ + int ok = 0, have_terp = 0; + TERP terp; + GEN_SCRIPT gen_script = {0}; + BIO *debug_bio = cfg->debug_bio; + + SCRIPT_INFO_print(script_info, debug_bio, /*error=*/0, "generating script"); + + /* Generate the script by calling the generator function. */ + if (!TEST_true(GEN_SCRIPT_init(&gen_script, script_info))) { + SCRIPT_INFO_print(script_info, debug_bio, /*error=*/1, + "error while generating script"); + goto err; + } + + /* Output the script for debugging purposes. */ + if (!TEST_true(GEN_SCRIPT_print(&gen_script, debug_bio, script_info))) { + SCRIPT_INFO_print(script_info, debug_bio, /*error=*/1, + "error while printing script"); + goto err; + } + + /* Execute the script. */ + if (!TEST_true(TERP_init(&terp, cfg, script_info, &gen_script))) + goto err; + + have_terp = 1; + + SCRIPT_INFO_print(script_info, debug_bio, /*error=*/0, "executing script"); + + if (!TERP_execute(&terp)) + goto err; + + if (terp.stk_end - terp.stk_cur != 0) { + TEST_error("stack not empty: %zu bytes left", + terp.stk_end - terp.stk_cur); + goto err; + } + + ok = 1; +err: + if (have_terp) { + TERP_print_stack(&terp, debug_bio, "Final state of stack"); + TERP_cleanup(&terp); + } + + GEN_SCRIPT_cleanup(&gen_script); + BIO_printf(debug_bio, "Stats:\n Ops executed: %16llu\n\n", + (unsigned long long)terp.ops_executed); + SCRIPT_INFO_print(script_info, debug_bio, /*error=*/!ok, + ok ? "completed" : "failed, exiting"); + return ok; +} + +#define SCRIPT(name) (&script_info_##name) +#define USE(name) SCRIPT(name),