From adfcde29cdba95a9b8a765adf2231963c638ff75 Mon Sep 17 00:00:00 2001 From: Lev Walkin Date: Sun, 5 Nov 2017 22:45:54 -0800 Subject: [PATCH] generic hashing mechanism --- libasn1common/Makefile.am | 3 +- libasn1common/genhash.c | 811 ++++++++++++++++++++++++++++++++++++++ libasn1common/genhash.h | 230 +++++++++++ 3 files changed, 1043 insertions(+), 1 deletion(-) create mode 100644 libasn1common/genhash.c create mode 100644 libasn1common/genhash.h diff --git a/libasn1common/Makefile.am b/libasn1common/Makefile.am index 92ef8d51..78f05fd2 100644 --- a/libasn1common/Makefile.am +++ b/libasn1common/Makefile.am @@ -7,5 +7,6 @@ noinst_LTLIBRARIES = libasn1common.la libasn1common_la_SOURCES = \ asn1_ref.c asn1_ref.h \ asn1_buffer.c asn1_buffer.h \ - asn1_namespace.c asn1_namespace.h + asn1_namespace.c asn1_namespace.h \ + genhash.c genhash.h diff --git a/libasn1common/genhash.c b/libasn1common/genhash.c new file mode 100644 index 00000000..275e23e2 --- /dev/null +++ b/libasn1common/genhash.c @@ -0,0 +1,811 @@ +/* + * Copyright (c) 2002-2005 Lev Walkin . All rights reserved. + * Copyright (c) 2001-2004 Netli, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: genhash.c 447 2005-06-07 06:51:10Z vlm $ + */ +/* + * Implementation of a hash data structure. + * This particular implementation is supposed to be space-efficient + * particularly in the case of tiny number of hash elements. + * It also has an aggressive hash buckets expanding technique, which allows + * to deal with increasing number of elements without a loss of search speed. + * + * Generally, one structure of type genhash_t is allocated per hash set. + * This structure is supposed to hold all information related to the current + * set, and also holds a tiny number of hash elements, when hash hasn't yet + * grown up. When the number of elements reaches some point, part of the + * genhash_t structure is reused to contain the pointers to the actual + * hash buckets and LRU (Least Recently Used) list's head and tail. + * Elements which were held inside genhash_t will be moved to the hash buckets. + * + * Said above effectively means two modes of operation: TINY and NORMAL. + * They can be distinguished by examining the h->numbuckets value, which + * is 0 for TINY and greater for NORMAL mode. + * + * In the TINY mode we use a lower part of the genhash_t structure + * (lower 32 bytes from 64 bytes of genhash_t) to hold up to IH_VALUE (4) + * key/value pairs. + * + * In the NORMAL mode we use the lower part of the genhash_t structure + * to hold a set of pointers, including a pointer to the hash buckets. + * We agressively expand hash buckets size when adding new elements + * to lower the number of key comparisons. + */ + +#include +#include +#include +#include +#include +#include "genhash.h" + +/* 1M entries, 4M RAM */ +#define DEFAULT_MAXIMUM_HASH_BUCKETS_NUMBER (1024 * 1024) +static int maximum_hash_buckets_number = DEFAULT_MAXIMUM_HASH_BUCKETS_NUMBER; + +/* + * A single hash element structure which binds a value to its key. + */ +typedef struct genhash_el_s { + unsigned int key_hash; /* Saved hash of the key */ + void *key; + void *value; + struct genhash_el_s *hash_next; /* Collision list inside the bucket */ + struct genhash_el_s *hash_prev; + struct genhash_el_s *lru_prev; /* Per-hash LRU list */ + struct genhash_el_s *lru_next; +} genhash_el; + +/* + * A hash structure with buckets etc. + */ +struct genhash_s { + int (*keycmpf) (const void *lkey1, const void *rkey2); + unsigned int (*keyhashf) (const void *key); /* hash function */ + void (*keydestroyf) (void *key); /* key destructor */ + void (*valuedestroyf) (void *value); /* value destructor */ + + int numelements; /* Total number of hash elements */ + int numbuckets; /* 0 means "use _TINY" */ + int lru_limit; /* Must be initialized explicitly */ + genhash_iter_t *iters; /* Active iterators */ + + /* 32-byte boundary here */ + + union { +#define IH_VALUES 4 /* Internally held key/value pairs for TINY mode */ + struct _internal_tiny_s { + void *keys[IH_VALUES]; + void *values[IH_VALUES]; + } _TINY; /* 32-byte structure */ + struct _internal_normal_s { + genhash_el *lru_head; /* LRU list head */ + genhash_el *lru_tail; /* LRU list tail */ + genhash_el **buckets; /* Hash buckets */ + /* void *unused; */ + } _NORMAL; + } un; +#define tiny_keys un._TINY.keys +#define tiny_values un._TINY.values +#define lru_head un._NORMAL.lru_head +#define lru_tail un._NORMAL.lru_tail +#define buckets un._NORMAL.buckets +}; + + +static int +_genhash_normal_add(genhash_t *h, genhash_el *el, void *key, void *value); + + +genhash_t * +genhash_new( + int (*keycmpf) (const void *key1, const void *key2), + unsigned int (*keyhashf) (const void *key), + void (*keydestroyf) (void *key), + void (*valuedestroyf) (void *value) +) { + genhash_t *h; + + h = (genhash_t *)malloc(sizeof(genhash_t)); + if (!h) + return NULL; + + memset(h, 0, sizeof(genhash_t)); + + genhash_reinit(h, keycmpf, keyhashf, keydestroyf, valuedestroyf); + + return h; +} + +int +genhash_reinit( + genhash_t *h, + int (*keycmpf) (const void *key1, const void *key2), + unsigned int (*keyhashf) (const void *key), + void (*keydestroyf) (void *key), + void (*valuedestroyf) (void *value) +) { + + assert(keycmpf && keyhashf); + + h->keycmpf = keycmpf; + h->keyhashf = keyhashf; + h->keydestroyf = keydestroyf; + h->valuedestroyf = valuedestroyf; + + return 0; +} + +int +genhash_count(genhash_t *h) { + if(h) { + return h->numelements; + } else { + return 0; + } +} + + +static void +_remove_normal_hash_el(genhash_t *h, genhash_el *el) { + genhash_iter_t *iter; + void *kd_arg; + void *vd_arg; + + /* Remove from the collision list */ + if (el->hash_prev) { + if((el->hash_prev->hash_next = el->hash_next)) + el->hash_next->hash_prev = el->hash_prev; + + } else { + if((h->buckets[el->key_hash % h->numbuckets] = el->hash_next)) + el->hash_next->hash_prev = NULL; + } + + /* Remove from LRU list */ + if(el->lru_prev) { + if((el->lru_prev->lru_next = el->lru_next)) + el->lru_next->lru_prev = el->lru_prev; + else + h->lru_tail = el->lru_prev; + } else { + if(h->lru_head == el) { + if((h->lru_head = el->lru_next) == NULL) + h->lru_tail = NULL; + else + h->lru_head->lru_prev = NULL; + } + } + + /* Remember key and value */ + kd_arg = el->key; + vd_arg = el->value; + + /* Move iterators off the element being deleted */ + for(iter = h->iters; iter; iter = iter->iter_next) { + assert(iter->hash_ptr == h); + if(iter->un.location == el) { + iter->un.location = iter->order_lru_first + ? el->lru_prev : el->lru_next; + } + } + + free(el); + h->numelements--; + + /* Remove key and value */ + if (h->keydestroyf) h->keydestroyf(kd_arg); + if (h->valuedestroyf) h->valuedestroyf(vd_arg); +} + +static inline void +_genhash_normal_el_move2top(genhash_t *h, genhash_el *el) { + + /* Disable sorting if iterators are running */ + if(h->iters) return; + + /* Move to the top of the hash bucket */ + if(el->hash_prev) { + int bucket = el->key_hash % h->numbuckets; + + /* Remove from the current location */ + if((el->hash_prev->hash_next = el->hash_next)) + el->hash_next->hash_prev = el->hash_prev; + + /* Move to the top of the hash bucket */ + if((el->hash_next = h->buckets[bucket])) + el->hash_next->hash_prev = el; + h->buckets[bucket] = el; + el->hash_prev = NULL; + } + + /* Move to the top of LRU list */ + if(h->lru_limit && el->lru_prev) { + + /* Remove from current location */ + if((el->lru_prev->lru_next = el->lru_next)) + el->lru_next->lru_prev = el->lru_prev; + else + h->lru_tail = el->lru_prev; + + /* Append to the head */ + el->lru_prev = NULL; + h->lru_head->lru_prev = el; + el->lru_next = h->lru_head; + h->lru_head = el; + } +} + +static int +_expand_hash(genhash_t *h) { + int newbuckets_count; + genhash_el **newbuckets; + + /* + * Compute a new number of buckets value. + */ + if(h->numbuckets) { + newbuckets_count = h->numbuckets << 2; + /* Too big hash table */ + if(newbuckets_count > maximum_hash_buckets_number) { + if(h->numbuckets < maximum_hash_buckets_number) { + newbuckets_count = maximum_hash_buckets_number; + } else { + /* No need to set errno here. */ + return -1; + } + } + } else { + /* 8 buckets -> 32 bytes of memory */ + newbuckets_count = IH_VALUES << 1; + if(newbuckets_count > maximum_hash_buckets_number) { + if(maximum_hash_buckets_number) { + newbuckets_count = maximum_hash_buckets_number; + } else { + /* Allowed to store only IH_VALUES elements */ + errno = EPERM; + return -1; + } + } + } + + /* + * Allocate a new storage for buckets. + */ + newbuckets = malloc(newbuckets_count * sizeof(*newbuckets)); + if(newbuckets) { + memset(newbuckets, 0, newbuckets_count * sizeof(*newbuckets)); + } else { + return -1; + } + + if(h->numbuckets) { + genhash_el *el; + int bucket; + + /* + * Rehash elements from old h->buckets to newbuckets. + * No need to touch LRU pointers and other stuff - it is okay. + */ + for(el = h->lru_tail; el; el = el->lru_prev) { + bucket = el->key_hash % newbuckets_count; + el->hash_prev = NULL; + if((el->hash_next = newbuckets[bucket])) + el->hash_next->hash_prev = el; + newbuckets[bucket] = el; + } + + free(h->buckets); + h->buckets = newbuckets; + h->numbuckets = newbuckets_count; + } else { + /* + * Moving from inline tiny storage into buckets. + */ + genhash_el *els[IH_VALUES] = { NULL }; + struct _internal_tiny_s tiny_substruct; + int i; + int saved_numelements; + int saved_lru_limit; + genhash_iter_t *iter; + + /* Pre-allocate hash elements (for "undo") */ + for(i = 0; i < h->numelements; i++) { + els[i] = (genhash_el *)malloc(sizeof(genhash_el)); + if(els[i] == NULL) { + for(i = 0; i < h->numelements; i++) + if(els[i]) + free(els[i]); + free(newbuckets); + return -1; + } + } + + /* Save part of the union */ + tiny_substruct = h->un._TINY; + /* Re-initialize this part in NORMAL model */ + memset(&h->un._NORMAL, 0, sizeof(h->un._NORMAL)); + + /* There was no allocated buckets, when in tiny hash mode. */ + h->buckets = newbuckets; + h->numbuckets = newbuckets_count; + + saved_numelements = h->numelements; + saved_lru_limit = h->lru_limit; + h->numelements = 0; + h->lru_limit = 0; /* Disable LRU expiration for a while */ + + for(i = saved_numelements - 1; i >= 0; --i) { + /* + * genhash_normal_add won't fail, if we supply + * an already allocated genhash_el *. + */ + (void)_genhash_normal_add(h, els[i], + tiny_substruct.keys[i], + tiny_substruct.values[i]); + } + + /* Now, scan through iterators and convert them TINY->NORMAL */ + for(iter = h->iters; iter; iter = iter->iter_next) { + assert(iter->hash_ptr == h); + if(iter->un.item_number < 0 + || iter->un.item_number >= saved_numelements) { + iter->un.location = 0; + } else { + iter->un.location = els[iter->un.item_number]; + } + } + + h->lru_limit = saved_lru_limit; + } + + return 0; +} + +/* + * Won't return with error if el is provided. + */ +static int +_genhash_normal_add(genhash_t *h, genhash_el *el, void *key, void *value) { + genhash_el **bucket; + + if(el == NULL) { + el = malloc(sizeof (*el)); + if(el == NULL) { + /* Errno will be set by malloc() */ + return -1; + } + } + + /* Maintain maximum number of entries */ + if(h->lru_limit) { + while(h->numelements >= h->lru_limit) + _remove_normal_hash_el(h, h->lru_tail); + } + + memset(el, 0, sizeof(genhash_el)); + + /* Compute the index of the collision list */ + el->key_hash = h->keyhashf(key); + bucket = &h->buckets[el->key_hash % h->numbuckets]; + + el->key = key; + el->value = value; + + /* + * Add to the collision list + */ + el->hash_prev = NULL; + if((el->hash_next = *bucket)) + (*bucket)->hash_prev = el; + *bucket = el; + + /* + * Add to the LRU list. + */ + if(h->lru_head) { + el->lru_next = h->lru_head; + el->lru_next->lru_prev = el; + h->lru_head = el; + } else { + h->lru_head = el; + h->lru_tail = el; + } + + h->numelements++; + + return 0; +} + + +int +genhash_add(genhash_t *h, void *key, void *value) { + + if(key == NULL) { + errno = EINVAL; + return -1; + } + + if(h->numbuckets == 0) { + + /* We have a tiny internally-held set of elements */ + if(h->numelements < IH_VALUES) { + h->tiny_keys[h->numelements] = key; + h->tiny_values[h->numelements] = value; + h->numelements++; + return 0; + } + + if(_expand_hash(h) == -1) + return -1; + + } else { + + if((h->numelements / h->numbuckets) > 2) + (void)_expand_hash(h); + } + + return _genhash_normal_add(h, NULL, key, value); +} + +int +genhash_addunique(genhash_t *h, void *key, void *value) { + if(genhash_get(h, key)) { + errno = EEXIST; + return -1; + } + return genhash_add(h, key, value); +} + +void * +genhash_get(genhash_t *h, const void *key) { + + if(h->numbuckets) { + + genhash_el *walk; + int bucket = h->keyhashf(key) % h->numbuckets; + + for(walk = h->buckets[bucket]; + walk; walk = walk->hash_next) { + + if (h->keycmpf(walk->key, key) == 0) { + _genhash_normal_el_move2top(h, walk); + return walk->value; + } + } + + } else { + /* TINY mode */ + int i; + + assert(h->numelements <= IH_VALUES); + for(i = 0; i < h->numelements; i++) { + if(h->keycmpf(h->tiny_keys[i], key) == 0) + /* Don't reorder in TINY mode */ + return h->tiny_values[i]; + } + + } + + errno = ESRCH; + return NULL; +} + +int +genhash_del(genhash_t *h, void *key) { + + if(h->numbuckets) { + /* NORMAL mode */ + genhash_el *walk; + int bucket; + + if(h->numelements == 0) { + errno = ESRCH; + return -1; /* not found */ + } + + bucket = h->keyhashf(key) % h->numbuckets; + + for(walk = h->buckets[bucket]; walk; walk = walk->hash_next) + if(h->keycmpf(walk->key, key) == 0) + break; + + if(walk) { + _remove_normal_hash_el(h, walk); + return 0; + } + } else { + /* TINY mode */ + int i; + + /* Look for matching key */ + for(i = 0; i < h->numelements; i++) + if(h->keycmpf(h->tiny_keys[i], key) == 0) + break; + + if(i < h->numelements) { + /* Remember values */ + void *kd_arg = h->tiny_keys[i]; + void *vd_arg = h->tiny_values[i]; + + h->numelements--; + + if(h->iters) { + /* If iterators are involved, we have to + * shift elements to maintain iteration order + * and avoid duplications */ + genhash_iter_t *iter; + memmove(&h->tiny_keys[i], + &h->tiny_keys[i+1], + (h->numelements - i) + * sizeof(h->tiny_keys[0])); + memmove(&h->tiny_values[i], + &h->tiny_values[i+1], + (h->numelements - i) + * sizeof(h->tiny_values[0])); + /* Shift the iterator's indexes */ + for(iter = h->iters; iter; + iter = iter->iter_next) { + int in = iter->un.item_number; + if(iter->order_lru_first) { + if(in > i) + iter->un.item_number--; + } else { + if(in >= i) + iter->un.item_number--; + } + } + } else { + /* Substitute it with the last one */ + /* No harm if overwriting itself */ + h->tiny_keys[i] = h->tiny_keys[h->numelements]; + h->tiny_values[i] = h->tiny_values[h->numelements]; + } + h->tiny_keys[h->numelements] = 0; + h->tiny_values[h->numelements] = 0; + /* Delete for real */ + if(h->keydestroyf) h->keydestroyf(kd_arg); + if(h->valuedestroyf) h->valuedestroyf(vd_arg); + return 0; + } + } + + errno = ESRCH; + return -1; +} + +/* + * Initialize a hash iterator. + */ +int +genhash_iter_init(genhash_iter_t *iter, genhash_t *h, int reverse_order) { + + iter->hash_ptr = h; + iter->iter_prev = 0; /* Add itself to the iterators list */ + iter->iter_next = h->iters; + h->iters = iter; + iter->order_lru_first = reverse_order; + + if(h->numbuckets) { + /* NORMAL mode */ + if(reverse_order) { + /* Least recent first order */ + iter->un.location = h->lru_tail; + } else { + /* Most recent first order */ + iter->un.location = h->lru_head; + } + } else { + /* TINY mode */ + if(reverse_order) { + iter->un.item_number = 0; + } else { + iter->un.item_number = h->numelements - 1; + } + } + + return h->numelements; +} + +int +genhash_iter(genhash_iter_t *iter, void *key_p, void *val_p) { + void **key = key_p; + void **val = val_p; + genhash_t *h = iter->hash_ptr; + + if(h->numbuckets) { + /* NORMAL mode */ + genhash_el *cur_el = iter->un.location; + if(!cur_el) + /* Already finished */ + return 0; + + if(key) *key = cur_el->key; + if(val) *val = cur_el->value; + + /* Move pointer to the next hash element */ + iter->un.location = iter->order_lru_first + ? cur_el->lru_prev : cur_el->lru_next; + } else { + /* TINY mode */ + if(iter->un.item_number < 0 + || iter->un.item_number >= h->numelements + || h->tiny_keys[iter->un.item_number] == 0) + return 0; + + if(key) *key = h->tiny_keys[iter->un.item_number]; + if(val) *val = h->tiny_values[iter->un.item_number]; + + /* Advance to the next element */ + if(iter->order_lru_first) + iter->un.item_number++; + else + iter->un.item_number--; + } + + + return 1; +} + +void +genhash_iter_done(genhash_iter_t *iter) { + assert(iter->hash_ptr->iters); + /* Remove itself from the iterators list */ + if(iter->iter_next) + iter->iter_next->iter_prev = iter->iter_prev; + if(iter->iter_prev) + iter->iter_prev->iter_next = iter->iter_next; + else + iter->hash_ptr->iters = iter->iter_next; /* Shift the head */ + iter->hash_ptr = (void *)0xdeadbeef; +} + +int +genhash_set_lru_limit(genhash_t *h, int value) { + if(h) { + int prev_limit = h->lru_limit; + if(value >= 0) + h->lru_limit = value; + return prev_limit; + } else { + errno = EINVAL; + return -1; + } +} + +int +genhash_set_buckets_limit(int value) { + int prev_limit = maximum_hash_buckets_number; + if(value > 0) { + maximum_hash_buckets_number = value; + } + return prev_limit; +} + +void +genhash_destroy(genhash_t *h) { + if(h) { + assert(h->iters == 0); /* All iterators MUST be _done(). */ + genhash_empty(h, 1, 1); + free(h); + } +} + +void +genhash_empty(genhash_t *h, int freekeys, int freevalues) { + genhash_iter_t *iter; + + if(h == NULL) return; + + /* + * Don't free what could not be freed. + */ + if(h->keydestroyf == NULL) freekeys = 0; + if(h->valuedestroyf == NULL) freevalues = 0; + + if(h->numbuckets == 0) { + while(h->numelements > 0) { + int n = --h->numelements; + void *kd_arg = h->tiny_keys[n]; + void *vd_arg = h->tiny_values[n]; + + if (freekeys) h->keydestroyf(kd_arg); + if (freevalues) h->valuedestroyf(vd_arg); + } + } else { + genhash_el *el, *el_next; + + for(el = h->lru_head; el; el = el_next) { + void *kd_arg = el->key; + void *vd_arg = el->value; + el_next = el->lru_next; + free(el); + + h->numelements --; + + if (freekeys) h->keydestroyf(kd_arg); + if (freevalues) h->valuedestroyf(vd_arg); + } + free(h->buckets); + h->numbuckets = 0; /* Move back to TINY model */ + } + memset(&h->un, 0, sizeof(h->un)); + + /* Invalidate iterators in TINY model */ + for(iter = h->iters; iter; iter = iter->iter_next) { + assert(iter->hash_ptr == h); + iter->un.item_number = -1; + } + + assert(h->numelements == 0); +} + + +/*----- Simple hash and compare functions for common data types ------*/ + +unsigned int +hashf_int (const void *key) { + return (*(const int *)key ^ (*(const int *)key >> 16)); +} + +int +cmpf_int (const void *key1, const void *key2) { + return (*(const int *)key1 != *(const int *)key2); +} + +unsigned int +hashf_void (const void *key) { + return ((int)key ^ ((int)key >> 16)); +} + +int +cmpf_void (const void *key1, const void *key2) { + return (key1 != key2); +} + + +/* + * Phong's linear congruential hash + */ +#define dcharhash(h, c) ((h) = 0x63c63cd9*(h) + 0x9c39c33d + (c)) + +unsigned int +hashf_string(const void *keyarg) { + register const unsigned char *key; + register unsigned int h; + register unsigned char c; + + key = keyarg; + for (h = 0; (c = *key++);) + dcharhash(h, c); + + return (h); +} + +int +cmpf_string(const void *key1, const void *key2) { + return strcmp((const char *)key1, (const char *)key2); +} + diff --git a/libasn1common/genhash.h b/libasn1common/genhash.h new file mode 100644 index 00000000..8fff9a6c --- /dev/null +++ b/libasn1common/genhash.h @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2002-2005 Lev Walkin . All rights reserved. + * Copyright (c) 2001-2004 Netli, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: genhash.h 447 2005-06-07 06:51:10Z vlm $ + */ +#ifndef __GENHASH_H__ +#define __GENHASH_H__ + +/* + * General purpose hashing framework. + * Refer to the corresponding .c source file for the detailed description. + * + * WARNING: Generally, functions don't allow NULL's to be passed + * as the genhash_t pointers, if not explicitly stated otherwise. + */ + +typedef struct genhash_s genhash_t; + +/* + * Create a new hash table + * keycmpf : function which returns 0 if keys are equal, else !0 + * keyhashf : function which computes the hash value of a key + * keydestroyf : function for destroying keys, can be NULL for no destructor + * valuedestroyf: function for destroying values, can be NULL for no destructor + */ +genhash_t *genhash_new( + int (*keycmpf) (const void *key1, const void *key2), + unsigned int (*keyhashf) (const void *key), + void (*keydestroyf) (void *key), + void (*valuedestroyf) (void *value)); + +/* + * Re-initialize genhash structure with new callback functions. + * (Rarely ever used). + */ +int genhash_reinit( + genhash_t *hash, + int (*keycmpf) (const void *key1, const void *key2), + unsigned int (*keyhashf) (const void *key), + void (*keydestroyf) (void *key), + void (*valuedestroyf) (void *value)); + +/* + * Initialize the LRU-driven elements count limiting + * and/or set a new Least Recently Used list size limit. + * If a new entry is being added to the hash, the least recently used entry + * (one at the bottom of the LRU list) will be automatically deleted. + * The deletion may be skipped if the hash is very small + * (currently, "small" means no longer than 4 entries). + * This function is immune to NULL argument. + * + * RETURN VALUES: + * The previous LRU limit, or -1/EINVAL when h is NULL. + * EXAMPLE: + * genhash_set_lru_limit(h, 1500); // Maximum 1500 entries in the hash + */ +int genhash_set_lru_limit(genhash_t *h, int new_lru_limit); + +/* + * Set the system-wide (!!!) limit on maximum number of buckets. + * If the value is 0, the hash is allowed to store only 4 elements inline + * (buckets allocation is suppressed). + * If the value is 1, the hash turns out into a linked list. + * The default limit is about 1M buckets. + * RETURN VALUES: + * The previous buckets number limit. + */ +int genhash_set_buckets_limit(int new_max_number_of_buckets); + +/* + * destroys a hash, freeing each key and/or value. + * Keys are always destroyed before values using the destructors + * specified upon hash creation. + * This function is immune to NULL argument. + */ +void genhash_destroy(genhash_t *h); + +/* + * Delete all elements from the hash, retaining the hash structure itself. + * Optionally, it may be told to invoke, or not invoke the corresponding + * key/value destructors. + * This function is immune to NULL argument. + * + * EXAMPLE: + * genhash_empty(h, 1, 1); // Remove all entries, invoking destructors + */ +void genhash_empty(genhash_t *h, int freekeys, int freevalues); + +/* + * Add, returns 0 on success, -1 on failure (ENOMEM). Note, you CAN add + * records with duplicate keys. No guarantees about order preservations. + * + * EXAMPLE: + * char *key_str = strdup("key"); + * char *val_str = strdup("arbitrary value"); + * if(genhash_add(h, key_str, val_str) != 0) { + * free(key_str); + * free(val_str); + * perror("genhash_add failed"); + * exit(EX_SOFTWARE); + * } + */ +int genhash_add(genhash_t *h, void *key, void *value); + +/* + * Add, but only if a mapping is not there already. + * RETURN VALUES: + * 0: Element added successfully. + * -1/EINVAL: Invalid arguments (key == NULL). + * -1/EEXIST: Duplicate entry is found. + * -1/ENOMEM: Memory allocation failed + */ +int genhash_addunique(genhash_t *h, void *key, void *value); + +/* + * Fetch - returns pointer to a value, NULL/ESRCH if not found + */ +void *genhash_get(genhash_t *h, const void *key); + +/* + * Delete - returns 0 on success, -1/ESRCH if not found. + * Keys are always destroyed before values using the destructors + * specified upon hash creation. + */ +int genhash_del(genhash_t *h, void *key); + +/* + * Return the number of elements in a hash. + * This function is immune to NULL argument. + */ +int genhash_count(genhash_t *h); + +/* + * External iterator structure for using with iterator-based walking functions. + * This declaration is NOT INTENDED TO BE USED BY AN APPLICATION DIRECTLY + * The pointer to the already allocated structure must be passed to + * genhash_iter*() functions. + */ +typedef struct genhash_iter_s { + genhash_t *hash_ptr; + union { + int item_number; + void *location; + } un; + int order_lru_first; + struct genhash_iter_s *iter_prev; + struct genhash_iter_s *iter_next; +} genhash_iter_t; + +/* + * Initialize the iterator for walking through the hash. + * The memory block to be used as iterator is provided by the (*iter) pointer. + * This memory must be allocated (possibly, on the stack) by the caller. + * OWNERSHIP: + * The initialized iterator must be disposed of by calling + * genhash_iter_done(). + * ORDER: + * By default, the elements are iterated in the "most recent first" order, + * use reverse_order to change that. For very small number of entries + * (currently, 4) the order may be IGNORED. + * RETURN VALUES: + * number of entries the hash had at the moment. + */ +int genhash_iter_init(genhash_iter_t *iter, + genhash_t *hash_to_use, int reverse_order); + +/* + * Returns the key and value of each element in optional (key) and (value), + * which must be passed as the pointers to pointers (hence these ***'s). + * OWNERSHIP: + * The key and value are pointers to the internally manageed locations. + * RETURN VALUES: + * 0 if no more elements will be returned, otherwise 1. + * EXAMPLE: + * key_type_t *key; // Pointer to key + * value_type_t *val; // Pointer to value + * genhash_iter_t iter; // Iterator structure + * genhash_iter_init(&iter, hash_ptr, 0); // Prepare iterator + * while(genhash_iter(&iter, &key, &val)) // Iterate over hash elements + * print_keyval(key, val); // Use key and value + * genhash_iter_done(&iter); // Done iterations. + */ +int genhash_iter(genhash_iter_t *iter, void */***/key, void */***/val); + +/* + * Dispose of the iterator. + * After this operations, the iterator contents unusable + * and shall not be accesed. (genhash_iter_init() is OK). + */ +void genhash_iter_done(genhash_iter_t *iter); + + +/****************************************************************************/ + +/* + * The following hashing and comparison functions are provided for + * you, or you may supply your own. + */ +unsigned int hashf_int (const void *key); /* Key is an int * */ +int cmpf_int (const void *key1, const void *key2); + +unsigned int hashf_void (const void *key); +int cmpf_void (const void *key1, const void *key2); + +unsigned int hashf_string (const void *key); +int cmpf_string (const void *key1, const void *key2); + +#endif /* __GENHASH_H__ */