Coverage Report

Created: 2025-12-10 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl/crypto/hashtable/hashtable.c
Line
Count
Source
1
/*
2
 * Copyright 2024-2025 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
/*
75
 * When we do a lookup/insert/delete, there is a high likelihood
76
 * that we will iterate over at least part of the neighborhood list
77
 * As such, because we design a neighborhood entry to fit into a single
78
 * cache line it is advantageous, when supported to fetch the entire
79
 * structure for faster lookups
80
 */
81
#if defined(__GNUC__) || defined(__CLANG__)
82
1.03M
#define PREFETCH_NEIGHBORHOOD(x) __builtin_prefetch(x.entries)
83
2.06M
#define PREFETCH(x) __builtin_prefetch(x)
84
#else
85
#define PREFETCH_NEIGHBORHOOD(x)
86
#define PREFETCH(x)
87
#endif
88
89
/*
90
 * Define our neighborhood list length
91
 * Note: It should always be a power of 2
92
 */
93
9
#define DEFAULT_NEIGH_LEN_LOG 4
94
9
#define DEFAULT_NEIGH_LEN (1 << DEFAULT_NEIGH_LEN_LOG)
95
96
/*
97
 * For now assume cache line size is 64 bytes
98
 */
99
1.48M
#define CACHE_LINE_BYTES 64
100
#define CACHE_LINE_ALIGNMENT CACHE_LINE_BYTES
101
102
1.48M
#define NEIGHBORHOOD_LEN (CACHE_LINE_BYTES / sizeof(struct ht_neighborhood_entry_st))
103
/*
104
 * Defines our chains of values
105
 */
106
struct ht_internal_value_st {
107
    HT_VALUE value;
108
    HT *ht;
109
};
110
111
struct ht_neighborhood_entry_st {
112
    uint64_t hash;
113
    struct ht_internal_value_st *value;
114
};
115
116
struct ht_neighborhood_st {
117
    struct ht_neighborhood_entry_st entries[NEIGHBORHOOD_LEN];
118
};
119
120
/*
121
 * Updates to data in this struct
122
 * require an rcu sync after modification
123
 * prior to free
124
 */
125
struct ht_mutable_data_st {
126
    struct ht_neighborhood_st *neighborhoods;
127
    void *neighborhood_ptr_to_free;
128
    uint64_t neighborhood_mask;
129
};
130
131
/*
132
 * Private data may be updated on the write
133
 * side only, and so do not require rcu sync
134
 */
135
struct ht_write_private_data_st {
136
    size_t neighborhood_len;
137
    size_t value_count;
138
    int need_sync;
139
};
140
141
struct ht_internal_st {
142
    HT_CONFIG config;
143
    CRYPTO_RCU_LOCK *lock;
144
    CRYPTO_RWLOCK *atomic_lock;
145
    struct ht_mutable_data_st *md;
146
    struct ht_write_private_data_st wpd;
147
};
148
149
static void free_value(struct ht_internal_value_st *v);
150
151
static struct ht_neighborhood_st *alloc_new_neighborhood_list(size_t len,
152
    void **freeptr)
