%PDF- %PDF-
| Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/cares/src/lib/ |
| Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/cares/src/lib/ares__htable.c |
/* MIT License
*
* Copyright (c) 2023 Brad House
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_setup.h"
#include "ares.h"
#include "ares_private.h"
#include "ares__llist.h"
#include "ares__htable.h"
#define ARES__HTABLE_MAX_BUCKETS (1U << 24)
#define ARES__HTABLE_MIN_BUCKETS (1U << 4)
#define ARES__HTABLE_EXPAND_PERCENT 75
struct ares__htable {
ares__htable_hashfunc_t hash;
ares__htable_bucket_key_t bucket_key;
ares__htable_bucket_free_t bucket_free;
ares__htable_key_eq_t key_eq;
unsigned int seed;
unsigned int size;
size_t num_keys;
size_t num_collisions;
/* NOTE: if we converted buckets into ares__slist_t we could guarantee on
* hash collisions we would have O(log n) worst case insert and search
* performance. (We'd also need to make key_eq into a key_cmp to
* support sort). That said, risk with a random hash seed is near zero,
* and ares__slist_t is heavier weight, so I think using ares__llist_t
* is an overall win. */
ares__llist_t **buckets;
};
static unsigned int ares__htable_generate_seed(ares__htable_t *htable)
{
unsigned int seed = 0;
time_t t = time(NULL);
/* Mix stack address, heap address, and time to generate a random seed, it
* doesn't have to be super secure, just quick. Likelihood of a hash
* collision attack is very low with a small amount of effort */
seed |= (unsigned int)((size_t)htable & 0xFFFFFFFF);
seed |= (unsigned int)((size_t)&seed & 0xFFFFFFFF);
seed |= (unsigned int)(t & 0xFFFFFFFF);
return seed;
}
static void ares__htable_buckets_destroy(ares__llist_t **buckets,
unsigned int size,
ares_bool_t destroy_vals)
{
unsigned int i;
if (buckets == NULL) {
return;
}
for (i = 0; i < size; i++) {
if (buckets[i] == NULL) {
continue;
}
if (!destroy_vals) {
ares__llist_replace_destructor(buckets[i], NULL);
}
ares__llist_destroy(buckets[i]);
}
ares_free(buckets);
}
void ares__htable_destroy(ares__htable_t *htable)
{
if (htable == NULL) {
return;
}
ares__htable_buckets_destroy(htable->buckets, htable->size, ARES_TRUE);
ares_free(htable);
}
ares__htable_t *ares__htable_create(ares__htable_hashfunc_t hash_func,
ares__htable_bucket_key_t bucket_key,
ares__htable_bucket_free_t bucket_free,
ares__htable_key_eq_t key_eq)
{
ares__htable_t *htable = NULL;
if (hash_func == NULL || bucket_key == NULL || bucket_free == NULL ||
key_eq == NULL) {
goto fail;
}
htable = ares_malloc_zero(sizeof(*htable));
if (htable == NULL) {
goto fail;
}
htable->hash = hash_func;
htable->bucket_key = bucket_key;
htable->bucket_free = bucket_free;
htable->key_eq = key_eq;
htable->seed = ares__htable_generate_seed(htable);
htable->size = ARES__HTABLE_MIN_BUCKETS;
htable->buckets = ares_malloc_zero(sizeof(*htable->buckets) * htable->size);
if (htable->buckets == NULL) {
goto fail;
}
return htable;
fail:
ares__htable_destroy(htable);
return NULL;
}
const void **ares__htable_all_buckets(const ares__htable_t *htable, size_t *num)
{
const void **out = NULL;
size_t cnt = 0;
size_t i;
if (htable == NULL || num == NULL) {
return NULL;
}
*num = 0;
out = ares_malloc_zero(sizeof(*out) * htable->num_keys);
if (out == NULL) {
return NULL;
}
for (i = 0; i < htable->size; i++) {
ares__llist_node_t *node;
for (node = ares__llist_node_first(htable->buckets[i]); node != NULL;
node = ares__llist_node_next(node)) {
out[cnt++] = ares__llist_node_val(node);
}
}
*num = cnt;
return out;
}
/*! Grabs the Hashtable index from the key and length. The h index is
* the hash of the function reduced to the size of the bucket list.
* We are doing "hash & (size - 1)" since we are guaranteeing a power of
* 2 for size. This is equivalent to "hash % size", but should be more
* efficient */
#define HASH_IDX(h, key) h->hash(key, h->seed) & (h->size - 1)
static ares__llist_node_t *ares__htable_find(const ares__htable_t *htable,
unsigned int idx, const void *key)
{
ares__llist_node_t *node = NULL;
for (node = ares__llist_node_first(htable->buckets[idx]); node != NULL;
node = ares__llist_node_next(node)) {
if (htable->key_eq(key, htable->bucket_key(ares__llist_node_val(node)))) {
break;
}
}
return node;
}
static ares_bool_t ares__htable_expand(ares__htable_t *htable)
{
ares__llist_t **buckets = NULL;
unsigned int old_size = htable->size;
size_t i;
ares__llist_t **prealloc_llist = NULL;
size_t prealloc_llist_len = 0;
ares_bool_t rv = ARES_FALSE;
/* Not a failure, just won't expand */
if (old_size == ARES__HTABLE_MAX_BUCKETS) {
return ARES_TRUE;
}
htable->size <<= 1;
/* We must pre-allocate all memory we'll need before moving entries to the
* new hash array. Otherwise if there's a memory allocation failure in the
* middle, we wouldn't be able to recover. */
buckets = ares_malloc_zero(sizeof(*buckets) * htable->size);
if (buckets == NULL) {
goto done;
}
/* The maximum number of new llists we'll need is the number of collisions
* that were recorded */
prealloc_llist_len = htable->num_collisions;
if (prealloc_llist_len) {
prealloc_llist =
ares_malloc_zero(sizeof(*prealloc_llist) * prealloc_llist_len);
if (prealloc_llist == NULL) {
goto done;
}
}
for (i = 0; i < prealloc_llist_len; i++) {
prealloc_llist[i] = ares__llist_create(htable->bucket_free);
if (prealloc_llist[i] == NULL) {
goto done;
}
}
/* Iterate across all buckets and move the entries to the new buckets */
htable->num_collisions = 0;
for (i = 0; i < old_size; i++) {
ares__llist_node_t *node;
/* Nothing in this bucket */
if (htable->buckets[i] == NULL) {
continue;
}
/* Fast path optimization (most likely case), there is likely only a single
* entry in both the source and destination, check for this to confirm and
* if so, just move the bucket over */
if (ares__llist_len(htable->buckets[i]) == 1) {
const void *val = ares__llist_first_val(htable->buckets[i]);
size_t idx = HASH_IDX(htable, htable->bucket_key(val));
if (buckets[idx] == NULL) {
/* Swap! */
buckets[idx] = htable->buckets[i];
htable->buckets[i] = NULL;
continue;
}
}
/* Slow path, collisions */
while ((node = ares__llist_node_first(htable->buckets[i])) != NULL) {
const void *val = ares__llist_node_val(node);
size_t idx = HASH_IDX(htable, htable->bucket_key(val));
/* Try fast path again as maybe we popped one collision off and the
* next we can reuse the llist parent */
if (buckets[idx] == NULL && ares__llist_len(htable->buckets[i]) == 1) {
/* Swap! */
buckets[idx] = htable->buckets[i];
htable->buckets[i] = NULL;
break;
}
/* Grab one off our preallocated list */
if (buckets[idx] == NULL) {
/* Silence static analysis, this isn't possible but it doesn't know */
if (prealloc_llist == NULL || prealloc_llist_len == 0) {
goto done;
}
buckets[idx] = prealloc_llist[prealloc_llist_len - 1];
prealloc_llist_len--;
} else {
/* Collision occurred since the bucket wasn't empty */
htable->num_collisions++;
}
ares__llist_node_move_parent_first(node, buckets[idx]);
}
/* Abandoned bucket, destroy */
if (htable->buckets[i] != NULL) {
ares__llist_destroy(htable->buckets[i]);
htable->buckets[i] = NULL;
}
}
/* We have guaranteed all the buckets have either been moved or destroyed,
* so we just call ares_free() on the array and swap out the pointer */
ares_free(htable->buckets);
htable->buckets = buckets;
buckets = NULL;
rv = ARES_TRUE;
done:
ares_free(buckets);
/* destroy any unused preallocated buckets */
ares__htable_buckets_destroy(prealloc_llist, (unsigned int)prealloc_llist_len,
ARES_FALSE);
/* On failure, we need to restore the htable size */
if (rv != ARES_TRUE) {
htable->size = old_size;
}
return rv;
}
ares_bool_t ares__htable_insert(ares__htable_t *htable, void *bucket)
{
unsigned int idx = 0;
ares__llist_node_t *node = NULL;
const void *key = NULL;
if (htable == NULL || bucket == NULL) {
return ARES_FALSE;
}
key = htable->bucket_key(bucket);
idx = HASH_IDX(htable, key);
/* See if we have a matching bucket already, if so, replace it */
node = ares__htable_find(htable, idx, key);
if (node != NULL) {
ares__llist_node_replace(node, bucket);
return ARES_TRUE;
}
/* Check to see if we should rehash because likelihood of collisions has
* increased beyond our threshold */
if (htable->num_keys + 1 >
(htable->size * ARES__HTABLE_EXPAND_PERCENT) / 100) {
if (!ares__htable_expand(htable)) {
return ARES_FALSE;
}
/* If we expanded, need to calculate a new index */
idx = HASH_IDX(htable, key);
}
/* We lazily allocate the linked list */
if (htable->buckets[idx] == NULL) {
htable->buckets[idx] = ares__llist_create(htable->bucket_free);
if (htable->buckets[idx] == NULL) {
return ARES_FALSE;
}
}
node = ares__llist_insert_first(htable->buckets[idx], bucket);
if (node == NULL) {
return ARES_FALSE;
}
/* Track collisions for rehash stability */
if (ares__llist_len(htable->buckets[idx]) > 1) {
htable->num_collisions++;
}
htable->num_keys++;
return ARES_TRUE;
}
void *ares__htable_get(const ares__htable_t *htable, const void *key)
{
unsigned int idx;
if (htable == NULL || key == NULL) {
return NULL;
}
idx = HASH_IDX(htable, key);
return ares__llist_node_val(ares__htable_find(htable, idx, key));
}
ares_bool_t ares__htable_remove(ares__htable_t *htable, const void *key)
{
ares__llist_node_t *node;
unsigned int idx;
if (htable == NULL || key == NULL) {
return ARES_FALSE;
}
idx = HASH_IDX(htable, key);
node = ares__htable_find(htable, idx, key);
if (node == NULL) {
return ARES_FALSE;
}
htable->num_keys--;
/* Reduce collisions */
if (ares__llist_len(ares__llist_node_parent(node)) > 1) {
htable->num_collisions--;
}
ares__llist_node_destroy(node);
return ARES_TRUE;
}
size_t ares__htable_num_keys(const ares__htable_t *htable)
{
if (htable == NULL) {
return 0;
}
return htable->num_keys;
}
unsigned int ares__htable_hash_FNV1a(const unsigned char *key, size_t key_len,
unsigned int seed)
{
/* recommended seed is 2166136261U, but we don't want collisions */
unsigned int hv = seed;
size_t i;
for (i = 0; i < key_len; i++) {
hv ^= (unsigned int)key[i];
/* hv *= 0x01000193 */
hv += (hv << 1) + (hv << 4) + (hv << 7) + (hv << 8) + (hv << 24);
}
return hv;
}
/* Case insensitive version, meant for ASCII strings */
unsigned int ares__htable_hash_FNV1a_casecmp(const unsigned char *key,
size_t key_len, unsigned int seed)
{
/* recommended seed is 2166136261U, but we don't want collisions */
unsigned int hv = seed;
size_t i;
for (i = 0; i < key_len; i++) {
hv ^= (unsigned int)ares__tolower(key[i]);
/* hv *= 0x01000193 */
hv += (hv << 1) + (hv << 4) + (hv << 7) + (hv << 8) + (hv << 24);
}
return hv;
}