# Note that these directories are filtered in Configure. Look for %skipdir
# there for further explanations.
-SUBDIRS=objects buffer bio stack lhash rand evp asn1 pem x509 conf \
+SUBDIRS=objects buffer bio stack lhash hashtable rand evp asn1 pem x509 conf \
txt_db pkcs7 pkcs12 ui kdf store property \
md2 md4 md5 sha mdc2 hmac ripemd whrlpool poly1305 \
siphash sm3 des aes rc2 rc4 rc5 idea aria bf cast camellia \
--- /dev/null
+LIBS=../../libcrypto
+SOURCE[../../libcrypto]=\
+ hashtable.c
+
--- /dev/null
+/*
+ * Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ *
+ *
+ *
+ * Notes On hash table design and layout
+ * This hashtable uses a hopscotch algorithm to do indexing. The data structure
+ * looks as follows:
+ *
+ * hash +--------------+
+ * value+------->+ HT_VALUE |
+ * + +--------------+
+ * +-------+
+ * | |
+ * +---------------------------------------------------------+
+ * | | | | | |
+ * | entry | entry | entry | entry | |
+ * | | | | | |
+ * +---------------------------------------------------------+
+ * | | |
+ * | | |
+ * +---------------------------------------------------------+
+ * | + + +
+ * | neighborhood[0] neighborhood[1] |
+ * | |
+ * | |
+ * +---------------------------------------------------------+
+ * |
+ * +
+ * neighborhoods
+ *
+ * On lookup/insert/delete, the items key is hashed to a 64 bit value
+ * and the result is masked to provide an index into the neighborhoods
+ * table. Once a neighborhood is determined, an in-order search is done
+ * of the elements in the neighborhood indexes entries for a matching hash
+ * value, if found, the corresponding HT_VALUE is used for the respective
+ * operation. The number of entries in a neighborhood is determined at build
+ * time based on the cacheline size of the target CPU. The intent is for a
+ * neighborhood to have all entries in the neighborhood fit into a single cache
+ * line to speed up lookups. If all entries in a neighborhood are in use at the
+ * time of an insert, the table is expanded and rehashed.
+ */
+
+#include <string.h>
+#include <internal/rcu.h>
+#include <internal/hashtable.h>
+#include <openssl/rand.h>
+
+/*
+ * gcc defines __SANITIZE_THREAD__
+ * but clang uses the feature attributes api
+ * map the latter to the former
+ */
+#if defined(__clang__) && defined(__has_feature)
+# if __has_feature(thread_sanitizer)
+# define __SANITIZE_THREADS__
+# endif
+#endif
+
+#ifdef __SANITIZE_THREADS__
+# include <sanitizer/tsan_interface.h>
+#endif
+
+#include "internal/numbers.h"
+/*
+ * When we do a lookup/insert/delete, there is a high likelyhood
+ * that we will iterate over at least part of the neighborhood list
+ * As such, because we design a neighborhood entry to fit into a single
+ * cache line it is advantageous, when supported to fetch the entire
+ * structure for faster lookups
+ */
+#if defined(__GNUC__) || defined(__CLANG__)
+#define PREFETCH_NEIGHBORHOOD(x) __builtin_prefetch(x.entries)
+#else
+#define PREFETCH_NEIGHBORHOOD(x)
+#endif
+
+static ossl_unused uint64_t fnv1a_hash(uint8_t *key, size_t len)
+{
+ uint64_t hash = 0xcbf29ce484222325ULL;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ hash ^= key[i];
+ hash *= 0x00000100000001B3ULL;
+ }
+ return hash;
+}
+
+/*
+ * Define our neighborhood list length
+ * Note: It should always be a power of 2
+ */
+#define DEFAULT_NEIGH_LEN_LOG 4
+#define DEFAULT_NEIGH_LEN (1 << DEFAULT_NEIGH_LEN_LOG)
+
+/*
+ * For now assume cache line size is 64 bytes
+ */
+#define CACHE_LINE_BYTES 64
+#define CACHE_LINE_ALIGNMENT CACHE_LINE_BYTES
+
+#define NEIGHBORHOOD_LEN (CACHE_LINE_BYTES / sizeof(struct ht_neighborhood_entry_st))
+/*
+ * Defines our chains of values
+ */
+struct ht_internal_value_st {
+ HT_VALUE value;
+ HT *ht;
+};
+
+struct ht_neighborhood_entry_st {
+ uint64_t hash;
+ struct ht_internal_value_st *value;
+};
+
+struct ht_neighborhood_st {
+ struct ht_neighborhood_entry_st entries[NEIGHBORHOOD_LEN];
+};
+
+/*
+ * Updates to data in this struct
+ * require an rcu sync after modification
+ * prior to free
+ */
+struct ht_mutable_data_st {
+ struct ht_neighborhood_st *neighborhoods;
+ void *neighborhood_ptr_to_free;
+ uint64_t neighborhood_mask;
+};
+
+/*
+ * Private data may be updated on the write
+ * side only, and so do not require rcu sync
+ */
+struct ht_write_private_data_st {
+ size_t neighborhood_len;
+ size_t value_count;
+ int need_sync;
+};
+
+struct ht_internal_st {
+ HT_CONFIG config;
+ CRYPTO_RCU_LOCK *lock;
+ CRYPTO_RWLOCK *atomic_lock;
+ struct ht_mutable_data_st *md;
+ struct ht_write_private_data_st wpd;
+};
+
+static void free_value(struct ht_internal_value_st *v);
+
+static struct ht_neighborhood_st *alloc_new_neighborhood_list(size_t len,
+ void **freeptr)
+{
+ struct ht_neighborhood_st *ret;
+
+ ret = OPENSSL_aligned_alloc(sizeof(struct ht_neighborhood_st) * len,
+ CACHE_LINE_BYTES, freeptr);
+
+ /* fall back to regular malloc */
+ if (ret == NULL) {
+ ret = *freeptr = OPENSSL_malloc(sizeof(struct ht_neighborhood_st) * len);
+ if (ret == NULL)
+ return NULL;
+ }
+ memset(ret, 0, sizeof(struct ht_neighborhood_st) * len);
+ return ret;
+}
+
+static void internal_free_nop(HT_VALUE *v)
+{
+ return;
+}
+
+HT *ossl_ht_new(HT_CONFIG *conf)
+{
+ HT *new = OPENSSL_zalloc(sizeof(*new));
+
+ if (new == NULL)
+ return NULL;
+
+ new->atomic_lock = CRYPTO_THREAD_lock_new();
+ if (new->atomic_lock == NULL)
+ goto err;
+
+ memcpy(&new->config, conf, sizeof(*conf));
+
+ if (new->config.init_neighborhoods != 0) {
+ new->wpd.neighborhood_len = new->config.init_neighborhoods;
+ /* round up to the next power of 2 */
+ new->wpd.neighborhood_len--;
+ new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 1;
+ new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 2;
+ new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 4;
+ new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 8;
+ new->wpd.neighborhood_len |= new->wpd.neighborhood_len >> 16;
+ new->wpd.neighborhood_len++;
+ } else {
+ new->wpd.neighborhood_len = DEFAULT_NEIGH_LEN;
+ }
+
+ if (new->config.ht_free_fn == NULL)
+ new->config.ht_free_fn = internal_free_nop;
+
+ new->md = OPENSSL_zalloc(sizeof(*new->md));
+ if (new->md == NULL)
+ goto err;
+
+ new->md->neighborhoods =
+ alloc_new_neighborhood_list(new->wpd.neighborhood_len,
+ &new->md->neighborhood_ptr_to_free);
+ if (new->md->neighborhoods == NULL)
+ goto err;
+ new->md->neighborhood_mask = new->wpd.neighborhood_len - 1;
+
+ new->lock = ossl_rcu_lock_new(1, conf->ctx);
+ if (new->lock == NULL)
+ goto err;
+
+ if (new->config.ht_hash_fn == NULL)
+ new->config.ht_hash_fn = fnv1a_hash;
+
+ return new;
+
+err:
+ CRYPTO_THREAD_lock_free(new->atomic_lock);
+ ossl_rcu_lock_free(new->lock);
+ OPENSSL_free(new->md->neighborhood_ptr_to_free);
+ OPENSSL_free(new->md);
+ OPENSSL_free(new);
+ return NULL;
+}
+
+void ossl_ht_read_lock(HT *htable)
+{
+ ossl_rcu_read_lock(htable->lock);
+}
+
+void ossl_ht_read_unlock(HT *htable)
+{
+ ossl_rcu_read_unlock(htable->lock);
+}
+
+void ossl_ht_write_lock(HT *htable)
+{
+ ossl_rcu_write_lock(htable->lock);
+ htable->wpd.need_sync = 0;
+}
+
+void ossl_ht_write_unlock(HT *htable)
+{
+ int need_sync = htable->wpd.need_sync;
+
+ htable->wpd.need_sync = 0;
+ ossl_rcu_write_unlock(htable->lock);
+ if (need_sync)
+ ossl_synchronize_rcu(htable->lock);
+}
+
+static void free_oldmd(void *arg)
+{
+ struct ht_mutable_data_st *oldmd = arg;
+ size_t i, j;
+ size_t neighborhood_len = (size_t)oldmd->neighborhood_mask + 1;
+ struct ht_internal_value_st *v;
+
+ for (i = 0; i < neighborhood_len; i++) {
+ PREFETCH_NEIGHBORHOOD(oldmd->neighborhoods[i + 1]);
+ for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
+ if (oldmd->neighborhoods[i].entries[j].value != NULL) {
+ v = oldmd->neighborhoods[i].entries[j].value;
+ v->ht->config.ht_free_fn((HT_VALUE *)v);
+ free_value(v);
+ }
+ }
+ }
+
+ OPENSSL_free(oldmd->neighborhood_ptr_to_free);
+ OPENSSL_free(oldmd);
+}
+
+static int ossl_ht_flush_internal(HT *h)
+{
+ struct ht_mutable_data_st *newmd = NULL;
+ struct ht_mutable_data_st *oldmd = NULL;
+
+ newmd = OPENSSL_zalloc(sizeof(*newmd));
+ if (newmd == NULL)
+ return 0;
+
+ newmd->neighborhoods = alloc_new_neighborhood_list(DEFAULT_NEIGH_LEN,
+ &newmd->neighborhood_ptr_to_free);
+ if (newmd->neighborhoods == NULL) {
+ OPENSSL_free(newmd);
+ return 0;
+ }
+
+ newmd->neighborhood_mask = DEFAULT_NEIGH_LEN - 1;
+
+ /* Swap the old and new mutable data sets */
+ oldmd = ossl_rcu_deref(&h->md);
+ ossl_rcu_assign_ptr(&h->md, &newmd);
+
+ /* Set the number of entries to 0 */
+ h->wpd.value_count = 0;
+ h->wpd.neighborhood_len = DEFAULT_NEIGH_LEN;
+
+ ossl_rcu_call(h->lock, free_oldmd, oldmd);
+ h->wpd.need_sync = 1;
+ return 1;
+}
+
+int ossl_ht_flush(HT *h)
+{
+ return ossl_ht_flush_internal(h);
+}
+
+void ossl_ht_free(HT *h)
+{
+ if (h == NULL)
+ return;
+
+ ossl_ht_write_lock(h);
+ ossl_ht_flush_internal(h);
+ ossl_ht_write_unlock(h);
+ /* Freeing the lock does a final sync for us */
+ CRYPTO_THREAD_lock_free(h->atomic_lock);
+ ossl_rcu_lock_free(h->lock);
+ OPENSSL_free(h->md->neighborhood_ptr_to_free);
+ OPENSSL_free(h->md);
+ OPENSSL_free(h);
+ return;
+}
+
+size_t ossl_ht_count(HT *h)
+{
+ size_t count;
+
+ count = h->wpd.value_count;
+ return count;
+}
+
+void ossl_ht_foreach_until(HT *h, int (*cb)(HT_VALUE *obj, void *arg),
+ void *arg)
+{
+ size_t i, j;
+ struct ht_mutable_data_st *md;
+
+ md = ossl_rcu_deref(&h->md);
+ for (i = 0; i < md->neighborhood_mask + 1; i++) {
+ PREFETCH_NEIGHBORHOOD(md->neighborhoods[i + 1]);
+ for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
+ if (md->neighborhoods[i].entries[j].value != NULL) {
+ if (!cb((HT_VALUE *)md->neighborhoods[i].entries[j].value, arg))
+ goto out;
+ }
+ }
+ }
+out:
+ return;
+}
+
+HT_VALUE_LIST *ossl_ht_filter(HT *h, size_t max_len,
+ int (*filter)(HT_VALUE *obj, void *arg),
+ void *arg)
+{
+ struct ht_mutable_data_st *md;
+ HT_VALUE_LIST *list = OPENSSL_zalloc(sizeof(HT_VALUE_LIST)
+ + (sizeof(HT_VALUE *) * max_len));
+ size_t i, j;
+ struct ht_internal_value_st *v;
+
+ if (list == NULL)
+ return NULL;
+
+ /*
+ * The list array lives just beyond the end of
+ * the struct
+ */
+ list->list = (HT_VALUE **)(list + 1);
+
+ md = ossl_rcu_deref(&h->md);
+ for (i = 0; i < md->neighborhood_mask + 1; i++) {
+ PREFETCH_NEIGHBORHOOD(md->neighborhoods[i+1]);
+ for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
+ v = md->neighborhoods[i].entries[j].value;
+ if (v != NULL && filter((HT_VALUE *)v, arg)) {
+ list->list[list->list_len++] = (HT_VALUE *)v;
+ if (list->list_len == max_len)
+ goto out;
+ }
+ }
+ }
+out:
+ return list;
+}
+
+void ossl_ht_value_list_free(HT_VALUE_LIST *list)
+{
+ OPENSSL_free(list);
+}
+
+static int compare_hash(uint64_t hash1, uint64_t hash2)
+{
+ return (hash1 == hash2);
+}
+
+static void free_old_neigh_table(void *arg)
+{
+ struct ht_mutable_data_st *oldmd = arg;
+
+ OPENSSL_free(oldmd->neighborhood_ptr_to_free);
+ OPENSSL_free(oldmd);
+}
+
+/*
+ * Increase hash table bucket list
+ * must be called with write_lock held
+ */
+static int grow_hashtable(HT *h, size_t oldsize)
+{
+ struct ht_mutable_data_st *newmd = OPENSSL_zalloc(sizeof(*newmd));
+ struct ht_mutable_data_st *oldmd = ossl_rcu_deref(&h->md);
+ int rc = 0;
+ uint64_t oldi, oldj, newi, newj;
+ uint64_t oldhash;
+ struct ht_internal_value_st *oldv;
+ int rehashed;
+ size_t newsize = oldsize * 2;
+
+ if (newmd == NULL)
+ goto out;
+
+ /* bucket list is always a power of 2 */
+ newmd->neighborhoods = alloc_new_neighborhood_list(oldsize * 2,
+ &newmd->neighborhood_ptr_to_free);
+ if (newmd->neighborhoods == NULL)
+ goto out_free;
+
+ /* being a power of 2 makes for easy mask computation */
+ newmd->neighborhood_mask = (newsize - 1);
+
+ /*
+ * Now we need to start rehashing entries
+ * Note we don't need to use atomics here as the new
+ * mutable data hasn't been published
+ */
+ for (oldi = 0; oldi < h->wpd.neighborhood_len; oldi++) {
+ PREFETCH_NEIGHBORHOOD(oldmd->neighborhoods[oldi + 1]);
+ for (oldj = 0; oldj < NEIGHBORHOOD_LEN; oldj++) {
+ oldv = oldmd->neighborhoods[oldi].entries[oldj].value;
+ if (oldv == NULL)
+ continue;
+ oldhash = oldmd->neighborhoods[oldi].entries[oldj].hash;
+ newi = oldhash & newmd->neighborhood_mask;
+ rehashed = 0;
+ for (newj = 0; newj < NEIGHBORHOOD_LEN; newj++) {
+ if (newmd->neighborhoods[newi].entries[newj].value == NULL) {
+ newmd->neighborhoods[newi].entries[newj].value = oldv;
+ newmd->neighborhoods[newi].entries[newj].hash = oldhash;
+ rehashed = 1;
+ break;
+ }
+ }
+ if (rehashed == 0) {
+ /* we ran out of space in a neighborhood, grow again */
+ OPENSSL_free(newmd->neighborhoods);
+ OPENSSL_free(newmd);
+ return grow_hashtable(h, newsize);
+ }
+ }
+ }
+ /*
+ * Now that our entries are all hashed into the new bucket list
+ * update our bucket_len and target_max_load
+ */
+ h->wpd.neighborhood_len = newsize;
+
+ /*
+ * Now we replace the old mutable data with the new
+ */
+ oldmd = ossl_rcu_deref(&h->md);
+ ossl_rcu_assign_ptr(&h->md, &newmd);
+ ossl_rcu_call(h->lock, free_old_neigh_table, oldmd);
+ h->wpd.need_sync = 1;
+ /*
+ * And we're done
+ */
+ rc = 1;
+
+out:
+ return rc;
+out_free:
+ OPENSSL_free(newmd->neighborhoods);
+ OPENSSL_free(newmd);
+ goto out;
+}
+
+static void free_old_ht_value(void *arg)
+{
+ HT_VALUE *h = (HT_VALUE *)arg;
+
+ /*
+ * Note, this is only called on replacement,
+ * the caller is responsible for freeing the
+ * held data, we just need to free the wrapping
+ * struct here
+ */
+ OPENSSL_free(h);
+}
+
+static int ossl_ht_insert_locked(HT *h, uint64_t hash,
+ struct ht_internal_value_st *newval,
+ HT_VALUE **olddata)
+{
+ struct ht_mutable_data_st *md = h->md;
+ uint64_t neigh_idx = hash & md->neighborhood_mask;
+ size_t j;
+ uint64_t ihash;
+ HT_VALUE *ival;
+ size_t empty_idx = SIZE_MAX;
+
+ PREFETCH_NEIGHBORHOOD(md->neighborhoods[neigh_idx]);
+
+ for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
+ ival = ossl_rcu_deref(&md->neighborhoods[neigh_idx].entries[j].value);
+ CRYPTO_atomic_load(&md->neighborhoods[neigh_idx].entries[j].hash,
+ &ihash, h->atomic_lock);
+ if (ival == NULL)
+ empty_idx = j;
+ if (compare_hash(hash, ihash)) {
+ if (olddata == NULL) {
+ /* invalid */
+ return 0;
+ }
+ /* Do a replacement */
+ CRYPTO_atomic_store(&md->neighborhoods[neigh_idx].entries[j].hash,
+ hash, h->atomic_lock);
+ *olddata = (HT_VALUE *)md->neighborhoods[neigh_idx].entries[j].value;
+ ossl_rcu_assign_ptr(&md->neighborhoods[neigh_idx].entries[j].value,
+ &newval);
+ ossl_rcu_call(h->lock, free_old_ht_value, *olddata);
+ h->wpd.need_sync = 1;
+ return 1;
+ }
+ }
+ /* If we get to here, its just an insert */
+ if (empty_idx == SIZE_MAX)
+ return -1; /* out of space */
+ h->wpd.value_count++;
+ CRYPTO_atomic_store(&md->neighborhoods[neigh_idx].entries[empty_idx].hash,
+ hash, h->atomic_lock);
+ ossl_rcu_assign_ptr(&md->neighborhoods[neigh_idx].entries[empty_idx].value,
+ &newval);
+ return 1;
+}
+
+static struct ht_internal_value_st *alloc_new_value(HT *h, HT_KEY *key,
+ void *data,
+ uintptr_t *type)
+{
+ struct ht_internal_value_st *new;
+ struct ht_internal_value_st *tmp;
+
+ new = OPENSSL_malloc(sizeof(*new));
+
+ if (new == NULL)
+ return NULL;
+
+ tmp = (struct ht_internal_value_st *)ossl_rcu_deref(&new);
+ tmp->ht = h;
+ tmp->value.value = data;
+ tmp->value.type_id = type;
+
+ return tmp;
+}
+
+static void free_value(struct ht_internal_value_st *v)
+{
+ OPENSSL_free(v);
+}
+
+int ossl_ht_insert(HT *h, HT_KEY *key, HT_VALUE *data, HT_VALUE **olddata)
+{
+ struct ht_internal_value_st *newval = NULL;
+ uint64_t hash;
+ int rc = 0;
+
+ if (data->value == NULL)
+ goto out;
+
+ newval = alloc_new_value(h, key, data->value, data->type_id);
+ if (newval == NULL)
+ goto out;
+
+ /*
+ * we have to take our lock here to prevent other changes
+ * to the bucket list
+ */
+ hash = h->config.ht_hash_fn(key->keybuf, key->keysize);
+
+try_again:
+ rc = ossl_ht_insert_locked(h, hash, newval, olddata);
+
+ if (rc == -1) {
+ grow_hashtable(h, h->wpd.neighborhood_len);
+ goto try_again;
+ }
+
+ if (rc == 0)
+ free_value(newval);
+
+out:
+ return rc;
+}
+
+HT_VALUE *ossl_ht_get(HT *h, HT_KEY *key)
+{
+ struct ht_mutable_data_st *md;
+ uint64_t hash;
+ uint64_t neigh_idx;
+ struct ht_internal_value_st *vidx = NULL;
+ size_t j;
+ uint64_t ehash;
+ HT_VALUE *ret = NULL;
+
+ hash = h->config.ht_hash_fn(key->keybuf, key->keysize);
+
+ md = ossl_rcu_deref(&h->md);
+ neigh_idx = hash & md->neighborhood_mask;
+ PREFETCH_NEIGHBORHOOD(md->neighborhoods[neigh_idx]);
+ for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
+ CRYPTO_atomic_load(&md->neighborhoods[neigh_idx].entries[j].hash,
+ &ehash, h->atomic_lock);
+ if (compare_hash(hash, ehash)) {
+ CRYPTO_atomic_load((uint64_t *)&md->neighborhoods[neigh_idx].entries[j].value,
+ (uint64_t *)&vidx, h->atomic_lock);
+ ret = (HT_VALUE *)vidx;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void free_old_entry(void *arg)
+{
+ struct ht_internal_value_st *v = arg;
+
+ v->ht->config.ht_free_fn((HT_VALUE *)v);
+ free_value(v);
+}
+
+int ossl_ht_delete(HT *h, HT_KEY *key)
+{
+ uint64_t hash;
+ uint64_t neigh_idx;
+ size_t j;
+ struct ht_internal_value_st *v = NULL;
+ HT_VALUE *nv = NULL;
+ int rc = 0;
+
+ hash = h->config.ht_hash_fn(key->keybuf, key->keysize);
+
+ neigh_idx = hash & h->md->neighborhood_mask;
+ PREFETCH_NEIGHBORHOOD(h->md->neighborhoods[neigh_idx]);
+ for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
+ if (compare_hash(hash, h->md->neighborhoods[neigh_idx].entries[j].hash)) {
+ h->wpd.value_count--;
+ CRYPTO_atomic_store(&h->md->neighborhoods[neigh_idx].entries[j].hash,
+ 0, h->atomic_lock);
+ v = (struct ht_internal_value_st *)h->md->neighborhoods[neigh_idx].entries[j].value;
+ ossl_rcu_assign_ptr(&h->md->neighborhoods[neigh_idx].entries[j].value,
+ &nv);
+ rc = 1;
+ break;
+ }
+ }
+ if (rc == 1) {
+ ossl_rcu_call(h->lock, free_old_entry, v);
+ h->wpd.need_sync = 1;
+ }
+ return rc;
+}
+
return ret;
}
+void *CRYPTO_aligned_alloc(size_t num, size_t alignment, void **freeptr,
+ const char *file, int line)
+{
+ void *ret;
+
+ *freeptr = NULL;
+
+#if defined(OPENSSL_SMALL_FOOTPRINT)
+ ret = freeptr = NULL;
+ return ret;
+#endif
+
+#if defined (_BSD_SOURCE) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L)
+ if (posix_memalign(&ret, alignment, num))
+ return NULL;
+ *freeptr = ret;
+ return ret;
+#elif defined(_ISOC11_SOURCE)
+ ret = *freeptr = aligned_alloc(alignment, num);
+ return ret;
+#else
+ /* we have to do this the hard way */
+
+ /*
+ * Note: Windows supports an _aligned_malloc call, but we choose
+ * not to use it here, because allocations from that function
+ * require that they be freed via _aligned_free. Given that
+ * we can't differentiate plain malloc blocks from blocks obtained
+ * via _aligned_malloc, just avoid its use entirely
+ */
+
+ /*
+ * Step 1: Allocate an amount of memory that is <alignment>
+ * bytes bigger than requested
+ */
+ *freeptr = malloc(num + alignment);
+ if (*freeptr == NULL)
+ return NULL;
+
+ /*
+ * Step 2: Add <alignment - 1> bytes to the pointer
+ * This will cross the alignment boundary that is
+ * requested
+ */
+ ret = (void *)((char *)*freeptr + (alignment - 1));
+
+ /*
+ * Step 3: Use the alignment as a mask to translate the
+ * least significant bits of the allocation at the alignment
+ * boundary to 0. ret now holds a pointer to the memory
+ * buffer at the requested alignment
+ * NOTE: It is a documented requirement that alignment be a
+ * power of 2, which is what allows this to work
+ */
+ ret = (void *)((uintptr_t)ret & (uintptr_t)(~(alignment - 1)));
+ return ret;
+#endif
+}
+
void *CRYPTO_realloc(void *str, size_t num, const char *file, int line)
{
INCREMENT(realloc_count);
--- /dev/null
+=pod
+
+=head1 NAME
+
+ossl_ht_new, ossl_ht_free,
+ossl_ht_read_lock, ossl_ht_read_unlock,
+ossl_ht_write_lock, ossl_ht_write_unlock,
+ossl_ht_flush, ossl_ht_insert,
+ossl_ht_delete, ossl_ht_count,
+ossl_ht_foreach_until, ossl_ht_filter,
+ossl_ht_value_list_free, ossl_ht_get,
+ossl_ht_put, HT_START_KEY_DEFN,
+HT_END_KEY_DEFN, HT_DEF_KEY_FIELD_CHAR_ARRAY,
+HT_DEF_KEY_FIELD_UINT8T_ARRAY, HT_DEF_KEY_FIELD,
+HT_INIT_KEY, HT_KEY_RESET, HT_SET_KEY_FIELD,
+HT_SET_KEY_STRING, HT_SET_KEY_BLOB,
+TO_HT_KEY, FROM_HT_KEY,
+IMPLEMENT_HT_VALUE_TYPE_FNS
+- internal rcu locked hashtables
+
+=head1 SYNOPSIS
+
+ HT *ossl_ht_new(HT_CONFIG *conf);
+ void ossl_ht_free(HT *htable);
+ void ossl_ht_read_lock(HT *htable);
+ void ossl_ht_read_unlock(HT *htable);
+ void ossl_ht_write_lock(HT *htable);
+ void ossl_ht_write_unlock(HT *htable);
+ int ossl_ht_flush(HT *htable);
+ int ossl_ht_insert(HT *htable, HT_KEY *key, HT_VALUE *data, HT_VALUE **olddata);
+ int ossl_ht_delete(HT *htable, HT_KEY *key);
+ size_t ossl_ht_count(HT *htable);
+ void ossl_ht_foreach_until(HT *htable, int (*cb)(HT_VALUE *obj, void *arg), void *arg);
+ HT_VALUE_LIST *ossl_ht_filter(HT *htable, size_t max_len, int (*filter)(HT_VALUE *obj));
+ void ossl_ht_value_list_free(HT_VALUE_LIST *list);
+ HT_VALUE *ossl_ht_get(HT *htable, HT_KEY *key);
+ void ossl_ht_put(HT_VALUE *value);
+ HT_START_KEY_DEFN(keyname);
+ HT_END_KEY_DEFN(keyname);
+ HT_DEF_KEY_FIELD(name, type);
+ HT_DEF_KEY_FIELD_CHAR_ARRAY(name, size);
+ HT_DEF_KEY_FIELD_UINT8T_ARRAY(name, size);
+ HT_INIT_KEY(key);
+ HT_KEY_RESET(key);
+ HT_SET_KEY_FIELD(key, member, value);
+ HT_SET_KEY_STRING(key, member, value);
+ HT_SET_KEY_BLOB(key, member, value, len);
+ TO_HT_KEY(key);
+ FROM_HT_KEY(key, type);
+ IMPLEMENT_HT_VALUE_TYPE_FNS(vtype, name, pfx);
+
+=head1 DESCRIPTION
+
+This API provides a library-internal implementation of a hashtable that provides
+reference counted object retrieval under the protection of an rcu lock. API
+type safety is offered via conversion macros to and from the generic HT_VALUE
+type.
+
+=over 2
+
+=item *
+
+ossl_ht_new() returns a new HT (hashtable object) used to store data
+elements based on a defined key. The call accepts an HT_CONFIG pointer which
+contains configurations options for hashtable. Current config options consist
+of:
+ I<ht_free_fn> The function to call to free a value, may be B<NULL>.
+ I<ht_hash_fn> The function to generate a hash value for a key, may be B<NULL>.
+ I<init_neighborhood_len> The initial number of neighborhoods in the hash table.
+
+Note that init_bucket_len may be set to zero, which will use the default initial
+bucket size, which will be automatically expanded with the hash table load
+average reaches 0.75.
+
+Note that lockless_read operation implies behavioral restrictions. Specifically
+ Only element additions are allowed, deletion operations will fail
+ Hash table growth is inhibited. init_bucket_len should be set to an
+ appropriate value to prevent performance degradation
+ The table owner is responsible for ensuring there are no readers during a
+ freeing of the table.
+
+Note that lockless_write operations are done at your own risk. Lockless
+ operation in a multithreaded environment will cause data corruption. It
+ is the callers responsibility in this mode of operation to provide thread
+ synchronization.
+
+=item *
+
+ossl_ht_free() frees an allocated hash table. Each element in the table
+will have its reference count dropped, and, if said count reaches zero, the hash
+tables registered free function will be called to release the element data.
+
+=item *
+
+ossl_ht_read_lock(), ossl_ht_read_unlock(), ossl_ht_write_lock() and
+ossl_ht_write_unlock() lock the table for reading and writing/modification.
+These function are not required for use in the event a table is to be used in a
+lockless fashion, but if they are not used, it is the responsibility of the caller
+to ensure thread synchronization. Note that an rcu lock is used internally for these
+operations, so for table modifying actions (ossl_ht_flush() and ossl_ht_delete()
+the write lock must be taken and released to ensure rcu synchronization takes
+place.
+
+=item *
+
+ossl_ht_flush() empties a hash table. All elements will have their
+reference counts decremented, and, on reaching zero, the free function will be
+called to release the element data.
+
+=item *
+
+ossl_ht_insert() inserts an HT_VALUE element into the hash table, to be
+hashed using the corresponding HT_KEY value.
+
+=item *
+
+ossl_ht_delete() deletes an entry from the hashtable indexed by the passed
+HT_KEY value.
+
+=item *
+
+ossl_ht_count() returns the number of elements within the hash table.
+
+=item *
+
+ossl_ht_foreach_until() iterates over all elements in the hash table, calling
+the passed callback function for each. The return value of the callback
+indicates if the iteration should continue or not. Returning 1 indicates
+iteration should continue, while returning 0 indicates that iteration should
+terminate.
+
+Note that the iteration is done under read lock protection, and as such
+modifications to the table are disallowed in the callback function.
+Modification to the value content are permitted, if the caller is able to
+properly synchronize such modifications with other threads.
+
+=item *
+
+ossl_ht_filter() iterates over all elements of the hash table, calling
+the filter callback for each element. If the callback returns 1, the
+corresponding HT_VALUE is placed on a list, and its reference count incremented.
+The completed list is returned to the caller as an HT_VALUE_LIST object
+
+=item *
+
+ossl_ht_value_list_free() frees an HT_VALUE_LIST. For each element on
+the list, its reference count is decremented, and after traversing the list, the
+list object is freed. Note, NULL elements are allowed on the list, but for any
+element which is taken from the list by a caller, they must call
+ossl_ht_put on the HT_VALUE to prevent memory leaks.
+
+=item *
+
+ossl_ht_get() preforms a lookup of an HT_KEY in the hashtable, returning
+its corresponding value.
+
+=item *
+
+HT_START_KEY_DEFN() Begins the definition of a key type. the keyname parameter
+defines the structure name, and presets a common key header.
+
+=item *
+
+HT_END_KEY_DEFN() Finalizes a key definition. the keyname parameter (which may
+differ from the name passed in HT_START_KEY_DEFN(), defines the key type name.
+The resulting type may be converted to an HT_KEY variable via the HT_TO_KEY()
+macro, and back using the HT_FROM_KEY() macro.
+
+=item *
+
+HT_DEF_KEY_FIELD() Allows for the creation of data fields within a key. Note,
+this macro can be used for any data type, but it is recommended that strings and
+binary arrays be created with the HT_DEF_KEY_FIELD_CHAR_ARRAY() and
+HT_DEF_KEY_FIELD_UINT8T_ARRAY() macros to ensure proper in-lining of key data.
+
+=item *
+
+HT_DEF_KEY_FIELD_CHAR_ARRAY() Creates a string field of fixed size
+within a key definition. Note these items will be NULL terminated.
+
+=item *
+
+HT_DEF_KEY_FIELD_UINT8T_ARRAY() Creates an array of uint8_t elements within a
+key.
+
+=item *
+
+HT_INIT_KEY() Initializes a key for use. Can be called multiple times, but must
+be called at least once before using in any hashtable method.
+
+=item *
+
+HT_KEY_RESET() Resets a key's data to all zeros.
+
+=item *
+
+HT_SET_KEY_FIELD() Sets a field in a key (as defined by HT_DEF_KEY_FIELD()) to a
+given value.
+
+=item *
+
+HT_SET_KEY_STRING() Preforms a strncpy() of a source string to the destination
+key field.
+
+=item *
+
+HT_SET_KEY_BLOB() Preforms a memcpy() of a source uint8_t buffer to a
+destination key field.
+
+=item *
+
+TO_HT_KEY() Converts a key type as defined by HT_START_KEY_DEFN() and
+HE_END_KEY_DEFN() to the generic HT_KEY type
+
+=item *
+
+FROM_HT_KEY() Converts an HT_KEY back to a specific key type as defined by
+HT_START_KEY_DEFN() and HT_END_KEY_DEFN()
+
+=item *
+
+IMPLEMENT_HT_VALUE_TYPE_FNS() creates template conversion functions for
+manipulating the hashtable using specific data types. This macro accepts two
+parameters, a NAME, which is used to prefix the hashtable function so that it
+may be associated with a specific hash table, and TYPE which defines the type of
+data the instantiated function accepts. The list of functions instantiated by
+this macro are below.
+
+=over 2
+
+=item *
+
+int ossl_ht_NAME_TYPE_insert(HT* h, HT_KEY *key, <type> *value, HT_VALUE **olddata)
+Inserts a value to the hash table of type TYPE into the hash table using the
+provided key. If olddata is not NULL, and a matching key already exists in the
+table, the operation is a replacement, and the old data is returned in this
+pointer
+
+=item *
+
+<TYPE> ossl_ht_NAME_TYPE_get(HT *h, HT_KEY *key, HT_VALUE **v)
+Looks up an item in the hash table based on key, and returns the data it found,
+if any. v holds a pointer to the HT_VALUE associated with the data.
+
+=item *
+
+<TYPE> *ossl_ht_NAME_TYPE_from_value(HT_VALUE *v)
+Validates that the HT_VALUE provided matches the TYPE specified, and returns the
+value data. If there is a type mismatch, NULL is returned
+
+=item *
+
+HT_VALUE *ossl_ht_NAME_TYPE_to_value(<TYPE> *data)
+Converts the data pointer provided to an HT_VALUE object
+
+=item *
+
+int ossl_ht_NAME_TYPE_type(HT_VALUE *v)
+Returns true if the HT_VALUE object passed in is of type <TYPE>
+
+=back
+
+=back
+
+=head1 RETURN VALUES
+
+ossl_ht_new() returns an HT* struct on success and NULL on error
+
+void ossl_ht_free(HT *htable);
+
+ossl_ht_flush() and ossl_ht_insert() return 1 on success and 0 on error
+
+ossl_ht_delete() returns 1 if the key was successfully deleted, and 0 if the
+key was not found.
+
+ossl_ht_count() returns the number of elements in the hash table
+
+ossl_ht_filter() returns an HT_VALUE_LIST of all elements matching the
+provided filter
+
+ossl_ht_get() returns an HT_VALUE pointer, or NULL if the element was not
+found.
+
+=head1 EXAMPLES
+
+#include <stdio.h>
+#include <string.h>
+
+#include <openssl/err.h>
+#include <openssl/crypto.h>
+#include <internal/hashtable.h>
+
+HT_START_KEY_DEFN(intkey)
+HT_DEF_KEY_FIELD(myintkey, int)
+HT_END_KEY_DEFN(INTKEY)
+
+IMPLEMENT_HT_VALUE_TYPE_FNS(int, test, static)
+
+static void int_free_fn(HT_VALUE *v)
+{
+ int *i = ossl_crypto_test_int_from_value(v);
+
+ fprintf(stderr, "Freeing an element\n");
+ OPENSSL_free(i);
+}
+
+static int test_int_hashtable(void)
+{
+ /*
+ * our config says:
+ * int_free_fn - Our free handler
+ * NULL - Use default hash fn
+ * 0 - use default initial bucket size
+ */
+ HT_CONFIG hash_conf = {
+ int_free_fn,
+ NULL,
+ 0
+ };
+ INTKEY key;
+ HT *ht = NULL;
+ HT_VALUE *v;
+ int rc;
+ int *newval = OPENSSL_malloc(sizeof(int));
+
+ ht = ossl_ht_new(&hash_conf);
+
+ if (ht == NULL)
+ return 0;
+
+ if (newval == NULL)
+ goto out;
+
+ *newval = 1;
+
+ /* insert */
+ HT_INIT_KEY(&key);
+ HT_SET_KEY_FIELD(&key, myintkey, 47);
+ ossl_ht_write_lock(ht);
+ rc = ossl_ht_test_int_insert(ht, TO_HT_KEY(&key), newval, NULL);
+ ossl_ht_write_unlock(ht);
+ if (rc == 0)
+ goto out;
+
+ /* num_items */
+ if (ossl_ht_count(ht) != 1)
+ goto out;
+
+ /* lookup */
+ HT_RESET_KEY(&key);
+ HT_SET_KEY_FIELD(&key, myintkey, 47);
+ ossl_ht_read_lock(ht);
+ v = ossl_ht_get(ht, TO_HT_KEY(&key);
+ fprintf(stderr, "found element with key 47 holding value %d\n",
+ *ossl_ht_test_int_from_value(v));
+ ossl_ht_read_unlock(ht);
+
+ rc = 1;
+end:
+ /* this will call the free function for our element */
+ ossl_ht_free(ht);
+ return rc;
+}
+
+=head1 COPYRIGHT
+
+Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
+
+Licensed under the Apache License 2.0 (the "License"). You may not use
+this file except in compliance with the License. You can obtain a copy
+in the file LICENSE in the source distribution or at
+L<https://www.openssl.org/source/license.html>.
+
+=cut
L<crypto(7)>, L<openssl-threads(7)>.
+=head1 HISTORY
+
+CRYPTO_atomic_store() was added in OpenSSL 3.4.0
+
=head1 COPYRIGHT
Copyright 2000-2023 The OpenSSL Project Authors. All Rights Reserved.
=head1 NAME
OPENSSL_malloc_init,
-OPENSSL_malloc, OPENSSL_zalloc, OPENSSL_realloc, OPENSSL_free,
-OPENSSL_clear_realloc, OPENSSL_clear_free, OPENSSL_cleanse,
-CRYPTO_malloc, CRYPTO_zalloc, CRYPTO_realloc, CRYPTO_free,
+OPENSSL_malloc, OPENSSL_aligned_alloc, OPENSSL_zalloc, OPENSSL_realloc,
+OPENSSL_free, OPENSSL_clear_realloc, OPENSSL_clear_free, OPENSSL_cleanse,
+CRYPTO_malloc, CRYPTO_aligned_alloc, CRYPTO_zalloc, CRYPTO_realloc, CRYPTO_free,
OPENSSL_strdup, OPENSSL_strndup,
OPENSSL_memdup, OPENSSL_strlcpy, OPENSSL_strlcat,
CRYPTO_strdup, CRYPTO_strndup,
int OPENSSL_malloc_init(void);
void *OPENSSL_malloc(size_t num);
+ void *OPENSSL_aligned_alloc(size_t num, size_t alignment, void **freeptr);
void *OPENSSL_zalloc(size_t num);
void *OPENSSL_realloc(void *addr, size_t num);
void OPENSSL_free(void *addr);
void OPENSSL_cleanse(void *ptr, size_t len);
void *CRYPTO_malloc(size_t num, const char *file, int line);
+ void *CRYPTO_aligned_alloc(size_t num, size_t align, void **freeptr,
+ const char *file, int line);
void *CRYPTO_zalloc(size_t num, const char *file, int line);
void *CRYPTO_realloc(void *p, size_t num, const char *file, int line);
void CRYPTO_free(void *str, const char *, int);
C malloc(), realloc(), and free() functions.
OPENSSL_zalloc() calls memset() to zero the memory before returning.
+OPENSSL_aligned_alloc() operates just as OPENSSL_malloc does, but it
+allows for the caller to specify an alignment value, for instances in
+which the default alignment of malloc is insufficient for the callers
+needs. Note, the alignment value must be a power of 2, and the size
+specified must be a multiple of the alignment.
+NOTE: The call to OPENSSL_aligned_alloc() accepts a 3rd argument, I<freeptr>
+which must point to a void pointer. On some platforms, there is no available
+library call to obtain memory allocations greater than what malloc provides. In
+this case, OPENSSL_aligned_alloc implements its own alignment routine,
+allocating additional memory and offsetting the returned pointer to be on the
+requested alignment boundary. In order to safely free allocations made by this
+method, the caller must return the value in the I<freeptr> variable, rather than
+the returned pointer.
+
OPENSSL_clear_realloc() and OPENSSL_clear_free() should be used
when the buffer at B<addr> holds sensitive information.
The old buffer is filled with zero's by calling OPENSSL_cleanse()
CRYPTO_free(), CRYPTO_clear_free() and CRYPTO_get_mem_functions()
return no value.
-OPENSSL_malloc(), OPENSSL_zalloc(), OPENSSL_realloc(),
+OPENSSL_malloc(), OPENSSL_aligned_alloc(), OPENSSL_zalloc(), OPENSSL_realloc(),
OPENSSL_clear_realloc(),
CRYPTO_malloc(), CRYPTO_zalloc(), CRYPTO_realloc(),
CRYPTO_clear_realloc(),
were deprecated in OpenSSL 3.0.
The memory-leak checking has been deprecated in OpenSSL 3.0 in favor of
clang's memory and leak sanitizer.
-
+OPENSSL_aligned_alloc(), CRYPTO_aligned_alloc() were added in OpenSSL 3.4.0
=head1 COPYRIGHT
--- /dev/null
+/*
+ * Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#ifndef OPENSSL_HASHTABLE_H
+# define OPENSSL_HASHTABLE_H
+# pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <openssl/e_os2.h>
+#include <internal/rcu.h>
+#include "crypto/context.h"
+
+typedef struct ht_internal_st HT;
+
+/*
+ * Represents a value in the hash table
+ */
+typedef struct ht_value_st {
+ void *value;
+ uintptr_t *type_id;
+} HT_VALUE;
+
+/*
+ * Represents a list of values filtered from a hash table
+ */
+typedef struct ht_value_list_st {
+ size_t list_len;
+ HT_VALUE **list;
+} HT_VALUE_LIST;
+
+/*
+ * Hashtable configuration
+ */
+typedef struct ht_config_st {
+ OSSL_LIB_CTX *ctx;
+ void (*ht_free_fn)(HT_VALUE *obj);
+ uint64_t (*ht_hash_fn)(uint8_t *key, size_t keylen);
+ uint32_t init_neighborhoods;
+} HT_CONFIG;
+
+/*
+ * Key value for a hash lookup
+ */
+typedef struct ht_key_header_st {
+ size_t keysize;
+ uint8_t *keybuf;
+} HT_KEY;
+
+/*
+ * Hashtable key rules
+ * Any struct can be used to formulate a hash table key, as long as the
+ * following rules
+ * 1) The first element of the struct defining the key must be an HT_KEY
+ * 2) All struct elements must have a compile time defined length
+ * 3) Pointers can be used, but the value of the pointer, rather than
+ * the contents of the address it points to will be used to compute
+ * the hash
+ * The key definition macros will assist with enforcing these rules
+ */
+
+/*
+ * Starts the definition of a hash table key
+ */
+#define HT_START_KEY_DEFN(keyname) \
+typedef struct keyname##_st { \
+ HT_KEY key_header; \
+ struct {
+
+/*
+ * Ends a hash table key definitions
+ */
+#define HT_END_KEY_DEFN(keyname) \
+ } keyfields; \
+} keyname;
+
+/*
+ * Defines a field in a hash table key
+ */
+#define HT_DEF_KEY_FIELD(name, type) type name;
+
+/*
+ * convenience macro to define a static char
+ * array field in a hash table key
+ */
+#define HT_DEF_KEY_FIELD_CHAR_ARRAY(name, size) \
+ HT_DEF_KEY_FIELD(name[size], char)
+
+/*
+ * Defines a uint8_t (blob) field in a hash table key
+ */
+#define HT_DEF_KEY_FIELD_UINT8T_ARRAY(name, size) \
+ HT_DEF_KEY_FIELD(name[size], uint8_t)
+
+/*
+ * Initializes a key
+ */
+#define HT_INIT_KEY(key) do { \
+memset((key), 0, sizeof(*(key))); \
+(key)->key_header.keysize = (sizeof(*(key)) - sizeof(HT_KEY)); \
+(key)->key_header.keybuf = (((uint8_t *)key) + sizeof(HT_KEY)); \
+} while(0)
+
+/*
+ * Resets a hash table key to a known state
+ */
+#define HT_KEY_RESET(key) memset((key)->key_header.keybuf, 0, (key)->key_header.keysize)
+
+/*
+ * Sets a scalar field in a hash table key
+ */
+#define HT_SET_KEY_FIELD(key, member, value) (key)->keyfields.member = value;
+
+/*
+ * Sets a string field in a hash table key, preserving
+ * null terminator
+ */
+#define HT_SET_KEY_STRING(key, member, value) do { \
+ if ((value) != NULL) \
+ strncpy((key)->keyfields.member, value, sizeof((key)->keyfields.member) - 1); \
+} while(0)
+
+/*
+ * This is the same as HT_SET_KEY_STRING, except that it uses
+ * ossl_ht_strcase to make the value being passed case insensitive
+ * This is useful for instances in which we want upper and lower case
+ * key value to hash to the same entry
+ */
+#define HT_SET_KEY_STRING_CASE(key, member, value) do { \
+ ossl_ht_strcase((key)->keyfields.member, value, sizeof((key)->keyfields.member) -1); \
+} while(0)
+
+/*
+ * Sets a uint8_t (blob) field in a hash table key
+ */
+#define HT_SET_KEY_BLOB(key, member, value, len) do { \
+ if (value != NULL) \
+ memcpy((key)->keyfields.member, value, len); \
+} while(0)
+
+/*
+ * Converts a defined key type to an HT_KEY
+ */
+#define TO_HT_KEY(key) &(key)->key_header
+
+/*
+ * Converts an HT_KEY back to its defined
+ * type
+ */
+#define FROM_HT_KEY(key, type) (type)(key)
+
+/*
+ * Implements the following type safe operations for a hash table
+ * ossl_ht_NAME_TYPE_insert - insert a value to a hash table of type TYPE
+ * ossl_ht_NAME_TYPE_get - gets a value of a specific type from the hash table
+ * ossl_ht_NAME_TYPE_from_value - converts an HT_VALUE to its type
+ * ossl_ht_NAME_TYPE_to_value - converts a TYPE to an HT_VALUE
+ * ossl_ht_NAME_TYPE_type - boolean to detect if a value is of TYPE
+ */
+#define IMPLEMENT_HT_VALUE_TYPE_FNS(vtype, name, pfx) \
+static uintptr_t name##_##vtype##_id = 0; \
+pfx ossl_unused int ossl_ht_##name##_##vtype##_insert(HT *h, HT_KEY *key, \
+ vtype *data, \
+ vtype **olddata) { \
+ HT_VALUE inval; \
+ HT_VALUE *oval = NULL; \
+ int rc; \
+ \
+ inval.value = data; \
+ inval.type_id = &name##_##vtype##_id; \
+ rc = ossl_ht_insert(h, key, &inval, olddata == NULL ? NULL : &oval); \
+ if (oval != NULL) \
+ *olddata = (vtype *)oval->value; \
+ return rc; \
+} \
+ \
+pfx ossl_unused vtype *ossl_ht_##name##_##vtype##_from_value(HT_VALUE *v) \
+{ \
+ uintptr_t *expect_type = &name##_##vtype##_id; \
+ if (v == NULL) \
+ return NULL; \
+ if (v->type_id != expect_type) \
+ return NULL; \
+ return (vtype *)v->value; \
+} \
+ \
+pfx ossl_unused vtype *ossl_unused ossl_ht_##name##_##vtype##_get(HT *h, \
+ HT_KEY *key, \
+ HT_VALUE **v)\
+{ \
+ HT_VALUE *vv; \
+ vv = ossl_ht_get(h, key); \
+ if (vv == NULL) \
+ return NULL; \
+ *v = ossl_rcu_deref(&vv); \
+ return ossl_ht_##name##_##vtype##_from_value(*v); \
+} \
+ \
+pfx ossl_unused HT_VALUE *ossl_ht_##name##_##vtype##_to_value(vtype *data, \
+ HT_VALUE *v) \
+{ \
+ v->type_id = &name##_##vtype##_id; \
+ v->value = data; \
+ return v; \
+} \
+ \
+pfx ossl_unused int ossl_ht_##name##_##vtype##_type(HT_VALUE *h) \
+{ \
+ return h->type_id == &name##_##vtype##_id; \
+}
+
+#define DECLARE_HT_VALUE_TYPE_FNS(vtype, name) \
+int ossl_ht_##name##_##vtype##_insert(HT *h, HT_KEY *key, vtype *data, \
+ vtype **olddata); \
+vtype *ossl_ht_##name##_##vtype##_from_value(HT_VALUE *v); \
+vtype *ossl_unused ossl_ht_##name##_##vtype##_get(HT *h, \
+ HT_KEY *key, \
+ HT_VALUE **v); \
+HT_VALUE *ossl_ht_##name##_##vtype##_to_value(vtype *data, HT_VALUE *v); \
+int ossl_ht_##name##_##vtype##_type(HT_VALUE *h); \
+
+/*
+ * Helper function to construct case insensitive keys
+ */
+static void ossl_unused ossl_ht_strcase(char *tgt, const char *src, int len)
+{
+ int i;
+#if defined(CHARSET_EBCDIC) && !defined(CHARSET_EBCDIC_TEST)
+ const long int case_adjust = ~0x40;
+#else
+ const long int case_adjust = ~0x20;
+#endif
+
+ if (src == NULL)
+ return;
+
+ for (i = 0; src[i] != '\0' && i < len; i++)
+ tgt[i] = case_adjust & src[i];
+}
+
+/*
+ * Create a new hashtable
+ */
+HT *ossl_ht_new(HT_CONFIG *conf);
+
+/*
+ * Frees a hash table, potentially freeing all elements
+ */
+void ossl_ht_free(HT *htable);
+
+/*
+ * Lock the table for reading
+ */
+void ossl_ht_read_lock(HT *htable);
+
+/*
+ * Lock the table for writing
+ */
+void ossl_ht_write_lock(HT *htable);
+
+/*
+ * Read unlock
+ */
+void ossl_ht_read_unlock(HT *htable);
+
+/*
+ * Write unlock
+ */
+void ossl_ht_write_unlock (HT *htable);
+
+/*
+ * Empties a hash table, potentially freeing all elements
+ */
+int ossl_ht_flush(HT *htable);
+
+/*
+ * Inserts an element to a hash table, optionally returning
+ * replaced data to caller
+ * Returns 1 if the insert was successful, 0 on error
+ */
+int ossl_ht_insert(HT *htable, HT_KEY *key, HT_VALUE *data,
+ HT_VALUE **olddata);
+
+/*
+ * Deletes a value from a hash table, based on key
+ * Returns 1 if the key was removed, 0 if they key was not found
+ */
+int ossl_ht_delete(HT *htable, HT_KEY *key);
+
+/*
+ * Returns number of elements in the hash table
+ */
+size_t ossl_ht_count(HT *htable);
+
+/*
+ * Iterates over each element in the table.
+ * aborts the loop when cb returns 0
+ * Contents of elements in the list may be modified during
+ * this traversal, assuming proper thread safety is observed while doing
+ * so (holding the table write lock is sufficient). However, elements of the
+ * table may not be inserted or removed while iterating.
+ */
+void ossl_ht_foreach_until(HT *htable, int (*cb)(HT_VALUE *obj, void *arg),
+ void *arg);
+/*
+ * Returns a list of elements in a hash table based on
+ * filter function return value. Returns NULL on error,
+ * or an HT_VALUE_LIST object on success. Note it is possible
+ * That a list will be returned with 0 entries, if none were found.
+ * The zero length list must still be freed via ossl_ht_value_list_free
+ */
+HT_VALUE_LIST *ossl_ht_filter(HT *htable, size_t max_len,
+ int (*filter)(HT_VALUE *obj, void *arg),
+ void *arg);
+/*
+ * Frees the list returned from ossl_ht_filter
+ */
+void ossl_ht_value_list_free(HT_VALUE_LIST *list);
+
+/*
+ * Fetches a value from the hash table, based
+ * on key. Returns NULL if the element was not found.
+ */
+HT_VALUE *ossl_ht_get(HT *htable, HT_KEY *key);
+
+#endif
CRYPTO_malloc(num, OPENSSL_FILE, OPENSSL_LINE)
# define OPENSSL_zalloc(num) \
CRYPTO_zalloc(num, OPENSSL_FILE, OPENSSL_LINE)
+# define OPENSSL_aligned_alloc(num, alignment, freeptr) \
+ CRYPTO_aligned_alloc(num, alignment, freeptr, \
+ OPENSSL_FILE, OPENSSL_LINE)
# define OPENSSL_realloc(addr, num) \
CRYPTO_realloc(addr, num, OPENSSL_FILE, OPENSSL_LINE)
# define OPENSSL_clear_realloc(addr, old_num, num) \
OSSL_CRYPTO_ALLOC void *CRYPTO_malloc(size_t num, const char *file, int line);
OSSL_CRYPTO_ALLOC void *CRYPTO_zalloc(size_t num, const char *file, int line);
+OSSL_CRYPTO_ALLOC void *CRYPTO_aligned_alloc(size_t num, size_t align,
+ void **freeptr, const char *file,
+ int line);
OSSL_CRYPTO_ALLOC void *CRYPTO_memdup(const void *str, size_t siz, const char *file, int line);
OSSL_CRYPTO_ALLOC char *CRYPTO_strdup(const char *str, const char *file, int line);
OSSL_CRYPTO_ALLOC char *CRYPTO_strndup(const char *str, size_t s, const char *file, int line);
SOURCE[lhash_test]=lhash_test.c
INCLUDE[lhash_test]=../include ../apps/include
- DEPEND[lhash_test]=../libcrypto libtestutil.a
+ DEPEND[lhash_test]=../libcrypto.a libtestutil.a
SOURCE[dtlsv1listentest]=dtlsv1listentest.c
INCLUDE[dtlsv1listentest]=../include ../apps/include
#include <openssl/lhash.h>
#include <openssl/err.h>
#include <openssl/crypto.h>
+#include <internal/hashtable.h>
#include "internal/nelem.h"
#include "testutil.h"
static int int_tests[] = { 65537, 13, 1, 3, -5, 6, 7, 4, -10, -12, -14, 22, 9,
-17, 16, 17, -23, 35, 37, 173, 11 };
-static const unsigned int n_int_tests = OSSL_NELEM(int_tests);
+static const size_t n_int_tests = OSSL_NELEM(int_tests);
static short int_found[OSSL_NELEM(int_tests)];
static short int_not_found;
}
/* num_items */
- if (!TEST_int_eq(lh_int_num_items(h), n_int_tests))
+ if (!TEST_int_eq((size_t)lh_int_num_items(h), n_int_tests))
goto end;
/* retrieve */
return testresult;
}
+
+static int int_filter_all(HT_VALUE *v, void *arg)
+{
+ return 1;
+}
+
+HT_START_KEY_DEFN(intkey)
+HT_DEF_KEY_FIELD(mykey, int)
+HT_END_KEY_DEFN(INTKEY)
+
+IMPLEMENT_HT_VALUE_TYPE_FNS(int, test, static)
+
+static int int_foreach(HT_VALUE *v, void *arg)
+{
+ int *vd = ossl_ht_test_int_from_value(v);
+ const int n = int_find(*vd);
+
+ if (n < 0)
+ int_not_found++;
+ else
+ int_found[n]++;
+ return 1;
+}
+
+static uint64_t hashtable_hash(uint8_t *key, size_t keylen)
+{
+ return (uint64_t)(*(uint32_t *)key);
+}
+
+static int test_int_hashtable(void)
+{
+ static struct {
+ int data;
+ int should_del;
+ } dels[] = {
+ { 65537 , 1},
+ { 173 , 1},
+ { 999 , 0 },
+ { 37 , 1 },
+ { 1 , 1 },
+ { 34 , 0 }
+ };
+ const size_t n_dels = OSSL_NELEM(dels);
+ HT_CONFIG hash_conf = {
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ };
+ INTKEY key;
+ int rc = 0;
+ size_t i;
+ HT *ht = NULL;
+ int todel;
+ HT_VALUE_LIST *list = NULL;
+
+ ht = ossl_ht_new(&hash_conf);
+
+ if (ht == NULL)
+ return 0;
+
+ /* insert */
+ HT_INIT_KEY(&key);
+ for (i = 0; i < n_int_tests; i++) {
+ HT_SET_KEY_FIELD(&key, mykey, int_tests[i]);
+ if (!TEST_int_eq(ossl_ht_test_int_insert(ht, TO_HT_KEY(&key),
+ &int_tests[i], NULL), 1)) {
+ TEST_info("int insert %zu", i);
+ goto end;
+ }
+ }
+
+ /* num_items */
+ if (!TEST_int_eq((size_t)ossl_ht_count(ht), n_int_tests))
+ goto end;
+
+ /* foreach, no arg */
+ memset(int_found, 0, sizeof(int_found));
+ int_not_found = 0;
+ ossl_ht_foreach_until(ht, int_foreach, NULL);
+ if (!TEST_int_eq(int_not_found, 0)) {
+ TEST_info("hashtable int foreach encountered a not found condition");
+ goto end;
+ }
+
+ for (i = 0; i < n_int_tests; i++)
+ if (!TEST_int_eq(int_found[i], 1)) {
+ TEST_info("hashtable int foreach %zu", i);
+ goto end;
+ }
+
+ /* filter */
+ list = ossl_ht_filter(ht, 64, int_filter_all, NULL);
+ if (!TEST_int_eq((size_t)list->list_len, n_int_tests))
+ goto end;
+ ossl_ht_value_list_free(list);
+
+ /* delete */
+ for (i = 0; i < n_dels; i++) {
+ HT_SET_KEY_FIELD(&key, mykey, dels[i].data);
+ todel = ossl_ht_delete(ht, TO_HT_KEY(&key));
+ if (dels[i].should_del) {
+ if (!TEST_int_eq(todel, 1)) {
+ TEST_info("hashtable couldn't find entry to delete\n");
+ goto end;
+ }
+ } else {
+ if (!TEST_int_eq(todel, 0)) {
+ TEST_info("%d found an entry that shouldn't be there\n", dels[i].data);
+ goto end;
+ }
+ }
+ }
+
+ rc = 1;
+end:
+ ossl_ht_free(ht);
+ return rc;
+}
+
static unsigned long int stress_hash(const int *p)
{
return *p;
}
+#ifdef MEASURE_HASH_PERFORMANCE
+static int
+timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
+{
+ /* Perform the carry for the later subtraction by updating y. */
+ if (x->tv_usec < y->tv_usec) {
+ int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
+ y->tv_usec -= 1000000 * nsec;
+ y->tv_sec += nsec;
+ }
+ if (x->tv_usec - y->tv_usec > 1000000) {
+ int nsec = (x->tv_usec - y->tv_usec) / 1000000;
+ y->tv_usec += 1000000 * nsec;
+ y->tv_sec -= nsec;
+ }
+
+ /*
+ * Compute the time remaining to wait.
+ * tv_usec is certainly positive.
+ */
+ result->tv_sec = x->tv_sec - y->tv_sec;
+ result->tv_usec = x->tv_usec - y->tv_usec;
+
+ /* Return 1 if result is negative. */
+ return x->tv_sec < y->tv_sec;
+}
+#endif
+
static int test_stress(void)
{
LHASH_OF(int) *h = lh_int_new(&stress_hash, &int_cmp);
const unsigned int n = 2500000;
unsigned int i;
int testresult = 0, *p;
+#ifdef MEASURE_HASH_PERFORMANCE
+ struct timeval start, end, delta;
+#endif
if (!TEST_ptr(h))
goto end;
-
+#ifdef MEASURE_HASH_PERFORMANCE
+ gettimeofday(&start, NULL);
+#endif
/* insert */
for (i = 0; i < n; i++) {
p = OPENSSL_malloc(sizeof(i));
testresult = 1;
end:
+#ifdef MEASURE_HASH_PERFORMANCE
+ gettimeofday(&end, NULL);
+ timeval_subtract(&delta, &end, &start);
+ TEST_info("lhash stress runs in %ld.%ld seconds", delta.tv_sec, delta.tv_usec);
+#endif
lh_int_free(h);
return testresult;
}
+static void hashtable_intfree(HT_VALUE *v)
+{
+ OPENSSL_free(v->value);
+}
+
+static int test_hashtable_stress(void)
+{
+ const unsigned int n = 2500000;
+ unsigned int i;
+ int testresult = 0, *p;
+ HT_CONFIG hash_conf = {
+ NULL, /* use default context */
+ hashtable_intfree, /* our free function */
+ hashtable_hash, /* our hash function */
+ 625000, /* preset hash size */
+ };
+ HT *h;
+ INTKEY key;
+#ifdef MEASURE_HASH_PERFORMANCE
+ struct timeval start, end, delta;
+#endif
+
+ h = ossl_ht_new(&hash_conf);
+
+
+ if (!TEST_ptr(h))
+ goto end;
+#ifdef MEASURE_HASH_PERFORMANCE
+ gettimeofday(&start, NULL);
+#endif
+
+ HT_INIT_KEY(&key);
+
+ /* insert */
+ for (i = 0; i < n; i++) {
+ p = OPENSSL_malloc(sizeof(i));
+ if (!TEST_ptr(p)) {
+ TEST_info("hashtable stress out of memory %d", i);
+ goto end;
+ }
+ *p = 3 * i + 1;
+ HT_SET_KEY_FIELD(&key, mykey, *p);
+ if (!TEST_int_eq(ossl_ht_test_int_insert(h, TO_HT_KEY(&key),
+ p, NULL), 1)) {
+ TEST_info("hashtable unable to insert element %d\n", *p);
+ goto end;
+ }
+ }
+
+ /* make sure we stored everything */
+ if (!TEST_int_eq((size_t)ossl_ht_count(h), n))
+ goto end;
+
+ /* delete in a different order */
+ for (i = 0; i < n; i++) {
+ const int j = (7 * i + 4) % n * 3 + 1;
+ HT_SET_KEY_FIELD(&key, mykey, j);
+ if (!TEST_int_eq((ossl_ht_delete(h, TO_HT_KEY(&key))), 1)) {
+ TEST_info("hashtable didn't delete key %d\n", j);
+ goto end;
+ }
+ }
+
+ testresult = 1;
+end:
+#ifdef MEASURE_HASH_PERFORMANCE
+ gettimeofday(&end, NULL);
+ timeval_subtract(&delta, &end, &start);
+ TEST_info("hashtable stress runs in %ld.%ld seconds", delta.tv_sec, delta.tv_usec);
+#endif
+ ossl_ht_free(h);
+ return testresult;
+}
+
int setup_tests(void)
{
ADD_TEST(test_int_lhash);
ADD_TEST(test_stress);
+ ADD_TEST(test_int_hashtable);
+ ADD_TEST(test_hashtable_stress);
return 1;
}
OPENSSL_LH_doall_arg_thunk 5677 3_3_0 EXIST::FUNCTION:
OSSL_HTTP_REQ_CTX_set_max_response_hdr_lines 5678 3_3_0 EXIST::FUNCTION:HTTP
CRYPTO_atomic_store ? 3_4_0 EXIST::FUNCTION:
+CRYPTO_aligned_alloc ? 3_4_0 EXIST::FUNCTION:
OPENSSL_clear_realloc define
OPENSSL_free define
OPENSSL_malloc define
+OPENSSL_aligned_alloc define
OPENSSL_malloc_init define
OPENSSL_mem_debug_pop define deprecated 3.0.0
OPENSSL_mem_debug_push define deprecated 3.0.0
abort
accept
+aligned_alloc
bcmp
bind
calloc
opendir
openlog
poll
+posix_memalign
pthread_attr_destroy
pthread_attr_init
pthread_attr_setdetachstate