153
12
{
154
12
    struct ht_neighborhood_st *ret;
155
156
12
#if !defined(OPENSSL_SMALL_FOOTPRINT)
157
12
    ret = OPENSSL_aligned_alloc_array(len, sizeof(struct ht_neighborhood_st),
158
12
        CACHE_LINE_BYTES, freeptr);
159
160
    /* fall back to regular malloc */
161
12
    if (ret == NULL)
162
0
#endif
163
0
    {
164
0
        ret = *freeptr = OPENSSL_malloc_array(len, sizeof(struct ht_neighborhood_st));
165
0
        if (ret == NULL)
166
0
            return NULL;
167
0
    }
168
12
    memset(ret, 0, sizeof(struct ht_neighborhood_st) * len);
169
12
    return ret;
170
12
}
171
172
static void internal_free_nop(HT_VALUE *v)
173
291
{
174
291
    return;
175
291
}
176
177
static uint64_t internal_ht_hash_fn(HT_KEY *key)
178
1.03M
{
179
1.03M
    return ossl_fnv1a_hash(key->keybuf, key->keysize);
180
1.03M
}
181
182
HT *ossl_ht_new(const HT_CONFIG *conf)
183
9
{
184
9
    HT *new = OPENSSL_zalloc(sizeof(*new));
185
186
9
    if (new == NULL)
187
0
        return NULL;
188
189
9
    if (conf->lockless_reads && conf->no_rcu)
190
0
        goto err;
191
192
9
    if (!conf->no_rcu) {
193
9
        new->atomic_lock = CRYPTO_THREAD_lock_new();
194
9
        if (new->atomic_lock == NULL)
195
0
            goto err;
196
9
    }
197
9
    memcpy(&new->config, conf, sizeof(*conf));
198
199
9
    if (new->config.init_neighborhoods != 0) {
200
9
        new->wpd.neighborhood_len = new->config.init_neighborhoods;
201
        /* round up to the next power of 2 */
202
9
        new->wpd.neighborhood_len--;
203
9
        new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 1;
204
9
        new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 2;
205
9
        new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 4;
206
9
        new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 8;
207
9
        new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 16;
208
9
        new->wpd.neighborhood_len++;
209
9
    } else {
210
0
        new->wpd.neighborhood_len = DEFAULT_NEIGH_LEN;
211
0
    }
212
213
9
    if (new->config.ht_free_fn == NULL)
214
9
        new->config.ht_free_fn = internal_free_nop;
215
216
9
    new->md = OPENSSL_zalloc(sizeof(*new->md));
217
9
    if (new->md == NULL)
218
0
        goto err;
219
220
9
    new->md->neighborhoods = alloc_new_neighborhood_list(new->wpd.neighborhood_len,
221
9
        &new->md->neighborhood_ptr_to_free);
222
9
    if (new->md->neighborhoods == NULL)
223
0
        goto err;
224
9
    new->md->neighborhood_mask = new->wpd.neighborhood_len - 1;
225
226
9
    if (!conf->no_rcu) {
227
9
        new->lock = ossl_rcu_lock_new(1, conf->ctx);
228
9
        if (new->lock == NULL)
229
0
            goto err;
230
9
    }
231
9
    if (new->config.ht_hash_fn == NULL)
232
9
        new->config.ht_hash_fn = internal_ht_hash_fn;
233
234
9
    return new;
235
236
0
err:
237
0
    if (!conf->no_rcu) {
238
0
        CRYPTO_THREAD_lock_free(new->atomic_lock);
239
0
        ossl_rcu_lock_free(new->lock);
240
0
    }
241
0
    if (new->md != NULL)
242
0
        OPENSSL_free(new->md->neighborhood_ptr_to_free);
243
0
    OPENSSL_free(new->md);
244
0
    OPENSSL_free(new);
245
0
    return NULL;
246
9
}
247
248
int ossl_ht_read_lock(HT *htable)
249
0
{
250
0
    if (htable->config.no_rcu)
251
0
        return 1;
252
253
0
    return ossl_rcu_read_lock(htable->lock);
254
0
}
255
256
void ossl_ht_read_unlock(HT *htable)
257
0
{
258
0
    if (htable->config.no_rcu)
259
0
        return;
260
261
0
    ossl_rcu_read_unlock(htable->lock);
262
0
}
263
264
void ossl_ht_write_lock(HT *htable)
265
3
{
266
3
    if (htable->config.no_rcu)
267
0
        return;
268
269
3
    ossl_rcu_write_lock(htable->lock);
270
3
    htable->wpd.need_sync = 0;
271
3
}
272
273
void ossl_ht_write_unlock(HT *htable)
274
3
{
275
3
    int need_sync = htable->wpd.need_sync;
276
277
3
    if (htable->config.no_rcu)
278
0
        return;
279
280
3
    htable->wpd.need_sync = 0;
281
3
    ossl_rcu_write_unlock(htable->lock);
282
3
    if (need_sync)
283
3
        ossl_synchronize_rcu(htable->lock);
284
3
}
285
286
static void free_oldmd(void *arg)
287
3
{
288
3
    struct ht_mutable_data_st *oldmd = arg;
289
3
    size_t i, j;
290
3
    size_t neighborhood_len = (size_t)oldmd->neighborhood_mask + 1;
291
3
    struct ht_internal_value_st *v;
292
293
1.53k
    for (i = 0; i < neighborhood_len; i++) {
294
1.53k
        PREFETCH_NEIGHBORHOOD(oldmd->neighborhoods[i + 1]);
295
7.68k
        for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
296
6.14k
            if (oldmd->neighborhoods[i].entries[j].value != NULL) {
297
291
                v = oldmd->neighborhoods[i].entries[j].value;
298
291
                v->ht->config.ht_free_fn((HT_VALUE *)v);
299
291
                free_value(v);
300
291
            }
301
6.14k
        }
302
1.53k
    }
