--- /dev/null
+/**
+ * \file
+ * <!--
+ * This file is part of BeRTOS.
+ *
+ * Bertos 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2004, 2008 Develer S.r.l. (http://www.develer.com/)
+ * Copyright 2004 Giovanni Bajo
+ * -->
+ *
+ * \brief Portable hash table implementation
+ *
+ * Some rationales of our choices in implementation:
+ *
+ * \li For embedded systems, it is vital to allocate the table in static memory. To do
+ * so, it is necessary to expose the \c HashNode and \c HashTable structures in the header file.
+ * Nevertheless, they should be used as opaque types (that is, the users should not
+ * access the structure fields directly).
+ *
+ * \li To statically allocate the structures, a macro is provided. With this macro, we
+ * are hiding completely \c HashNode to the user (who only manipulates \c HashTable). Without
+ * the macro, the user would have had to define both the \c HashNode and the \c HashTable
+ * manually, and pass both of them to \c ht_init() (which would have created the link between
+ * the two). Instead, the link is created with a literal initialization.
+ *
+ * \li The hash table is created as power of two to remove the divisions from the code.
+ * Of course, hash functions work at their best when the table size is a prime number.
+ * When calculating the modulus to convert the hash value to an index, the actual operation
+ * becomes a bitwise AND: this is fast, but truncates the value losing bits. Thus, the higher
+ * bits are first "merged" with the lower bits through some XOR operations (see the last line of
+ * \c calc_hash()).
+ *
+ * \li To minimize the memory occupation, there is no flag to set for the empty node. An
+ * empty node is recognized by its data pointer set to NULL. It is then invalid to store
+ * NULL as data pointer in the table.
+ *
+ * \li The visiting interface through iterators is implemented with pass-by-value semantic.
+ * While this is overkill for medium-to-stupid compilers, it is the best designed from an
+ * user point of view. Moreover, being totally inlined (defined completely in the header),
+ * even a stupid compiler should be able to perform basic optimizations on it.
+ * We thought about using a pass-by-pointer semantic but it was much more awful to use, and
+ * the compiler is then forced to spill everything to the stack (unless it is *very* smart).
+ *
+ * \li The current implementation allows to either store the key internally (that is, copy
+ * the key within the hash table) or keep it external (that is, a hook is used to extract
+ * the key from the data in the node). The former is more memory-hungry of course, as it
+ * allocated static space to store the key copies. The overhead to keep both methods at
+ * the same time is minimal:
+ * <ul>
+ * <li>There is a run-time check in node_get_key which is execute per each node visited.</li>
+ * <li>Theoretically, there is no memory overhead. In practice, there were no
+ * flags in \c struct HashTable till now, so we had to add a first bit flag, but the
+ * overhead will disappear if a second flag is added for a different reason later.</li>
+ * <li>There is a little interface overhead, since we have two different versions of
+ * \c ht_insert(), one with the key passed as parameter and one without, but in
+ * the common case (external keys) both can be used.</li>
+ * </ul>
+ *
+ * \version $Id$
+ * \author Giovanni Bajo <rasky@develer.com>
+ */
+
+#include "hashtable.h"
+#include <cfg/debug.h>
+#include <cfg/compiler.h>
+#include <cfg/macros.h> //ROTL(), ROTR();
+
+#include <string.h>
+
+
+typedef const void** HashNodePtr;
+#define NODE_EMPTY(node) (!*(node))
+#define HT_HAS_INTERNAL_KEY(ht) (CONFIG_HT_OPTIONAL_INTERNAL_KEY && ht->flags.key_internal)
+
+/** For hash tables with internal keys, compute the pointer to the internal key for a given \a node. */
+INLINE uint8_t *key_internal_get_ptr(struct HashTable *ht, HashNodePtr node)
+{
+ uint8_t* key_buf = ht->key_data.mem;
+ size_t index;
+
+ // Compute the index of the node and use it to move within the whole key buffer
+ index = node - &ht->mem[0];
+ ASSERT(index < (size_t)(1 << ht->max_elts_log2));
+ key_buf += index * (INTERNAL_KEY_MAX_LENGTH + 1);
+
+ return key_buf;
+}
+
+
+INLINE void node_get_key(struct HashTable* ht, HashNodePtr node, const void** key, uint8_t* key_length)
+{
+ if (HT_HAS_INTERNAL_KEY(ht))
+ {
+ uint8_t* k = key_internal_get_ptr(ht, node);
+
+ // Key has its length stored in the first byte
+ *key_length = *k++;
+ *key = k;
+ }
+ else
+ *key = ht->key_data.hook(*node, key_length);
+}
+
+
+INLINE bool node_key_match(struct HashTable* ht, HashNodePtr node, const void* key, uint8_t key_length)
+{
+ const void* key2;
+ uint8_t key2_length;
+
+ node_get_key(ht, node, &key2, &key2_length);
+
+ return (key_length == key2_length && memcmp(key, key2, key_length) == 0);
+}
+
+
+static uint16_t calc_hash(const void* _key, uint8_t key_length)
+{
+ const char* key = (const char*)_key;
+ uint16_t hash = key_length;
+ int i;
+ int len = (int)key_length;
+
+ for (i = 0; i < len; ++i)
+ hash = ROTL(hash, 4) ^ key[i];
+
+ return hash ^ (hash >> 6) ^ (hash >> 13);
+}
+
+
+static HashNodePtr perform_lookup(struct HashTable* ht,
+ const void* key, uint8_t key_length)
+{
+ uint16_t hash = calc_hash(key, key_length);
+ uint16_t mask = ((1 << ht->max_elts_log2) - 1);
+ uint16_t index = hash & mask;
+ uint16_t first_index = index;
+ uint16_t step;
+ HashNodePtr node;
+
+ // Fast-path optimization: we check immediately if the current node
+ // is the one we were looking for, so we save the computation of the
+ // increment step in the common case.
+ node = &ht->mem[index];
+ if (NODE_EMPTY(node)
+ || node_key_match(ht, node, key, key_length))
+ return node;
+
+ // Increment while going through the hash table in case of collision.
+ // This implements the double-hash technique: we use the higher part
+ // of the hash as a step increment instead of just going to the next
+ // element, to minimize the collisions.
+ // Notice that the number must be odd to be sure that the whole table
+ // is traversed. Actually MCD(table_size, step) must be 1, but
+ // table_size is always a power of 2, so we just ensure that step is
+ // never a multiple of 2.
+ step = (ROTR(hash, ht->max_elts_log2) & mask) | 1;
+
+ do
+ {
+ index += step;
+ index &= mask;
+
+ node = &ht->mem[index];
+ if (NODE_EMPTY(node)
+ || node_key_match(ht, node, key, key_length))
+ return node;
+
+ // The check is done after the key compare. This actually causes
+ // one more compare in the case the table is full (since the first
+ // element was compared at the very start, and then at the end),
+ // but it makes faster the common path where we enter this loop
+ // for the first time, and index will not match first_index for
+ // sure.
+ } while (index != first_index);
+
+ return NULL;
+}
+
+
+void ht_init(struct HashTable* ht)
+{
+ memset(ht->mem, 0, sizeof(ht->mem[0]) * (1 << ht->max_elts_log2));
+}
+
+
+static bool insert(struct HashTable* ht, const void* key, uint8_t key_length, const void* data)
+{
+ HashNodePtr node;
+
+ if (!data)
+ return false;
+
+ if (HT_HAS_INTERNAL_KEY(ht))
+ key_length = MIN(key_length, (uint8_t)INTERNAL_KEY_MAX_LENGTH);
+
+ node = perform_lookup(ht, key, key_length);
+ if (!node)
+ return false;
+
+ if (HT_HAS_INTERNAL_KEY(ht))
+ {
+ uint8_t* k = key_internal_get_ptr(ht, node);
+ *k++ = key_length;
+ memcpy(k, key, key_length);
+ }
+
+ *node = data;
+ return true;
+}
+
+
+bool ht_insert_with_key(struct HashTable* ht, const void* key, uint8_t key_length, const void* data)
+{
+#ifdef _DEBUG
+ if (!HT_HAS_INTERNAL_KEY(ht))
+ {
+ // Construct a fake node and use it to match the key
+ HashNodePtr node = &data;
+ if (!node_key_match(ht, node, key, key_length))
+ {
+ ASSERT2(0, "parameter key is different from the external key");
+ return false;
+ }
+ }
+#endif
+
+ return insert(ht, key, key_length, data);
+}
+
+
+bool ht_insert(struct HashTable* ht, const void* data)
+{
+ const void* key;
+ uint8_t key_length;
+
+#ifdef _DEBUG
+ if (HT_HAS_INTERNAL_KEY(ht))
+ {
+ ASSERT("parameter cannot be a hash table with internal keys - use ht_insert_with_key()"
+ && 0);
+ return false;
+ }
+#endif
+
+ key = ht->key_data.hook(data, &key_length);
+
+ return insert(ht, key, key_length, data);
+}
+
+
+const void* ht_find(struct HashTable* ht, const void* key, uint8_t key_length)
+{
+ HashNodePtr node;
+
+ if (HT_HAS_INTERNAL_KEY(ht))
+ key_length = MIN(key_length, (uint8_t)INTERNAL_KEY_MAX_LENGTH);
+
+ node = perform_lookup(ht, key, key_length);
+
+ if (!node || NODE_EMPTY(node))
+ return NULL;
+
+ return *node;
+}
+
+
+#if 0
+
+#include <stdlib.h>
+
+bool ht_test(void);
+
+static const void* test_get_key(const void* ptr, uint8_t* length)
+{
+ const char* s = ptr;
+ *length = strlen(s);
+ return s;
+}
+
+#define NUM_ELEMENTS 256
+DECLARE_HASHTABLE_STATIC(test1, 256, test_get_key);
+DECLARE_HASHTABLE_INTERNALKEY_STATIC(test2, 256);
+
+static char data[NUM_ELEMENTS][10];
+static char keydomain[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+static bool single_test(void)
+{
+ int i;
+
+ ht_init(&test1);
+ ht_init(&test2);
+
+ for (i=0;i<NUM_ELEMENTS;i++)
+ {
+ int k;
+ int klen;
+
+ do
+ {
+ klen = (rand() % 8) + 1;
+ for (k=0;k<klen;k++)
+ data[i][k] = keydomain[rand() % (sizeof(keydomain)-1)];
+ data[i][k]=0;
+ } while (ht_find_str(&test1, data[i]) != NULL);
+
+ ASSERT(ht_insert(&test1, data[i]));
+ ASSERT(ht_insert_str(&test2, data[i], data[i]));
+ }
+
+ for (i=0;i<NUM_ELEMENTS;i++)
+ {
+ char *found1, *found2;
+
+ found1 = (char*)ht_find_str(&test1, data[i]);
+ if (strcmp(found1, data[i]) != 0)
+ {
+ ASSERT(strcmp(found1,data[i]) == 0);
+ return false;
+ }
+
+ found2 = (char*)ht_find_str(&test2, data[i]);
+ if (strcmp(found2, data[i]) != 0)
+ {
+ ASSERT(strcmp(found2,data[i]) == 0);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static uint16_t rand_seeds[] = { 1, 42, 666, 0xDEAD, 0xBEEF, 0x1337, 0xB00B };
+
+bool ht_test(void)
+{
+ int i;
+
+ for (i=0;i<countof(rand_seeds);++i)
+ {
+ srand(rand_seeds[i]);
+ if (!single_test())
+ {
+ kprintf("ht_test failed\n");
+ return false;
+ }
+ }
+
+ kprintf("ht_test successful\n");
+ return true;
+}
+
+#endif