1 /* 2 * Copyright 2024-2026 The OpenSSL Project Authors. All Rights Reserved. 3 * 4 * Licensed under the Apache License 2.0 (the "License"). You may not use 5 * this file except in compliance with the License. You can obtain a copy 6 * in the file LICENSE in the source distribution or at 7 * https://www.openssl.org/source/license.html 8 * 9 * 10 * 11 * Notes On hash table design and layout 12 * This hashtable uses a hopscotch algorithm to do indexing. The data structure 13 * looks as follows: 14 * 15 * hash +--------------+ 16 * value+------->+ HT_VALUE | 17 * + +--------------+ 18 * +-------+ 19 * | | 20 * +---------------------------------------------------------+ 21 * | | | | | | 22 * | entry | entry | entry | entry | | 23 * | | | | | | 24 * +---------------------------------------------------------+ 25 * | | | 26 * | | | 27 * +---------------------------------------------------------+ 28 * | + + + 29 * | neighborhood[0] neighborhood[1] | 30 * | | 31 * | | 32 * +---------------------------------------------------------+ 33 * | 34 * + 35 * neighborhoods 36 * 37 * On lookup/insert/delete, the items key is hashed to a 64 bit value 38 * and the result is masked to provide an index into the neighborhoods 39 * table. Once a neighborhood is determined, an in-order search is done 40 * of the elements in the neighborhood indexes entries for a matching hash 41 * value, if found, the corresponding HT_VALUE is used for the respective 42 * operation. The number of entries in a neighborhood is determined at build 43 * time based on the cacheline size of the target CPU. The intent is for a 44 * neighborhood to have all entries in the neighborhood fit into a single cache 45 * line to speed up lookups. If all entries in a neighborhood are in use at the 46 * time of an insert, the table is expanded and rehashed. 47 * 48 * Lockless reads hash table is based on the same design but does not 49 * allow growing and deletion. Thus subsequent neighborhoods are always 50 * searched for a match until an empty entry is found. 51 */ 52 53 #include <string.h> 54 #include <internal/rcu.h> 55 #include <internal/hashtable.h> 56 #include <internal/hashfunc.h> 57 #include <openssl/rand.h> 58 59 /* 60 * gcc defines __SANITIZE_THREAD__ 61 * but clang uses the feature attributes api 62 * map the latter to the former 63 */ 64 #if defined(__clang__) && defined(__has_feature) 65 #if __has_feature(thread_sanitizer) 66 #define __SANITIZE_THREADS__ 67 #endif 68 #endif 69 70 #ifdef __SANITIZE_THREADS__ 71 #include <sanitizer/tsan_interface.h> 72 #endif 73 74 #include "internal/numbers.h" 75 /* 76 * When we do a lookup/insert/delete, there is a high likelihood 77 * that we will iterate over at least part of the neighborhood list 78 * As such, because we design a neighborhood entry to fit into a single 79 * cache line it is advantageous, when supported to fetch the entire 80 * structure for faster lookups 81 */ 82 #if defined(__GNUC__) || defined(__CLANG__) 83 #define PREFETCH_NEIGHBORHOOD(x) __builtin_prefetch(x.entries) 84 #define PREFETCH(x) __builtin_prefetch(x) 85 #define ALIGN __attribute__((aligned(8))) 86 #else 87 #define PREFETCH_NEIGHBORHOOD(x) 88 #define PREFETCH(x) 89 #define ALIGN 90 #endif 91 92 /* 93 * Define our neighborhood list length 94 * Note: It should always be a power of 2 95 */ 96 #define DEFAULT_NEIGH_LEN_LOG 4 97 #define DEFAULT_NEIGH_LEN (1 << DEFAULT_NEIGH_LEN_LOG) 98 99 /* 100 * For now assume cache line size is 64 bytes 101 */ 102 #define CACHE_LINE_BYTES 64 103 #define CACHE_LINE_ALIGNMENT CACHE_LINE_BYTES 104 105 #define NEIGHBORHOOD_LEN (CACHE_LINE_BYTES / sizeof(struct ht_neighborhood_entry_st)) 106 /* 107 * Defines our chains of values 108 */ 109 struct ht_internal_value_st { 110 HT_VALUE value; 111 HT *ht; 112 }; 113 114 struct ht_neighborhood_entry_st { 115 uint64_t hash; 116 struct ht_internal_value_st *value; 117 } ALIGN; 118 119 struct ht_neighborhood_st { 120 struct ht_neighborhood_entry_st entries[NEIGHBORHOOD_LEN]; 121 }; 122 123 /* 124 * Updates to data in this struct 125 * require an rcu sync after modification 126 * prior to free 127 */ 128 struct ht_mutable_data_st { 129 struct ht_neighborhood_st *neighborhoods; 130 void *neighborhood_ptr_to_free; 131 uint64_t neighborhood_mask; 132 }; 133 134 /* 135 * Private data may be updated on the write 136 * side only, and so do not require rcu sync 137 */ 138 struct ht_write_private_data_st { 139 size_t neighborhood_len; 140 size_t value_count; 141 int need_sync; 142 }; 143 144 struct ht_internal_st { 145 HT_CONFIG config; 146 CRYPTO_RCU_LOCK *lock; 147 CRYPTO_RWLOCK *atomic_lock; 148 struct ht_mutable_data_st *md; 149 struct ht_write_private_data_st wpd; 150 }; 151 152 static void free_value(struct ht_internal_value_st *v); 153 154 static struct ht_neighborhood_st *alloc_new_neighborhood_list(size_t len, 155 void **freeptr) 156 { 157 struct ht_neighborhood_st *ret; 158 159 ret = OPENSSL_aligned_alloc(sizeof(struct ht_neighborhood_st) * len, 160 CACHE_LINE_BYTES, freeptr); 161 162 /* fall back to regular malloc */ 163 if (ret == NULL) { 164 ret = *freeptr = OPENSSL_malloc(sizeof(struct ht_neighborhood_st) * len); 165 if (ret == NULL) 166 return NULL; 167 } 168 memset(ret, 0, sizeof(struct ht_neighborhood_st) * len); 169 return ret; 170 } 171 172 static void internal_free_nop(HT_VALUE *v) 173 { 174 return; 175 } 176 177 HT *ossl_ht_new(const HT_CONFIG *conf) 178 { 179 HT *new = OPENSSL_zalloc(sizeof(*new)); 180 181 if (new == NULL) 182 return NULL; 183 184 new->atomic_lock = CRYPTO_THREAD_lock_new(); 185 if (new->atomic_lock == NULL) 186 goto err; 187 188 memcpy(&new->config, conf, sizeof(*conf)); 189 190 if (new->config.init_neighborhoods != 0) { 191 new->wpd.neighborhood_len = new->config.init_neighborhoods; 192 /* round up to the next power of 2 */ 193 new->wpd.neighborhood_len--; 194 new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 1; 195 new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 2; 196 new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 4; 197 new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 8; 198 new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 16; 199 new->wpd.neighborhood_len++; 200 } else { 201 new->wpd.neighborhood_len = DEFAULT_NEIGH_LEN; 202 } 203 204 if (new->config.ht_free_fn == NULL) 205 new->config.ht_free_fn = internal_free_nop; 206 207 new->md = OPENSSL_zalloc(sizeof(*new->md)); 208 if (new->md == NULL) 209 goto err; 210 211 new->md->neighborhoods = alloc_new_neighborhood_list(new->wpd.neighborhood_len, 212 &new->md->neighborhood_ptr_to_free); 213 if (new->md->neighborhoods == NULL) 214 goto err; 215 new->md->neighborhood_mask = new->wpd.neighborhood_len - 1; 216 217 new->lock = ossl_rcu_lock_new(1, conf->ctx); 218 if (new->lock == NULL) 219 goto err; 220 221 if (new->config.ht_hash_fn == NULL) 222 new->config.ht_hash_fn = ossl_fnv1a_hash; 223 224 return new; 225 226 err: 227 CRYPTO_THREAD_lock_free(new->atomic_lock); 228 ossl_rcu_lock_free(new->lock); 229 if (new->md != NULL) 230 OPENSSL_free(new->md->neighborhood_ptr_to_free); 231 OPENSSL_free(new->md); 232 OPENSSL_free(new); 233 return NULL; 234 } 235 236 void ossl_ht_read_lock(HT *htable) 237 { 238 ossl_rcu_read_lock(htable->lock); 239 } 240 241 void ossl_ht_read_unlock(HT *htable) 242 { 243 ossl_rcu_read_unlock(htable->lock); 244 } 245 246 void ossl_ht_write_lock(HT *htable) 247 { 248 ossl_rcu_write_lock(htable->lock); 249 htable->wpd.need_sync = 0; 250 } 251 252 void ossl_ht_write_unlock(HT *htable) 253 { 254 int need_sync = htable->wpd.need_sync; 255 256 htable->wpd.need_sync = 0; 257 ossl_rcu_write_unlock(htable->lock); 258 if (need_sync) 259 ossl_synchronize_rcu(htable->lock); 260 } 261 262 static void free_oldmd(void *arg) 263 { 264 struct ht_mutable_data_st *oldmd = arg; 265 size_t i, j; 266 size_t neighborhood_len = (size_t)oldmd->neighborhood_mask + 1; 267 struct ht_internal_value_st *v; 268 269 for (i = 0; i < neighborhood_len; i++) { 270 PREFETCH_NEIGHBORHOOD(oldmd->neighborhoods[i + 1]); 271 for (j = 0; j < NEIGHBORHOOD_LEN; j++) { 272 if (oldmd->neighborhoods[i].entries[j].value != NULL) { 273 v = oldmd->neighborhoods[i].entries[j].value; 274 v->ht->config.ht_free_fn((HT_VALUE *)v); 275 free_value(v); 276 } 277 } 278 } 279 280 OPENSSL_free(oldmd->neighborhood_ptr_to_free); 281 OPENSSL_free(oldmd); 282 } 283 284 static int ossl_ht_flush_internal(HT *h) 285 { 286 struct ht_mutable_data_st *newmd = NULL; 287 struct ht_mutable_data_st *oldmd = NULL; 288 CRYPTO_RCU_CB_ITEM *cbi = NULL; 289 290 newmd = OPENSSL_zalloc(sizeof(*newmd)); 291 if (newmd == NULL) 292 return 0; 293 294 newmd->neighborhoods = alloc_new_neighborhood_list(DEFAULT_NEIGH_LEN, 295 &newmd->neighborhood_ptr_to_free); 296 if (newmd->neighborhoods == NULL) { 297 OPENSSL_free(newmd); 298 return 0; 299 } 300 301 newmd->neighborhood_mask = DEFAULT_NEIGH_LEN - 1; 302 303 cbi = ossl_rcu_cb_item_new(); 304 if (cbi == NULL) { 305 OPENSSL_free(newmd->neighborhood_ptr_to_free); 306 OPENSSL_free(newmd); 307 return 0; 308 } 309 310 /* Swap the old and new mutable data sets */ 311 oldmd = ossl_rcu_deref(&h->md); 312 ossl_rcu_assign_ptr(&h->md, &newmd); 313 314 /* Set the number of entries to 0 */ 315 h->wpd.value_count = 0; 316 h->wpd.neighborhood_len = DEFAULT_NEIGH_LEN; 317 318 ossl_rcu_call(h->lock, cbi, free_oldmd, oldmd); 319 h->wpd.need_sync = 1; 320 321 return 1; 322 } 323 324 int ossl_ht_flush(HT *h) 325 { 326 return ossl_ht_flush_internal(h); 327 } 328 329 void ossl_ht_free(HT *h) 330 { 331 int flush_ok; 332 333 if (h == NULL) 334 return; 335 336 ossl_ht_write_lock(h); 337 flush_ok = ossl_ht_flush_internal(h); 338 ossl_ht_write_unlock(h); 339 /* Freeing the lock does a final sync for us */ 340 CRYPTO_THREAD_lock_free(h->atomic_lock); 341 ossl_rcu_lock_free(h->lock); 342 if (flush_ok) { 343 OPENSSL_free(h->md->neighborhood_ptr_to_free); 344 OPENSSL_free(h->md); 345 } else { 346 free_oldmd(h->md); 347 } 348 OPENSSL_free(h); 349 return; 350 } 351 352 size_t ossl_ht_count(HT *h) 353 { 354 size_t count; 355 356 count = h->wpd.value_count; 357 return count; 358 } 359 360 void ossl_ht_foreach_until(HT *h, int (*cb)(HT_VALUE *obj, void *arg), 361 void *arg) 362 { 363 size_t i, j; 364 struct ht_mutable_data_st *md; 365 366 md = ossl_rcu_deref(&h->md); 367 for (i = 0; i < md->neighborhood_mask + 1; i++) { 368 PREFETCH_NEIGHBORHOOD(md->neighborhoods[i + 1]); 369 for (j = 0; j < NEIGHBORHOOD_LEN; j++) { 370 if (md->neighborhoods[i].entries[j].value != NULL) { 371 if (!cb((HT_VALUE *)md->neighborhoods[i].entries[j].value, arg)) 372 goto out; 373 } 374 } 375 } 376 out: 377 return; 378 } 379 380 HT_VALUE_LIST *ossl_ht_filter(HT *h, size_t max_len, 381 int (*filter)(HT_VALUE *obj, void *arg), 382 void *arg) 383 { 384 struct ht_mutable_data_st *md; 385 HT_VALUE_LIST *list = OPENSSL_zalloc(sizeof(HT_VALUE_LIST) 386 + (sizeof(HT_VALUE *) * max_len)); 387 size_t i, j; 388 struct ht_internal_value_st *v; 389 390 if (list == NULL) 391 return NULL; 392 393 /* 394 * The list array lives just beyond the end of 395 * the struct 396 */ 397 list->list = (HT_VALUE **)(list + 1); 398 399 md = ossl_rcu_deref(&h->md); 400 for (i = 0; i < md->neighborhood_mask + 1; i++) { 401 PREFETCH_NEIGHBORHOOD(md->neighborhoods[i + 1]); 402 for (j = 0; j < NEIGHBORHOOD_LEN; j++) { 403 v = md->neighborhoods[i].entries[j].value; 404 if (v != NULL && filter((HT_VALUE *)v, arg)) { 405 list->list[list->list_len++] = (HT_VALUE *)v; 406 if (list->list_len == max_len) 407 goto out; 408 } 409 } 410 } 411 out: 412 return list; 413 } 414 415 void ossl_ht_value_list_free(HT_VALUE_LIST *list) 416 { 417 OPENSSL_free(list); 418 } 419 420 static int compare_hash(uint64_t hash1, uint64_t hash2) 421 { 422 return (hash1 == hash2); 423 } 424 425 static void free_old_neigh_table(void *arg) 426 { 427 struct ht_mutable_data_st *oldmd = arg; 428 429 OPENSSL_free(oldmd->neighborhood_ptr_to_free); 430 OPENSSL_free(oldmd); 431 } 432 433 /* 434 * Increase hash table bucket list 435 * must be called with write_lock held 436 */ 437 static int grow_hashtable(HT *h, size_t oldsize) 438 { 439 struct ht_mutable_data_st *newmd; 440 struct ht_mutable_data_st *oldmd = ossl_rcu_deref(&h->md); 441 CRYPTO_RCU_CB_ITEM *cbi = NULL; 442 int rc = 0; 443 uint64_t oldi, oldj, newi, newj; 444 uint64_t oldhash; 445 struct ht_internal_value_st *oldv; 446 int rehashed; 447 size_t newsize = oldsize * 2; 448 449 if (h->config.lockless_reads) 450 goto out; 451 452 if ((newmd = OPENSSL_zalloc(sizeof(*newmd))) == NULL) 453 goto out; 454 455 /* bucket list is always a power of 2 */ 456 newmd->neighborhoods = alloc_new_neighborhood_list(oldsize * 2, 457 &newmd->neighborhood_ptr_to_free); 458 if (newmd->neighborhoods == NULL) 459 goto out_free; 460 461 /* being a power of 2 makes for easy mask computation */ 462 newmd->neighborhood_mask = (newsize - 1); 463 464 /* 465 * Now we need to start rehashing entries 466 * Note we don't need to use atomics here as the new 467 * mutable data hasn't been published 468 */ 469 for (oldi = 0; oldi < h->wpd.neighborhood_len; oldi++) { 470 PREFETCH_NEIGHBORHOOD(oldmd->neighborhoods[oldi + 1]); 471 for (oldj = 0; oldj < NEIGHBORHOOD_LEN; oldj++) { 472 oldv = oldmd->neighborhoods[oldi].entries[oldj].value; 473 if (oldv == NULL) 474 continue; 475 oldhash = oldmd->neighborhoods[oldi].entries[oldj].hash; 476 newi = oldhash & newmd->neighborhood_mask; 477 rehashed = 0; 478 for (newj = 0; newj < NEIGHBORHOOD_LEN; newj++) { 479 if (newmd->neighborhoods[newi].entries[newj].value == NULL) { 480 newmd->neighborhoods[newi].entries[newj].value = oldv; 481 newmd->neighborhoods[newi].entries[newj].hash = oldhash; 482 rehashed = 1; 483 break; 484 } 485 } 486 if (rehashed == 0) { 487 /* we ran out of space in a neighborhood, grow again */ 488 OPENSSL_free(newmd->neighborhood_ptr_to_free); 489 OPENSSL_free(newmd); 490 return grow_hashtable(h, newsize); 491 } 492 } 493 } 494 495 /* 496 * Pre allocate the rcu callback item before assigning the newmd. 497 */ 498 cbi = ossl_rcu_cb_item_new(); 499 if (cbi == NULL) 500 goto out_free; 501 502 /* 503 * Now that our entries are all hashed into the new bucket list 504 * update our bucket_len and target_max_load 505 */ 506 h->wpd.neighborhood_len = newsize; 507 508 /* 509 * Now we replace the old mutable data with the new 510 */ 511 ossl_rcu_assign_ptr(&h->md, &newmd); 512 ossl_rcu_call(h->lock, cbi, free_old_neigh_table, oldmd); 513 h->wpd.need_sync = 1; 514 /* 515 * And we're done 516 */ 517 rc = 1; 518 519 out: 520 return rc; 521 out_free: 522 OPENSSL_free(newmd->neighborhood_ptr_to_free); 523 OPENSSL_free(newmd); 524 goto out; 525 } 526 527 static void free_old_ht_value(void *arg) 528 { 529 HT_VALUE *h = (HT_VALUE *)arg; 530 531 /* 532 * Note, this is only called on replacement, 533 * the caller is responsible for freeing the 534 * held data, we just need to free the wrapping 535 * struct here 536 */ 537 OPENSSL_free(h); 538 } 539 540 static ossl_inline int match_key(HT_KEY *a, HT_KEY *b) 541 { 542 /* 543 * keys match if they are both present, the same size 544 * and compare equal in memory 545 */ 546 PREFETCH(a->keybuf); 547 PREFETCH(b->keybuf); 548 if (a->keybuf != NULL && b->keybuf != NULL && a->keysize == b->keysize) 549 return !memcmp(a->keybuf, b->keybuf, a->keysize); 550 551 return 1; 552 } 553 554 static int ossl_ht_insert_locked(HT *h, uint64_t hash, 555 struct ht_internal_value_st *newval, 556 HT_VALUE **olddata) 557 { 558 struct ht_mutable_data_st *md = h->md; 559 uint64_t neigh_idx_start = hash & md->neighborhood_mask; 560 uint64_t neigh_idx = neigh_idx_start; 561 size_t j; 562 uint64_t ihash; 563 HT_VALUE *ival; 564 size_t empty_idx = SIZE_MAX; 565 int lockless_reads = h->config.lockless_reads; 566 CRYPTO_RCU_CB_ITEM *cbi; 567 568 do { 569 PREFETCH_NEIGHBORHOOD(md->neighborhoods[neigh_idx]); 570 571 for (j = 0; j < NEIGHBORHOOD_LEN; j++) { 572 ival = ossl_rcu_deref(&md->neighborhoods[neigh_idx].entries[j].value); 573 if (ival == NULL) { 574 empty_idx = j; 575 /* lockless_reads implies no deletion, we can break out */ 576 if (lockless_reads) 577 goto not_found; 578 continue; 579 } 580 if (!CRYPTO_atomic_load(&md->neighborhoods[neigh_idx].entries[j].hash, 581 &ihash, h->atomic_lock)) 582 return 0; 583 if (compare_hash(hash, ihash) && match_key(&newval->value.key, &ival->key)) { 584 if (olddata == NULL) { 585 /* This would insert a duplicate -> fail */ 586 return 0; 587 } 588 /* Do a replacement */ 589 cbi = ossl_rcu_cb_item_new(); 590 if (cbi == NULL) 591 return 0; 592 if (!CRYPTO_atomic_store(&md->neighborhoods[neigh_idx].entries[j].hash, 593 hash, h->atomic_lock)) 594 return 0; 595 *olddata = (HT_VALUE *)md->neighborhoods[neigh_idx].entries[j].value; 596 ossl_rcu_assign_ptr(&md->neighborhoods[neigh_idx].entries[j].value, 597 &newval); 598 ossl_rcu_call(h->lock, cbi, free_old_ht_value, *olddata); 599 h->wpd.need_sync = 1; 600 return 1; 601 } 602 } 603 if (!lockless_reads) 604 break; 605 /* Continue search in subsequent neighborhoods */ 606 neigh_idx = (neigh_idx + 1) & md->neighborhood_mask; 607 } while (neigh_idx != neigh_idx_start); 608 609 not_found: 610 /* If we get to here, its just an insert */ 611 if (empty_idx == SIZE_MAX) 612 return -1; /* out of space */ 613 if (!CRYPTO_atomic_store(&md->neighborhoods[neigh_idx].entries[empty_idx].hash, 614 hash, h->atomic_lock)) 615 return 0; 616 h->wpd.value_count++; 617 ossl_rcu_assign_ptr(&md->neighborhoods[neigh_idx].entries[empty_idx].value, 618 &newval); 619 return 1; 620 } 621 622 static struct ht_internal_value_st *alloc_new_value(HT *h, HT_KEY *key, 623 void *data, 624 uintptr_t *type) 625 { 626 struct ht_internal_value_st *tmp; 627 size_t nvsize = sizeof(*tmp); 628 629 if (h->config.collision_check == 1) 630 nvsize += key->keysize; 631 632 tmp = OPENSSL_malloc(nvsize); 633 634 if (tmp == NULL) 635 return NULL; 636 637 tmp->ht = h; 638 tmp->value.value = data; 639 tmp->value.type_id = type; 640 tmp->value.key.keybuf = NULL; 641 if (h->config.collision_check) { 642 tmp->value.key.keybuf = (uint8_t *)(tmp + 1); 643 tmp->value.key.keysize = key->keysize; 644 memcpy(tmp->value.key.keybuf, key->keybuf, key->keysize); 645 } 646 647 return tmp; 648 } 649 650 static void free_value(struct ht_internal_value_st *v) 651 { 652 OPENSSL_free(v); 653 } 654 655 int ossl_ht_insert(HT *h, HT_KEY *key, HT_VALUE *data, HT_VALUE **olddata) 656 { 657 struct ht_internal_value_st *newval = NULL; 658 uint64_t hash; 659 int rc = 0; 660 int i; 661 662 if (data->value == NULL) 663 goto out; 664 665 newval = alloc_new_value(h, key, data->value, data->type_id); 666 if (newval == NULL) 667 goto out; 668 669 /* 670 * we have to take our lock here to prevent other changes 671 * to the bucket list 672 */ 673 hash = h->config.ht_hash_fn(key->keybuf, key->keysize); 674 675 for (i = 0; 676 (rc = ossl_ht_insert_locked(h, hash, newval, olddata)) == -1 677 && i <= (int)NEIGHBORHOOD_LEN; 678 ++i) 679 if (!grow_hashtable(h, h->wpd.neighborhood_len)) { 680 rc = -1; 681 break; 682 } 683 684 if (rc <= 0) 685 free_value(newval); 686 687 out: 688 return rc; 689 } 690 691 HT_VALUE *ossl_ht_get(HT *h, HT_KEY *key) 692 { 693 struct ht_mutable_data_st *md; 694 uint64_t hash; 695 uint64_t neigh_idx_start; 696 uint64_t neigh_idx; 697 struct ht_internal_value_st *ival = NULL; 698 size_t j; 699 uint64_t ehash; 700 int lockless_reads = h->config.lockless_reads; 701 702 hash = h->config.ht_hash_fn(key->keybuf, key->keysize); 703 704 md = ossl_rcu_deref(&h->md); 705 neigh_idx = neigh_idx_start = hash & md->neighborhood_mask; 706 do { 707 PREFETCH_NEIGHBORHOOD(md->neighborhoods[neigh_idx]); 708 for (j = 0; j < NEIGHBORHOOD_LEN; j++) { 709 ival = ossl_rcu_deref(&md->neighborhoods[neigh_idx].entries[j].value); 710 if (ival == NULL) { 711 if (lockless_reads) 712 /* lockless_reads implies no deletion, we can break out */ 713 return NULL; 714 continue; 715 } 716 if (!CRYPTO_atomic_load(&md->neighborhoods[neigh_idx].entries[j].hash, 717 &ehash, h->atomic_lock)) 718 return NULL; 719 if (compare_hash(hash, ehash) && match_key(&ival->value.key, key)) 720 return (HT_VALUE *)ival; 721 } 722 if (!lockless_reads) 723 break; 724 /* Continue search in subsequent neighborhoods */ 725 neigh_idx = (neigh_idx + 1) & md->neighborhood_mask; 726 } while (neigh_idx != neigh_idx_start); 727 728 return NULL; 729 } 730 731 static void free_old_entry(void *arg) 732 { 733 struct ht_internal_value_st *v = arg; 734 735 v->ht->config.ht_free_fn((HT_VALUE *)v); 736 free_value(v); 737 } 738 739 int ossl_ht_delete(HT *h, HT_KEY *key) 740 { 741 uint64_t hash; 742 uint64_t neigh_idx; 743 size_t j; 744 struct ht_internal_value_st *v = NULL; 745 HT_VALUE *nv = NULL; 746 int rc = 0; 747 748 if (h->config.lockless_reads) 749 return 0; 750 751 hash = h->config.ht_hash_fn(key->keybuf, key->keysize); 752 753 neigh_idx = hash & h->md->neighborhood_mask; 754 PREFETCH_NEIGHBORHOOD(h->md->neighborhoods[neigh_idx]); 755 for (j = 0; j < NEIGHBORHOOD_LEN; j++) { 756 v = (struct ht_internal_value_st *)h->md->neighborhoods[neigh_idx].entries[j].value; 757 if (v == NULL) 758 continue; 759 if (compare_hash(hash, h->md->neighborhoods[neigh_idx].entries[j].hash) 760 && match_key(key, &v->value.key)) { 761 CRYPTO_RCU_CB_ITEM *cbi = ossl_rcu_cb_item_new(); 762 if (cbi == NULL) 763 break; 764 if (!CRYPTO_atomic_store(&h->md->neighborhoods[neigh_idx].entries[j].hash, 765 0, h->atomic_lock)) 766 break; 767 h->wpd.value_count--; 768 ossl_rcu_assign_ptr(&h->md->neighborhoods[neigh_idx].entries[j].value, 769 &nv); 770 ossl_rcu_call(h->lock, cbi, free_old_entry, v); 771 h->wpd.need_sync = 1; 772 rc = 1; 773 break; 774 } 775 } 776 return rc; 777 } 778