Browse Source

Add a data-invariant linear-search map structure

I'm going to use this for looking op keys server-side for ntor.
Nick Mathewson 11 years ago
parent
commit
cfab9f0755
3 changed files with 131 additions and 0 deletions
  1. 72 0
      src/common/di_ops.c
  2. 14 0
      src/common/di_ops.h
  3. 45 0
      src/test/test_containers.c

+ 72 - 0
src/common/di_ops.c

@@ -8,6 +8,8 @@
 
 #include "orconfig.h"
 #include "di_ops.h"
+#include "torlog.h"
+#include "util.h"
 
 /**
  * Timing-safe version of memcmp.  As memcmp, compare the <b>sz</b> bytes at
@@ -131,3 +133,73 @@ tor_memeq(const void *a, const void *b, size_t sz)
   return 1 & ((any_difference - 1) >> 8);
 }
 
+/* Implement di_digest256_map_t as a linked list of entries. */
+struct di_digest256_map_t {
+  struct di_digest256_map_t *next;
+  uint8_t key[32];
+  void *val;
+};
+
+/** Release all storage held in <b>map</b>, calling free_fn on each value
+ * as we go. */
+void
+dimap_free(di_digest256_map_t *map, dimap_free_fn free_fn)
+{
+  while (map) {
+    di_digest256_map_t *victim = map;
+    map = map->next;
+    if (free_fn)
+      free_fn(victim->val);
+    tor_free(victim);
+  }
+}
+
+/** Adjust the map at *<b>map</b>, adding an entry for <b>key</b> ->
+ * <b>val</b>, where <b>key</b> is a DIGEST256_LEN-byte key.
+ *
+ * The caller MUST NOT add a key that already appears in the map.
+ */
+void
+dimap_add_entry(di_digest256_map_t **map,
+                const uint8_t *key, void *val)
+{
+  di_digest256_map_t *new_ent;
+  {
+    void *old_val = dimap_search(*map, key, NULL);
+    tor_assert(! old_val);
+    tor_assert(val);
+  }
+  new_ent = tor_malloc_zero(sizeof(di_digest256_map_t));
+  new_ent->next = *map;
+  memcpy(new_ent->key, key, 32);
+  new_ent->val = val;
+  *map = new_ent;
+}
+
+/** Search the map at <b>map</b> for an entry whose key is <b>key</b> (a
+ * DIGEST256_LEN-byte key) returning the corresponding value if we found one,
+ * and returning <b>dflt_val</b> if the key wasn't found.
+ *
+ * This operation takes an amount of time dependent only on the length of
+ * <b>map</b>, not on the position or presence of <b>key</b> within <b>map</b>.
+ */
+void *
+dimap_search(const di_digest256_map_t *map, const uint8_t *key,
+             void *dflt_val)
+{
+  uintptr_t result = (uintptr_t)dflt_val;
+
+  while (map) {
+    uintptr_t r = (uintptr_t) tor_memeq(map->key, key, 32);
+    r -= 1; /* Now r is (uintptr_t)-1 if memeq returned false, and
+             * 0 if memeq returned true. */
+
+    result &= r;
+    result |= ((uintptr_t)(map->val)) & ~r;
+
+    map = map->next;
+  }
+
+  return (void *)result;
+}
+

+ 14 - 0
src/common/di_ops.h

@@ -27,5 +27,19 @@ int tor_memeq(const void *a, const void *b, size_t sz);
 #define fast_memeq(a,b,c)  (0==memcmp((a),(b),(c)))
 #define fast_memneq(a,b,c) (0!=memcmp((a),(b),(c)))
 
+/** A type for a map from DIGEST256_LEN-byte blobs to void*, such that
+ * data lookups take an amount of time proportional only to the size
+ * of the map, and not to the position or presence of the item in the map.
+ *
+ * Not efficient for large maps! */
+typedef struct di_digest256_map_t di_digest256_map_t;
+typedef void (*dimap_free_fn)(void *);
+
+void dimap_free(di_digest256_map_t *map, dimap_free_fn free_fn);
+void dimap_add_entry(di_digest256_map_t **map,
+                     const uint8_t *key, void *val);
+void *dimap_search(const di_digest256_map_t *map, const uint8_t *key,
+                   void *dflt_val);
+
 #endif
 

+ 45 - 0
src/test/test_containers.c

@@ -782,6 +782,50 @@ test_container_order_functions(void)
   ;
 }
 
+static void
+test_di_map(void *arg)
+{
+  di_digest256_map_t *map = NULL;
+  const uint8_t key1[] = "In view of the fact that it was ";
+  const uint8_t key2[] = "superficially convincing, being ";
+  const uint8_t key3[] = "properly enciphered in a one-tim";
+  const uint8_t key4[] = "e cipher scheduled for use today";
+  char *v1 = tor_strdup(", it came close to causing a disaster...");
+  char *v2 = tor_strdup("I regret to have to advise you that the mission");
+  char *v3 = tor_strdup("was actually initiated...");
+  /* -- John Brunner, _The Shockwave Rider_ */
+
+  (void)arg;
+
+  /* Try searching on an empty map. */
+  tt_ptr_op(NULL, ==, dimap_search(map, key1, NULL));
+  tt_ptr_op(NULL, ==, dimap_search(map, key2, NULL));
+  tt_ptr_op(v3, ==, dimap_search(map, key2, v3));
+  dimap_free(map, NULL);
+  map = NULL;
+
+  /* Add a single entry. */
+  dimap_add_entry(&map, key1, v1);
+  tt_ptr_op(NULL, ==, dimap_search(map, key2, NULL));
+  tt_ptr_op(v3, ==, dimap_search(map, key2, v3));
+  tt_ptr_op(v1, ==, dimap_search(map, key1, NULL));
+
+  /* Now try it with three entries in the map. */
+  dimap_add_entry(&map, key2, v2);
+  dimap_add_entry(&map, key3, v3);
+  tt_ptr_op(v1, ==, dimap_search(map, key1, NULL));
+  tt_ptr_op(v2, ==, dimap_search(map, key2, NULL));
+  tt_ptr_op(v3, ==, dimap_search(map, key3, NULL));
+  tt_ptr_op(NULL, ==, dimap_search(map, key4, NULL));
+  tt_ptr_op(v1, ==, dimap_search(map, key4, v1));
+
+ done:
+  tor_free(v1);
+  tor_free(v2);
+  tor_free(v3);
+  dimap_free(map, NULL);
+}
+
 #define CONTAINER_LEGACY(name)                                          \
   { #name, legacy_test_helper, 0, &legacy_setup, test_container_ ## name }
 
@@ -796,6 +840,7 @@ struct testcase_t container_tests[] = {
   CONTAINER_LEGACY(strmap),
   CONTAINER_LEGACY(pqueue),
   CONTAINER_LEGACY(order_functions),
+  { "di_map", test_di_map, 0, NULL, NULL },
   END_OF_TESTCASES
 };