Commit cfab9f07 authored by Nick Mathewson's avatar Nick Mathewson 🥔
Browse files

Add a data-invariant linear-search map structure

I'm going to use this for looking op keys server-side for ntor.
parent 014e6905
Loading
Loading
Loading
Loading
+72 −0
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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
};