| /* | 
 |  * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. | 
 |  * | 
 |  * Licensed under the OpenSSL license (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 | 
 |  */ | 
 |  | 
 | #include <openssl/lhash.h> | 
 |  | 
 | #include <assert.h> | 
 | #include <limits.h> | 
 | #include <string.h> | 
 |  | 
 | #include <openssl/mem.h> | 
 |  | 
 | #include "../internal.h" | 
 | #include "internal.h" | 
 |  | 
 |  | 
 | // kMinNumBuckets is the minimum size of the buckets array in an |_LHASH|. | 
 | static const size_t kMinNumBuckets = 16; | 
 |  | 
 | // kMaxAverageChainLength contains the maximum, average chain length. When the | 
 | // average chain length exceeds this value, the hash table will be resized. | 
 | static const size_t kMaxAverageChainLength = 2; | 
 | static const size_t kMinAverageChainLength = 1; | 
 |  | 
 | // lhash_item_st is an element of a hash chain. It points to the opaque data | 
 | // for this element and to the next item in the chain. The linked-list is NULL | 
 | // terminated. | 
 | typedef struct lhash_item_st { | 
 |   void *data; | 
 |   struct lhash_item_st *next; | 
 |   // hash contains the cached, hash value of |data|. | 
 |   uint32_t hash; | 
 | } LHASH_ITEM; | 
 |  | 
 | struct lhash_st { | 
 |   // num_items contains the total number of items in the hash table. | 
 |   size_t num_items; | 
 |   // buckets is an array of |num_buckets| pointers. Each points to the head of | 
 |   // a chain of LHASH_ITEM objects that have the same hash value, mod | 
 |   // |num_buckets|. | 
 |   LHASH_ITEM **buckets; | 
 |   // num_buckets contains the length of |buckets|. This value is always >= | 
 |   // kMinNumBuckets. | 
 |   size_t num_buckets; | 
 |   // callback_depth contains the current depth of |lh_doall| or |lh_doall_arg| | 
 |   // calls. If non-zero then this suppresses resizing of the |buckets| array, | 
 |   // which would otherwise disrupt the iteration. | 
 |   unsigned callback_depth; | 
 |  | 
 |   lhash_cmp_func comp; | 
 |   lhash_hash_func hash; | 
 | }; | 
 |  | 
 | _LHASH *OPENSSL_lh_new(lhash_hash_func hash, lhash_cmp_func comp) { | 
 |   _LHASH *ret = reinterpret_cast<_LHASH *>(OPENSSL_zalloc(sizeof(_LHASH))); | 
 |   if (ret == NULL) { | 
 |     return NULL; | 
 |   } | 
 |  | 
 |   ret->num_buckets = kMinNumBuckets; | 
 |   ret->buckets = reinterpret_cast<LHASH_ITEM **>( | 
 |       OPENSSL_calloc(ret->num_buckets, sizeof(LHASH_ITEM *))); | 
 |   if (ret->buckets == NULL) { | 
 |     OPENSSL_free(ret); | 
 |     return NULL; | 
 |   } | 
 |  | 
 |   ret->comp = comp; | 
 |   ret->hash = hash; | 
 |   return ret; | 
 | } | 
 |  | 
 | void OPENSSL_lh_free(_LHASH *lh) { | 
 |   if (lh == NULL) { | 
 |     return; | 
 |   } | 
 |  | 
 |   for (size_t i = 0; i < lh->num_buckets; i++) { | 
 |     LHASH_ITEM *next; | 
 |     for (LHASH_ITEM *n = lh->buckets[i]; n != NULL; n = next) { | 
 |       next = n->next; | 
 |       OPENSSL_free(n); | 
 |     } | 
 |   } | 
 |  | 
 |   OPENSSL_free(lh->buckets); | 
 |   OPENSSL_free(lh); | 
 | } | 
 |  | 
 | size_t OPENSSL_lh_num_items(const _LHASH *lh) { return lh->num_items; } | 
 |  | 
 | // get_next_ptr_and_hash returns a pointer to the pointer that points to the | 
 | // item equal to |data|. In other words, it searches for an item equal to |data| | 
 | // and, if it's at the start of a chain, then it returns a pointer to an | 
 | // element of |lh->buckets|, otherwise it returns a pointer to the |next| | 
 | // element of the previous item in the chain. If an element equal to |data| is | 
 | // not found, it returns a pointer that points to a NULL pointer. If |out_hash| | 
 | // is not NULL, then it also puts the hash value of |data| in |*out_hash|. | 
 | static LHASH_ITEM **get_next_ptr_and_hash(const _LHASH *lh, uint32_t *out_hash, | 
 |                                           const void *data, | 
 |                                           lhash_hash_func_helper call_hash_func, | 
 |                                           lhash_cmp_func_helper call_cmp_func) { | 
 |   const uint32_t hash = call_hash_func(lh->hash, data); | 
 |   if (out_hash != NULL) { | 
 |     *out_hash = hash; | 
 |   } | 
 |  | 
 |   LHASH_ITEM **ret = &lh->buckets[hash % lh->num_buckets]; | 
 |   for (LHASH_ITEM *cur = *ret; cur != NULL; cur = *ret) { | 
 |     if (call_cmp_func(lh->comp, cur->data, data) == 0) { | 
 |       break; | 
 |     } | 
 |     ret = &cur->next; | 
 |   } | 
 |  | 
 |   return ret; | 
 | } | 
 |  | 
 | // get_next_ptr_by_key behaves like |get_next_ptr_and_hash| but takes a key | 
 | // which may be a different type from the values stored in |lh|. | 
 | static LHASH_ITEM **get_next_ptr_by_key(const _LHASH *lh, const void *key, | 
 |                                         uint32_t key_hash, | 
 |                                         int (*cmp_key)(const void *key, | 
 |                                                        const void *value)) { | 
 |   LHASH_ITEM **ret = &lh->buckets[key_hash % lh->num_buckets]; | 
 |   for (LHASH_ITEM *cur = *ret; cur != NULL; cur = *ret) { | 
 |     if (cmp_key(key, cur->data) == 0) { | 
 |       break; | 
 |     } | 
 |     ret = &cur->next; | 
 |   } | 
 |  | 
 |   return ret; | 
 | } | 
 |  | 
 | void *OPENSSL_lh_retrieve(const _LHASH *lh, const void *data, | 
 |                           lhash_hash_func_helper call_hash_func, | 
 |                           lhash_cmp_func_helper call_cmp_func) { | 
 |   LHASH_ITEM **next_ptr = | 
 |       get_next_ptr_and_hash(lh, NULL, data, call_hash_func, call_cmp_func); | 
 |   return *next_ptr == NULL ? NULL : (*next_ptr)->data; | 
 | } | 
 |  | 
 | void *OPENSSL_lh_retrieve_key(const _LHASH *lh, const void *key, | 
 |                               uint32_t key_hash, | 
 |                               int (*cmp_key)(const void *key, | 
 |                                              const void *value)) { | 
 |   LHASH_ITEM **next_ptr = get_next_ptr_by_key(lh, key, key_hash, cmp_key); | 
 |   return *next_ptr == NULL ? NULL : (*next_ptr)->data; | 
 | } | 
 |  | 
 | // lh_rebucket allocates a new array of |new_num_buckets| pointers and | 
 | // redistributes the existing items into it before making it |lh->buckets| and | 
 | // freeing the old array. | 
 | static void lh_rebucket(_LHASH *lh, const size_t new_num_buckets) { | 
 |   LHASH_ITEM **new_buckets, *cur, *next; | 
 |   size_t i, alloc_size; | 
 |  | 
 |   alloc_size = sizeof(LHASH_ITEM *) * new_num_buckets; | 
 |   if (alloc_size / sizeof(LHASH_ITEM *) != new_num_buckets) { | 
 |     return; | 
 |   } | 
 |  | 
 |   new_buckets = reinterpret_cast<LHASH_ITEM **>(OPENSSL_zalloc(alloc_size)); | 
 |   if (new_buckets == NULL) { | 
 |     return; | 
 |   } | 
 |  | 
 |   for (i = 0; i < lh->num_buckets; i++) { | 
 |     for (cur = lh->buckets[i]; cur != NULL; cur = next) { | 
 |       const size_t new_bucket = cur->hash % new_num_buckets; | 
 |       next = cur->next; | 
 |       cur->next = new_buckets[new_bucket]; | 
 |       new_buckets[new_bucket] = cur; | 
 |     } | 
 |   } | 
 |  | 
 |   OPENSSL_free(lh->buckets); | 
 |  | 
 |   lh->num_buckets = new_num_buckets; | 
 |   lh->buckets = new_buckets; | 
 | } | 
 |  | 
 | // lh_maybe_resize resizes the |buckets| array if needed. | 
 | static void lh_maybe_resize(_LHASH *lh) { | 
 |   size_t avg_chain_length; | 
 |  | 
 |   if (lh->callback_depth > 0) { | 
 |     // Don't resize the hash if we are currently iterating over it. | 
 |     return; | 
 |   } | 
 |  | 
 |   assert(lh->num_buckets >= kMinNumBuckets); | 
 |   avg_chain_length = lh->num_items / lh->num_buckets; | 
 |  | 
 |   if (avg_chain_length > kMaxAverageChainLength) { | 
 |     const size_t new_num_buckets = lh->num_buckets * 2; | 
 |  | 
 |     if (new_num_buckets > lh->num_buckets) { | 
 |       lh_rebucket(lh, new_num_buckets); | 
 |     } | 
 |   } else if (avg_chain_length < kMinAverageChainLength && | 
 |              lh->num_buckets > kMinNumBuckets) { | 
 |     size_t new_num_buckets = lh->num_buckets / 2; | 
 |  | 
 |     if (new_num_buckets < kMinNumBuckets) { | 
 |       new_num_buckets = kMinNumBuckets; | 
 |     } | 
 |  | 
 |     lh_rebucket(lh, new_num_buckets); | 
 |   } | 
 | } | 
 |  | 
 | int OPENSSL_lh_insert(_LHASH *lh, void **old_data, void *data, | 
 |                       lhash_hash_func_helper call_hash_func, | 
 |                       lhash_cmp_func_helper call_cmp_func) { | 
 |   uint32_t hash; | 
 |   LHASH_ITEM **next_ptr, *item; | 
 |  | 
 |   *old_data = NULL; | 
 |   next_ptr = | 
 |       get_next_ptr_and_hash(lh, &hash, data, call_hash_func, call_cmp_func); | 
 |  | 
 |  | 
 |   if (*next_ptr != NULL) { | 
 |     // An element equal to |data| already exists in the hash table. It will be | 
 |     // replaced. | 
 |     *old_data = (*next_ptr)->data; | 
 |     (*next_ptr)->data = data; | 
 |     return 1; | 
 |   } | 
 |  | 
 |   // An element equal to |data| doesn't exist in the hash table yet. | 
 |   item = reinterpret_cast<LHASH_ITEM *>(OPENSSL_malloc(sizeof(LHASH_ITEM))); | 
 |   if (item == NULL) { | 
 |     return 0; | 
 |   } | 
 |  | 
 |   item->data = data; | 
 |   item->hash = hash; | 
 |   item->next = NULL; | 
 |   *next_ptr = item; | 
 |   lh->num_items++; | 
 |   lh_maybe_resize(lh); | 
 |  | 
 |   return 1; | 
 | } | 
 |  | 
 | void *OPENSSL_lh_delete(_LHASH *lh, const void *data, | 
 |                         lhash_hash_func_helper call_hash_func, | 
 |                         lhash_cmp_func_helper call_cmp_func) { | 
 |   LHASH_ITEM **next_ptr, *item, *ret; | 
 |  | 
 |   next_ptr = | 
 |       get_next_ptr_and_hash(lh, NULL, data, call_hash_func, call_cmp_func); | 
 |  | 
 |   if (*next_ptr == NULL) { | 
 |     // No such element. | 
 |     return NULL; | 
 |   } | 
 |  | 
 |   item = *next_ptr; | 
 |   *next_ptr = item->next; | 
 |   ret = reinterpret_cast<LHASH_ITEM *>(item->data); | 
 |   OPENSSL_free(item); | 
 |  | 
 |   lh->num_items--; | 
 |   lh_maybe_resize(lh); | 
 |  | 
 |   return ret; | 
 | } | 
 |  | 
 | void OPENSSL_lh_doall_arg(_LHASH *lh, void (*func)(void *, void *), void *arg) { | 
 |   if (lh == NULL) { | 
 |     return; | 
 |   } | 
 |  | 
 |   if (lh->callback_depth < UINT_MAX) { | 
 |     // |callback_depth| is a saturating counter. | 
 |     lh->callback_depth++; | 
 |   } | 
 |  | 
 |   for (size_t i = 0; i < lh->num_buckets; i++) { | 
 |     LHASH_ITEM *next; | 
 |     for (LHASH_ITEM *cur = lh->buckets[i]; cur != NULL; cur = next) { | 
 |       next = cur->next; | 
 |       func(cur->data, arg); | 
 |     } | 
 |   } | 
 |  | 
 |   if (lh->callback_depth < UINT_MAX) { | 
 |     lh->callback_depth--; | 
 |   } | 
 |  | 
 |   // The callback may have added or removed elements and the non-zero value of | 
 |   // |callback_depth| will have suppressed any resizing. Thus any needed | 
 |   // resizing is done here. | 
 |   lh_maybe_resize(lh); | 
 | } |