Coverage Report

Created: 2026-09-12 06:55

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