303
304
3
    OPENSSL_free(oldmd->neighborhood_ptr_to_free);
305
3
    OPENSSL_free(oldmd);
306
3
}
307
308
static int ossl_ht_flush_internal(HT *h)
309
3
{
310
3
    struct ht_mutable_data_st *newmd = NULL;
311
3
    struct ht_mutable_data_st *oldmd = NULL;
312
313
3
    newmd = OPENSSL_zalloc(sizeof(*newmd));
314
3
    if (newmd == NULL)
315
0
        return 0;
316
317
3
    newmd->neighborhoods = alloc_new_neighborhood_list(DEFAULT_NEIGH_LEN,
318
3
        &newmd->neighborhood_ptr_to_free);
319
3
    if (newmd->neighborhoods == NULL) {
320
0
        OPENSSL_free(newmd);
321
0
        return 0;
322
0
    }
323
324
3
    newmd->neighborhood_mask = DEFAULT_NEIGH_LEN - 1;
325
326
    /* Swap the old and new mutable data sets */
327
3
    if (!h->config.no_rcu) {
328
3
        oldmd = ossl_rcu_deref(&h->md);
329
3
        ossl_rcu_assign_ptr(&h->md, &newmd);
330
3
    } else {
331
0
        oldmd = h->md;
332
0
        h->md = newmd;
333
0
    }
334
335
    /* Set the number of entries to 0 */
336
3
    h->wpd.value_count = 0;
337
3
    h->wpd.neighborhood_len = DEFAULT_NEIGH_LEN;
338
339
3
    if (!h->config.no_rcu) {
340
3
        ossl_rcu_call(h->lock, free_oldmd, oldmd);
341
3
    } else {
342
0
        free_oldmd(oldmd);
343
0
    }
344
3
    h->wpd.need_sync = 1;
345
346
3
    return 1;
347
3
}
348
349
int ossl_ht_flush(HT *h)
350
0
{
351
0
    return ossl_ht_flush_internal(h);
352
0
}
353
354
void ossl_ht_free(HT *h)
355
3
{
356
3
    if (h == NULL)
357
0
        return;
358
359
3
    ossl_ht_write_lock(h);
360
3
    ossl_ht_flush_internal(h);
361
3
    ossl_ht_write_unlock(h);
362
    /* Freeing the lock does a final sync for us */
363
3
    if (!h->config.no_rcu) {
364
3
        CRYPTO_THREAD_lock_free(h->atomic_lock);
365
3
        ossl_rcu_lock_free(h->lock);
366
3
    }
367
3
    OPENSSL_free(h->md->neighborhood_ptr_to_free);
368
3
    OPENSSL_free(h->md);
369
3
    OPENSSL_free(h);
370
3
    return;
371
3
}
372
373
size_t ossl_ht_count(HT *h)
374
0
{
375
0
    size_t count;
376
377
0
    count = h->wpd.value_count;
378
0
    return count;
379
0
}
380
381
void ossl_ht_foreach_until(HT *h, int (*cb)(HT_VALUE *obj, void *arg),
382
    void *arg)
