diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b8b6725d..27efe5ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,7 +73,7 @@ jobs: - name: "Config perftools build" working-directory: "./perftools/source" run: | - cmake -S . -B build -DOPENSSL_ROOT_DIR="$GITHUB_WORKSPACE/openssl" -Drun_add_version_dep:BOOL=OFF ${{ matrix.release.cmakeopts }} + cmake -S . -B build -DOPENSSL_ROOT_DIR="$GITHUB_WORKSPACE/openssl" -Denable_run_target:BOOL=ON -Drun_add_version_dep:BOOL=OFF ${{ matrix.release.cmakeopts }} - name: "Build perftools" working-directory: "./perftools/source" run: | @@ -157,7 +157,7 @@ jobs: - name: "Config perftools build" working-directory: "./perftools/source" run: | - cmake -S . -B build -DOPENSSL_ROOT_DIR="$GITHUB_WORKSPACE/openssl" -Drun_add_version_dep:BOOL=OFF ${{ matrix.release.cmakeopts }} + cmake -S . -B build -DOPENSSL_ROOT_DIR="$GITHUB_WORKSPACE/openssl" -Denable_run_target:BOOL=ON -Drun_add_version_dep:BOOL=OFF ${{ matrix.release.cmakeopts }} - name: "Build perftools" working-directory: "./perftools/source" run: | @@ -262,7 +262,7 @@ jobs: shutdown_vm: false run: | cd perftools/source - cmake -S . -B build -DOPENSSL_ROOT_DIR="$GITHUB_WORKSPACE/openssl" -Drun_add_version_dep:BOOL=OFF ${{ matrix.release.cmakeopts }} + cmake -S . -B build -DOPENSSL_ROOT_DIR="$GITHUB_WORKSPACE/openssl" -Denable_run_target:BOOL=ON -Drun_add_version_dep:BOOL=OFF ${{ matrix.release.cmakeopts }} - name: "Build perftools" uses: "cross-platform-actions/action@fe0167d8082ac584754ef3ffb567fded22642c7d" #v0.27.0 with: @@ -358,7 +358,7 @@ jobs: - name: "Config perftools build" working-directory: ".\\perftools\\source" run: | - cmake -S . -B .\build -DOPENSSL_ROOT_DIR="$env:GITHUB_WORKSPACE\openssl" -Drun_add_version_dep:BOOL=OFF ${{ matrix.release.cmakeopts }} + cmake -S . -B .\build -DOPENSSL_ROOT_DIR="$env:GITHUB_WORKSPACE\openssl" -Denable_run_target:BOOL=ON -Drun_add_version_dep:BOOL=OFF ${{ matrix.release.cmakeopts }} - name: "Build perftools" working-directory: ".\\perftools\\source" run: | @@ -435,7 +435,7 @@ jobs: - name: "Config perftools build" working-directory: "./perftools/source" run: | - cmake -S . -B build -DOPENSSL_ROOT_DIR="$GITHUB_WORKSPACE/openssl" -Drun_add_version_dep:BOOL=OFF ${{ matrix.release.cmakeopts }} + cmake -S . -B build -DOPENSSL_ROOT_DIR="$GITHUB_WORKSPACE/openssl" -Denable_run_target:BOOL=ON -Drun_add_version_dep:BOOL=OFF ${{ matrix.release.cmakeopts }} - name: "Build perftools" working-directory: "./perftools/source" run: | diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 61296480..6cc05f20 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -151,6 +151,12 @@ if(WIN32) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${OUTPUT_DIRECTORY}") endif() +if(WIN32) + set(libm_dep) +else() + set(libm_dep PUBLIC m) +endif() + target_include_directories(perf PUBLIC "${PROJECT_SOURCE_DIR}") target_link_libraries(perf PUBLIC OpenSSL::SSL OpenSSL::Crypto) @@ -188,17 +194,13 @@ add_executable(rsasign rsasign.c) target_link_libraries(rsasign PRIVATE perf) add_executable(x509storeissuer x509storeissuer.c) -target_link_libraries(x509storeissuer PRIVATE perf) +target_link_libraries(x509storeissuer ${libm_dep} PRIVATE perf) add_executable(rwlocks rwlocks.c) target_link_libraries(rwlocks PRIVATE perf) add_executable(pkeyread pkeyread.c) -if(WIN32) - target_link_libraries(pkeyread PRIVATE perf) -else() - target_link_libraries(pkeyread PUBLIC m PRIVATE perf) -endif() +target_link_libraries(pkeyread ${libm_dep} PRIVATE perf) add_executable(evp_setpeer evp_setpeer.c) target_link_libraries(evp_setpeer PRIVATE perf) @@ -211,6 +213,8 @@ target_link_libraries(evp_hash PRIVATE perf) ## Running tests # Options +option(enable_run_target "Enable generation of run* targets") + set(run_tests evp_fetch evp_hash evp_setpeer @@ -277,6 +281,36 @@ set(run_writeread_dtls set(run_writeread_buffers writeread "" "" "-b 256" "-b 4096" CACHE STRING "Buffer size for writeread") +# Note that this essentially forces all x509storeissuer runs to take +# 0.12 seconds by default, as otherwise it takes too long due to the size +# of the test matrix. +set(run_x509storeissuer_timeout + x509storeissuer "" "-T 0.12" + CACHE STRING "Test timeout for x509storeissuer") +set(run_x509storeissuer_verbosity + x509storeissuer "" "" "-v" + CACHE STRING "Output verbosity level for x509storeissuer") +set(run_x509storeissuer_quantiles + x509storeissuer "" "" "-q 1" "-q 20" + CACHE STRING "Number of quantiles for x509storeissuer") +set(run_x509storeissuer_nonces + x509storeissuer "" "" "-n file:server-pss-cert.pem" + CACHE STRING "Nonce configurations for x509storeissuer") +set(run_x509storeissuer_modes + x509storeissuer "" "" "-m rw" + CACHE STRING "Run modes for x509storeissuer") +set(run_x509storeissuer_max_writers + x509storeissuer "" "" "-w 3" + CACHE STRING "Maximum writers count for x509storeissuer") +set(run_x509storeissuer_write_prob + x509storeissuer "" "" "-W 0.1" + CACHE STRING "Write probability for x509storeissuer") +set(run_x509storeissuer_cert_load_opts + x509storeissuer "" "" "-L 3" "-L 1 -l 23 -E" + CACHE STRING "Certificate count limit for x509storeissuer") +set(run_x509storeissuer_ctx_share + x509storeissuer "" "" "-C 2" + CACHE STRING "X509_STORE_CTX share options for x509storeissuer") # The list of per-tet options set(run_opts run_evp_fetch_pqs @@ -294,7 +328,16 @@ set(run_opts run_evp_fetch_pqs run_writeread_ctx_sharing run_writeread_dtls run_writeread_buffers - CACHE STRING "List of per-text options") + run_x509storeissuer_timeout + run_x509storeissuer_verbosity + run_x509storeissuer_quantiles + run_x509storeissuer_nonces + run_x509storeissuer_modes + run_x509storeissuer_max_writers + run_x509storeissuer_write_prob + run_x509storeissuer_cert_load_opts + run_x509storeissuer_ctx_share + CACHE STRING "List of per-test options") # Used across multiple tests set(run_certdir_tests handshake writeread x509storeissuer @@ -405,61 +448,63 @@ function(gen_run_taget _CMD _TEST) endfunction() # run targets generation -foreach(test IN LISTS run_tests) - set(cmds "${test}") +if (enable_run_target) + foreach(test IN LISTS run_tests) + set(cmds "${test}") + + # test-specific options + foreach(opt_name IN LISTS run_opts) + set(opt "${${opt_name}}") + list(GET opt 0 test_name) + list(GET opt 1 test_opt) + list(REMOVE_AT opt 0 1) + + if(test IN_LIST test_name) + set(new_cmds) + foreach(cmd IN LISTS cmds) + foreach(val IN LISTS opt) + list(APPEND new_cmds "${cmd} ${test_opt} ${val}") + endforeach() + endforeach() + set(cmds ${new_cmds}) + endif() + endforeach() - # test-specific options - foreach(opt_name IN LISTS run_opts) - set(opt "${${opt_name}}") - list(GET opt 0 test_name) - list(GET opt 1 test_opt) - list(REMOVE_AT opt 0 1) + # terse + set(new_cmds) + foreach(cmd IN LISTS cmds) + foreach(val IN LISTS run_terse) + list(APPEND new_cmds "${cmd} ${val}") + endforeach() + endforeach() + set(cmds ${new_cmds}) - if(test IN_LIST test_name) + # certdir + if(test IN_LIST run_certdir_tests) set(new_cmds) foreach(cmd IN LISTS cmds) - foreach(val IN LISTS opt) - list(APPEND new_cmds "${cmd} ${test_opt} ${val}") - endforeach() + list(APPEND new_cmds "${cmd} ${run_certdir}") endforeach() set(cmds ${new_cmds}) endif() - endforeach() - - # terse - set(new_cmds) - foreach(cmd IN LISTS cmds) - foreach(val IN LISTS run_terse) - list(APPEND new_cmds "${cmd} ${val}") - endforeach() - endforeach() - set(cmds ${new_cmds}) - # certdir - if(test IN_LIST run_certdir_tests) + # threads set(new_cmds) foreach(cmd IN LISTS cmds) - list(APPEND new_cmds "${cmd} ${run_certdir}") + foreach(val IN LISTS run_threads) + list(APPEND new_cmds "${cmd} ${val}") + endforeach() endforeach() set(cmds ${new_cmds}) - endif() - # threads - set(new_cmds) - foreach(cmd IN LISTS cmds) - foreach(val IN LISTS run_threads) - list(APPEND new_cmds "${cmd} ${val}") + # Run targets + foreach(cmd IN LISTS cmds) + gen_run_taget("${cmd}" "${test}" VAR run_target_name) + add_dependencies(run "${run_target_name}") endforeach() - endforeach() - set(cmds ${new_cmds}) - # Run targets - foreach(cmd IN LISTS cmds) - gen_run_taget("${cmd}" "${test}" VAR run_target_name) - add_dependencies(run "${run_target_name}") + # Per-test version target + gen_run_taget("${test} -V" "${test}" NAME "run-version-${test}") + add_dependencies(run-version "run-version-${test}") endforeach() - - # Per-test version target - gen_run_taget("${test} -V" "${test}" NAME "run-version-${test}") - add_dependencies(run-version "run-version-${test}") -endforeach() +endif() diff --git a/source/evp_fetch.c b/source/evp_fetch.c index 3ec64083..8fa8b668 100644 --- a/source/evp_fetch.c +++ b/source/evp_fetch.c @@ -68,7 +68,7 @@ size_t *counts; OSSL_TIME max_time; -int err = 0; +int error = 0; int pq = 0; static int threadcount; @@ -197,7 +197,7 @@ void do_fetch(size_t num) fetch_alg = exclusive_fetch_alg; } - if (err == 1) + if (error == 1) return; switch (j) { @@ -206,7 +206,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (md == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_MD_free(md); @@ -217,7 +217,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (cph == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_CIPHER_free(cph); @@ -228,7 +228,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (kdf == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_KDF_free(kdf); @@ -239,7 +239,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (mac == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_MAC_free(mac); @@ -250,7 +250,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (rnd == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_RAND_free(rnd); @@ -261,7 +261,7 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (kem == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_KEM_free(kem); @@ -272,14 +272,14 @@ void do_fetch(size_t num) fetch_entries[j].propq); if (sig == NULL) { fprintf(stderr, "Failed to fetch %s\n", fetch_alg); - err = 1; + error = 1; return; } EVP_SIGNATURE_free(sig); break; } default: - err = 1; + error = 1; return; } counts[num]++; @@ -392,7 +392,7 @@ int main(int argc, char *argv[]) goto out; } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/evp_setpeer.c b/source/evp_setpeer.c index 797049d8..6aed4087 100644 --- a/source/evp_setpeer.c +++ b/source/evp_setpeer.c @@ -26,7 +26,7 @@ #define RUN_TIME 5 -int err = 0; +int error = 0; size_t num_calls; static int threadcount; @@ -43,13 +43,13 @@ void do_setpeer(size_t num) pkey_ctx = EVP_PKEY_CTX_new(pkey, NULL); if (pkey_ctx == NULL) { - err = 1; + error = 1; printf("Failed to create pkey_ctx\n"); return; } if (EVP_PKEY_derive_init(pkey_ctx) <= 0) { - err = 1; + error = 1; printf("Failed to init pkey_ctx\n"); EVP_PKEY_CTX_free(pkey_ctx); return; @@ -59,7 +59,7 @@ void do_setpeer(size_t num) do { if (EVP_PKEY_derive_set_peer(pkey_ctx, pkey) <= 0) { - err = 1; + error = 1; break; } counts[num]++; @@ -98,7 +98,7 @@ static double get_avcalltime(void) static void report_result(int key_id, int terse) { - if (err) { + if (error) { fprintf(stderr, "Error during test of %s\n", sample_names[key_id]); exit(EXIT_FAILURE); @@ -232,7 +232,7 @@ int main(int argc, char *argv[]) EVP_PKEY_free(pkey); } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/handshake.c b/source/handshake.c index 87685953..3ed57b65 100644 --- a/source/handshake.c +++ b/source/handshake.c @@ -25,7 +25,7 @@ #define RUN_TIME 5 -int err = 0; +int error = 0; #if OPENSSL_VERSION_NUMBER >= 0x30000000L typedef enum { @@ -107,7 +107,7 @@ static void do_handshake(size_t num) } while (time.t < max_time.t); if (!ret) - err = 1; + error = 1; } #if OPENSSL_VERSION_NUMBER >= 0x30000000L @@ -124,7 +124,7 @@ static void do_handshake_ossl_lib_ctx_per_thread(size_t num) libctx = OSSL_LIB_CTX_new(); if (libctx == NULL) { fprintf(stderr, "%s:%d: Failed to create ossl lib context\n", __FILE__, __LINE__); - err = 1; + error = 1; return; } @@ -139,7 +139,7 @@ static void do_handshake_ossl_lib_ctx_per_thread(size_t num) privkey)) { ERR_print_errors_fp(stderr); fprintf(stderr, "%s:%d: Failed to create SSL_CTX pair\n", __FILE__, __LINE__); - err = 1; + error = 1; return; } } @@ -164,7 +164,7 @@ static void do_handshake_ossl_lib_ctx_per_thread(size_t num) SSL_CTX_free(lcctx); if (!ret) - err = 1; + error = 1; OSSL_LIB_CTX_free(libctx); } @@ -194,7 +194,7 @@ static void do_handshake_ctx_pool(size_t num) privkey)) { ERR_print_errors_fp(stderr); fprintf(stderr, "%s:%d: Failed to create SSL_CTX pair\n", __FILE__, __LINE__); - err = 1; + error = 1; return; } } @@ -210,7 +210,7 @@ static void do_handshake_ctx_pool(size_t num) privkey)) { ERR_print_errors_fp(stderr); fprintf(stderr, "%s:%d: Failed to create SSL_CTX pair\n", __FILE__, __LINE__); - err = 1; + error = 1; return; } } @@ -237,7 +237,7 @@ static void do_handshake_ctx_pool(size_t num) } if (!ret) - err = 1; + error = 1; } static void free_ctx_pool() @@ -509,7 +509,7 @@ int main(int argc, char * const argv[]) goto err; }; - if (err) { + if (error) { printf("Error during test\n"); goto err; } diff --git a/source/newrawkey.c b/source/newrawkey.c index 850e5c22..613687f2 100644 --- a/source/newrawkey.c +++ b/source/newrawkey.c @@ -37,7 +37,7 @@ enum { } algorithm = ALGO_X25519; const char *alg_name = "X25519"; -int err = 0; +int error = 0; static unsigned char key_x25519[32] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, @@ -376,7 +376,7 @@ void do_newrawkey(size_t num) sizeof(key_data)); #endif if (pkey == NULL) - err = 1; + error = 1; else EVP_PKEY_free(pkey); counts[num]++; @@ -454,7 +454,7 @@ int main(int argc, char *argv[]) goto out; } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/perflib/err.c b/source/perflib/err.c index 3a3164d7..46142372 100644 --- a/source/perflib/err.c +++ b/source/perflib/err.c @@ -7,9 +7,11 @@ * https://www.openssl.org/source/license.html */ +#include #include #include #include +#include "err.h" const char *progname; @@ -23,6 +25,20 @@ vwarnx(const char *fmt, va_list ap) } void +vwarn(const char *fmt, va_list ap) +{ + int saved_errno = errno; + + if (progname != NULL) + fprintf(stderr, "%s: ", progname); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": "); + + errno = saved_errno; + perror(NULL); +} + +ossl_noreturn void errx(int status, const char *fmt, ...) { va_list ap; @@ -33,6 +49,17 @@ errx(int status, const char *fmt, ...) exit(status); } +ossl_noreturn void +err(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vwarn(fmt, ap); + va_end(ap); + exit(status); +} + void warnx(const char *fmt, ...) { @@ -42,3 +69,13 @@ warnx(const char *fmt, ...) vwarnx(fmt, ap); va_end(ap); } + +void +warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vwarn(fmt, ap); + va_end(ap); +} diff --git a/source/perflib/err.h b/source/perflib/err.h index 0bb7a53f..a246f1d8 100644 --- a/source/perflib/err.h +++ b/source/perflib/err.h @@ -11,13 +11,23 @@ # define OSSL_PERFLIB_ERR_H # pragma once -#include +# if !defined(_WIN32) +# include +# else /* _WIN32 */ +# include +# include "noreturn.h" extern const char *progname; extern void vwarnx(const char *, va_list); -extern void errx(int, const char *, ...); +extern void vwarn(const char *, va_list); + +extern ossl_noreturn void errx(int, const char *, ...); +extern ossl_noreturn void err(int, const char *, ...); + extern void warnx(const char *, ...); +extern void warn(const char *, ...); +# endif /* !_WIN32 */ #endif diff --git a/source/perflib/noreturn.h b/source/perflib/noreturn.h new file mode 100644 index 00000000..ddfe893e --- /dev/null +++ b/source/perflib/noreturn.h @@ -0,0 +1,30 @@ +/* + * Copyright 2025 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 + */ + +#ifndef OSSL_PERFLIB_NORETURN_H +# define OSSL_PERFLIB_BASENAME_H +# pragma once + +# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L \ + && !defined(__cplusplus) +/* _Noreturn is deprecated in C23 in favor to [[noreturn]] */ +# define ossl_noreturn [[noreturn]] +# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L \ + && !defined(__cplusplus) +# define ossl_noreturn _Noreturn +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define ossl_noreturn __attribute__((noreturn)) +# elif defined(_MSC_VER) +# define ossl_noreturn __declspec(noreturn) +# else +# define ossl_noreturn +# endif + + +#endif /* OSSL_PERFLIB_NORETURN_H */ diff --git a/source/pkeyread.c b/source/pkeyread.c index 262734de..f72a2eff 100644 --- a/source/pkeyread.c +++ b/source/pkeyread.c @@ -51,7 +51,7 @@ size_t num_calls; size_t *counts; OSSL_TIME max_time; -int err = 0; +int error = 0; static int threadcount; @@ -70,7 +70,7 @@ static void do_pemread(size_t num) if (sample_id >= SAMPLE_ALL) { fprintf(stderr, "%s no sample key set for test\n", __func__); - err = 1; + error = 1; return; } @@ -81,7 +81,7 @@ static void do_pemread(size_t num) if (pem == NULL) { fprintf(stderr, "%s Cannot create mem BIO [%s PEM]\n", __func__, sample_names[sample_id]); - err = 1; + error = 1; return; } @@ -94,14 +94,14 @@ static void do_pemread(size_t num) if (key == NULL) { fprintf(stderr, "Failed to create key [%s PEM]\n", sample_names[sample_id]); - err = 1; + error = 1; goto end; } EVP_PKEY_free(key); if (BIO_reset(pem) == 0) { fprintf(stderr, "Failed to reset BIO [%s PEM]\n", sample_names[sample_id]); - err = 1; + error = 1; goto end; } @@ -131,7 +131,7 @@ static void do_derread(size_t num) if (sample_id >= SAMPLE_ALL) { fprintf(stderr, "%s no sample key set for test\n", __func__); - err = 1; + error = 1; return; } @@ -145,7 +145,7 @@ static void do_derread(size_t num) if (pkey == NULL) { fprintf(stderr, "%s pkey is NULL [%s DER]\n", __func__, sample_names[sample_id]); - err = 1; + error = 1; goto error; } error: @@ -239,7 +239,7 @@ static void report_result(int key_id, int format_id, int verbosity) { struct call_times times = { 0 }; - if (err) { + if (error) { fprintf(stderr, "Error during test of %s in %s format\n", sample_names[key_id], format_names[format_id]); exit(EXIT_FAILURE); diff --git a/source/randbytes.c b/source/randbytes.c index 7d14fcf6..230b39f7 100644 --- a/source/randbytes.c +++ b/source/randbytes.c @@ -28,7 +28,7 @@ size_t num_calls; size_t *counts; OSSL_TIME max_time; -int err = 0; +int error = 0; static int threadcount; @@ -41,7 +41,7 @@ void do_randbytes(size_t num) do { if (!RAND_bytes(buf, sizeof(buf))) - err = 1; + error = 1; counts[num]++; time = ossl_time_now(); } while (time.t < max_time.t); @@ -95,7 +95,7 @@ int main(int argc, char *argv[]) goto out; } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/rsasign.c b/source/rsasign.c index b7775015..b2bac80c 100644 --- a/source/rsasign.c +++ b/source/rsasign.c @@ -26,7 +26,7 @@ #define RUN_TIME 5 -int err = 0; +int error = 0; EVP_PKEY *rsakey = NULL; size_t *counts; @@ -61,7 +61,7 @@ void do_rsasign(size_t num) if (EVP_PKEY_sign_init(ctx) <= 0 || EVP_PKEY_sign(ctx, sig, &siglen, (const unsigned char*)tbs, SHA_DIGEST_LENGTH) <= 0) { - err = 1; + error = 1; break; } counts[num]++; @@ -134,7 +134,7 @@ int main(int argc, char *argv[]) goto out; } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/rwlocks.c b/source/rwlocks.c index 0807a1c9..806dbef2 100644 --- a/source/rwlocks.c +++ b/source/rwlocks.c @@ -26,7 +26,7 @@ #define RUN_TIME 5 size_t threadcount = 0; -int err = 0; +int error = 0; unsigned long *dataval = NULL; int writers = 0; int readers = 0; @@ -165,7 +165,7 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } - if (err) { + if (error) { printf("Error during test\n"); return EXIT_FAILURE; } diff --git a/source/ssl_poll_perf.c b/source/ssl_poll_perf.c index 1982d64e..771912de 100644 --- a/source/ssl_poll_perf.c +++ b/source/ssl_poll_perf.c @@ -33,7 +33,6 @@ #ifdef _WIN32 /* Windows */ # include #else /* Linux/Unix */ -# include # include # include # include @@ -54,10 +53,11 @@ #else # include # include "perflib/basename.h" -# include "perflib/err.h" # include "perflib/getopt.h" #endif /* _WIN32 */ +#include "perflib/err.h" + /* * The code here is based on QUIC poll server found in demos/quic/poll-server * in OpenSSL source code repository. Here we take the demo one step further diff --git a/source/sslnew.c b/source/sslnew.c index 4efa89a0..df2a8db9 100644 --- a/source/sslnew.c +++ b/source/sslnew.c @@ -24,7 +24,7 @@ #define RUN_TIME 5 -int err = 0; +int error = 0; static SSL_CTX *ctx; static int threadcount; @@ -46,7 +46,7 @@ void do_sslnew(size_t num) wbio = BIO_new(BIO_s_mem()); if (s == NULL || rbio == NULL || wbio == NULL) { - err = 1; + error = 1; BIO_free(rbio); BIO_free(wbio); } else { @@ -115,7 +115,7 @@ int main(int argc, char *argv[]) goto out; } - if (err) { + if (error) { printf("Error during test\n"); goto out; } diff --git a/source/writeread.c b/source/writeread.c index 60550664..9369134f 100644 --- a/source/writeread.c +++ b/source/writeread.c @@ -23,7 +23,7 @@ #define RUN_TIME 5 -int err = 0; +int error = 0; static SSL_CTX *sctx = NULL, *cctx = NULL; static int share_ctx = 1; @@ -57,7 +57,7 @@ static void do_writeread(size_t num) if (!perflib_create_ssl_ctx_pair(smethod, cmethod, 0, 0, &lsctx, &lcctx, cert, privkey)) { fprintf(stderr, "Failed to create SSL_CTX pair\n"); - err = 1; + error = 1; return; } } @@ -68,7 +68,7 @@ static void do_writeread(size_t num) ret &= perflib_create_bare_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE); if (!ret) { - err = 1; + error = 1; return; } @@ -76,18 +76,18 @@ static void do_writeread(size_t num) size_t written = 0; if (SSL_write_ex(clientssl, cbuf, buf_size, &written) <= 0) { fprintf(stderr, "Failed to write data\n"); - err = 1; + error = 1; return; } size_t readbytes; if (SSL_read_ex(serverssl, sbuf, buf_size, &readbytes) <= 0) { fprintf(stderr, "Failed to read data\n"); - err = 1; + error = 1; return; } if (readbytes != written) { fprintf(stderr, "Failed to read %ld bytes, got %ld\n", written, readbytes); - err = 1; + error = 1; return; } counts[num]++; @@ -194,7 +194,7 @@ int main(int argc, char * const argv[]) goto err; } - if (err) { + if (error) { fprintf(stderr, "Error during test\n"); goto err; } diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index bd927d0b..47c0b180 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -7,10 +7,16 @@ * https://www.openssl.org/source/license.html */ -#include +#include +#include +#include +#include #include +#include #include +#include #ifndef _WIN32 +# include # include # include #else @@ -20,158 +26,999 @@ #endif /* _WIN32 */ #include #include +#include "perflib/err.h" #include "perflib/perflib.h" +#define W_PROBABILITY 50 +#define MAX_WRITERS 0 #define RUN_TIME 5 +#define QUANTILES 5 +#define NONCE_CFG "file:servercert.pem" +#define MAX_LOAD_CERTS INT_MAX +#define MAX_LOAD_CERT_DIRS 0 +#define CTX_SHARE_THREADS 1 + +static size_t timeout_us = RUN_TIME * 1000000; +static size_t quantiles = QUANTILES; +static size_t max_writers = MAX_WRITERS; +static size_t w_probability = W_PROBABILITY * 65536 / 100; + +enum verbosity { + VERBOSITY_TERSE, + VERBOSITY_DEFAULT, + VERBOSITY_VERBOSE, + VERBOSITY_DEBUG_STATS, + VERBOSITY_DEBUG, + + VERBOSITY_MAX__ +}; + +static enum mode { + MODE_R, + MODE_RW, /* "MODE_W" is just MODE_RW with 100% write probability */ +} mode = MODE_R; + +enum nonce_type { + NONCE_PATH, +}; + +struct call_times { + uint64_t duration; + uint64_t total_count; + uint64_t total_found; + uint64_t total_added; + uint64_t min_count; + uint64_t max_count; + double avg; + double min; + double max; + double stddev; + double median; + size_t min_idx; + size_t max_idx; +}; + +struct nonce_cfg { + enum nonce_type type; + const char *path; + char **dirs; + size_t num_dirs; +}; -static int err = 0; +struct x509_pool { + size_t size; + size_t capacity; + X509 **entries; +}; + +/* Cache line size is either 32 or 64 bytes on most arches of interest */ +#if defined(__GNUC__) || defined(__clang__) +# define ALIGN64 __attribute((aligned(64))) +#elif defined(_MSC_VER) +# define ALIGN64 __declspec(align(64)) +#else +# define ALIGN64 +#endif + +ALIGN64 struct thread_data { + OSSL_TIME start_time; + struct { + uint64_t count; + uint64_t found; + uint64_t added_certs; + OSSL_TIME end_time; + } *q_data; + size_t cert_count; + X509 **certs; + X509_STORE_CTX *ctx; +} *thread_data; + +static int error = 0; +static int verbosity = VERBOSITY_DEFAULT; static X509_STORE *store = NULL; -static X509 *x509 = NULL; +static X509 *x509_nonce = NULL; static int threadcount; -size_t *counts; OSSL_TIME max_time; -static void do_x509storeissuer(size_t num) +#define OSSL_MIN(p, q) ((p) < (q) ? (p) : (q)) +#define OSSL_MAX(p, q) ((p) > (q) ? (p) : (q)) + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L && \ + !defined(__cplusplus) +# define ossl_thread thread_local +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && \ + !defined(__cplusplus) +# define ossl_thread _Thread_local +#elif defined(__GNUC__) +# define ossl_thread __thread +#elif defined(_MSC_VER) +# define ossl_thread __declspec(thread) +#else +# define ossl_thread +#endif + +static ossl_thread uint32_t prng_seed; + +static uint32_t prng(void) { - X509_STORE_CTX *ctx = X509_STORE_CTX_new(); - X509 *issuer = NULL; - OSSL_TIME time; + /* Taken from FreeBSD's lib/libc/stdlib/rand.c */ + return (prng_seed = prng_seed * 1103515245U + 12345U); +} + +/** + * Extend the x509_pool, by the increment, if inc is 0, the increment size + * is decided automatically. + */ +static struct x509_pool * +x509_pool_extend(struct x509_pool *pool, size_t inc) +{ + X509 **realloc_ptr; + + if (inc == 0) { + inc = OSSL_MIN(OSSL_MAX(64, pool->capacity), + OSSL_MAX(65536, pool->capacity / 16)); + } + + if (inc >= SIZE_MAX / sizeof(pool->entries[0]) - pool->capacity) + errx(EXIT_FAILURE, "x509_pool_extend: the increment is too big" + " (capacity %zu, increment %zu)", + pool->capacity, inc); + + realloc_ptr = OPENSSL_realloc(pool->entries, (pool-> capacity + inc) + * sizeof(pool->entries[0])); + if (realloc_ptr == NULL) + errx(EXIT_FAILURE, "x509_pool_extend: realloc() failed ()" + " (capacity %zu, increment %zu)", + pool->capacity, inc); + + pool->entries = realloc_ptr; + pool->capacity += inc; + + return pool; +} + +/** + * Returns a pointer to a next entry in the pool, automatically extending (and, + * thusly, potentially reallocating) the pool if needed. + */ +static X509 ** +x509_pool_next(struct x509_pool *pool) +{ + if (pool->size >= pool->capacity) + x509_pool_extend(pool, 0); - if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509, NULL)) { - printf("Failed to initialise X509_STORE_CTX\n"); - err = 1; - goto err; + return pool->entries + pool->size++; +} + +static void +x509_pool_empty(struct x509_pool *pool) +{ + if (pool->entries != NULL) { + for (size_t i = 0; i < pool->size; i++) + X509_free(pool->entries[i]); } - counts[num] = 0; + OPENSSL_free(pool->entries); + + pool->entries = NULL; + pool->size = pool->capacity = 0; +} + +static X509 * +load_cert_from_file(const char *path) +{ + BIO *bio = BIO_new_file(path, "rb"); + X509 *x509 = NULL; + + if (bio == NULL) { + warnx("Unable to create BIO for reading \"%s\"", path); + return NULL; + } + + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (x509 == NULL) { + if (verbosity >= VERBOSITY_DEBUG) + warnx("Failed to read certificate \"%s\"", path); + } + + BIO_free(bio); + + return x509; +} + +static bool +is_abs_path(const char *path) +{ + if (path == NULL) + return false; + +#if defined(_WIN32) + /* + * So, we don't try to concatenate the provided path with the directory + * paths if the path starts with the following: + * - volume character and a colon ("C:"): it is either absolute path + * (if followed by a backslash), or a relative path to a current + * directory of that volume (and we don't want to implement any logic + * that handles that); + * - backslash ("\"): it is an "absolute path" on the "current" drive, + * or (if there are two backslashes in the beginning) an UNC path. + */ + return (isalpha(path[0]) && path[1] == ':') || path[0] == '\\'; +#else /* !_WIN32 */ + return path[0] == '/'; +#endif +} + +static X509 * +load_nonce_from_path(struct nonce_cfg *cfg) +{ + if (is_abs_path(cfg->path)) + return load_cert_from_file(cfg->path); + + for (size_t i = 0; i < cfg->num_dirs; i++) { + char *cert; + X509 *ret; + + cert = perflib_mk_file_path(cfg->dirs[i], cfg->path); + if (cert == NULL) { + warnx("Failed to allocate file path for directory \"%s\"" + " and path \"%s\"", cfg->dirs[i], cfg->path); + continue; + } + + ret = load_cert_from_file(cert); + OPENSSL_free(cert); + + if (ret != NULL) + return ret; + } + + return NULL; +} + +static X509 * +make_nonce(struct nonce_cfg *cfg) +{ + switch (cfg->type) { + case NONCE_PATH: + return load_nonce_from_path(cfg); + default: + errx(EXIT_FAILURE, "Unknown nonce type: %lld", (long long) cfg->type); + } +} + +static size_t +read_cert(const char * const dir, const char * const name, + X509_STORE * const store, + struct x509_pool * const pool, size_t * const pool_cnt) +{ + X509 *x509 = NULL; + char *path = NULL; + size_t ret = 0; + + if (store == NULL && pool == NULL) + return 0; + + path = perflib_mk_file_path(dir, name); + if (path == NULL) { + warn("Failed to allocate cert name in directory \"%s\" for file \"%s\"", + dir, name); + goto out; + } + + x509 = load_cert_from_file(path); + if (x509 == NULL) { + goto out; + } + + if (store != NULL) { + if (!X509_STORE_add_cert(store, x509)) { + warnx("Failed to add a certificate from \"%s\" to the store\n", + path); + goto out; + } + + if (verbosity >= VERBOSITY_DEBUG) + fprintf(stderr, "Successfully added a certificate from \"%s\"" + " to the store\n", path); + } else { + X509 **ptr = x509_pool_next(pool); + + *ptr = x509; + + if (verbosity >= VERBOSITY_DEBUG) { + fprintf(stderr, "Added a certificate from \"%s\" to the pool\n", + path); + } + + if (pool_cnt != NULL) + *pool_cnt += 1; + } + + ret = 1; + + out: + if (store != NULL) + X509_free(x509); + OPENSSL_free(path); + + return ret; +} + +#if defined(_WIN32) +static size_t +read_certsdir(char * const dir, const size_t max_certs, + X509_STORE * const store, + struct x509_pool * const pool, size_t * const pool_cnt) +{ + const size_t dir_len = strlen(dir); + const size_t glob_len = dir_len + sizeof("\\*"); + size_t cnt = 0; + char *search_glob = NULL; + HANDLE find_handle = INVALID_HANDLE_VALUE; + WIN32_FIND_DATA find_data; + DWORD last_err; + + if (pool == NULL && max_certs == 0) + return 0; + + search_glob = OPENSSL_malloc(glob_len); + if (search_glob == NULL) { + warnx("Error allocating a search glob for \"%s\"", dir); + return 0; + } + + if (snprintf(search_glob, glob_len, "%s\\*", dir) != glob_len - 1) { + warnx("Error generating a search glob for \"%s\"", dir); + goto out; + } + + find_handle = FindFirstFileA(search_glob, &find_data); + if (find_handle == INVALID_HANDLE_VALUE) { + warnx("Error in FindFirstFile(): %#lx", GetLastError()); + goto out; + } do { - /* - * We actually expect this to fail. We've not configured any - * certificates inside our store. We're just testing calling this - * against an empty store. - */ - if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509) != 0) { - printf("Unexpected result from X509_STORE_CTX_get1_issuer\n"); - err = 1; - X509_free(issuer); - goto err; + if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (verbosity >= VERBOSITY_DEBUG) + warnx("\"%s\\%s\" is a directory file, skipping", + dir, find_data.cFileName); + continue; + } + + cnt += read_cert(dir, find_data.cFileName, + cnt < max_certs ? store : NULL, pool, pool_cnt); + + if (pool == NULL && cnt >= max_certs) + break; + } while (FindNextFileA(find_handle, &find_data) != 0); + + last_err = GetLastError(); + if (last_err != ERROR_NO_MORE_FILES) + warnx("Error in FindNextFile(): %#lx", last_err); + + out: + if (find_handle != INVALID_HANDLE_VALUE) + FindClose(find_handle); + OPENSSL_free(search_glob); + + return cnt; +} +#else /* !defined(_WIN32) */ +static size_t +read_certsdir(char * const dir, const size_t max_certs, + X509_STORE * const store, + struct x509_pool * const pool, size_t * const pool_cnt) +{ + struct dirent *e; + DIR *d; + size_t cnt = 0; + + if (pool == NULL && max_certs == 0) + return 0; + + d = opendir(dir); + if (d == NULL) { + warn("Could not open \"%s\"", dir); + + return 0; + } + + while (1) { + errno = 0; + e = readdir(d); + + if (e == NULL) { + if (errno != 0) + warn("An error occurred while reading directory \"%s\"", dir); + + break; + } + + if (e->d_type != DT_REG && e->d_type != DT_UNKNOWN) { + if (verbosity >= VERBOSITY_DEBUG) + warnx("\"%s/%s\" is not a regular file, skipping", + dir, e->d_name); + continue; + } + + cnt += read_cert(dir, e->d_name, cnt < max_certs ? store : NULL, + pool, pool_cnt); + + if (pool == NULL && cnt >= max_certs) + break; + } + + closedir(d); + + return cnt; +} +#endif /* defined(_WIN32) */ + +/** + * Reads certificates (up to max_certs) from dirs (up to max_cert_dirs) into + * store, and stores the rest in pool for the later use (unless it is NULL, + * in which case certificate reading stops as soon as one of the aforementioned + * limit is reached). + * + * @param dirs Array of directory paths (dir_cnt in size) + * to (non-recursively) read certificates from. + * @param dir_cnt Number of elements in dirs parameter. + * @param max_certs Maximum number of certificates to read to store, + * the rest will be read to pool, if it is non-NULL. + * @param max_cert_dirs Maximum number of directories to read to the store, + * the rest will be read to pool, if it is non-NULL. + * @param store X509_STORE to read certificates to. + * @param pool Certificate pool where certificates are stored + * for later use. + * @param total_read If non-NULL, total number of certificates read + * in stored there. + * @return Number of certificates read into the store. + */ +static size_t +read_certsdirs(char * const * const dirs, const int dir_cnt, + const size_t max_certs, const int max_cert_dirs, + X509_STORE * const store, struct x509_pool *pool, + size_t * const total_read) +{ + /* + * For the ease of accounting, we count total and pool counts, + * but for the ease of reporting, we return store and total counts. + */ + size_t cnt = 0; + size_t pool_cnt = 0; + + for (int i = 0; i < dir_cnt; i++) { + cnt += read_certsdir(dirs[i], max_certs > cnt ? max_certs - cnt : 0, + i < max_cert_dirs ? store : NULL, + pool, pool ? &pool_cnt : NULL); + + if (pool == NULL && cnt >= max_certs) + break; + } + + if (total_read != NULL) + *total_read = cnt; + + return cnt - pool_cnt; +} + +static void +do_x509storeissuer(size_t num) +{ + struct thread_data *td = &thread_data[num]; + X509 *issuer = NULL; + OSSL_TIME time; + OSSL_TIME duration; + OSSL_TIME q_end; + size_t q = 0; + size_t count = 0; + size_t add = 0; + size_t found = 0; + + prng_seed = num; + td->start_time = ossl_time_now(); + duration.t = max_time.t - td->start_time.t; + q_end.t = duration.t / quantiles + td->start_time.t; + + do { + if (td->cert_count > 0 && (prng() % 65536 < w_probability)) { + size_t cert_id = add % td->cert_count; + + if (!X509_STORE_add_cert(store, td->certs[cert_id])) { + warnx("thread %zu: Failed to add generated certificate %zu" + " to the store", num, cert_id); + } else { + add++; + } + } else { + if (X509_STORE_CTX_get1_issuer(&issuer, td->ctx, x509_nonce) != 0) { + found++; + X509_free(issuer); + } + issuer = NULL; } - issuer = NULL; - counts[num]++; + + count++; time = ossl_time_now(); + if (time.t >= q_end.t) { + td->q_data[q].count = count; + td->q_data[q].found = found; + td->q_data[q].added_certs = add; + td->q_data[q].end_time = time; + q_end.t = (duration.t * (++q + 1)) / quantiles + td->start_time.t; + } } while (time.t < max_time.t); - err: - X509_STORE_CTX_free(ctx); + td->q_data[quantiles - 1].count = count; + td->q_data[quantiles - 1].found = found; + td->q_data[quantiles - 1].added_certs = add; + td->q_data[quantiles - 1].end_time = time; +} + +static void +report_store_size(X509_STORE * const store, const char * const suffix, + int verbosity) +{ + if (verbosity >= VERBOSITY_DEBUG_STATS) { + STACK_OF(X509_OBJECT) *sk = +#if OPENSSL_VERSION_NUMBER >= 0x30300000L + X509_STORE_get1_objects(store); +#else + X509_STORE_get0_objects(store); +#endif + + fprintf(stderr, "Number of certificates in the store %s: %d\n", + suffix, sk_X509_OBJECT_num(sk)); + +#if OPENSSL_VERSION_NUMBER >= 0x30300000L + sk_X509_OBJECT_pop_free(sk, X509_OBJECT_free); +#endif + } +} + +static int +cmp_double(const void *a_ptr, const void *b_ptr) +{ + const double * const a = a_ptr; + const double * const b = b_ptr; + + return *a - *b < 0 ? -1 : *a - *b > 0 ? 1 : 0; +} + +static void +get_calltimes(struct call_times *times, int verbosity) +{ + double *thread_times; + + for (size_t q = 0; q < quantiles; q++) { + for (size_t i = 0; i < threadcount; i++) { + uint64_t start_t = q ? thread_data[i].q_data[q - 1].end_time.t + : thread_data[i].start_time.t; + uint64_t count = thread_data[i].q_data[q].count - + (q ? thread_data[i].q_data[q - 1].count : 0); + uint64_t found = thread_data[i].q_data[q].found - + (q ? thread_data[i].q_data[q - 1].found : 0); + uint64_t add = thread_data[i].q_data[q].added_certs - + (q ? thread_data[i].q_data[q - 1].added_certs : 0); + + times[q].duration += thread_data[i].q_data[q].end_time.t - start_t; + times[q].total_count += count; + times[q].total_found += found; + times[q].total_added += add; + } + } + + for (size_t q = 0; q < quantiles; q++) { + times[quantiles].duration += times[q].duration; + times[quantiles].total_count += times[q].total_count; + times[quantiles].total_found += times[q].total_found; + times[quantiles].total_added += times[q].total_added; + } + + for (size_t q = (quantiles == 1); q <= quantiles; q++) + times[q].avg = (double) times[q].duration / OSSL_TIME_US / times[q].total_count; + + if (verbosity >= VERBOSITY_VERBOSE) { + thread_times = OPENSSL_zalloc(threadcount * sizeof(*thread_times)); + + for (size_t q = (quantiles == 1); q <= quantiles; q++) { + double variance = 0; + + for (size_t i = 0; i < threadcount; i++) { + uint64_t start_t = q && q != quantiles + ? thread_data[i].q_data[q - 1].end_time.t + : thread_data[i].start_time.t; + uint64_t duration = + thread_data[i].q_data[OSSL_MIN(q, quantiles - 1)].end_time.t + - start_t; + uint64_t count = + thread_data[i].q_data[OSSL_MIN(q, quantiles - 1)].count - + (q && q != quantiles ? thread_data[i].q_data[q - 1].count + : 0); + thread_times[i] = (double) duration / OSSL_TIME_US / count; + } + + times[q].min = times[q].max = thread_times[0]; + times[q].min_idx = times[q].max_idx = 0; + + for (size_t i = 0; i < threadcount; i++) { + if (thread_times[i] < times[q].min) { + times[q].min = thread_times[i]; + times[q].min_idx = i; + } + + if (thread_times[i] > times[q].max) { + times[q].max = thread_times[i]; + times[q].max_idx = i; + } + } + + qsort(thread_times, threadcount, sizeof(thread_times[0]), cmp_double); + times[q].median = thread_times[threadcount / 2]; + + for (size_t i = 0; i < threadcount; i++) { + double dev = thread_times[i] - times[q].avg; + + variance += dev * dev; + } + + times[q].stddev = sqrt(variance / threadcount); + } + + OPENSSL_free(thread_times); + } +} + +static void +report_result(int verbosity) +{ + struct call_times *times; + + times = OPENSSL_zalloc(sizeof(*times) * (quantiles + 1)); + + get_calltimes(times, verbosity); + + switch (verbosity) { + case VERBOSITY_TERSE: + printf("%lf\n", times[1].avg); + break; + case VERBOSITY_DEFAULT: + printf("Average time per call: %lfus\n", times[1].avg); + break; + case VERBOSITY_VERBOSE: + default: + /* if quantiles == 1, we only need to print total runtime info */ + for (size_t i = (quantiles == 1); i <= quantiles; i++) { + if (i < quantiles) + printf("Part %8zu", i + 1); + else + printf("Total runtime"); + + printf(": avg: %9.3lf us, median: %9.3lf us" + ", min: %9.3lf us @thread %3zu, max: %9.3lf us @thread %3zu" + ", stddev: %9.3lf us (%8.4lf%%)" + ", hits %9" PRIu64 " of %9" PRIu64 " (%8.4lf%%)" + ", added certs: %" PRIu64 "\n", + times[i].avg, times[i].median, + times[i].min, times[i].min_idx, + times[i].max, times[i].max_idx, + times[i].stddev, + 100.0 * times[i].stddev / times[i].avg, + times[i].total_found, + (times[i].total_count - times[i].total_added), + 100.0 * times[i].total_found + / (times[i].total_count - times[i].total_added), + times[i].total_added); + } + break; + } + + OPENSSL_free(times); +} + +static void +usage(char * const argv[]) +{ + fprintf(stderr, + "Usage: %s [-t] [-v] [-q N] [-T time] [-n nonce_type:type_args]" + " [-m mode] [-w writer_threads] [-W percentage]" + " [-l max_certs] [-L max_cert_dirs] [-E] [-C threads]" + " [-V] certsdir [certsdir...] threadcount\n" + "\t-t\tTerse output\n" + "\t-v\tVerbose output. Multiple usage increases verbosity.\n" + "\t-q\tGather information about temporal N-quantiles.\n" + "\t\tDone only when the output is verbose. Default: " + OPENSSL_MSTR(QUANTILES) "\n" + "\t-T\tTimeout for the test run in seconds,\n" + "\t\tcan be fractional. Default: " + OPENSSL_MSTR(RUN_TIME) "\n" + "\t-n\tNonce configuration, supported options:\n" + "\t\t\tfile:PATH - load nonce certificate from PATH;\n" + "\t\t\tif PATH is relative, the provided certsdir's are searched.\n" + "\t\tDefault: " NONCE_CFG "\n" + "\t-m\tTest mode, can be one of r, rw. Default: r\n" + "\t-w\tMaximum number of threads that attempt addition\n" + "\t\tof the new certificates to the store in rw mode,\n" + "\t\t0 is unlimited. Default: " OPENSSL_MSTR(MAX_WRITERS) "\n" + "\t-W\tProbability of a certificate being written\n" + "\t\tto the store, instead of being queried,\n" + "\t\tin percents. Default: " OPENSSL_MSTR(W_PROBABILITY) "\n" + "\t-l\tLimit on the number of initially loaded certificates.\n" + "\t\tDefault: " OPENSSL_MSTR(MAX_LOAD_CERTS) "\n" + "\t-L\tLimit on the number of initially loaded certificate\n" + "\t\tdirectories. Default: " OPENSSL_MSTR(MAX_LOAD_CERT_DIRS) "\n" + "\t-E\tDo not call X509_STORE_set_default_paths()\n" + "\t-C\tNumber of threads that share the same X.509\n" + "\t\tstore context object. Default: " + OPENSSL_MSTR(CTX_SHARE_THREADS) "\n" + "\t-V\tprint version information and exit\n" + , basename(argv[0])); } -int main(int argc, char *argv[]) +static size_t +parse_timeout(const char * const optarg) +{ + char *endptr = NULL; + double timeout_s; + + timeout_s = strtod(optarg, &endptr); + + if (endptr == NULL || *endptr != '\0' || timeout_s < 0) + errx(EXIT_FAILURE, "incorrect timeout value: \"%s\"", optarg); + + if (timeout_s > SIZE_MAX / 1000000) + errx(EXIT_FAILURE, "timeout is too large: %f", timeout_s); + + return (size_t)(timeout_s * 1e6); +} + +static double +parse_probability(const char * const optarg) +{ + char *endptr = NULL; + double prob; + + prob = strtod(optarg, &endptr); + + if (endptr == NULL || *endptr != '\0' || prob < 0 || prob > 100) + errx(EXIT_FAILURE, "incorrect probability value: \"%s\"", optarg); + + return prob; +} + +/** + * Parse nonce configuration string. Currently supported formats: + * * "gen" - generate a nonce certificate + * * "file:PATH" - where PATH is either a relative path (that will be then + * checked against the list of directories provided), + * or an absolute one. + */ +static void +parse_nonce_cfg(const char * const optarg, struct nonce_cfg *cfg) +{ + static const char file_pfx[] = "file:"; + + if (strncmp(optarg, file_pfx, sizeof(file_pfx) - 1) == 0) { + cfg->type = NONCE_PATH; + cfg->path = optarg + sizeof(file_pfx) - 1; + } else { + errx(EXIT_FAILURE, "incorrect nonce configuration: \"%s\"", optarg); + } +} + +static long long +parse_int(const char * const s, long long min, long long max, + const char * const what) +{ + char *endptr = NULL; + long long ret; + + ret = strtoll(s, &endptr, 0); + if (endptr == NULL || *endptr != '\0') + errx(EXIT_FAILURE, "failed to parse %s as a number: \"%s\"", what, s); + if (ret < min || ret > max) + errx(EXIT_FAILURE, "provided value of %s is out of the expected" + " %lld..%lld range: %lld", what, min, max, ret); + + return ret; +} + +int +main(int argc, char *argv[]) { - int i; OSSL_TIME duration; - size_t total_count = 0; - double avcalltime; - int terse = 0; - char *cert = NULL; + size_t ctx_share_cnt = CTX_SHARE_THREADS; int ret = EXIT_FAILURE; - BIO *bio = NULL; int opt; + int dirs_start; + size_t num_certs = 0; + size_t total_certs_read = 0; + size_t max_load_certs = MAX_LOAD_CERTS; + int max_load_cert_dirs = MAX_LOAD_CERT_DIRS; + bool skip_default_paths = false; + struct x509_pool cert_pool = { 0 }; + struct nonce_cfg nonce_cfg; - while ((opt = getopt(argc, argv, "tV")) != -1) { + parse_nonce_cfg(NONCE_CFG, &nonce_cfg); + + while ((opt = getopt(argc, argv, "tvq:T:n:m:w:W:l:L:EC:V")) != -1) { switch (opt) { - case 't': - terse = 1; + case 't': /* terse */ + verbosity = VERBOSITY_TERSE; + break; + case 'v': /* verbose */ + if (verbosity < VERBOSITY_VERBOSE) { + verbosity = VERBOSITY_VERBOSE; + } else { + if (verbosity < VERBOSITY_MAX__ - 1) + verbosity++; + } + break; + case 'q': /* quantiles */ + quantiles = parse_int(optarg, 1, INT_MAX, + "number of quantiles"); + break; + case 'T': /* timeout */ + timeout_us = parse_timeout(optarg); + break; + case 'n': /* nonce */ + parse_nonce_cfg(optarg, &nonce_cfg); + break; + case 'm': /* mode */ + if (strcasecmp(optarg, "r") == 0) { + mode = MODE_R; + } else if (strcasecmp(optarg, "rw") == 0) { + mode = MODE_RW; + } else { + errx(EXIT_FAILURE, "Unknown mode: \"%s\"", optarg); + } + break; + case 'w': /* maximum writers */ + max_writers = parse_int(optarg, 0, INT_MAX, + "maximum number of writers"); + break; + case 'W': /* percent of writes */ + w_probability = (size_t) (parse_probability(optarg) * 65536 / 100); + break; + case 'l': /* max certs to load */ + max_load_certs = parse_int(optarg, 0, INT_MAX, + "maximum certificate load count"); + break; + case 'L': /* max certs dirs to load*/ + max_load_cert_dirs = parse_int(optarg, 0, INT_MAX, + "maximum certificate directories" + " load count"); + break; + case 'E': + skip_default_paths = true; + break; + case 'C': /* how many threads share X509_STORE_CTX */ + ctx_share_cnt = parse_int(optarg, 1, INT_MAX, + "X509_STORE_CTX share degree"); break; case 'V': perflib_print_version(basename(argv[0])); return EXIT_SUCCESS; default: - printf("Usage: %s [-t] [-V] certsdir threadcount\n", basename(argv[0])); - printf("-t - terse output\n"); - printf("-V - print version information and exit\n"); + usage(argv); return EXIT_FAILURE; } } - if (argv[optind] == NULL) { - printf("certsdir is missing\n"); - goto err; - } - cert = perflib_mk_file_path(argv[optind], "servercert.pem"); - if (cert == NULL) { - printf("Failed to allocate cert\n"); - goto err; - } - optind++; + if (verbosity < VERBOSITY_VERBOSE) + quantiles = 1; - if (argv[optind] == NULL) { - printf("threadcount is missing\n"); - goto err; - } - threadcount = atoi(argv[optind]); - if (threadcount < 1) { - printf("threadcount must be > 0\n"); - goto err; + if (argv[optind] == NULL) + errx(EXIT_FAILURE, "certsdir is missing"); + + dirs_start = optind++; + + /* + * Store the part of argv containing directories to nonce_cfg so + * load_nonce_from_path can use it later. + */ + nonce_cfg.dirs = argv + dirs_start; + nonce_cfg.num_dirs = argc - 1 - dirs_start; + + if (optind >= argc) + errx(EXIT_FAILURE, "threadcount is missing"); + + threadcount = parse_int(argv[argc - 1], 1, INT_MAX, "threadcount"); + + thread_data = OPENSSL_zalloc(threadcount * sizeof(*thread_data)); + if (thread_data == NULL) + errx(EXIT_FAILURE, "Failed to create thread_data array"); + + for (size_t i = 0; i < threadcount; i++) { + thread_data[i].q_data = OPENSSL_zalloc(quantiles * + sizeof(*(thread_data[i].q_data))); + if (thread_data[i].q_data == NULL) + errx(EXIT_FAILURE, "Failed to create quantiles array for thread" + " %zu", i); } store = X509_STORE_new(); - if (store == NULL || !X509_STORE_set_default_paths(store)) { - printf("Failed to create X509_STORE\n"); - goto err; + if (store == NULL) + errx(EXIT_FAILURE, "Failed to create X509_STORE"); + if (!skip_default_paths) { + if (!X509_STORE_set_default_paths(store)) + errx(EXIT_FAILURE, "Failed to load certificates from default paths"); } - bio = BIO_new_file(cert, "rb"); - if (bio == NULL) { - printf("Unable to load certificate\n"); - goto err; - } - x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); - if (x509 == NULL) { - printf("Failed to read certificate\n"); - goto err; - } - BIO_free(bio); - bio = NULL; + num_certs += read_certsdirs(argv + dirs_start, argc - dirs_start - 1, + max_load_certs, max_load_cert_dirs, + store, mode == MODE_RW ? &cert_pool : NULL, + &total_certs_read); - counts = OPENSSL_malloc(sizeof(size_t) * threadcount); - if (counts == NULL) { - printf("Failed to create counts array\n"); - goto err; - } + if (verbosity >= VERBOSITY_DEBUG_STATS) + fprintf(stderr, "Added %zu certificates to the store" + " (%zu certificates read in total)\n", + num_certs, total_certs_read); - max_time = ossl_time_add(ossl_time_now(), ossl_seconds2time(RUN_TIME)); + report_store_size(store, "before the test run", verbosity); - if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) { - printf("Failed to run the test\n"); - goto err; + x509_nonce = make_nonce(&nonce_cfg); + if (x509_nonce == NULL) + errx(EXIT_FAILURE, "Unable to create the nonce X509 object"); + + for (size_t i = 0; i < threadcount; i++) { + if (i % ctx_share_cnt) { + thread_data[i].ctx = thread_data[i - i % ctx_share_cnt].ctx; + } else { + thread_data[i].ctx = X509_STORE_CTX_new(); + if (thread_data[i].ctx == NULL + || !X509_STORE_CTX_init(thread_data[i].ctx, store, x509_nonce, + NULL)) + errx(EXIT_FAILURE, "Failed to initialise X509_STORE_CTX" + " for thread %zu", i); + } } - if (err) { - printf("Error during test\n"); - goto err; + if (mode == MODE_RW && cert_pool.size > 0) { + const size_t writers = max_writers ? OSSL_MIN(max_writers, threadcount) + : threadcount; + + /* + * Point each writer thread at the part of the generated certs + * array it uses for store population. + */ + for (size_t i = 0; i < writers; i++) { + size_t offs = (cert_pool.size * i) / writers; + + thread_data[i].certs = cert_pool.entries + offs; + thread_data[i].cert_count = OSSL_MAX(cert_pool.size / writers, 1); + } } - for (i = 0; i < threadcount; i++) - total_count += counts[i]; + max_time = ossl_time_add(ossl_time_now(), ossl_us2time(timeout_us)); + + if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) + errx(EXIT_FAILURE, "Failed to run the test"); - avcalltime = (double)RUN_TIME * 1e6 * threadcount / total_count; + if (error) + errx(EXIT_FAILURE, "Error during test"); - if (terse) - printf("%lf\n", avcalltime); - else - printf("Average time per X509_STORE_CTX_get1_issuer() call: %lfus\n", - avcalltime); + if (mode == MODE_RW) + report_store_size(store, "after the test run", verbosity); + + report_result(verbosity); ret = EXIT_SUCCESS; - err: + X509_free(x509_nonce); + x509_pool_empty(&cert_pool); X509_STORE_free(store); - X509_free(x509); - BIO_free(bio); - OPENSSL_free(cert); - OPENSSL_free(counts); + if (thread_data != NULL) { + for (size_t i = 0; i < threadcount; i++) { + if (!(i % ctx_share_cnt)) + X509_STORE_CTX_free(thread_data[i].ctx); + OPENSSL_free(thread_data[i].q_data); + } + } + OPENSSL_free(thread_data); return ret; }