Home | History | Annotate | Line # | Download | only in hashtable
      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