From 6398216484edbfe0c75bd0dfffdcc8c81eabaff7 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 13:10:47 +0200 Subject: [PATCH 01/25] x509storeissuer: factor out usage printing Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index bd927d0b..ffd7b17e 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -68,6 +68,16 @@ static void do_x509storeissuer(size_t num) X509_STORE_CTX_free(ctx); } +static void +usage(char * const argv[]) +{ + fprintf(stderr, + "Usage: %s [-t] [-V] certsdir threadcount\n" + "-t - terse output\n" + "-V - print version information and exit\n", + basename(argv[0])); +} + int main(int argc, char *argv[]) { int i; @@ -89,9 +99,7 @@ int main(int argc, char *argv[]) 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; } } From 15c799bc8e794c856693f3f7ac5f000af6b957c9 Mon Sep 17 00:00:00 2001 From: sashan Date: Mon, 29 Sep 2025 14:33:19 +0200 Subject: [PATCH 02/25] s/err/error where apropriate easiest way to fix liner issues on windows --- source/evp_fetch.c | 22 +++++++++++----------- source/evp_setpeer.c | 12 ++++++------ source/handshake.c | 18 +++++++++--------- source/newrawkey.c | 6 +++--- source/pkeyread.c | 16 ++++++++-------- source/randbytes.c | 6 +++--- source/rsasign.c | 6 +++--- source/rwlocks.c | 4 ++-- source/sslnew.c | 6 +++--- source/writeread.c | 14 +++++++------- source/x509storeissuer.c | 9 +++++---- 11 files changed, 60 insertions(+), 59 deletions(-) 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/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/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 ffd7b17e..58242897 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -20,11 +20,12 @@ #endif /* _WIN32 */ #include #include +#include "perflib/err.h" #include "perflib/perflib.h" #define RUN_TIME 5 -static int err = 0; +static int error = 0; static X509_STORE *store = NULL; static X509 *x509 = NULL; @@ -41,7 +42,7 @@ static void do_x509storeissuer(size_t num) if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509, NULL)) { printf("Failed to initialise X509_STORE_CTX\n"); - err = 1; + error = 1; goto err; } @@ -55,7 +56,7 @@ static void do_x509storeissuer(size_t num) */ if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509) != 0) { printf("Unexpected result from X509_STORE_CTX_get1_issuer\n"); - err = 1; + error = 1; X509_free(issuer); goto err; } @@ -157,7 +158,7 @@ int main(int argc, char *argv[]) goto err; } - if (err) { + if (error) { printf("Error during test\n"); goto err; } From 74487895dc995e21615321de12e8e09d6d7e66c0 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Fri, 26 Sep 2025 01:26:11 +0200 Subject: [PATCH 03/25] Use perflib/err.h unconditionally Signed-off-by: Eugene Syromiatnikov --- source/perflib/err.h | 6 +++++- source/ssl_poll_perf.c | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/source/perflib/err.h b/source/perflib/err.h index 0bb7a53f..f00c7d9d 100644 --- a/source/perflib/err.h +++ b/source/perflib/err.h @@ -11,13 +11,17 @@ # define OSSL_PERFLIB_ERR_H # pragma once -#include +# if !defined(_WIN32) +# include +# else /* _WIN32 */ +# include extern const char *progname; extern void vwarnx(const char *, va_list); extern void errx(int, const char *, ...); extern void warnx(const char *, ...); +# endif /* !_WIN32 */ #endif 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 From ee5fd86ad101ba5ae3422355cdfd35fc02efa813 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Fri, 26 Sep 2025 01:28:17 +0200 Subject: [PATCH 04/25] perflib: add vwarn/err/warn Signed-off-by: Eugene Syromiatnikov --- source/perflib/err.c | 36 ++++++++++++++++++++++++++++++++++++ source/perflib/err.h | 5 +++++ 2 files changed, 41 insertions(+) diff --git a/source/perflib/err.c b/source/perflib/err.c index 3a3164d7..59764ffe 100644 --- a/source/perflib/err.c +++ b/source/perflib/err.c @@ -7,6 +7,7 @@ * https://www.openssl.org/source/license.html */ +#include #include #include #include @@ -22,6 +23,20 @@ vwarnx(const char *fmt, va_list ap) putc('\n', stderr); } +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); +} + void errx(int status, const char *fmt, ...) { @@ -33,6 +48,17 @@ errx(int status, const char *fmt, ...) exit(status); } +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 +68,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 f00c7d9d..25b64984 100644 --- a/source/perflib/err.h +++ b/source/perflib/err.h @@ -19,8 +19,13 @@ extern const char *progname; extern void vwarnx(const char *, va_list); +extern void vwarn(const char *, va_list); + extern void errx(int, const char *, ...); +extern void err(int, const char *, ...); + extern void warnx(const char *, ...); +extern void warn(const char *, ...); # endif /* !_WIN32 */ #endif From 6b108daff6fa23cd826c44b3e605939508086493 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 10 Nov 2025 15:45:16 +0100 Subject: [PATCH 05/25] perflib: add ossl_noreturn qualifier and use it for err/errx Signed-off-by: Eugene Syromiatnikov --- source/perflib/err.c | 5 +++-- source/perflib/err.h | 5 +++-- source/perflib/noreturn.h | 30 ++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 source/perflib/noreturn.h diff --git a/source/perflib/err.c b/source/perflib/err.c index 59764ffe..46142372 100644 --- a/source/perflib/err.c +++ b/source/perflib/err.c @@ -11,6 +11,7 @@ #include #include #include +#include "err.h" const char *progname; @@ -37,7 +38,7 @@ vwarn(const char *fmt, va_list ap) perror(NULL); } -void +ossl_noreturn void errx(int status, const char *fmt, ...) { va_list ap; @@ -48,7 +49,7 @@ errx(int status, const char *fmt, ...) exit(status); } -void +ossl_noreturn void err(int status, const char *fmt, ...) { va_list ap; diff --git a/source/perflib/err.h b/source/perflib/err.h index 25b64984..a246f1d8 100644 --- a/source/perflib/err.h +++ b/source/perflib/err.h @@ -15,14 +15,15 @@ # include # else /* _WIN32 */ # include +# include "noreturn.h" extern const char *progname; extern void vwarnx(const char *, va_list); extern void vwarn(const char *, va_list); -extern void errx(int, const char *, ...); -extern void err(int, const char *, ...); +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 *, ...); 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 */ From 2b7fcf480321848f8ebab9983c21755836b4c5b9 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 13:33:02 +0200 Subject: [PATCH 06/25] x509storeissuer: use errx/warnx for error message output Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 70 ++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 43 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 58242897..e2a75354 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -41,7 +41,7 @@ static void do_x509storeissuer(size_t num) OSSL_TIME time; if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509, NULL)) { - printf("Failed to initialise X509_STORE_CTX\n"); + warnx("Failed to initialise X509_STORE_CTX"); error = 1; goto err; } @@ -55,7 +55,7 @@ static void do_x509storeissuer(size_t num) * against an empty store. */ if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509) != 0) { - printf("Unexpected result from X509_STORE_CTX_get1_issuer\n"); + warnx("Unexpected result from X509_STORE_CTX_get1_issuer"); error = 1; X509_free(issuer); goto err; @@ -105,63 +105,48 @@ int main(int argc, char *argv[]) } } - if (argv[optind] == NULL) { - printf("certsdir is missing\n"); - goto err; - } + if (argv[optind] == NULL) + errx(EXIT_FAILURE, "certsdir is missing"); + cert = perflib_mk_file_path(argv[optind], "servercert.pem"); - if (cert == NULL) { - printf("Failed to allocate cert\n"); - goto err; - } + if (cert == NULL) + errx(EXIT_FAILURE, "Failed to allocate cert path"); + optind++; - if (argv[optind] == NULL) { - printf("threadcount is missing\n"); - goto err; - } + if (argv[optind] == NULL) + errx(EXIT_FAILURE, "threadcount is missing"); + threadcount = atoi(argv[optind]); - if (threadcount < 1) { - printf("threadcount must be > 0\n"); - goto err; - } + if (threadcount < 1) + errx(EXIT_FAILURE, "threadcount must be > 0"); 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 || !X509_STORE_set_default_paths(store)) + errx(EXIT_FAILURE, "Failed to create X509_STORE"); bio = BIO_new_file(cert, "rb"); - if (bio == NULL) { - printf("Unable to load certificate\n"); - goto err; - } + if (bio == NULL) + errx(EXIT_FAILURE, "Unable to load certificate\n"); + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); - if (x509 == NULL) { - printf("Failed to read certificate\n"); - goto err; - } + if (x509 == NULL) + errx(EXIT_FAILURE, "Failed to read certificate"); + BIO_free(bio); bio = NULL; counts = OPENSSL_malloc(sizeof(size_t) * threadcount); - if (counts == NULL) { - printf("Failed to create counts array\n"); - goto err; - } + if (counts == NULL) + errx(EXIT_FAILURE, "Failed to create counts array"); max_time = ossl_time_add(ossl_time_now(), ossl_seconds2time(RUN_TIME)); - if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) { - printf("Failed to run the test\n"); - goto err; - } + if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) + errx(EXIT_FAILURE, "Failed to run the test"); - if (error) { - printf("Error during test\n"); - goto err; - } + if (error) + errx(EXIT_FAILURE, "Error during test"); for (i = 0; i < threadcount; i++) total_count += counts[i]; @@ -176,7 +161,6 @@ int main(int argc, char *argv[]) ret = EXIT_SUCCESS; - err: X509_STORE_free(store); X509_free(x509); BIO_free(bio); From 68a7b918e67a27aa6c6fe6391397de786f72bebe Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 13:45:54 +0200 Subject: [PATCH 07/25] x509storeissuer: factor out integer parameter parsing into a separate function Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index e2a75354..fff83933 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -79,6 +79,23 @@ usage(char * const argv[]) basename(argv[0])); } +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; @@ -117,9 +134,7 @@ int main(int argc, char *argv[]) if (argv[optind] == NULL) errx(EXIT_FAILURE, "threadcount is missing"); - threadcount = atoi(argv[optind]); - if (threadcount < 1) - errx(EXIT_FAILURE, "threadcount must be > 0"); + threadcount = parse_int(argv[optind], 1, INT_MAX, "threadcount"); store = X509_STORE_new(); if (store == NULL || !X509_STORE_set_default_paths(store)) From 9c033116fb78c4f648e756ef5495c3194bef9687 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 16:16:19 +0200 Subject: [PATCH 08/25] x509storeissuer: add ability to configure verbosity level Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 42 ++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index fff83933..9053a6cd 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -25,7 +25,18 @@ #define RUN_TIME 5 +enum verbosity { + VERBOSITY_TERSE, + VERBOSITY_DEFAULT, + VERBOSITY_VERBOSE, + VERBOSITY_DEBUG_STATS, + VERBOSITY_DEBUG, + + VERBOSITY_MAX__ +}; + static int error = 0; +static int verbosity = VERBOSITY_DEFAULT; static X509_STORE *store = NULL; static X509 *x509 = NULL; @@ -73,10 +84,11 @@ static void usage(char * const argv[]) { fprintf(stderr, - "Usage: %s [-t] [-V] certsdir threadcount\n" - "-t - terse output\n" - "-V - print version information and exit\n", - basename(argv[0])); + "Usage: %s [-t] [-v] [-V] certsdir threadcount\n" + "\t-t\tterse output\n" + "\t-v\tVerbose output. Multiple usage increases verbosity.\n" + "\t-V\tprint version information and exit\n" + , basename(argv[0])); } static long long @@ -102,16 +114,23 @@ int main(int argc, char *argv[]) OSSL_TIME duration; size_t total_count = 0; double avcalltime; - int terse = 0; char *cert = NULL; int ret = EXIT_FAILURE; BIO *bio = NULL; int opt; - while ((opt = getopt(argc, argv, "tV")) != -1) { + while ((opt = getopt(argc, argv, "tvV")) != -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 'V': perflib_print_version(basename(argv[0])); @@ -168,11 +187,14 @@ int main(int argc, char *argv[]) avcalltime = (double)RUN_TIME * 1e6 * threadcount / total_count; - if (terse) + switch (verbosity) { + case VERBOSITY_TERSE: printf("%lf\n", avcalltime); - else + break; + default: printf("Average time per X509_STORE_CTX_get1_issuer() call: %lfus\n", avcalltime); + } ret = EXIT_SUCCESS; From be5de07db5dab872620b68bf562b60df7101de12 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 14:36:06 +0200 Subject: [PATCH 09/25] x509storeissuer: provide the initial infrastructure for nonce configuration Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 170 +++++++++++++++++++++++++++++++++------ 1 file changed, 146 insertions(+), 24 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 9053a6cd..d7def060 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -7,8 +7,9 @@ * https://www.openssl.org/source/license.html */ -#include +#include #include +#include #include #ifndef _WIN32 # include @@ -24,6 +25,7 @@ #include "perflib/perflib.h" #define RUN_TIME 5 +#define NONCE_CFG "file:servercert.pem" enum verbosity { VERBOSITY_TERSE, @@ -35,23 +37,117 @@ enum verbosity { VERBOSITY_MAX__ }; +enum nonce_type { + NONCE_PATH, +}; + +struct nonce_cfg { + enum nonce_type type; + const char *path; + char **dirs; + size_t num_dirs; +}; + 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 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 void do_x509storeissuer(size_t num) { X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; OSSL_TIME time; - if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509, NULL)) { + if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509_nonce, NULL)) { warnx("Failed to initialise X509_STORE_CTX"); error = 1; goto err; @@ -65,7 +161,7 @@ static void do_x509storeissuer(size_t num) * certificates inside our store. We're just testing calling this * against an empty store. */ - if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509) != 0) { + if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509_nonce) != 0) { warnx("Unexpected result from X509_STORE_CTX_get1_issuer"); error = 1; X509_free(issuer); @@ -84,13 +180,36 @@ static void usage(char * const argv[]) { fprintf(stderr, - "Usage: %s [-t] [-v] [-V] certsdir threadcount\n" - "\t-t\tterse output\n" - "\t-v\tVerbose output. Multiple usage increases verbosity.\n" + "Usage: %s [-t] [-v] [-n nonce_type:type_args]" + " [-V] certsdir threadcount\n" + "\t-t\tTerse output\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-V\tprint version information and exit\n" , basename(argv[0])); } +/** + * Parse nonce configuration string. Currently supported formats: + * * "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) @@ -118,8 +237,12 @@ int main(int argc, char *argv[]) int ret = EXIT_FAILURE; BIO *bio = NULL; int opt; + int dirs_start; + struct nonce_cfg nonce_cfg; + + parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvV")) != -1) { + while ((opt = getopt(argc, argv, "tvn:V")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -132,6 +255,9 @@ int main(int argc, char *argv[]) verbosity++; } break; + case 'n': /* nonce */ + parse_nonce_cfg(optarg, &nonce_cfg); + break; case 'V': perflib_print_version(basename(argv[0])); return EXIT_SUCCESS; @@ -144,11 +270,14 @@ int main(int argc, char *argv[]) if (argv[optind] == NULL) errx(EXIT_FAILURE, "certsdir is missing"); - cert = perflib_mk_file_path(argv[optind], "servercert.pem"); - if (cert == NULL) - errx(EXIT_FAILURE, "Failed to allocate cert path"); + dirs_start = optind++; - 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 = 1; if (argv[optind] == NULL) errx(EXIT_FAILURE, "threadcount is missing"); @@ -159,21 +288,14 @@ int main(int argc, char *argv[]) if (store == NULL || !X509_STORE_set_default_paths(store)) errx(EXIT_FAILURE, "Failed to create X509_STORE"); - bio = BIO_new_file(cert, "rb"); - if (bio == NULL) - errx(EXIT_FAILURE, "Unable to load certificate\n"); - - x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); - if (x509 == NULL) - errx(EXIT_FAILURE, "Failed to read certificate"); - - BIO_free(bio); - bio = NULL; - counts = OPENSSL_malloc(sizeof(size_t) * threadcount); if (counts == NULL) errx(EXIT_FAILURE, "Failed to create counts array"); + x509_nonce = make_nonce(&nonce_cfg); + if (x509_nonce == NULL) + errx(EXIT_FAILURE, "Unable to create the nonce X509 object"); + max_time = ossl_time_add(ossl_time_now(), ossl_seconds2time(RUN_TIME)); if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) @@ -198,8 +320,8 @@ int main(int argc, char *argv[]) ret = EXIT_SUCCESS; + X509_free(x509_nonce); X509_STORE_free(store); - X509_free(x509); BIO_free(bio); OPENSSL_free(cert); OPENSSL_free(counts); From 2cd00eb2420c45dbdd8e3c8ddcbee64a45230129 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 16:45:18 +0200 Subject: [PATCH 10/25] x509storeissuer: unify funtion definition formatting Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index d7def060..46382d64 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -141,7 +141,8 @@ make_nonce(struct nonce_cfg *cfg) } } -static void do_x509storeissuer(size_t num) +static void +do_x509storeissuer(size_t num) { X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; @@ -227,7 +228,8 @@ parse_int(const char * const s, long long min, long long max, return ret; } -int main(int argc, char *argv[]) +int +main(int argc, char *argv[]) { int i; OSSL_TIME duration; From 18fab94ab10b38bc48c59441b610e7bff44cd08f Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 16:45:46 +0200 Subject: [PATCH 11/25] x509storeissuer: add -T option to specify the run time Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 46382d64..2bec2da9 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -27,6 +27,8 @@ #define RUN_TIME 5 #define NONCE_CFG "file:servercert.pem" +static size_t timeout_us = RUN_TIME * 1000000; + enum verbosity { VERBOSITY_TERSE, VERBOSITY_DEFAULT, @@ -181,9 +183,13 @@ static void usage(char * const argv[]) { fprintf(stderr, - "Usage: %s [-t] [-v] [-n nonce_type:type_args]" + "Usage: %s [-t] [-v] [-T time] [-n nonce_type:type_args]" " [-V] certsdir threadcount\n" "\t-t\tTerse output\n" + "\t-v\tVerbose output. Multiple usage increases verbosity.\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" @@ -192,6 +198,23 @@ usage(char * const argv[]) , basename(argv[0])); } +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); +} + /** * Parse nonce configuration string. Currently supported formats: * * "file:PATH" - where PATH is either a relative path (that will be then @@ -244,7 +267,7 @@ main(int argc, char *argv[]) parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvn:V")) != -1) { + while ((opt = getopt(argc, argv, "tvT:n:V")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -257,6 +280,9 @@ main(int argc, char *argv[]) verbosity++; } break; + case 'T': /* timeout */ + timeout_us = parse_timeout(optarg); + break; case 'n': /* nonce */ parse_nonce_cfg(optarg, &nonce_cfg); break; @@ -298,7 +324,7 @@ main(int argc, char *argv[]) if (x509_nonce == NULL) errx(EXIT_FAILURE, "Unable to create the nonce X509 object"); - max_time = ossl_time_add(ossl_time_now(), ossl_seconds2time(RUN_TIME)); + 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"); @@ -309,7 +335,7 @@ main(int argc, char *argv[]) for (i = 0; i < threadcount; i++) total_count += counts[i]; - avcalltime = (double)RUN_TIME * 1e6 * threadcount / total_count; + avcalltime = (double)timeout_us * threadcount / total_count; switch (verbosity) { case VERBOSITY_TERSE: From b9e8a78c9bb56a20f584f83c1db6d61a64603fac Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Mon, 20 Oct 2025 16:52:22 +0200 Subject: [PATCH 12/25] x509storeissuer: support providing multiple certificate directories Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 2bec2da9..d93078dc 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -184,7 +184,7 @@ usage(char * const argv[]) { fprintf(stderr, "Usage: %s [-t] [-v] [-T time] [-n nonce_type:type_args]" - " [-V] certsdir threadcount\n" + " [-V] certsdir [certsdir...] threadcount\n" "\t-t\tTerse output\n" "\t-v\tVerbose output. Multiple usage increases verbosity.\n" "\t-T\tTimeout for the test run in seconds,\n" @@ -305,12 +305,12 @@ main(int argc, char *argv[]) * load_nonce_from_path can use it later. */ nonce_cfg.dirs = argv + dirs_start; - nonce_cfg.num_dirs = 1; + nonce_cfg.num_dirs = argc - 1 - dirs_start; - if (argv[optind] == NULL) + if (optind >= argc) errx(EXIT_FAILURE, "threadcount is missing"); - threadcount = parse_int(argv[optind], 1, INT_MAX, "threadcount"); + threadcount = parse_int(argv[argc - 1], 1, INT_MAX, "threadcount"); store = X509_STORE_new(); if (store == NULL || !X509_STORE_set_default_paths(store)) From 09f24e36c0f1653df8a53938bd037eff16226e1d Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 21 Oct 2025 15:49:35 +0200 Subject: [PATCH 13/25] x509storeissuer: count the iterations in a local variable to avoid hammering the common counts array's cache line Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index d93078dc..2d0a210a 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -149,6 +149,7 @@ do_x509storeissuer(size_t num) X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; OSSL_TIME time; + size_t count = 0; if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509_nonce, NULL)) { warnx("Failed to initialise X509_STORE_CTX"); @@ -156,8 +157,6 @@ do_x509storeissuer(size_t num) goto err; } - counts[num] = 0; - do { /* * We actually expect this to fail. We've not configured any @@ -171,12 +170,14 @@ do_x509storeissuer(size_t num) goto err; } issuer = NULL; - counts[num]++; + count++; time = ossl_time_now(); } while (time.t < max_time.t); err: X509_STORE_CTX_free(ctx); + + counts[num] = count; } static void From 92ca0877a53adcc4910e2493aa01f66a742e75bc Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 21 Oct 2025 16:06:16 +0200 Subject: [PATCH 14/25] x509storeissuer: tolerate X509_STORE_CTX_get1_issuer() successes, count and report successful calls Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 2d0a210a..0c6cf426 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -58,6 +58,7 @@ static X509 *x509_nonce = NULL; static int threadcount; size_t *counts; +size_t *founds; OSSL_TIME max_time; static X509 * @@ -150,6 +151,7 @@ do_x509storeissuer(size_t num) X509 *issuer = NULL; OSSL_TIME time; size_t count = 0; + size_t found = 0; if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509_nonce, NULL)) { warnx("Failed to initialise X509_STORE_CTX"); @@ -158,16 +160,9 @@ do_x509storeissuer(size_t num) } 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_nonce) != 0) { - warnx("Unexpected result from X509_STORE_CTX_get1_issuer"); - error = 1; + found++; X509_free(issuer); - goto err; } issuer = NULL; count++; @@ -178,6 +173,7 @@ do_x509storeissuer(size_t num) X509_STORE_CTX_free(ctx); counts[num] = count; + founds[num] = found; } static void @@ -258,6 +254,7 @@ main(int argc, char *argv[]) int i; OSSL_TIME duration; size_t total_count = 0; + size_t total_found = 0; double avcalltime; char *cert = NULL; int ret = EXIT_FAILURE; @@ -321,6 +318,10 @@ main(int argc, char *argv[]) if (counts == NULL) errx(EXIT_FAILURE, "Failed to create counts array"); + founds = OPENSSL_malloc(sizeof(size_t) * threadcount); + if (founds == NULL) + errx(EXIT_FAILURE, "Failed to create founds array"); + x509_nonce = make_nonce(&nonce_cfg); if (x509_nonce == NULL) errx(EXIT_FAILURE, "Unable to create the nonce X509 object"); @@ -333,8 +334,10 @@ main(int argc, char *argv[]) if (error) errx(EXIT_FAILURE, "Error during test"); - for (i = 0; i < threadcount; i++) + for (i = 0; i < threadcount; i++) { total_count += counts[i]; + total_found += founds[i]; + } avcalltime = (double)timeout_us * threadcount / total_count; @@ -345,6 +348,12 @@ main(int argc, char *argv[]) default: printf("Average time per X509_STORE_CTX_get1_issuer() call: %lfus\n", avcalltime); + if (verbosity >= VERBOSITY_VERBOSE) { + printf("Successful X509_STORE_CTX_get1_issuer() calls: %zu of %zu" + " (%lf%%)\n", + total_found, total_count, + (double)total_found / total_count * 100.0); + } } ret = EXIT_SUCCESS; @@ -353,6 +362,7 @@ main(int argc, char *argv[]) X509_STORE_free(store); BIO_free(bio); OPENSSL_free(cert); + OPENSSL_free(founds); OPENSSL_free(counts); return ret; } From 8a68e5390825f771a3067036c6ff9be02725a457 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 21 Oct 2025 16:09:37 +0200 Subject: [PATCH 15/25] x509storeissuer: add certificates from the provided directories to the store Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 156 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 4 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 0c6cf426..1c5743d0 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -7,11 +7,14 @@ * https://www.openssl.org/source/license.html */ +#include #include #include #include #include +#include #ifndef _WIN32 +# include # include # include #else @@ -144,6 +147,148 @@ make_nonce(struct nonce_cfg *cfg) } } +static size_t +read_cert(const char * const dir, const char * const name, X509_STORE * const store) +{ + X509 *x509 = NULL; + char *path = NULL; + size_t ret = 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 (!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); + + ret = 1; + + out: + X509_free(x509); + OPENSSL_free(path); + + return ret; +} + +#if defined(_WIN32) +static size_t +read_certsdir(char * const dir, X509_STORE * const store) +{ + 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; + + 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 { + 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, store); + + } 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, X509_STORE * const store) +{ + struct dirent *e; + DIR *d = opendir(dir); + size_t cnt = 0; + + 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, store); + } + + closedir(d); + + return cnt; +} +#endif /* defined(_WIN32) */ + +static size_t +read_certsdirs(char * const * const dirs, const int dir_cnt, + X509_STORE * const store) +{ + size_t cnt = 0; + + for (int i = 0; i < dir_cnt; i++) + cnt += read_certsdir(dirs[i], store); + + return cnt; +} + static void do_x509storeissuer(size_t num) { @@ -256,11 +401,10 @@ main(int argc, char *argv[]) size_t total_count = 0; size_t total_found = 0; double avcalltime; - char *cert = NULL; int ret = EXIT_FAILURE; - BIO *bio = NULL; int opt; int dirs_start; + size_t num_certs = 0; struct nonce_cfg nonce_cfg; parse_nonce_cfg(NONCE_CFG, &nonce_cfg); @@ -314,6 +458,12 @@ main(int argc, char *argv[]) if (store == NULL || !X509_STORE_set_default_paths(store)) errx(EXIT_FAILURE, "Failed to create X509_STORE"); + num_certs += read_certsdirs(argv + dirs_start, argc - dirs_start - 1, + store); + + if (verbosity >= VERBOSITY_DEBUG_STATS) + fprintf(stderr, "Added %zu certificates to the store\n", num_certs); + counts = OPENSSL_malloc(sizeof(size_t) * threadcount); if (counts == NULL) errx(EXIT_FAILURE, "Failed to create counts array"); @@ -360,8 +510,6 @@ main(int argc, char *argv[]) X509_free(x509_nonce); X509_STORE_free(store); - BIO_free(bio); - OPENSSL_free(cert); OPENSSL_free(founds); OPENSSL_free(counts); return ret; From 319e6ec05df2857c18a91ffbbd54b75c1e2dd531 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 21 Oct 2025 16:52:30 +0200 Subject: [PATCH 16/25] x509storeissuer: add and option to configure X509_STORE_CTX sharing Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 48 ++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 1c5743d0..7a02169a 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -29,6 +29,7 @@ #define RUN_TIME 5 #define NONCE_CFG "file:servercert.pem" +#define CTX_SHARE_THREADS 1 static size_t timeout_us = RUN_TIME * 1000000; @@ -53,6 +54,10 @@ struct nonce_cfg { size_t num_dirs; }; +struct thread_data { + X509_STORE_CTX *ctx; +} *thread_data; + static int error = 0; static int verbosity = VERBOSITY_DEFAULT; static X509_STORE *store = NULL; @@ -292,20 +297,15 @@ read_certsdirs(char * const * const dirs, const int dir_cnt, static void do_x509storeissuer(size_t num) { + struct thread_data *td = &thread_data[num]; X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; OSSL_TIME time; size_t count = 0; size_t found = 0; - if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509_nonce, NULL)) { - warnx("Failed to initialise X509_STORE_CTX"); - error = 1; - goto err; - } - do { - if (X509_STORE_CTX_get1_issuer(&issuer, ctx, x509_nonce) != 0) { + if (X509_STORE_CTX_get1_issuer(&issuer, td->ctx, x509_nonce) != 0) { found++; X509_free(issuer); } @@ -326,7 +326,7 @@ usage(char * const argv[]) { fprintf(stderr, "Usage: %s [-t] [-v] [-T time] [-n nonce_type:type_args]" - " [-V] certsdir [certsdir...] threadcount\n" + " [-C threads] [-V] certsdir [certsdir...] threadcount\n" "\t-t\tTerse output\n" "\t-v\tVerbose output. Multiple usage increases verbosity.\n" "\t-T\tTimeout for the test run in seconds,\n" @@ -336,6 +336,9 @@ usage(char * const argv[]) "\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-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])); } @@ -398,6 +401,7 @@ main(int argc, char *argv[]) { int i; OSSL_TIME duration; + size_t ctx_share_cnt = CTX_SHARE_THREADS; size_t total_count = 0; size_t total_found = 0; double avcalltime; @@ -409,7 +413,7 @@ main(int argc, char *argv[]) parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvT:n:V")) != -1) { + while ((opt = getopt(argc, argv, "tvT:n:C:V")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -428,6 +432,10 @@ main(int argc, char *argv[]) case 'n': /* nonce */ parse_nonce_cfg(optarg, &nonce_cfg); 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; @@ -454,6 +462,10 @@ main(int argc, char *argv[]) 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"); + store = X509_STORE_new(); if (store == NULL || !X509_STORE_set_default_paths(store)) errx(EXIT_FAILURE, "Failed to create X509_STORE"); @@ -476,6 +488,19 @@ main(int argc, char *argv[]) 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); + } + } + max_time = ossl_time_add(ossl_time_now(), ossl_us2time(timeout_us)); if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) @@ -512,5 +537,10 @@ main(int argc, char *argv[]) X509_STORE_free(store); OPENSSL_free(founds); OPENSSL_free(counts); + if (thread_data != NULL) { + for (size_t i = 0; i < threadcount; i += ctx_share_cnt) + X509_STORE_CTX_free(thread_data[i].ctx); + } + OPENSSL_free(thread_data); return ret; } From aec1748ba61c24fe7cb6e9b9fc55959502dc920f Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Wed, 22 Oct 2025 14:55:57 +0200 Subject: [PATCH 17/25] x509storeissuer: report the store size before the test run if the verbosity level is DEBUG_STATS or higher Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 7a02169a..361e98ca 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -321,6 +321,27 @@ do_x509storeissuer(size_t num) founds[num] = found; } +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 void usage(char * const argv[]) { @@ -484,6 +505,8 @@ main(int argc, char *argv[]) if (founds == NULL) errx(EXIT_FAILURE, "Failed to create founds array"); + report_store_size(store, "before the test run", verbosity); + x509_nonce = make_nonce(&nonce_cfg); if (x509_nonce == NULL) errx(EXIT_FAILURE, "Unable to create the nonce X509 object"); From ca94c96f831702fdcf51bbf4bf49caa6eb68ace1 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Wed, 22 Oct 2025 16:13:30 +0200 Subject: [PATCH 18/25] x509storeissuer: make the verbose reporting more elaborate Signed-off-by: Eugene Syromiatnikov --- source/CMakeLists.txt | 14 ++- source/x509storeissuer.c | 249 +++++++++++++++++++++++++++++++-------- 2 files changed, 210 insertions(+), 53 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 61296480..3edc5398 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) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 361e98ca..d2b4b8c5 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -8,6 +8,8 @@ */ #include +#include +#include #include #include #include @@ -28,10 +30,12 @@ #include "perflib/perflib.h" #define RUN_TIME 5 +#define QUANTILES 5 #define NONCE_CFG "file:servercert.pem" #define CTX_SHARE_THREADS 1 static size_t timeout_us = RUN_TIME * 1000000; +static size_t quantiles = QUANTILES; enum verbosity { VERBOSITY_TERSE, @@ -47,6 +51,21 @@ enum nonce_type { NONCE_PATH, }; +struct call_times { + uint64_t duration; + uint64_t total_count; + uint64_t total_found; + 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; @@ -55,6 +74,12 @@ struct nonce_cfg { }; struct thread_data { + OSSL_TIME start_time; + struct { + uint64_t count; + uint64_t found; + OSSL_TIME end_time; + } *q_data; X509_STORE_CTX *ctx; } *thread_data; @@ -65,10 +90,11 @@ static X509 *x509_nonce = NULL; static int threadcount; -size_t *counts; -size_t *founds; OSSL_TIME max_time; +#define OSSL_MIN(p, q) ((p) < (q) ? (p) : (q)) +#define OSSL_MAX(p, q) ((p) > (q) ? (p) : (q)) + static X509 * load_cert_from_file(const char *path) { @@ -298,12 +324,18 @@ static void do_x509storeissuer(size_t num) { struct thread_data *td = &thread_data[num]; - X509_STORE_CTX *ctx = X509_STORE_CTX_new(); X509 *issuer = NULL; OSSL_TIME time; + OSSL_TIME duration; + OSSL_TIME q_end; + size_t q = 0; size_t count = 0; size_t found = 0; + 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 (X509_STORE_CTX_get1_issuer(&issuer, td->ctx, x509_nonce) != 0) { found++; @@ -312,13 +344,17 @@ do_x509storeissuer(size_t num) issuer = NULL; 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].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); - - counts[num] = count; - founds[num] = found; + td->q_data[quantiles - 1].count = count; + td->q_data[quantiles - 1].found = found; + td->q_data[quantiles - 1].end_time = time; } static void @@ -342,14 +378,149 @@ report_store_size(X509_STORE * const store, const char * const suffix, } } +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); + + times[q].duration += thread_data[i].q_data[q].end_time.t - start_t; + times[q].total_count += count; + times[q].total_found += found; + } + } + + 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; + } + + 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%%)\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, + 100.0 * times[i].total_found / (times[i].total_count)); + } + break; + } + + OPENSSL_free(times); +} + static void usage(char * const argv[]) { fprintf(stderr, - "Usage: %s [-t] [-v] [-T time] [-n nonce_type:type_args]" + "Usage: %s [-t] [-v] [-q N] [-T time] [-n nonce_type:type_args]" " [-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" @@ -420,12 +591,8 @@ parse_int(const char * const s, long long min, long long max, int main(int argc, char *argv[]) { - int i; OSSL_TIME duration; size_t ctx_share_cnt = CTX_SHARE_THREADS; - size_t total_count = 0; - size_t total_found = 0; - double avcalltime; int ret = EXIT_FAILURE; int opt; int dirs_start; @@ -434,7 +601,7 @@ main(int argc, char *argv[]) parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvT:n:C:V")) != -1) { + while ((opt = getopt(argc, argv, "tvq:T:n:C:V")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -447,6 +614,10 @@ main(int argc, char *argv[]) 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; @@ -466,6 +637,9 @@ main(int argc, char *argv[]) } } + if (verbosity < VERBOSITY_VERBOSE) + quantiles = 1; + if (argv[optind] == NULL) errx(EXIT_FAILURE, "certsdir is missing"); @@ -487,6 +661,14 @@ main(int argc, char *argv[]) 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)) errx(EXIT_FAILURE, "Failed to create X509_STORE"); @@ -497,14 +679,6 @@ main(int argc, char *argv[]) if (verbosity >= VERBOSITY_DEBUG_STATS) fprintf(stderr, "Added %zu certificates to the store\n", num_certs); - counts = OPENSSL_malloc(sizeof(size_t) * threadcount); - if (counts == NULL) - errx(EXIT_FAILURE, "Failed to create counts array"); - - founds = OPENSSL_malloc(sizeof(size_t) * threadcount); - if (founds == NULL) - errx(EXIT_FAILURE, "Failed to create founds array"); - report_store_size(store, "before the test run", verbosity); x509_nonce = make_nonce(&nonce_cfg); @@ -532,37 +706,18 @@ main(int argc, char *argv[]) if (error) errx(EXIT_FAILURE, "Error during test"); - for (i = 0; i < threadcount; i++) { - total_count += counts[i]; - total_found += founds[i]; - } - - avcalltime = (double)timeout_us * threadcount / total_count; - - switch (verbosity) { - case VERBOSITY_TERSE: - printf("%lf\n", avcalltime); - break; - default: - printf("Average time per X509_STORE_CTX_get1_issuer() call: %lfus\n", - avcalltime); - if (verbosity >= VERBOSITY_VERBOSE) { - printf("Successful X509_STORE_CTX_get1_issuer() calls: %zu of %zu" - " (%lf%%)\n", - total_found, total_count, - (double)total_found / total_count * 100.0); - } - } + report_result(verbosity); ret = EXIT_SUCCESS; X509_free(x509_nonce); X509_STORE_free(store); - OPENSSL_free(founds); - OPENSSL_free(counts); if (thread_data != NULL) { - for (size_t i = 0; i < threadcount; i += ctx_share_cnt) - X509_STORE_CTX_free(thread_data[i].ctx); + 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; From a600b7eb789ce39343f15c9dc6c4e16054cfb889 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Thu, 23 Oct 2025 13:01:43 +0200 Subject: [PATCH 19/25] x509storeissuer: make thread_data cache-line-aligned Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index d2b4b8c5..f8be7bca 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -73,7 +73,16 @@ struct nonce_cfg { size_t num_dirs; }; -struct thread_data { +/* 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; From 5acec49383d5ccc18dd7d7461e1924d3b590b928 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 4 Nov 2025 17:02:15 +0100 Subject: [PATCH 20/25] x509storeissuer: add limits for certificate file/directory load counts Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 58 +++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index f8be7bca..97368de5 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -32,6 +32,8 @@ #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; @@ -226,7 +228,8 @@ read_cert(const char * const dir, const char * const name, X509_STORE * const st #if defined(_WIN32) static size_t -read_certsdir(char * const dir, X509_STORE * const store) +read_certsdir(char * const dir, const size_t max_certs, + X509_STORE * const store) { const size_t dir_len = strlen(dir); const size_t glob_len = dir_len + sizeof("\\*"); @@ -236,6 +239,9 @@ read_certsdir(char * const dir, X509_STORE * const store) WIN32_FIND_DATA find_data; DWORD last_err; + if (max_certs == 0) + return 0; + search_glob = OPENSSL_malloc(glob_len); if (search_glob == NULL) { warnx("Error allocating a search glob for \"%s\"", dir); @@ -263,6 +269,8 @@ read_certsdir(char * const dir, X509_STORE * const store) cnt += read_cert(dir, find_data.cFileName, store); + if (cnt >= max_certs) + break; } while (FindNextFileA(find_handle, &find_data) != 0); last_err = GetLastError(); @@ -278,12 +286,17 @@ read_certsdir(char * const dir, X509_STORE * const store) } #else /* !defined(_WIN32) */ static size_t -read_certsdir(char * const dir, X509_STORE * const store) +read_certsdir(char * const dir, const size_t max_certs, + X509_STORE * const store) { struct dirent *e; - DIR *d = opendir(dir); + DIR *d; size_t cnt = 0; + if (max_certs == 0) + return 0; + + d = opendir(dir); if (d == NULL) { warn("Could not open \"%s\"", dir); @@ -309,6 +322,9 @@ read_certsdir(char * const dir, X509_STORE * const store) } cnt += read_cert(dir, e->d_name, store); + + if (cnt >= max_certs) + break; } closedir(d); @@ -318,13 +334,17 @@ read_certsdir(char * const dir, X509_STORE * const store) #endif /* defined(_WIN32) */ static size_t -read_certsdirs(char * const * const dirs, const int dir_cnt, - X509_STORE * const store) +read_certsdirs(char * const * const dirs, const size_t max_certs, + const int dir_cnt, X509_STORE * const store) { size_t cnt = 0; - for (int i = 0; i < dir_cnt; i++) - cnt += read_certsdir(dirs[i], store); + for (int i = 0; i < dir_cnt; i++) { + cnt += read_certsdir(dirs[i], max_certs - cnt, store); + + if (cnt >= max_certs) + break; + } return cnt; } @@ -524,7 +544,8 @@ usage(char * const argv[]) { fprintf(stderr, "Usage: %s [-t] [-v] [-q N] [-T time] [-n nonce_type:type_args]" - " [-C threads] [-V] certsdir [certsdir...] threadcount\n" + " [-l max_certs] [-L max_cert_dirs] [-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" @@ -537,6 +558,10 @@ usage(char * const argv[]) "\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-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-C\tNumber of threads that share the same X.509\n" "\t\tstore context object. Default: " OPENSSL_MSTR(CTX_SHARE_THREADS) "\n" @@ -606,11 +631,13 @@ main(int argc, char *argv[]) int opt; int dirs_start; size_t num_certs = 0; + size_t max_load_certs = MAX_LOAD_CERTS; + int max_load_cert_dirs = MAX_LOAD_CERT_DIRS; struct nonce_cfg nonce_cfg; parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvq:T:n:C:V")) != -1) { + while ((opt = getopt(argc, argv, "tvq:T:n:l:L:C:V")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -633,6 +660,15 @@ main(int argc, char *argv[]) case 'n': /* nonce */ parse_nonce_cfg(optarg, &nonce_cfg); 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 'C': /* how many threads share X509_STORE_CTX */ ctx_share_cnt = parse_int(optarg, 1, INT_MAX, "X509_STORE_CTX share degree"); @@ -682,7 +718,9 @@ main(int argc, char *argv[]) if (store == NULL || !X509_STORE_set_default_paths(store)) errx(EXIT_FAILURE, "Failed to create X509_STORE"); - num_certs += read_certsdirs(argv + dirs_start, argc - dirs_start - 1, + num_certs += read_certsdirs(argv + dirs_start, max_load_certs, + OSSL_MIN(argc - dirs_start - 1, + max_load_cert_dirs), store); if (verbosity >= VERBOSITY_DEBUG_STATS) From 753613f0ef35924e39307a02ea6bca946b7c9eeb Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 4 Nov 2025 17:02:45 +0100 Subject: [PATCH 21/25] x509storeissuer: add an ability to skip loading default paths Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 97368de5..12f90f57 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -544,7 +544,7 @@ usage(char * const argv[]) { fprintf(stderr, "Usage: %s [-t] [-v] [-q N] [-T time] [-n nonce_type:type_args]" - " [-l max_certs] [-L max_cert_dirs] [-C threads]" + " [-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" @@ -562,6 +562,7 @@ usage(char * const argv[]) "\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" @@ -633,11 +634,12 @@ main(int argc, char *argv[]) size_t num_certs = 0; size_t max_load_certs = MAX_LOAD_CERTS; int max_load_cert_dirs = MAX_LOAD_CERT_DIRS; + bool skip_default_paths = false; struct nonce_cfg nonce_cfg; parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvq:T:n:l:L:C:V")) != -1) { + while ((opt = getopt(argc, argv, "tvq:T:n:l:L:EC:V")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -669,6 +671,9 @@ main(int argc, char *argv[]) "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"); @@ -715,8 +720,12 @@ main(int argc, char *argv[]) } store = X509_STORE_new(); - if (store == NULL || !X509_STORE_set_default_paths(store)) + 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"); + } num_certs += read_certsdirs(argv + dirs_start, max_load_certs, OSSL_MIN(argc - dirs_start - 1, From 5444f4851f6bede78c6b19b20546c978398f0b32 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Wed, 5 Nov 2025 15:26:50 +0100 Subject: [PATCH 22/25] x509storeissuer: store excess certificates in a pool for later use Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 180 +++++++++++++++++++++++++++++++++------ 1 file changed, 153 insertions(+), 27 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index 12f90f57..da46ece9 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -75,6 +75,12 @@ struct nonce_cfg { size_t num_dirs; }; +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))) @@ -106,6 +112,65 @@ OSSL_TIME max_time; #define OSSL_MIN(p, q) ((p) < (q) ? (p) : (q)) #define OSSL_MAX(p, q) ((p) > (q) ? (p) : (q)) +/** + * 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); + + 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]); + } + + OPENSSL_free(pool->entries); + + pool->entries = NULL; + pool->size = pool->capacity = 0; +} + static X509 * load_cert_from_file(const char *path) { @@ -190,12 +255,17 @@ make_nonce(struct nonce_cfg *cfg) } static size_t -read_cert(const char * const dir, const char * const name, X509_STORE * const store) +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\"", @@ -208,19 +278,35 @@ read_cert(const char * const dir, const char * const name, X509_STORE * const st goto out; } - if (!X509_STORE_add_cert(store, x509)) { - warnx("Failed to add a certificate from \"%s\" to the store\n", path); - 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); - if (verbosity >= VERBOSITY_DEBUG) - fprintf(stderr, "Successfully added a certificate from \"%s\"" - " to the store\n", path); + *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: - X509_free(x509); + if (store != NULL) + X509_free(x509); OPENSSL_free(path); return ret; @@ -229,7 +315,8 @@ read_cert(const char * const dir, const char * const name, X509_STORE * const st #if defined(_WIN32) static size_t read_certsdir(char * const dir, const size_t max_certs, - X509_STORE * const store) + 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("\\*"); @@ -239,7 +326,7 @@ read_certsdir(char * const dir, const size_t max_certs, WIN32_FIND_DATA find_data; DWORD last_err; - if (max_certs == 0) + if (pool == NULL && max_certs == 0) return 0; search_glob = OPENSSL_malloc(glob_len); @@ -267,9 +354,10 @@ read_certsdir(char * const dir, const size_t max_certs, continue; } - cnt += read_cert(dir, find_data.cFileName, store); + cnt += read_cert(dir, find_data.cFileName, + cnt < max_certs ? store : NULL, pool, pool_cnt); - if (cnt >= max_certs) + if (pool == NULL && cnt >= max_certs) break; } while (FindNextFileA(find_handle, &find_data) != 0); @@ -287,13 +375,14 @@ read_certsdir(char * const dir, const size_t max_certs, #else /* !defined(_WIN32) */ static size_t read_certsdir(char * const dir, const size_t max_certs, - X509_STORE * const store) + X509_STORE * const store, + struct x509_pool * const pool, size_t * const pool_cnt) { struct dirent *e; DIR *d; size_t cnt = 0; - if (max_certs == 0) + if (pool == NULL && max_certs == 0) return 0; d = opendir(dir); @@ -321,9 +410,10 @@ read_certsdir(char * const dir, const size_t max_certs, continue; } - cnt += read_cert(dir, e->d_name, store); + cnt += read_cert(dir, e->d_name, cnt < max_certs ? store : NULL, + pool, pool_cnt); - if (cnt >= max_certs) + if (pool == NULL && cnt >= max_certs) break; } @@ -333,20 +423,52 @@ read_certsdir(char * const dir, const size_t max_certs, } #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 size_t max_certs, - const int dir_cnt, X509_STORE * const store) +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, store); + cnt += read_certsdir(dirs[i], max_certs > cnt ? max_certs - cnt : 0, + i < max_cert_dirs ? store : NULL, + pool, pool ? &pool_cnt : NULL); - if (cnt >= max_certs) + if (pool == NULL && cnt >= max_certs) break; } - return cnt; + if (total_read != NULL) + *total_read = cnt; + + return cnt - pool_cnt; } static void @@ -632,9 +754,11 @@ main(int argc, char *argv[]) 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; parse_nonce_cfg(NONCE_CFG, &nonce_cfg); @@ -727,13 +851,14 @@ main(int argc, char *argv[]) errx(EXIT_FAILURE, "Failed to load certificates from default paths"); } - num_certs += read_certsdirs(argv + dirs_start, max_load_certs, - OSSL_MIN(argc - dirs_start - 1, - max_load_cert_dirs), - store); + num_certs += read_certsdirs(argv + dirs_start, argc - dirs_start - 1, + max_load_certs, max_load_cert_dirs, + store, &cert_pool, &total_certs_read); if (verbosity >= VERBOSITY_DEBUG_STATS) - fprintf(stderr, "Added %zu certificates to the store\n", num_certs); + fprintf(stderr, "Added %zu certificates to the store" + " (%zu certificates read in total)\n", + num_certs, total_certs_read); report_store_size(store, "before the test run", verbosity); @@ -767,6 +892,7 @@ main(int argc, char *argv[]) ret = EXIT_SUCCESS; X509_free(x509_nonce); + x509_pool_empty(&cert_pool); X509_STORE_free(store); if (thread_data != NULL) { for (size_t i = 0; i < threadcount; i++) { From 8ba8e6b3abeb1b4e811144f3ec0a68b6bfbcb392 Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Wed, 22 Oct 2025 16:47:44 +0200 Subject: [PATCH 23/25] x509storeissuer: add ability to add certificates to the store during the run Signed-off-by: Eugene Syromiatnikov --- source/x509storeissuer.c | 136 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 127 insertions(+), 9 deletions(-) diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c index da46ece9..47c0b180 100644 --- a/source/x509storeissuer.c +++ b/source/x509storeissuer.c @@ -29,6 +29,8 @@ #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" @@ -38,6 +40,8 @@ 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, @@ -49,6 +53,11 @@ enum verbosity { 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, }; @@ -57,6 +66,7 @@ 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; @@ -95,8 +105,11 @@ ALIGN64 struct thread_data { 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; @@ -112,6 +125,28 @@ OSSL_TIME max_time; #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) +{ + /* 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. @@ -481,23 +516,38 @@ do_x509storeissuer(size_t num) 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 (X509_STORE_CTX_get1_issuer(&issuer, td->ctx, x509_nonce) != 0) { - found++; - X509_free(issuer); + 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; + 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; } @@ -505,6 +555,7 @@ do_x509storeissuer(size_t num) 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; } @@ -551,10 +602,13 @@ get_calltimes(struct call_times *times, int verbosity) (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; } } @@ -562,6 +616,7 @@ get_calltimes(struct call_times *times, int verbosity) 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++) @@ -646,14 +701,18 @@ report_result(int verbosity) 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%%)\n", + ", 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, - 100.0 * times[i].total_found / (times[i].total_count)); + 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; } @@ -666,6 +725,7 @@ 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" @@ -680,6 +740,13 @@ usage(char * const argv[]) "\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" @@ -709,8 +776,23 @@ parse_timeout(const char * const optarg) 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. @@ -763,7 +845,7 @@ main(int argc, char *argv[]) parse_nonce_cfg(NONCE_CFG, &nonce_cfg); - while ((opt = getopt(argc, argv, "tvq:T:n:l:L:EC:V")) != -1) { + while ((opt = getopt(argc, argv, "tvq:T:n:m:w:W:l:L:EC:V")) != -1) { switch (opt) { case 't': /* terse */ verbosity = VERBOSITY_TERSE; @@ -786,6 +868,22 @@ main(int argc, char *argv[]) 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"); @@ -853,7 +951,8 @@ main(int argc, char *argv[]) num_certs += read_certsdirs(argv + dirs_start, argc - dirs_start - 1, max_load_certs, max_load_cert_dirs, - store, &cert_pool, &total_certs_read); + store, mode == MODE_RW ? &cert_pool : NULL, + &total_certs_read); if (verbosity >= VERBOSITY_DEBUG_STATS) fprintf(stderr, "Added %zu certificates to the store" @@ -879,6 +978,22 @@ main(int argc, char *argv[]) } } + 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); + } + } + max_time = ossl_time_add(ossl_time_now(), ossl_us2time(timeout_us)); if (!perflib_run_multi_thread_test(do_x509storeissuer, threadcount, &duration)) @@ -887,6 +1002,9 @@ main(int argc, char *argv[]) if (error) errx(EXIT_FAILURE, "Error during test"); + if (mode == MODE_RW) + report_store_size(store, "after the test run", verbosity); + report_result(verbosity); ret = EXIT_SUCCESS; From 176619a34a3565a926ae9314a139dc9a148083ba Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Thu, 6 Nov 2025 16:57:19 +0100 Subject: [PATCH 24/25] CMakeLists.txt: add recently added x509storeissuer options to the test run matrix Signed-off-by: Eugene Syromiatnikov --- source/CMakeLists.txt | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 3edc5398..5d4b2bfd 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -279,6 +279,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 @@ -296,7 +326,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 From 14fa76ea21c3c524c8890a139411e692189febbc Mon Sep 17 00:00:00 2001 From: Eugene Syromiatnikov Date: Tue, 11 Nov 2025 17:16:51 +0100 Subject: [PATCH 25/25] CMakeLists.txt, test.yml: make "run" target optional, continute to use in CI Signed-off-by: Eugene Syromiatnikov --- .github/workflows/test.yml | 10 ++--- source/CMakeLists.txt | 88 ++++++++++++++++++++------------------ 2 files changed, 51 insertions(+), 47 deletions(-) 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 5d4b2bfd..6cc05f20 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -213,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 @@ -446,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()