383
0
{
384
0
    size_t i, j;
385
0
    struct ht_mutable_data_st *md;
386
387
0
    md = ossl_rcu_deref(&h->md);
388
0
    for (i = 0; i < md->neighborhood_mask + 1; i++) {
389
0
        PREFETCH_NEIGHBORHOOD(md->neighborhoods[i + 1]);
390
0
        for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
391
0
            if (md->neighborhoods[i].entries[j].value != NULL) {
392
0
                if (!cb((HT_VALUE *)md->neighborhoods[i].entries[j].value, arg))
393
0
                    goto out;
394
0
            }
395
0
        }
396
0
    }
397
0
out:
398
0
    return;
399
0
}
400
401
HT_VALUE_LIST *ossl_ht_filter(HT *h, size_t max_len,
402
    int (*filter)(HT_VALUE *obj, void *arg),
403
    void *arg)
404
0
{
405
0
    struct ht_mutable_data_st *md;
406
0
    HT_VALUE_LIST *list = OPENSSL_zalloc(sizeof(HT_VALUE_LIST)
407
0
        + (sizeof(HT_VALUE *) * max_len));
408
0
    size_t i, j;
409
0
    struct ht_internal_value_st *v;
410
411
0
    if (list == NULL)
412
0
        return NULL;
413
414
    /*
415
     * The list array lives just beyond the end of
416
     * the struct
417
     */
418
0
    list->list = (HT_VALUE **)(list + 1);
419
420
0
    md = ossl_rcu_deref(&h->md);
421
0
    for (i = 0; i < md->neighborhood_mask + 1; i++) {
422
0
        PREFETCH_NEIGHBORHOOD(md->neighborhoods[i + 1]);
423
0
        for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
424
0
            v = md->neighborhoods[i].entries[j].value;
425
0
            if (v != NULL && filter((HT_VALUE *)v, arg)) {
426
0
                list->list[list->list_len++] = (HT_VALUE *)v;
427
0
                if (list->list_len == max_len)
428
0
                    goto out;
429
0
            }
430
0
        }
431
0
    }
432
0
out:
433
0
    return list;
434
0
}
435
436
void ossl_ht_value_list_free(HT_VALUE_LIST *list)
437
0
{
438
0
    OPENSSL_free(list);
439
0
}
440
441
static int compare_hash(uint64_t hash1, uint64_t hash2)
442
1.47M
{
443
1.47M
    return (hash1 == hash2);
444
1.47M
}
445
446
static void free_old_neigh_table(void *arg)
447
0
{
448
0
    struct ht_mutable_data_st *oldmd = arg;
449
450
0
    OPENSSL_free(oldmd->neighborhood_ptr_to_free);
451
0
    OPENSSL_free(oldmd);
452
0
}
453
454
/*
455
 * Increase hash table bucket list
456
 * must be called with write_lock held
457
 */
