diff options
Diffstat (limited to 'src/libstrongswan/collections/hashtable.c')
-rw-r--r-- | src/libstrongswan/collections/hashtable.c | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/src/libstrongswan/collections/hashtable.c b/src/libstrongswan/collections/hashtable.c new file mode 100644 index 000000000..d181d8ec8 --- /dev/null +++ b/src/libstrongswan/collections/hashtable.c @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2008-2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + + +#include "hashtable.h" + +/** The maximum capacity of the hash table (MUST be a power of 2) */ +#define MAX_CAPACITY (1 << 30) + +typedef struct pair_t pair_t; + +/** + * This pair holds a pointer to the key and value it represents. + */ +struct pair_t { + /** + * Key of a hash table item. + */ + void *key; + + /** + * Value of a hash table item. + */ + void *value; + + /** + * Cached hash (used in case of a resize). + */ + u_int hash; + + /** + * Next pair in an overflow list. + */ + pair_t *next; +}; + +/** + * Creates an empty pair object. + */ +static inline pair_t *pair_create(void *key, void *value, u_int hash) +{ + pair_t *this; + + INIT(this, + .key = key, + .value = value, + .hash = hash, + ); + + return this; +} + +typedef struct private_hashtable_t private_hashtable_t; + +/** + * Private data of a hashtable_t object. + * + */ +struct private_hashtable_t { + /** + * Public part of hash table. + */ + hashtable_t public; + + /** + * The number of items in the hash table. + */ + u_int count; + + /** + * The current capacity of the hash table (always a power of 2). + */ + u_int capacity; + + /** + * The current mask to calculate the row index (capacity - 1). + */ + u_int mask; + + /** + * The load factor. + */ + float load_factor; + + /** + * The actual table. + */ + pair_t **table; + + /** + * The hashing function. + */ + hashtable_hash_t hash; + + /** + * The equality function. + */ + hashtable_equals_t equals; +}; + +typedef struct private_enumerator_t private_enumerator_t; + +/** + * hash table enumerator implementation + */ +struct private_enumerator_t { + + /** + * implements enumerator interface + */ + enumerator_t enumerator; + + /** + * associated hash table + */ + private_hashtable_t *table; + + /** + * current row index + */ + u_int row; + + /** + * number of remaining items in hashtable + */ + u_int count; + + /** + * current pair + */ + pair_t *current; + + /** + * previous pair (used by remove_at) + */ + pair_t *prev; + +}; + +/** + * This function returns the next-highest power of two for the given number. + * The algorithm works by setting all bits on the right-hand side of the most + * significant 1 to 1 and then increments the whole number so it rolls over + * to the nearest power of two. Note: returns 0 for n == 0 + */ +static u_int get_nearest_powerof2(u_int n) +{ + u_int i; + + --n; + for (i = 1; i < sizeof(u_int) * 8; i <<= 1) + { + n |= n >> i; + } + return ++n; +} + +/** + * Init hash table parameters + */ +static void init_hashtable(private_hashtable_t *this, u_int capacity) +{ + capacity = max(1, min(capacity, MAX_CAPACITY)); + this->capacity = get_nearest_powerof2(capacity); + this->mask = this->capacity - 1; + this->load_factor = 0.75; + + this->table = calloc(this->capacity, sizeof(pair_t*)); +} + +/** + * Double the size of the hash table and rehash all the elements. + */ +static void rehash(private_hashtable_t *this) +{ + pair_t **old_table; + u_int row, old_capacity; + + if (this->capacity >= MAX_CAPACITY) + { + return; + } + + old_capacity = this->capacity; + old_table = this->table; + + init_hashtable(this, old_capacity << 1); + + for (row = 0; row < old_capacity; row++) + { + pair_t *pair, *next; + u_int new_row; + + pair = old_table[row]; + while (pair) + { /* insert pair at the front of new bucket*/ + next = pair->next; + new_row = pair->hash & this->mask; + pair->next = this->table[new_row]; + this->table[new_row] = pair; + pair = next; + } + } + free(old_table); +} + +METHOD(hashtable_t, put, void*, + private_hashtable_t *this, void *key, void *value) +{ + void *old_value = NULL; + pair_t *pair; + u_int hash, row; + + hash = this->hash(key); + row = hash & this->mask; + pair = this->table[row]; + while (pair) + { /* search existing bucket for key */ + if (this->equals(key, pair->key)) + { + old_value = pair->value; + pair->value = value; + pair->key = key; + break; + } + pair = pair->next; + } + if (!pair) + { /* insert at the front of bucket */ + pair = pair_create(key, value, hash); + pair->next = this->table[row]; + this->table[row] = pair; + this->count++; + } + if (this->count >= this->capacity * this->load_factor) + { + rehash(this); + } + return old_value; +} + +static void *get_internal(private_hashtable_t *this, void *key, + hashtable_equals_t equals) +{ + void *value = NULL; + pair_t *pair; + + if (!this->count) + { /* no need to calculate the hash */ + return NULL; + } + + pair = this->table[this->hash(key) & this->mask]; + while (pair) + { + if (equals(key, pair->key)) + { + value = pair->value; + break; + } + pair = pair->next; + } + return value; +} + +METHOD(hashtable_t, get, void*, + private_hashtable_t *this, void *key) +{ + return get_internal(this, key, this->equals); +} + +METHOD(hashtable_t, get_match, void*, + private_hashtable_t *this, void *key, hashtable_equals_t match) +{ + return get_internal(this, key, match); +} + +METHOD(hashtable_t, remove_, void*, + private_hashtable_t *this, void *key) +{ + void *value = NULL; + pair_t *pair, *prev = NULL; + u_int row; + + row = this->hash(key) & this->mask; + pair = this->table[row]; + while (pair) + { + if (this->equals(key, pair->key)) + { + if (prev) + { + prev->next = pair->next; + } + else + { + this->table[row] = pair->next; + } + value = pair->value; + this->count--; + free(pair); + break; + } + prev = pair; + pair = pair->next; + } + return value; +} + +METHOD(hashtable_t, remove_at, void, + private_hashtable_t *this, private_enumerator_t *enumerator) +{ + if (enumerator->table == this && enumerator->current) + { + pair_t *current = enumerator->current; + if (enumerator->prev) + { + enumerator->prev->next = current->next; + } + else + { + this->table[enumerator->row] = current->next; + } + enumerator->current = enumerator->prev; + free(current); + this->count--; + } +} + +METHOD(hashtable_t, get_count, u_int, + private_hashtable_t *this) +{ + return this->count; +} + +METHOD(enumerator_t, enumerate, bool, + private_enumerator_t *this, void **key, void **value) +{ + while (this->count && this->row < this->table->capacity) + { + this->prev = this->current; + if (this->current) + { + this->current = this->current->next; + } + else + { + this->current = this->table->table[this->row]; + } + if (this->current) + { + if (key) + { + *key = this->current->key; + } + if (value) + { + *value = this->current->value; + } + this->count--; + return TRUE; + } + this->row++; + } + return FALSE; +} + +METHOD(hashtable_t, create_enumerator, enumerator_t*, + private_hashtable_t *this) +{ + private_enumerator_t *enumerator; + + INIT(enumerator, + .enumerator = { + .enumerate = (void*)_enumerate, + .destroy = (void*)free, + }, + .table = this, + .count = this->count, + ); + + return &enumerator->enumerator; +} + +METHOD(hashtable_t, destroy, void, + private_hashtable_t *this) +{ + pair_t *pair, *next; + u_int row; + + for (row = 0; row < this->capacity; row++) + { + pair = this->table[row]; + while (pair) + { + next = pair->next; + free(pair); + pair = next; + } + } + free(this->table); + free(this); +} + +/* + * Described in header. + */ +hashtable_t *hashtable_create(hashtable_hash_t hash, hashtable_equals_t equals, + u_int capacity) +{ + private_hashtable_t *this; + + INIT(this, + .public = { + .put = _put, + .get = _get, + .get_match = _get_match, + .remove = _remove_, + .remove_at = (void*)_remove_at, + .get_count = _get_count, + .create_enumerator = _create_enumerator, + .destroy = _destroy, + }, + .hash = hash, + .equals = equals, + ); + + init_hashtable(this, capacity); + + return &this->public; +} + |