Coverage Report

Created: 2025-12-04 06:33

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