458
static int grow_hashtable(HT *h, size_t oldsize)
459
0
{
460
0
    struct ht_mutable_data_st *newmd;
461
0
    struct ht_mutable_data_st *oldmd = ossl_rcu_deref(&h->md);
462
0
    int rc = 0;
463
0
    uint64_t oldi, oldj, newi, newj;
464
0
    uint64_t oldhash;
465
0
    struct ht_internal_value_st *oldv;
466
0
    int rehashed;
467
0
    size_t newsize = oldsize * 2;
468
469
0
    if (h->config.lockless_reads)
470
0
        goto out;
471
472
0
    if ((newmd = OPENSSL_zalloc(sizeof(*newmd))) == NULL)
473
0
        goto out;
474
475
    /* bucket list is always a power of 2 */
476
0
    newmd->neighborhoods = alloc_new_neighborhood_list(oldsize * 2,
477
0
        &newmd->neighborhood_ptr_to_free);
478
0
    if (newmd->neighborhoods == NULL)
479
0
        goto out_free;
480
481
    /* being a power of 2 makes for easy mask computation */
482
0
    newmd->neighborhood_mask = (newsize - 1);
483
484
    /*
485
     * Now we need to start rehashing entries
486
     * Note we don't need to use atomics here as the new
487
     * mutable data hasn't been published
488
     */
489
0
    for (oldi = 0; oldi < h->wpd.neighborhood_len; oldi++) {
490
0
        PREFETCH_NEIGHBORHOOD(oldmd->neighborhoods[oldi + 1]);
491
0
        for (oldj = 0; oldj < NEIGHBORHOOD_LEN; oldj++) {
492
0
            oldv = oldmd->neighborhoods[oldi].entries[oldj].value;
493
0
            if (oldv == NULL)
494
0
                continue;
495
0
            oldhash = oldmd->neighborhoods[oldi].entries[oldj].hash;
496
0
            newi = oldhash & newmd->neighborhood_mask;
497
0
            rehashed = 0;
498
0
            for (newj = 0; newj < NEIGHBORHOOD_LEN; newj++) {
499
0
                if (newmd->neighborhoods[newi].entries[newj].value == NULL) {
500
0
                    newmd->neighborhoods[newi].entries[newj].value = oldv;
501
0
                    newmd->neighborhoods[newi].entries[newj].hash = oldhash;
502
0
                    rehashed = 1;
503
0
                    break;
504
0
                }
505
0
            }
506
0
            if (rehashed == 0) {
507
                /* we ran out of space in a neighborhood, grow again */
508
0
                OPENSSL_free(newmd->neighborhoods);
509
0
                OPENSSL_free(newmd);
510
0
                return grow_hashtable(h, newsize);
511
0
            }
512
0
        }
513
0
    }
514
    /*
515
     * Now that our entries are all hashed into the new bucket list
516
     * update our bucket_len and target_max_load
517
     */
518
0
    h->wpd.neighborhood_len = newsize;
519
520
    /*
521
     * Now we replace the old mutable data with the new
522
     */
523
0
    if (!h->config.no_rcu) {
524
0
        ossl_rcu_assign_ptr(&h->md, &newmd);
525
0
        ossl_rcu_call(h->lock, free_old_neigh_table, oldmd);
526
0
        h->wpd.need_sync = 1;
527
0
    } else {
528
0
        h->md = newmd;
529
0
        free_old_neigh_table(oldmd);
530
0
    }
531
    /*
532
     * And we're done
533
     */
534
0
    rc = 1;
535
536
0
out:
537
0
    return rc;
538
0
out_free:
539
0
    OPENSSL_free(newmd->neighborhoods);
540
0
    OPENSSL_free(newmd);
541
0
    goto out;
542
0
}
543
544
static void free_old_ht_value(void *arg)
545
0
{
546
0
    HT_VALUE *h = (HT_VALUE *)arg;
547
548
    /*
549
     * Note, this is only called on replacement,
550
     * the caller is responsible for freeing the
551
     * held data, we just need to free the wrapping
552
     * struct here
553
     */
554
0
    OPENSSL_free(h);
555
0
}
556
557
static ossl_inline int match_key(HT_KEY *a, HT_KEY *b)
558
1.03M
{
559
    /*
560
     * keys match if they are both present, the same size
561
     * and compare equal in memory
562
     */
563
1.03M
    PREFETCH(a->keybuf);
564
1.03M
    PREFETCH(b->keybuf);
565
1.03M
    if (a->keybuf != NULL && b->keybuf != NULL && a->keysize == b->keysize)
566
1.03M
        return !memcmp(a->keybuf, b->keybuf, a->keysize);
567
568
0
    return 1;
569
1.03M
}
570
571
static int ossl_ht_insert_locked(HT *h, uint64_t hash,
572
    struct ht_internal_value_st *newval,
573
    HT_VALUE **olddata)
