Coverage Report

Created: 2025-10-10 07:01

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