1 /* 2 * Copyright 2017-2025 The OpenSSL Project Authors. All Rights Reserved. 3 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 4 * 5 * Licensed under the Apache License 2.0 (the "License"). You may not use 6 * this file except in compliance with the License. You can obtain a copy 7 * in the file LICENSE in the source distribution or at 8 * https://www.openssl.org/source/license.html 9 */ 10 11 #include <stdio.h> 12 #include <string.h> 13 14 #include <openssl/opensslconf.h> 15 #include <openssl/lhash.h> 16 #include <openssl/err.h> 17 #include <openssl/rand.h> 18 #include <openssl/crypto.h> 19 #include <internal/hashtable.h> 20 21 #include "internal/nelem.h" 22 #include "threadstest.h" 23 #include "testutil.h" 24 25 /* 26 * The macros below generate unused functions which error out one of the clang 27 * builds. We disable this check here. 28 */ 29 #ifdef __clang__ 30 #pragma clang diagnostic ignored "-Wunused-function" 31 #endif 32 33 DEFINE_LHASH_OF_EX(int); 34 35 static int int_tests[] = { 65537, 13, 1, 3, -5, 6, 7, 4, -10, -12, -14, 22, 9, 36 -17, 16, 17, -23, 35, 37, 173, 11 }; 37 static const size_t n_int_tests = OSSL_NELEM(int_tests); 38 static short int_found[OSSL_NELEM(int_tests)]; 39 static short int_not_found; 40 41 static unsigned long int int_hash(const int *p) 42 { 43 return 3 & *p; /* To force collisions */ 44 } 45 46 static int int_cmp(const int *p, const int *q) 47 { 48 return *p != *q; 49 } 50 51 static int int_find(int n) 52 { 53 unsigned int i; 54 55 for (i = 0; i < n_int_tests; i++) 56 if (int_tests[i] == n) 57 return i; 58 return -1; 59 } 60 61 static void int_doall(int *v) 62 { 63 const int n = int_find(*v); 64 65 if (n < 0) 66 int_not_found++; 67 else 68 int_found[n]++; 69 } 70 71 static void int_doall_arg(int *p, short *f) 72 { 73 const int n = int_find(*p); 74 75 if (n < 0) 76 int_not_found++; 77 else 78 f[n]++; 79 } 80 81 IMPLEMENT_LHASH_DOALL_ARG(int, short); 82 83 static int test_int_lhash(void) 84 { 85 static struct { 86 int data; 87 int null; 88 } dels[] = { 89 { 65537, 0 }, 90 { 173, 0 }, 91 { 999, 1 }, 92 { 37, 0 }, 93 { 1, 0 }, 94 { 34, 1 } 95 }; 96 const unsigned int n_dels = OSSL_NELEM(dels); 97 LHASH_OF(int) *h = lh_int_new(&int_hash, &int_cmp); 98 unsigned int i; 99 int testresult = 0, j, *p; 100 101 if (!TEST_ptr(h)) 102 goto end; 103 104 /* insert */ 105 for (i = 0; i < n_int_tests; i++) 106 if (!TEST_ptr_null(lh_int_insert(h, int_tests + i))) { 107 TEST_info("int insert %d", i); 108 goto end; 109 } 110 111 /* num_items */ 112 if (!TEST_int_eq((size_t)lh_int_num_items(h), n_int_tests)) 113 goto end; 114 115 /* retrieve */ 116 for (i = 0; i < n_int_tests; i++) 117 if (!TEST_int_eq(*lh_int_retrieve(h, int_tests + i), int_tests[i])) { 118 TEST_info("lhash int retrieve value %d", i); 119 goto end; 120 } 121 for (i = 0; i < n_int_tests; i++) 122 if (!TEST_ptr_eq(lh_int_retrieve(h, int_tests + i), int_tests + i)) { 123 TEST_info("lhash int retrieve address %d", i); 124 goto end; 125 } 126 j = 1; 127 if (!TEST_ptr_eq(lh_int_retrieve(h, &j), int_tests + 2)) 128 goto end; 129 130 /* replace */ 131 j = 13; 132 if (!TEST_ptr(p = lh_int_insert(h, &j))) 133 goto end; 134 if (!TEST_ptr_eq(p, int_tests + 1)) 135 goto end; 136 if (!TEST_ptr_eq(lh_int_retrieve(h, int_tests + 1), &j)) 137 goto end; 138 139 /* do_all */ 140 memset(int_found, 0, sizeof(int_found)); 141 int_not_found = 0; 142 lh_int_doall(h, &int_doall); 143 if (!TEST_int_eq(int_not_found, 0)) { 144 TEST_info("lhash int doall encountered a not found condition"); 145 goto end; 146 } 147 for (i = 0; i < n_int_tests; i++) 148 if (!TEST_int_eq(int_found[i], 1)) { 149 TEST_info("lhash int doall %d", i); 150 goto end; 151 } 152 153 /* do_all_arg */ 154 memset(int_found, 0, sizeof(int_found)); 155 int_not_found = 0; 156 lh_int_doall_short(h, int_doall_arg, int_found); 157 if (!TEST_int_eq(int_not_found, 0)) { 158 TEST_info("lhash int doall arg encountered a not found condition"); 159 goto end; 160 } 161 for (i = 0; i < n_int_tests; i++) 162 if (!TEST_int_eq(int_found[i], 1)) { 163 TEST_info("lhash int doall arg %d", i); 164 goto end; 165 } 166 167 /* delete */ 168 for (i = 0; i < n_dels; i++) { 169 const int b = lh_int_delete(h, &dels[i].data) == NULL; 170 if (!TEST_int_eq(b ^ dels[i].null, 0)) { 171 TEST_info("lhash int delete %d", i); 172 goto end; 173 } 174 } 175 176 /* error */ 177 if (!TEST_int_eq(lh_int_error(h), 0)) 178 goto end; 179 180 testresult = 1; 181 end: 182 lh_int_free(h); 183 return testresult; 184 } 185 186 187 static int int_filter_all(HT_VALUE *v, void *arg) 188 { 189 return 1; 190 } 191 192 HT_START_KEY_DEFN(intkey) 193 HT_DEF_KEY_FIELD(mykey, int) 194 HT_END_KEY_DEFN(INTKEY) 195 196 IMPLEMENT_HT_VALUE_TYPE_FNS(int, test, static) 197 198 static int int_foreach(HT_VALUE *v, void *arg) 199 { 200 int *vd = ossl_ht_test_int_from_value(v); 201 const int n = int_find(*vd); 202 203 if (n < 0) 204 int_not_found++; 205 else 206 int_found[n]++; 207 return 1; 208 } 209 210 static uint64_t hashtable_hash(uint8_t *key, size_t keylen) 211 { 212 return (uint64_t)(*(uint32_t *)key); 213 } 214 215 static int test_int_hashtable(void) 216 { 217 static struct { 218 int data; 219 int should_del; 220 } dels[] = { 221 { 65537 , 1}, 222 { 173 , 1}, 223 { 999 , 0 }, 224 { 37 , 1 }, 225 { 1 , 1 }, 226 { 34 , 0 } 227 }; 228 const size_t n_dels = OSSL_NELEM(dels); 229 HT_CONFIG hash_conf = { 230 NULL, 231 NULL, 232 NULL, 233 0, 234 1, 235 }; 236 INTKEY key; 237 int rc = 0; 238 size_t i; 239 HT *ht = NULL; 240 int todel; 241 HT_VALUE_LIST *list = NULL; 242 243 ht = ossl_ht_new(&hash_conf); 244 245 if (ht == NULL) 246 return 0; 247 248 /* insert */ 249 HT_INIT_KEY(&key); 250 for (i = 0; i < n_int_tests; i++) { 251 HT_SET_KEY_FIELD(&key, mykey, int_tests[i]); 252 if (!TEST_int_eq(ossl_ht_test_int_insert(ht, TO_HT_KEY(&key), 253 &int_tests[i], NULL), 1)) { 254 TEST_info("int insert %zu", i); 255 goto end; 256 } 257 } 258 259 /* num_items */ 260 if (!TEST_int_eq((size_t)ossl_ht_count(ht), n_int_tests)) 261 goto end; 262 263 /* foreach, no arg */ 264 memset(int_found, 0, sizeof(int_found)); 265 int_not_found = 0; 266 ossl_ht_foreach_until(ht, int_foreach, NULL); 267 if (!TEST_int_eq(int_not_found, 0)) { 268 TEST_info("hashtable int foreach encountered a not found condition"); 269 goto end; 270 } 271 272 for (i = 0; i < n_int_tests; i++) 273 if (!TEST_int_eq(int_found[i], 1)) { 274 TEST_info("hashtable int foreach %zu", i); 275 goto end; 276 } 277 278 /* filter */ 279 list = ossl_ht_filter(ht, 64, int_filter_all, NULL); 280 if (!TEST_int_eq((size_t)list->list_len, n_int_tests)) 281 goto end; 282 ossl_ht_value_list_free(list); 283 284 /* delete */ 285 for (i = 0; i < n_dels; i++) { 286 HT_SET_KEY_FIELD(&key, mykey, dels[i].data); 287 todel = ossl_ht_delete(ht, TO_HT_KEY(&key)); 288 if (dels[i].should_del) { 289 if (!TEST_int_eq(todel, 1)) { 290 TEST_info("hashtable couldn't find entry %d to delete\n", 291 dels[i].data); 292 goto end; 293 } 294 } else { 295 if (!TEST_int_eq(todel, 0)) { 296 TEST_info("%d found an entry that shouldn't be there\n", dels[i].data); 297 goto end; 298 } 299 } 300 } 301 302 rc = 1; 303 end: 304 ossl_ht_free(ht); 305 return rc; 306 } 307 308 static unsigned long int stress_hash(const int *p) 309 { 310 return *p; 311 } 312 313 #ifdef MEASURE_HASH_PERFORMANCE 314 static int 315 timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y) 316 { 317 /* Perform the carry for the later subtraction by updating y. */ 318 if (x->tv_usec < y->tv_usec) { 319 int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; 320 y->tv_usec -= 1000000 * nsec; 321 y->tv_sec += nsec; 322 } 323 if (x->tv_usec - y->tv_usec > 1000000) { 324 int nsec = (x->tv_usec - y->tv_usec) / 1000000; 325 y->tv_usec += 1000000 * nsec; 326 y->tv_sec -= nsec; 327 } 328 329 /* 330 * Compute the time remaining to wait. 331 * tv_usec is certainly positive. 332 */ 333 result->tv_sec = x->tv_sec - y->tv_sec; 334 result->tv_usec = x->tv_usec - y->tv_usec; 335 336 /* Return 1 if result is negative. */ 337 return x->tv_sec < y->tv_sec; 338 } 339 #endif 340 341 static int test_stress(void) 342 { 343 LHASH_OF(int) *h = lh_int_new(&stress_hash, &int_cmp); 344 const unsigned int n = 2500000; 345 unsigned int i; 346 int testresult = 0, *p; 347 #ifdef MEASURE_HASH_PERFORMANCE 348 struct timeval start, end, delta; 349 #endif 350 351 if (!TEST_ptr(h)) 352 goto end; 353 #ifdef MEASURE_HASH_PERFORMANCE 354 gettimeofday(&start, NULL); 355 #endif 356 /* insert */ 357 for (i = 0; i < n; i++) { 358 p = OPENSSL_malloc(sizeof(i)); 359 if (!TEST_ptr(p)) { 360 TEST_info("lhash stress out of memory %d", i); 361 goto end; 362 } 363 *p = 3 * i + 1; 364 lh_int_insert(h, p); 365 } 366 367 /* num_items */ 368 if (!TEST_int_eq(lh_int_num_items(h), n)) 369 goto end; 370 371 /* delete in a different order */ 372 for (i = 0; i < n; i++) { 373 const int j = (7 * i + 4) % n * 3 + 1; 374 375 if (!TEST_ptr(p = lh_int_delete(h, &j))) { 376 TEST_info("lhash stress delete %d\n", i); 377 goto end; 378 } 379 if (!TEST_int_eq(*p, j)) { 380 TEST_info("lhash stress bad value %d", i); 381 goto end; 382 } 383 OPENSSL_free(p); 384 } 385 386 testresult = 1; 387 end: 388 #ifdef MEASURE_HASH_PERFORMANCE 389 gettimeofday(&end, NULL); 390 timeval_subtract(&delta, &end, &start); 391 TEST_info("lhash stress runs in %ld.%ld seconds", delta.tv_sec, delta.tv_usec); 392 #endif 393 lh_int_free(h); 394 return testresult; 395 } 396 397 static void hashtable_intfree(HT_VALUE *v) 398 { 399 OPENSSL_free(v->value); 400 } 401 402 static int test_hashtable_stress(int idx) 403 { 404 const unsigned int n = 2500000; 405 unsigned int i; 406 int testresult = 0, *p; 407 HT_CONFIG hash_conf = { 408 NULL, /* use default context */ 409 hashtable_intfree, /* our free function */ 410 hashtable_hash, /* our hash function */ 411 625000, /* preset hash size */ 412 1, /* Check collisions */ 413 0 /* Lockless reads */ 414 }; 415 HT *h; 416 INTKEY key; 417 HT_VALUE *v; 418 #ifdef MEASURE_HASH_PERFORMANCE 419 struct timeval start, end, delta; 420 #endif 421 422 hash_conf.lockless_reads = idx; 423 h = ossl_ht_new(&hash_conf); 424 425 426 if (!TEST_ptr(h)) 427 goto end; 428 #ifdef MEASURE_HASH_PERFORMANCE 429 gettimeofday(&start, NULL); 430 #endif 431 432 HT_INIT_KEY(&key); 433 434 /* insert */ 435 for (i = 0; i < n; i++) { 436 p = OPENSSL_malloc(sizeof(i)); 437 if (!TEST_ptr(p)) { 438 TEST_info("hashtable stress out of memory %d", i); 439 goto end; 440 } 441 *p = 3 * i + 1; 442 HT_SET_KEY_FIELD(&key, mykey, *p); 443 if (!TEST_int_eq(ossl_ht_test_int_insert(h, TO_HT_KEY(&key), 444 p, NULL), 1)) { 445 TEST_info("hashtable unable to insert element %d\n", *p); 446 goto end; 447 } 448 } 449 450 /* make sure we stored everything */ 451 if (!TEST_int_eq((size_t)ossl_ht_count(h), n)) 452 goto end; 453 454 /* delete or get in a different order */ 455 for (i = 0; i < n; i++) { 456 const int j = (7 * i + 4) % n * 3 + 1; 457 HT_SET_KEY_FIELD(&key, mykey, j); 458 459 switch (idx) { 460 case 0: 461 if (!TEST_int_eq((ossl_ht_delete(h, TO_HT_KEY(&key))), 1)) { 462 TEST_info("hashtable didn't delete key %d\n", j); 463 goto end; 464 } 465 break; 466 case 1: 467 if (!TEST_ptr(p = ossl_ht_test_int_get(h, TO_HT_KEY(&key), &v)) 468 || !TEST_int_eq(*p, j)) { 469 TEST_info("hashtable didn't get key %d\n", j); 470 goto end; 471 } 472 break; 473 } 474 } 475 476 testresult = 1; 477 end: 478 #ifdef MEASURE_HASH_PERFORMANCE 479 gettimeofday(&end, NULL); 480 timeval_subtract(&delta, &end, &start); 481 TEST_info("hashtable stress runs in %ld.%ld seconds", delta.tv_sec, delta.tv_usec); 482 #endif 483 ossl_ht_free(h); 484 return testresult; 485 } 486 487 typedef struct test_mt_entry { 488 int in_table; 489 int pending_delete; 490 } TEST_MT_ENTRY; 491 492 static HT *m_ht = NULL; 493 #define TEST_MT_POOL_SZ 256 494 #define TEST_THREAD_ITERATIONS 1000000 495 #define NUM_WORKERS 16 496 497 static struct test_mt_entry test_mt_entries[TEST_MT_POOL_SZ]; 498 static char *worker_exits[NUM_WORKERS]; 499 500 HT_START_KEY_DEFN(mtkey) 501 HT_DEF_KEY_FIELD(index, uint32_t) 502 HT_END_KEY_DEFN(MTKEY) 503 504 IMPLEMENT_HT_VALUE_TYPE_FNS(TEST_MT_ENTRY, mt, static) 505 506 static int worker_num = 0; 507 static CRYPTO_RWLOCK *worker_lock; 508 static CRYPTO_RWLOCK *testrand_lock; 509 static int free_failure = 0; 510 static int shutting_down = 0; 511 static int global_iteration = 0; 512 513 static void hashtable_mt_free(HT_VALUE *v) 514 { 515 TEST_MT_ENTRY *m = ossl_ht_mt_TEST_MT_ENTRY_from_value(v); 516 int pending_delete; 517 int ret; 518 519 CRYPTO_atomic_load_int(&m->pending_delete, &pending_delete, worker_lock); 520 521 if (shutting_down == 1) 522 return; 523 524 if (pending_delete == 0) { 525 TEST_info("Freeing element which was not scheduled for free"); 526 free_failure = 1; 527 } else { 528 CRYPTO_atomic_add(&m->pending_delete, -1, 529 &ret, worker_lock); 530 } 531 } 532 533 #define DO_LOOKUP 0 534 #define DO_INSERT 1 535 #define DO_REPLACE 2 536 #define DO_DELETE 3 537 #define NUM_BEHAVIORS (DO_DELETE + 1) 538 539 static void do_mt_hash_work(void) 540 { 541 MTKEY key; 542 uint32_t index; 543 int num; 544 TEST_MT_ENTRY *m; 545 TEST_MT_ENTRY *expected_m = NULL; 546 HT_VALUE *v = NULL; 547 TEST_MT_ENTRY **r = NULL; 548 int expected_rc; 549 int ret; 550 char behavior; 551 size_t iter = 0; 552 int giter; 553 554 CRYPTO_atomic_add(&worker_num, 1, &num, worker_lock); 555 num--; /* atomic_add is an add/fetch operation */ 556 557 HT_INIT_KEY(&key); 558 559 for (iter = 0; iter < TEST_THREAD_ITERATIONS; iter++) { 560 if (!TEST_true(CRYPTO_THREAD_write_lock(testrand_lock))) 561 return; 562 index = test_random() % TEST_MT_POOL_SZ; 563 behavior = (char)(test_random() % NUM_BEHAVIORS); 564 CRYPTO_THREAD_unlock(testrand_lock); 565 566 expected_m = &test_mt_entries[index]; 567 HT_KEY_RESET(&key); 568 HT_SET_KEY_FIELD(&key, index, index); 569 570 if (!CRYPTO_atomic_add(&global_iteration, 1, &giter, worker_lock)) { 571 worker_exits[num] = "Unable to increment global iterator"; 572 return; 573 } 574 switch(behavior) { 575 case DO_LOOKUP: 576 ossl_ht_read_lock(m_ht); 577 m = ossl_ht_mt_TEST_MT_ENTRY_get(m_ht, TO_HT_KEY(&key), &v); 578 if (m != NULL && m != expected_m) { 579 worker_exits[num] = "Read unexpected value from hashtable"; 580 TEST_info("Iteration %d Read unexpected value %p when %p expected", 581 giter, (void *)m, (void *)expected_m); 582 } 583 ossl_ht_read_unlock(m_ht); 584 if (worker_exits[num] != NULL) 585 return; 586 break; 587 case DO_INSERT: 588 case DO_REPLACE: 589 ossl_ht_write_lock(m_ht); 590 if (behavior == DO_REPLACE) { 591 expected_rc = 1; 592 r = &m; 593 } else { 594 expected_rc = !expected_m->in_table; 595 r = NULL; 596 } 597 598 if (expected_rc != ossl_ht_mt_TEST_MT_ENTRY_insert(m_ht, 599 TO_HT_KEY(&key), 600 expected_m, r)) { 601 TEST_info("Iteration %d Expected rc %d on %s of element %u which is %s\n", 602 giter, expected_rc, behavior == DO_REPLACE ? "replace" : "insert", 603 (unsigned int)index, 604 expected_m->in_table ? "in table" : "not in table"); 605 worker_exits[num] = "Failure on insert"; 606 } 607 if (expected_rc == 1) 608 expected_m->in_table = 1; 609 ossl_ht_write_unlock(m_ht); 610 if (worker_exits[num] != NULL) 611 return; 612 break; 613 case DO_DELETE: 614 ossl_ht_write_lock(m_ht); 615 expected_rc = expected_m->in_table; 616 if (expected_rc == 1) { 617 /* 618 * We must set pending_delete before the actual deletion 619 * as another inserting or deleting thread can pick up 620 * the delete callback before the ossl_ht_write_unlock() call. 621 * This can happen only if no read locks are pending and 622 * only on Windows where we do not use the write mutex 623 * to get the callback list. 624 */ 625 expected_m->in_table = 0; 626 CRYPTO_atomic_add(&expected_m->pending_delete, 1, &ret, worker_lock); 627 } 628 if (expected_rc != ossl_ht_delete(m_ht, TO_HT_KEY(&key))) { 629 TEST_info("Iteration %d Expected rc %d on delete of element %u which is %s\n", 630 giter, expected_rc, (unsigned int)index, 631 expected_m->in_table ? "in table" : "not in table"); 632 worker_exits[num] = "Failure on delete"; 633 } 634 ossl_ht_write_unlock(m_ht); 635 if (worker_exits[num] != NULL) 636 return; 637 break; 638 default: 639 worker_exits[num] = "Undefined behavior specified"; 640 return; 641 } 642 } 643 } 644 645 static int test_hashtable_multithread(void) 646 { 647 HT_CONFIG hash_conf = { 648 NULL, /* use default context */ 649 hashtable_mt_free, /* our free function */ 650 NULL, /* default hash function */ 651 0, /* default hash size */ 652 1, /* Check collisions */ 653 }; 654 int ret = 0; 655 thread_t workers[NUM_WORKERS]; 656 int i; 657 #ifdef MEASURE_HASH_PERFORMANCE 658 struct timeval start, end, delta; 659 #endif 660 661 memset(worker_exits, 0, sizeof(char *) * NUM_WORKERS); 662 memset(test_mt_entries, 0, sizeof(TEST_MT_ENTRY) * TEST_MT_POOL_SZ); 663 memset(workers, 0, sizeof(thread_t) * NUM_WORKERS); 664 665 m_ht = ossl_ht_new(&hash_conf); 666 667 if (!TEST_ptr(m_ht)) 668 goto end; 669 670 if (!TEST_ptr(worker_lock = CRYPTO_THREAD_lock_new())) 671 goto end_free; 672 if (!TEST_ptr(testrand_lock = CRYPTO_THREAD_lock_new())) 673 goto end_free; 674 #ifdef MEASURE_HASH_PERFORMANCE 675 gettimeofday(&start, NULL); 676 #endif 677 678 for (i = 0; i < NUM_WORKERS; i++) { 679 if (!run_thread(&workers[i], do_mt_hash_work)) 680 goto shutdown; 681 } 682 683 shutdown: 684 for (--i; i >= 0; i--) { 685 wait_for_thread(workers[i]); 686 } 687 688 689 /* 690 * Now that the workers are done, check for any error 691 * conditions 692 */ 693 ret = 1; 694 for (i = 0; i < NUM_WORKERS; i++) { 695 if (worker_exits[i] != NULL) { 696 TEST_info("Worker %d failed: %s\n", i, worker_exits[i]); 697 ret = 0; 698 } 699 } 700 if (free_failure == 1) { 701 TEST_info("Encountered a free failure"); 702 ret = 0; 703 } 704 705 #ifdef MEASURE_HASH_PERFORMANCE 706 gettimeofday(&end, NULL); 707 timeval_subtract(&delta, &end, &start); 708 TEST_info("multithread stress runs 40000 ops in %ld.%ld seconds", delta.tv_sec, delta.tv_usec); 709 #endif 710 711 end_free: 712 shutting_down = 1; 713 ossl_ht_free(m_ht); 714 CRYPTO_THREAD_lock_free(worker_lock); 715 CRYPTO_THREAD_lock_free(testrand_lock); 716 end: 717 return ret; 718 } 719 720 int setup_tests(void) 721 { 722 ADD_TEST(test_int_lhash); 723 ADD_TEST(test_stress); 724 ADD_TEST(test_int_hashtable); 725 ADD_ALL_TESTS(test_hashtable_stress, 2); 726 ADD_TEST(test_hashtable_multithread); 727 return 1; 728 } 729