574
1.29k
{
575
1.29k
    struct ht_mutable_data_st *md = h->md;
576
1.29k
    uint64_t neigh_idx_start = hash & md->neighborhood_mask;
577
1.29k
    uint64_t neigh_idx = neigh_idx_start;
578
1.29k
    size_t j;
579
1.29k
    uint64_t ihash;
580
1.29k
    HT_VALUE *ival;
581
1.29k
    size_t empty_idx = SIZE_MAX;
582
1.29k
    int lockless_reads = h->config.lockless_reads;
583
584
1.29k
    do {
585
1.29k
        PREFETCH_NEIGHBORHOOD(md->neighborhoods[neigh_idx]);
586
587
1.70k
        for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
588
1.70k
            if (!h->config.no_rcu)
589
1.70k
                ival = ossl_rcu_deref(&md->neighborhoods[neigh_idx].entries[j].value);
590
0
            else
591
0
                ival = (HT_VALUE *)md->neighborhoods[neigh_idx].entries[j].value;
592
1.70k
            if (ival == NULL) {
593
1.29k
                empty_idx = j;
594
                /* lockless_reads implies no deletion, we can break out */
595
1.29k
                if (lockless_reads)
596
1.29k
                    goto not_found;
597
0
                continue;
598
1.29k
            }
599
413
            if (!h->config.no_rcu) {
600
413
                if (!CRYPTO_atomic_load(&md->neighborhoods[neigh_idx].entries[j].hash,
601
413
                        &ihash, h->atomic_lock))
602
0
                    return 0;
603
413
            } else {
604
0
                ihash = md->neighborhoods[neigh_idx].entries[j].hash;
605
0
            }
606
413
            if (compare_hash(hash, ihash) && match_key(&newval->value.key, &ival->key)) {
607
0
                if (olddata == NULL) {
608
                    /* This would insert a duplicate -> fail */
609
0
                    return 0;
610
0
                }
611
                /* Do a replacement */
612
0
                if (!h->config.no_rcu) {
613
0
                    if (!CRYPTO_atomic_store(&md->neighborhoods[neigh_idx].entries[j].hash,
614
0
                            hash, h->atomic_lock))
615
0
                        return 0;
616
0
                    *olddata = (HT_VALUE *)md->neighborhoods[neigh_idx].entries[j].value;
617
0
                    ossl_rcu_assign_ptr(&md->neighborhoods[neigh_idx].entries[j].value,
618
0
                        &newval);
619
0
                    ossl_rcu_call(h->lock, free_old_ht_value, *olddata);
620
0
                } else {
621
0
                    md->neighborhoods[neigh_idx].entries[j].hash = hash;
622
0
                    *olddata = (HT_VALUE *)md->neighborhoods[neigh_idx].entries[j].value;
623
0
                    md->neighborhoods[neigh_idx].entries[j].value = newval;
624
0
                }
625
0
                h->wpd.need_sync = 1;
626
0
                return 1;
627
0
            }
628
413
        }
629
4
        if (!lockless_reads)
630
0
            break;
631
        /* Continue search in subsequent neighborhoods */
632
4
        neigh_idx = (neigh_idx + 1) & md->neighborhood_mask;
633
4
    } while (neigh_idx != neigh_idx_start);
634
635
1.29k
not_found:
636
    /* If we get to here, its just an insert */
637
1.29k
    if (empty_idx == SIZE_MAX)
638
0
        return -1; /* out of space */
639
1.29k
    if (!h->config.no_rcu) {
640
1.29k
        if (!CRYPTO_atomic_store(&md->neighborhoods[neigh_idx].entries[empty_idx].hash,
641
1.29k
                hash, h->atomic_lock))
642
0
            return 0;
643
1.29k
        ossl_rcu_assign_ptr(&md->neighborhoods[neigh_idx].entries[empty_idx].value,
644
1.29k
            &newval);
645
1.29k
    } else {
646
0
        md->neighborhoods[neigh_idx].entries[empty_idx].hash = hash;
647
0
        md->neighborhoods[neigh_idx].entries[empty_idx].value = newval;
648
0
    }
649
1.29k
    h->wpd.value_count++;
650
1.29k
    return 1;
651
1.29k
}
652
653
static struct ht_internal_value_st *alloc_new_value(HT *h, HT_KEY *key,
654
    void *data,
655
    uintptr_t *type)
