diff --git a/kernel-src/include/hashtable.h b/kernel-src/include/hashtable.h new file mode 100644 index 00000000..21a5c0c5 --- /dev/null +++ b/kernel-src/include/hashtable.h @@ -0,0 +1,32 @@ +#ifndef _HASHTABLE_H +#define _HASHTABLE_H + +#include +#include +#include + +typedef struct hashentry_t { + struct hashentry_t *next; + struct hashentry_t *prev; + uintmax_t hash; + size_t keysize; + void *key; + void *value; +} hashentry_t; + +typedef struct { + size_t capacity; + hashentry_t **entries; +} hashtable_t; + +int hashtable_init(hashtable_t *table, size_t size); +int hashtable_set(hashtable_t *table, void *value, void *key, size_t keysize, bool allocate); +int hashtable_get(hashtable_t *table, void **value, void *key, size_t keysize); +int hashtable_remove(hashtable_t *table, void *key, size_t keysize); +int hashtable_destroy(hashtable_t *table); + +#define HASHTABLE_FOREACH(table) \ + for (uintmax_t toffset = 0; toffset < table->capacity; ++toffset) \ + for (hashentry_t *entry = table->entries[toffset]; entry != NULL; entry = entry->next) + +#endif diff --git a/kernel-src/lib/hashtable.c b/kernel-src/lib/hashtable.c new file mode 100644 index 00000000..e33a5b25 --- /dev/null +++ b/kernel-src/lib/hashtable.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include + +#define FNV1PRIME 0x100000001b3ull +#define FNV1OFFSET 0xcbf29ce484222325ull + +static scache_t *hashentrycache; + +// FNV-1a hash implementation +static uint64_t hashbuffer(void *buffer, size_t size) { + uint8_t *ptr = buffer; + uint8_t *top = ptr + size; + uint64_t h = FNV1OFFSET; + + while (ptr < top) { + h ^= *ptr++; + h *= FNV1PRIME; + } + + return h; +} +static hashentry_t *getentry(hashtable_t *table, void *key, size_t keysize, uintmax_t hash) { + uintmax_t tableoffset = hash % table->capacity; + + hashentry_t *entry = table->entries[tableoffset]; + + while (entry) { + if (entry->keysize == keysize && entry->hash == hash && memcmp(entry->key, key, keysize) == 0) + break; + entry = entry->next; + } + + return entry; +} + +int hashtable_set(hashtable_t *table, void *value, void *key, size_t keysize, bool allocate) { + uintmax_t hash = hashbuffer(key, keysize); + + hashentry_t *entry = getentry(table, key, keysize, hash); + + if (entry) { + entry->value = value; + return 0; + } else if (allocate) { + uintmax_t tableoffset = hash % table->capacity; + + entry = slab_allocate(hashentrycache); + if (entry == NULL) + return ENOMEM; + entry->key = alloc(keysize); + if (entry->key == NULL) { + slab_free(hashentrycache, entry); + return ENOMEM; + } + + memcpy(entry->key, key, keysize); + entry->prev = NULL; + entry->next = table->entries[tableoffset]; + entry->value = value; + entry->keysize = keysize; + entry->hash = hash; + table->entries[tableoffset] = entry; + return 0; + } + + return ENOENT; +} + +int hashtable_get(hashtable_t *table, void **value, void *key, size_t keysize) { + uintmax_t hash = hashbuffer(key, keysize); + + hashentry_t *entry = getentry(table, key, keysize, hash); + + if (entry == NULL) + return ENOENT; + + *value = entry->value; + return 0; +} + +int hashtable_remove(hashtable_t *table, void *key, size_t keysize) { + uintmax_t hash = hashbuffer(key, keysize); + + hashentry_t *entry = getentry(table, key, keysize, hash); + + if (entry == NULL) + return ENOENT; + + if (entry->prev) + entry->prev->next = entry->next; + + if (entry->next) + entry->next->prev = entry->prev; + + free(entry->key); + slab_free(hashentrycache, entry); + + return 0; +} + +int hashtable_destroy(hashtable_t *table) { + // as to avoid using freed data, keep a copy of the entry + hashentry_t entrysave; + HASHTABLE_FOREACH(table) { + entrysave = *entry; + free(entry->key); + slab_free(hashentrycache, entry); + entry = &entrysave; + } + return 0; +} + +int hashtable_init(hashtable_t *table, size_t size) { + // make sure the cache is initialised + if (hashentrycache == NULL) { + hashentrycache = slab_newcache(sizeof(hashentry_t), 0, NULL, NULL); + if (hashentrycache == NULL) + return ENOMEM; + } + + table->capacity = size; + table->entries = alloc(size * sizeof(hashentry_t *)); + if (table->entries == NULL) + return ENOMEM; + + return 0; +}