656
1.29k
{
657
1.29k
    struct ht_internal_value_st *tmp;
658
1.29k
    size_t nvsize = sizeof(*tmp);
659
660
1.29k
    if (h->config.collision_check == 1)
661
1.29k
        nvsize += key->keysize;
662
663
1.29k
    tmp = OPENSSL_malloc(nvsize);
664
665
1.29k
    if (tmp == NULL)
666
0
        return NULL;
667
668
1.29k
    tmp->ht = h;
669
1.29k
    tmp->value.value = data;
670
1.29k
    tmp->value.type_id = type;
671
1.29k
    tmp->value.key.keybuf = NULL;
672
1.29k
    if (h->config.collision_check) {
673
1.29k
        tmp->value.key.keybuf = (uint8_t *)(tmp + 1);
674
1.29k
        tmp->value.key.keysize = key->keysize;
675
1.29k
        memcpy(tmp->value.key.keybuf, key->keybuf, key->keysize);
676
1.29k
    }
677
678
1.29k
    return tmp;
679
1.29k
}
680
681
static void free_value(struct ht_internal_value_st *v)
682
291
{
683
291
    OPENSSL_free(v);
684
291
}
685
686
int ossl_ht_insert(HT *h, HT_KEY *key, HT_VALUE *data, HT_VALUE **olddata)
687
1.29k
{
688
1.29k
    struct ht_internal_value_st *newval = NULL;
689
1.29k
    uint64_t hash;
690
1.29k
    int rc = 0;
691
1.29k
    int i;
692
693
1.29k
    if (data->value == NULL)
694
0
        goto out;
695
696
1.29k
    rc = -1;
697
1.29k
    newval = alloc_new_value(h, key, data->value, data->type_id);
698
1.29k
    if (newval == NULL)
699
0
        goto out;
700
701
    /*
702
     * we have to take our lock here to prevent other changes
703
     * to the bucket list
704
     */
705
1.29k
    hash = h->config.ht_hash_fn(key);
706
707
1.29k
    for (i = 0;
708
1.29k
        (rc = ossl_ht_insert_locked(h, hash, newval, olddata)) == -1
709
0
        && i < 4;
710
1.29k
        ++i)
711
0
        if (!grow_hashtable(h, h->wpd.neighborhood_len)) {
712
0
            rc = -1;
713
0
            break;
714
0
        }
715
716
1.29k
    if (rc <= 0)
717
0
        free_value(newval);
718
719
1.29k
out:
720
1.29k
    return rc;
721
1.29k
}
722
723
HT_VALUE *ossl_ht_get(HT *h, HT_KEY *key)
724
1.03M
{
725
1.03M
    struct ht_mutable_data_st *md;
726
1.03M
    uint64_t hash;
727
1.03M
    uint64_t neigh_idx_start;
728
1.03M
    uint64_t neigh_idx;
729
1.03M
    struct ht_internal_value_st *ival = NULL;
730
1.03M
    size_t j;
731
1.03M
    uint64_t ehash;
732
1.03M
    int lockless_reads = h->config.lockless_reads;
733
734
1.03M
    hash = h->config.ht_hash_fn(key);
735
736
1.03M
    if (!h->config.no_rcu)
737
1.03M
        md = ossl_rcu_deref(&h->md);
738
0
    else
739
0
        md = h->md;
740
1.03M
    neigh_idx = neigh_idx_start = hash & md->neighborhood_mask;
741
1.03M
    do {
742
1.03M
        PREFETCH_NEIGHBORHOOD(md->neighborhoods[neigh_idx]);
743
1.47M
        for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
744
1.47M
            if (!h->config.no_rcu)
745
1.47M
                ival = ossl_rcu_deref(&md->neighborhoods[neigh_idx].entries[j].value);
746
0
            else
747
0
                ival = md->neighborhoods[neigh_idx].entries[j].value;
748
1.47M
            if (ival == NULL) {
749
3.38k
                if (lockless_reads)
750
                    /* lockless_reads implies no deletion, we can break out */
751
3.38k
                    return NULL;
752
0
                continue;
753
3.38k
            }
754
1.47M
            if (!h->config.no_rcu) {
755
1.47M
                if (!CRYPTO_atomic_load(&md->neighborhoods[neigh_idx].entries[j].hash,
756
1.47M
                        &ehash, h->atomic_lock))
757
0
                    return NULL;
758
1.47M
            } else {
759
0
                ehash = md->neighborhoods[neigh_idx].entries[j].hash;
760
0
            }
761
1.47M
            if (compare_hash(hash, ehash) && match_key(&ival->value.key, key))
762
1.03M
                return (HT_VALUE *)ival;
763
1.47M
        }
764
39
        if (!lockless_reads)
765
0
            break;
766
        /* Continue search in subsequent neighborhoods */
767
39
        neigh_idx = (neigh_idx + 1) & md->neighborhood_mask;
768
39
    } while (neigh_idx != neigh_idx_start);
769
770
0
    return NULL;
771
1.03M
}
772
773
static void free_old_entry(void *arg)
774
0
{
775
0
    struct ht_internal_value_st *v = arg;
776
777
0
    v->ht->config.ht_free_fn((HT_VALUE *)v);
778
0
    free_value(v);
779
0
}
780
781
int ossl_ht_delete(HT *h, HT_KEY *key)
782
0
{
783
0
    uint64_t hash;
784
0
    uint64_t neigh_idx;
785
0
    size_t j;
786
0
    struct ht_internal_value_st *v = NULL;
787
0
    HT_VALUE *nv = NULL;
788
0
    int rc = 0;
789
790
0
    if (h->config.lockless_reads)
791
0
        return 0;
792
793
0
    hash = h->config.ht_hash_fn(key);
794
795
0
    neigh_idx = hash & h->md->neighborhood_mask;
796
0
    PREFETCH_NEIGHBORHOOD(h->md->neighborhoods[neigh_idx]);
797
0
    for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
798
0
        v = (struct ht_internal_value_st *)h->md->neighborhoods[neigh_idx].entries[j].value;
799
0
        if (v == NULL)
800
0
            continue;
801
0
        if (compare_hash(hash, h->md->neighborhoods[neigh_idx].entries[j].hash)
802
0
            && match_key(key, &v->value.key)) {
803
0
            if (!h->config.no_rcu) {
804
0
                if (!CRYPTO_atomic_store(&h->md->neighborhoods[neigh_idx].entries[j].hash,
805
0
                        0, h->atomic_lock))
806
0
                    break;
807
0
                ossl_rcu_assign_ptr(&h->md->neighborhoods[neigh_idx].entries[j].value, &nv);
808
0
            } else {
809
0
                h->md->neighborhoods[neigh_idx].entries[j].hash = 0;
810
0
                h->md->neighborhoods[neigh_idx].entries[j].value = NULL;
811
0
            }
812
0
            h->wpd.value_count--;
813
0
            rc = 1;
814
0
            break;
815
0
        }
816
0
    }
817
0
    if (rc == 1) {
818
0
        if (!h->config.no_rcu)
819
0
            ossl_rcu_call(h->lock, free_old_entry, v);
820
0
        else
821
0
            free_old_entry(v);
822
0
        h->wpd.need_sync = 1;
823
0
    }
824
825
0
    return rc;
826
0
}
827
828
HT_VALUE *ossl_ht_deref_value(HT *h, HT_VALUE **val)
829
0
{
830
0
    HT_VALUE *v;
831
832
0
    if (!h->config.no_rcu)
833
0
        v = ossl_rcu_deref(val);
834
0
    else
835
0
        v = *val;
836
837
0
    return v;
838
0
}
839
840
void *ossl_ht_inner_value(HT *h, HT_VALUE *v)
841
0
{
842
0
    void *inner;
843
844
0
    if (!h->config.no_rcu) {
845
0
        inner = v->value;
846
0
    } else {
847
0
        inner = v->value;
848
0
        OPENSSL_free(v);
849
0
    }
850
851
0
    return inner;
852
0
}