[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] [tor/master] Merge remote branch 'origin/maint-0.2.1' into maint-0.2.2



commit 1393985768d760e11e45faabb537d28248306e8b
Merge: 9d13346 b97b0ef
Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Date:   Sat Jan 15 13:25:13 2011 -0500

    Merge remote branch 'origin/maint-0.2.1' into maint-0.2.2
    
    Conflicts:
    	src/or/routerparse.c
    	src/or/test.c

 changes/bug2352      |    6 ++++++
 src/common/crypto.c  |   14 ++++++++------
 src/common/crypto.h  |    2 +-
 src/or/routerparse.c |   16 ++++++++++++----
 src/test/test_dir.c  |    6 +++---
 5 files changed, 30 insertions(+), 14 deletions(-)

diff --combined src/common/crypto.c
index 15b5818,208e1c5..1d12a9d
--- a/src/common/crypto.c
+++ b/src/common/crypto.c
@@@ -27,7 -27,6 +27,7 @@@
  #include <openssl/rsa.h>
  #include <openssl/pem.h>
  #include <openssl/evp.h>
 +#include <openssl/engine.h>
  #include <openssl/rand.h>
  #include <openssl/opensslv.h>
  #include <openssl/bn.h>
@@@ -50,9 -49,9 +50,9 @@@
  
  #define CRYPTO_PRIVATE
  #include "crypto.h"
 -#include "log.h"
 +#include "../common/torlog.h"
  #include "aes.h"
 -#include "util.h"
 +#include "../common/util.h"
  #include "container.h"
  #include "compat.h"
  
@@@ -62,33 -61,6 +62,33 @@@
  
  #include <openssl/engine.h>
  
 +#ifdef ANDROID
 +/* Android's OpenSSL seems to have removed all of its Engine support. */
 +#define DISABLE_ENGINES
 +#endif
 +
 +#if OPENSSL_VERSION_NUMBER < 0x00908000l
 +/* On OpenSSL versions before 0.9.8, there is no working SHA256
 + * implementation, so we use Tom St Denis's nice speedy one, slightly adapted
 + * to our needs */
 +#define SHA256_CTX sha256_state
 +#define SHA256_Init sha256_init
 +#define SHA256_Update sha256_process
 +#define LTC_ARGCHK(x) tor_assert(x)
 +#include "sha256.c"
 +#define SHA256_Final(a,b) sha256_done(b,a)
 +
 +static unsigned char *
 +SHA256(const unsigned char *m, size_t len, unsigned char *d)
 +{
 +  SHA256_CTX ctx;
 +  SHA256_Init(&ctx);
 +  SHA256_Update(&ctx, m, len);
 +  SHA256_Final(d, &ctx);
 +  return d;
 +}
 +#endif
 +
  /** Macro: is k a valid RSA public or private key? */
  #define PUBLIC_KEY_OK(k) ((k) && (k)->key && (k)->key->n)
  /** Macro: is k a valid RSA private key? */
@@@ -122,7 -94,7 +122,7 @@@ struct crypto_dh_env_t 
  };
  
  static int setup_openssl_threading(void);
 -static int tor_check_dh_key(BIGNUM *bn);
 +static int tor_check_dh_key(int severity, BIGNUM *bn);
  
  /** Return the number of bytes added by padding method <b>padding</b>.
   */
@@@ -179,7 -151,6 +179,7 @@@ crypto_log_errors(int severity, const c
    }
  }
  
 +#ifndef DISABLE_ENGINES
  /** Log any OpenSSL engines we're using at NOTICE. */
  static void
  log_engine(const char *fn, ENGINE *e)
@@@ -194,82 -165,37 +194,82 @@@
      log(LOG_INFO, LD_CRYPTO, "Using default implementation for %s", fn);
    }
  }
 +#endif
 +
 +#ifndef DISABLE_ENGINES
 +/** Try to load an engine in a shared library via fully qualified path.
 + */
 +static ENGINE *
 +try_load_engine(const char *path, const char *engine)
 +{
 +  ENGINE *e = ENGINE_by_id("dynamic");
 +  if (e) {
 +    if (!ENGINE_ctrl_cmd_string(e, "ID", engine, 0) ||
 +        !ENGINE_ctrl_cmd_string(e, "DIR_LOAD", "2", 0) ||
 +        !ENGINE_ctrl_cmd_string(e, "DIR_ADD", path, 0) ||
 +        !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) {
 +      ENGINE_free(e);
 +      e = NULL;
 +    }
 +  }
 +  return e;
 +}
 +#endif
  
  /** Initialize the crypto library.  Return 0 on success, -1 on failure.
   */
  int
 -crypto_global_init(int useAccel)
 +crypto_global_init(int useAccel, const char *accelName, const char *accelDir)
  {
    if (!_crypto_global_initialized) {
      ERR_load_crypto_strings();
      OpenSSL_add_all_algorithms();
      _crypto_global_initialized = 1;
      setup_openssl_threading();
 -    /* XXX the below is a bug, since we can't know if we're supposed
 -     * to be using hardware acceleration or not. we should arrange
 -     * for this function to be called before init_keys. But make it
 -     * not complain loudly, at least until we make acceleration work. */
 -    if (useAccel < 0) {
 -      log_info(LD_CRYPTO, "Initializing OpenSSL via tor_tls_init().");
 -    }
      if (useAccel > 0) {
 +#ifdef DISABLE_ENGINES
 +      (void)accelName;
 +      (void)accelDir;
 +      log_warn(LD_CRYPTO, "No OpenSSL hardware acceleration support enabled.");
 +#else
 +      ENGINE *e = NULL;
 +
        log_info(LD_CRYPTO, "Initializing OpenSSL engine support.");
        ENGINE_load_builtin_engines();
 -      if (!ENGINE_register_all_complete())
 -        return -1;
 -
 -      /* XXXX make sure this isn't leaking. */
 +      ENGINE_register_all_complete();
 +
 +      if (accelName) {
 +        if (accelDir) {
 +          log_info(LD_CRYPTO, "Trying to load dynamic OpenSSL engine \"%s\""
 +                   " via path \"%s\".", accelName, accelDir);
 +          e = try_load_engine(accelName, accelDir);
 +        } else {
 +          log_info(LD_CRYPTO, "Initializing dynamic OpenSSL engine \"%s\""
 +                   " acceleration support.", accelName);
 +          e = ENGINE_by_id(accelName);
 +        }
 +        if (!e) {
 +          log_warn(LD_CRYPTO, "Unable to load dynamic OpenSSL engine \"%s\".",
 +                   accelName);
 +        } else {
 +          log_info(LD_CRYPTO, "Loaded dynamic OpenSSL engine \"%s\".",
 +                   accelName);
 +        }
 +      }
 +      if (e) {
 +        log_info(LD_CRYPTO, "Loaded OpenSSL hardware acceleration engine,"
 +                 " setting default ciphers.");
 +        ENGINE_set_default(e, ENGINE_METHOD_ALL);
 +      }
        log_engine("RSA", ENGINE_get_default_RSA());
        log_engine("DH", ENGINE_get_default_DH());
        log_engine("RAND", ENGINE_get_default_RAND());
        log_engine("SHA1", ENGINE_get_digest_engine(NID_sha1));
        log_engine("3DES", ENGINE_get_cipher_engine(NID_des_ede3_ecb));
        log_engine("AES", ENGINE_get_cipher_engine(NID_aes_128_ecb));
 +#endif
 +    } else {
 +      log_info(LD_CRYPTO, "NOT using OpenSSL engine support.");
      }
      return crypto_seed_rng(1);
    }
@@@ -291,11 -217,7 +291,11 @@@ crypto_global_cleanup(void
    EVP_cleanup();
    ERR_remove_state(0);
    ERR_free_strings();
 +
 +#ifndef DISABLE_ENGINES
    ENGINE_cleanup();
 +#endif
 +
    CONF_modules_unload(1);
    CRYPTO_cleanup_all_ex_data();
  #ifdef TOR_IS_MULTITHREADED
@@@ -337,8 -259,7 +337,8 @@@ _crypto_new_pk_env_evp_pkey(EVP_PKEY *p
    return _crypto_new_pk_env_rsa(rsa);
  }
  
 -/** Helper, used by tor-checkkey.c.  Return the RSA from a crypto_pk_env_t. */
 +/** Helper, used by tor-checkkey.c and tor-gencert.c.  Return the RSA from a
 + * crypto_pk_env_t. */
  RSA *
  _crypto_pk_env_get_rsa(crypto_pk_env_t *env)
  {
@@@ -400,12 -321,10 +400,12 @@@ crypto_new_pk_env(void
  void
  crypto_free_pk_env(crypto_pk_env_t *env)
  {
 -  tor_assert(env);
 +  if (!env)
 +    return;
  
    if (--env->refs > 0)
      return;
 +  tor_assert(env->refs == 0);
  
    if (env->key)
      RSA_free(env->key);
@@@ -428,7 -347,10 +428,7 @@@ crypto_create_init_cipher(const char *k
      return NULL;
    }
  
 -  if (crypto_cipher_set_key(crypto, key)) {
 -    crypto_log_errors(LOG_WARN, "setting symmetric key");
 -    goto error;
 -  }
 +  crypto_cipher_set_key(crypto, key);
  
    if (encrypt_mode)
      r = crypto_cipher_encrypt_init_cipher(crypto);
@@@ -462,8 -384,7 +462,8 @@@ crypto_new_cipher_env(void
  void
  crypto_free_cipher_env(crypto_cipher_env_t *env)
  {
 -  tor_assert(env);
 +  if (!env)
 +    return;
  
    tor_assert(env->cipher);
    aes_free_cipher(env->cipher);
@@@ -473,11 -394,11 +473,11 @@@
  
  /* public key crypto */
  
 -/** Generate a new public/private keypair in <b>env</b>.  Return 0 on
 - * success, -1 on failure.
 +/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>.
 + * Return 0 on success, -1 on failure.
   */
  int
 -crypto_pk_generate_key(crypto_pk_env_t *env)
 +crypto_pk_generate_key_with_bits(crypto_pk_env_t *env, int bits)
  {
    tor_assert(env);
  
@@@ -485,7 -406,7 +485,7 @@@
      RSA_free(env->key);
  #if OPENSSL_VERSION_NUMBER < 0x00908000l
    /* In OpenSSL 0.9.7, RSA_generate_key is all we have. */
 -  env->key = RSA_generate_key(PK_BYTES*8,65537, NULL, NULL);
 +  env->key = RSA_generate_key(bits, 65537, NULL, NULL);
  #else
    /* In OpenSSL 0.9.8, RSA_generate_key is deprecated. */
    {
@@@ -498,7 -419,7 +498,7 @@@
      r = RSA_new();
      if (!r)
        goto done;
 -    if (RSA_generate_key_ex(r, PK_BYTES*8, e, NULL) == -1)
 +    if (RSA_generate_key_ex(r, bits, e, NULL) == -1)
        goto done;
  
      env->key = r;
@@@ -518,21 -439,23 +518,23 @@@
    return 0;
  }
  
- /** Read a PEM-encoded private key from the string <b>s</b> into <b>env</b>.
-  * Return 0 on success, -1 on failure.
+ /** Read a PEM-encoded private key from the <b>len</b>-byte string <b>s</b>
+  * into <b>env</b>.  Return 0 on success, -1 on failure.  If len is -1,
+  * the string is nul-terminated.
   */
  /* Used here, and used for testing. */
  int
  crypto_pk_read_private_key_from_string(crypto_pk_env_t *env,
-                                        const char *s)
+                                        const char *s, ssize_t len)
  {
    BIO *b;
  
    tor_assert(env);
    tor_assert(s);
+   tor_assert(len < INT_MAX && len < SIZE_T_CEILING);
  
-   /* Create a read-only memory BIO, backed by the NUL-terminated string 's' */
-   b = BIO_new_mem_buf((char*)s, -1);
+   /* Create a read-only memory BIO, backed by the string 's' */
+   b = BIO_new_mem_buf((char*)s, (int)len);
  
    if (env->key)
      RSA_free(env->key);
@@@ -566,7 -489,7 +568,7 @@@ crypto_pk_read_private_key_from_filenam
    }
  
    /* Try to parse it. */
-   r = crypto_pk_read_private_key_from_string(env, contents);
+   r = crypto_pk_read_private_key_from_string(env, contents, -1);
    tor_free(contents);
    if (r)
      return -1; /* read_private_key_from_string already warned, so we don't.*/
@@@ -780,25 -703,14 +782,25 @@@ crypto_pk_env_t 
  crypto_pk_copy_full(crypto_pk_env_t *env)
  {
    RSA *new_key;
 +  int privatekey = 0;
    tor_assert(env);
    tor_assert(env->key);
  
    if (PRIVATE_KEY_OK(env)) {
      new_key = RSAPrivateKey_dup(env->key);
 +    privatekey = 1;
    } else {
      new_key = RSAPublicKey_dup(env->key);
    }
 +  if (!new_key) {
 +    log_err(LD_CRYPTO, "Unable to duplicate a %s key: openssl failed.",
 +            privatekey?"private":"public");
 +    crypto_log_errors(LOG_ERR,
 +                      privatekey ? "Duplicating a private key" :
 +                      "Duplicating a public key");
 +    tor_fragile_assert();
 +    return NULL;
 +  }
  
    return _crypto_new_pk_env_rsa(new_key);
  }
@@@ -1299,14 -1211,19 +1301,14 @@@ crypto_cipher_generate_key(crypto_ciphe
  
  /** Set the symmetric key for the cipher in <b>env</b> to the first
   * CIPHER_KEY_LEN bytes of <b>key</b>. Does not initialize the cipher.
 - * Return 0 on success, -1 on failure.
   */
 -int
 +void
  crypto_cipher_set_key(crypto_cipher_env_t *env, const char *key)
  {
    tor_assert(env);
    tor_assert(key);
  
 -  if (!env->key)
 -    return -1;
 -
    memcpy(env->key, key, CIPHER_KEY_LEN);
 -  return 0;
  }
  
  /** Generate an initialization vector for our AES-CTR cipher; store it
@@@ -1483,69 -1400,9 +1485,69 @@@ crypto_digest(char *digest, const char 
    return (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL);
  }
  
 +int
 +crypto_digest256(char *digest, const char *m, size_t len,
 +                 digest_algorithm_t algorithm)
 +{
 +  tor_assert(m);
 +  tor_assert(digest);
 +  tor_assert(algorithm == DIGEST_SHA256);
 +  return (SHA256((const unsigned char*)m,len,(unsigned char*)digest) == NULL);
 +}
 +
 +/** Set the digests_t in <b>ds_out</b> to contain every digest on the
 + * <b>len</b> bytes in <b>m</b> that we know how to compute.  Return 0 on
 + * success, -1 on failure. */
 +int
 +crypto_digest_all(digests_t *ds_out, const char *m, size_t len)
 +{
 +  digest_algorithm_t i;
 +  tor_assert(ds_out);
 +  memset(ds_out, 0, sizeof(*ds_out));
 +  if (crypto_digest(ds_out->d[DIGEST_SHA1], m, len) < 0)
 +    return -1;
 +  for (i = DIGEST_SHA256; i < N_DIGEST_ALGORITHMS; ++i) {
 +    if (crypto_digest256(ds_out->d[i], m, len, i) < 0)
 +      return -1;
 +  }
 +  return 0;
 +}
 +
 +/** Return the name of an algorithm, as used in directory documents. */
 +const char *
 +crypto_digest_algorithm_get_name(digest_algorithm_t alg)
 +{
 +  switch (alg) {
 +    case DIGEST_SHA1:
 +      return "sha1";
 +    case DIGEST_SHA256:
 +      return "sha256";
 +    default:
 +      tor_fragile_assert();
 +      return "??unknown_digest??";
 +  }
 +}
 +
 +/** Given the name of a digest algorithm, return its integer value, or -1 if
 + * the name is not recognized. */
 +int
 +crypto_digest_algorithm_parse_name(const char *name)
 +{
 +  if (!strcmp(name, "sha1"))
 +    return DIGEST_SHA1;
 +  else if (!strcmp(name, "sha256"))
 +    return DIGEST_SHA256;
 +  else
 +    return -1;
 +}
 +
  /** Intermediate information about the digest of a stream of data. */
  struct crypto_digest_env_t {
 -  SHA_CTX d;
 +  union {
 +    SHA_CTX sha1;
 +    SHA256_CTX sha2;
 +  } d;
 +  digest_algorithm_t algorithm : 8;
  };
  
  /** Allocate and return a new digest object.
@@@ -1555,19 -1412,7 +1557,19 @@@ crypto_new_digest_env(void
  {
    crypto_digest_env_t *r;
    r = tor_malloc(sizeof(crypto_digest_env_t));
 -  SHA1_Init(&r->d);
 +  SHA1_Init(&r->d.sha1);
 +  r->algorithm = DIGEST_SHA1;
 +  return r;
 +}
 +
 +crypto_digest_env_t *
 +crypto_new_digest256_env(digest_algorithm_t algorithm)
 +{
 +  crypto_digest_env_t *r;
 +  tor_assert(algorithm == DIGEST_SHA256);
 +  r = tor_malloc(sizeof(crypto_digest_env_t));
 +  SHA256_Init(&r->d.sha2);
 +  r->algorithm = algorithm;
    return r;
  }
  
@@@ -1576,8 -1421,6 +1578,8 @@@
  void
  crypto_free_digest_env(crypto_digest_env_t *digest)
  {
 +  if (!digest)
 +    return;
    memset(digest, 0, sizeof(crypto_digest_env_t));
    tor_free(digest);
  }
@@@ -1590,51 -1433,30 +1592,51 @@@ crypto_digest_add_bytes(crypto_digest_e
  {
    tor_assert(digest);
    tor_assert(data);
 -  /* Using the SHA1_*() calls directly means we don't support doing
 -   * SHA1 in hardware. But so far the delay of getting the question
 +  /* Using the SHA*_*() calls directly means we don't support doing
 +   * SHA in hardware. But so far the delay of getting the question
     * to the hardware, and hearing the answer, is likely higher than
     * just doing it ourselves. Hashes are fast.
     */
 -  SHA1_Update(&digest->d, (void*)data, len);
 +  switch (digest->algorithm) {
 +    case DIGEST_SHA1:
 +      SHA1_Update(&digest->d.sha1, (void*)data, len);
 +      break;
 +    case DIGEST_SHA256:
 +      SHA256_Update(&digest->d.sha2, (void*)data, len);
 +      break;
 +    default:
 +      tor_fragile_assert();
 +      break;
 +  }
  }
  
  /** Compute the hash of the data that has been passed to the digest
   * object; write the first out_len bytes of the result to <b>out</b>.
 - * <b>out_len</b> must be \<= DIGEST_LEN.
 + * <b>out_len</b> must be \<= DIGEST256_LEN.
   */
  void
  crypto_digest_get_digest(crypto_digest_env_t *digest,
                           char *out, size_t out_len)
  {
 -  unsigned char r[DIGEST_LEN];
 -  SHA_CTX tmpctx;
 +  unsigned char r[DIGEST256_LEN];
 +  crypto_digest_env_t tmpenv;
    tor_assert(digest);
    tor_assert(out);
 -  tor_assert(out_len <= DIGEST_LEN);
 -  /* memcpy into a temporary ctx, since SHA1_Final clears the context */
 -  memcpy(&tmpctx, &digest->d, sizeof(SHA_CTX));
 -  SHA1_Final(r, &tmpctx);
 +  /* memcpy into a temporary ctx, since SHA*_Final clears the context */
 +  memcpy(&tmpenv, digest, sizeof(crypto_digest_env_t));
 +  switch (digest->algorithm) {
 +    case DIGEST_SHA1:
 +      tor_assert(out_len <= DIGEST_LEN);
 +      SHA1_Final(r, &tmpenv.d.sha1);
 +      break;
 +    case DIGEST_SHA256:
 +      tor_assert(out_len <= DIGEST256_LEN);
 +      SHA256_Final(r, &tmpenv.d.sha2);
 +      break;
 +    default:
 +      tor_fragile_assert();
 +      break;
 +  }
    memcpy(out, r, out_len);
    memset(r, 0, sizeof(r));
  }
@@@ -1770,7 -1592,7 +1772,7 @@@ crypto_dh_generate_public(crypto_dh_env
      crypto_log_errors(LOG_WARN, "generating DH key");
      return -1;
    }
 -  if (tor_check_dh_key(dh->dh->pub_key)<0) {
 +  if (tor_check_dh_key(LOG_WARN, dh->dh->pub_key)<0) {
      log_warn(LD_CRYPTO, "Weird! Our own DH key was invalid.  I guess once-in-"
               "the-universe chances really do happen.  Trying again.");
      /* Free and clear the keys, so OpenSSL will actually try again. */
@@@ -1817,7 -1639,7 +1819,7 @@@ crypto_dh_get_public(crypto_dh_env_t *d
   * See http://www.cl.cam.ac.uk/ftp/users/rja14/psandqs.ps.gz for some tips.
   */
  static int
 -tor_check_dh_key(BIGNUM *bn)
 +tor_check_dh_key(int severity, BIGNUM *bn)
  {
    BIGNUM *x;
    char *s;
@@@ -1828,13 -1650,13 +1830,13 @@@
      init_dh_param();
    BN_set_word(x, 1);
    if (BN_cmp(bn,x)<=0) {
 -    log_warn(LD_CRYPTO, "DH key must be at least 2.");
 +    log_fn(severity, LD_CRYPTO, "DH key must be at least 2.");
      goto err;
    }
    BN_copy(x,dh_param_p);
    BN_sub_word(x, 1);
    if (BN_cmp(bn,x)>=0) {
 -    log_warn(LD_CRYPTO, "DH key must be at most p-2.");
 +    log_fn(severity, LD_CRYPTO, "DH key must be at most p-2.");
      goto err;
    }
    BN_free(x);
@@@ -1842,7 -1664,7 +1844,7 @@@
   err:
    BN_free(x);
    s = BN_bn2hex(bn);
 -  log_warn(LD_CRYPTO, "Rejecting insecure DH key [%s]", s);
 +  log_fn(severity, LD_CRYPTO, "Rejecting insecure DH key [%s]", s);
    OPENSSL_free(s);
    return -1;
  }
@@@ -1860,7 -1682,7 +1862,7 @@@
   * where || is concatenation.)
   */
  ssize_t
 -crypto_dh_compute_secret(crypto_dh_env_t *dh,
 +crypto_dh_compute_secret(int severity, crypto_dh_env_t *dh,
                           const char *pubkey, size_t pubkey_len,
                           char *secret_out, size_t secret_bytes_out)
  {
@@@ -1875,9 -1697,9 +1877,9 @@@
    if (!(pubkey_bn = BN_bin2bn((const unsigned char*)pubkey,
                                (int)pubkey_len, NULL)))
      goto error;
 -  if (tor_check_dh_key(pubkey_bn)<0) {
 +  if (tor_check_dh_key(severity, pubkey_bn)<0) {
      /* Check for invalid public keys. */
 -    log_warn(LD_CRYPTO,"Rejected invalid g^x");
 +    log_fn(severity, LD_CRYPTO,"Rejected invalid g^x");
      goto error;
    }
    secret_tmp = tor_malloc(crypto_dh_get_bytes(dh));
@@@ -1949,8 -1771,7 +1951,8 @@@ crypto_expand_key_material(const char *
  void
  crypto_dh_free(crypto_dh_env_t *dh)
  {
 -  tor_assert(dh);
 +  if (!dh)
 +    return;
    tor_assert(dh->dh);
    DH_free(dh->dh);
    tor_free(dh);
@@@ -1975,14 -1796,6 +1977,14 @@@
      OPENSSL_VERSION_NUMBER <= 0x00907fffl) ||   \
     (OPENSSL_VERSION_NUMBER >= 0x0090803fl))
  
 +static void
 +seed_weak_rng(void)
 +{
 +  unsigned seed;
 +  crypto_rand((void*)&seed, sizeof(seed));
 +  tor_init_weak_random(seed);
 +}
 +
  /** Seed OpenSSL's random number generator with bytes from the operating
   * system.  <b>startup</b> should be true iff we have just started Tor and
   * have not yet allocated a bunch of fds.  Return 0 on success, -1 on failure.
@@@ -1990,15 -1803,14 +1992,15 @@@
  int
  crypto_seed_rng(int startup)
  {
 -  char buf[ADD_ENTROPY];
    int rand_poll_status = 0;
  
    /* local variables */
  #ifdef MS_WINDOWS
 +  unsigned char buf[ADD_ENTROPY];
    static int provider_set = 0;
    static HCRYPTPROV provider;
  #else
 +  char buf[ADD_ENTROPY];
    static const char *filenames[] = {
      "/dev/srandom", "/dev/urandom", "/dev/random", NULL
    };
@@@ -2034,7 -1846,6 +2036,7 @@@
    }
    RAND_seed(buf, sizeof(buf));
    memset(buf, 0, sizeof(buf));
 +  seed_weak_rng();
    return 0;
  #else
    for (i = 0; filenames[i]; ++i) {
@@@ -2051,7 -1862,6 +2053,7 @@@
      }
      RAND_seed(buf, (int)sizeof(buf));
      memset(buf, 0, sizeof(buf));
 +    seed_weak_rng();
      return 0;
    }
  
@@@ -2119,26 -1929,6 +2121,26 @@@ crypto_rand_uint64(uint64_t max
    }
  }
  
 +/** Return a pseudorandom double d, chosen uniformly from the range
 + * 0.0 <= d < 1.0.
 + */
 +double
 +crypto_rand_double(void)
 +{
 +  /* We just use an unsigned int here; we don't really care about getting
 +   * more than 32 bits of resolution */
 +  unsigned int uint;
 +  crypto_rand((char*)&uint, sizeof(uint));
 +#if SIZEOF_INT == 4
 +#define UINT_MAX_AS_DOUBLE 4294967296.0
 +#elif SIZEOF_INT == 8
 +#define UINT_MAX_AS_DOUBLE 1.8446744073709552e+19
 +#else
 +#error SIZEOF_INT is neither 4 nor 8
 +#endif
 +  return ((double)uint) / UINT_MAX_AS_DOUBLE;
 +}
 +
  /** Generate and return a new random hostname starting with <b>prefix</b>,
   * ending with <b>suffix</b>, and containing no less than
   * <b>min_rand_len</b> and no more than <b>max_rand_len</b> random base32
@@@ -2399,54 -2189,15 +2401,54 @@@ digest_from_base64(char *digest, const 
  #endif
  }
  
 +/** Base-64 encode DIGEST256_LINE bytes from <b>digest</b>, remove the
 + * trailing = and newline characters, and store the nul-terminated result in
 + * the first BASE64_DIGEST256_LEN+1 bytes of <b>d64</b>.  */
 +int
 +digest256_to_base64(char *d64, const char *digest)
 +{
 +  char buf[256];
 +  base64_encode(buf, sizeof(buf), digest, DIGEST256_LEN);
 +  buf[BASE64_DIGEST256_LEN] = '\0';
 +  memcpy(d64, buf, BASE64_DIGEST256_LEN+1);
 +  return 0;
 +}
 +
 +/** Given a base-64 encoded, nul-terminated digest in <b>d64</b> (without
 + * trailing newline or = characters), decode it and store the result in the
 + * first DIGEST256_LEN bytes at <b>digest</b>. */
 +int
 +digest256_from_base64(char *digest, const char *d64)
 +{
 +#ifdef USE_OPENSSL_BASE64
 +  char buf_in[BASE64_DIGEST256_LEN+3];
 +  char buf[256];
 +  if (strlen(d64) != BASE64_DIGEST256_LEN)
 +    return -1;
 +  memcpy(buf_in, d64, BASE64_DIGEST256_LEN);
 +  memcpy(buf_in+BASE64_DIGEST256_LEN, "=\n\0", 3);
 +  if (base64_decode(buf, sizeof(buf), buf_in, strlen(buf_in)) != DIGEST256_LEN)
 +    return -1;
 +  memcpy(digest, buf, DIGEST256_LEN);
 +  return 0;
 +#else
 +  if (base64_decode(digest, DIGEST256_LEN, d64, strlen(d64)) == DIGEST256_LEN)
 +    return 0;
 +  else
 +    return -1;
 +#endif
 +}
 +
  /** Implements base32 encoding as in rfc3548.  Limitation: Requires
   * that srclen*8 is a multiple of 5.
   */
  void
  base32_encode(char *dest, size_t destlen, const char *src, size_t srclen)
  {
 -  unsigned int i, bit, v, u;
 -  size_t nbits = srclen * 8;
 +  unsigned int i, v, u;
 +  size_t nbits = srclen * 8, bit;
  
 +  tor_assert(srclen < SIZE_T_CEILING/8);
    tor_assert((nbits%5) == 0); /* We need an even multiple of 5 bits. */
    tor_assert((nbits/5)+1 <= destlen); /* We need enough space. */
    tor_assert(destlen < SIZE_T_CEILING);
@@@ -2470,12 -2221,11 +2472,12 @@@ base32_decode(char *dest, size_t destle
  {
    /* XXXX we might want to rewrite this along the lines of base64_decode, if
     * it ever shows up in the profile. */
 -  unsigned int i, j, bit;
 -  size_t nbits;
 +  unsigned int i;
 +  size_t nbits, j, bit;
    char *tmp;
    nbits = srclen * 5;
  
 +  tor_assert(srclen < SIZE_T_CEILING / 5);
    tor_assert((nbits%8) == 0); /* We need an even multiple of 8 bits. */
    tor_assert((nbits/8) <= destlen); /* We need enough space. */
    tor_assert(destlen < SIZE_T_CEILING);
diff --combined src/common/crypto.h
index 29ba36c,d6f5555..c306bec
--- a/src/common/crypto.h
+++ b/src/common/crypto.h
@@@ -18,9 -18,6 +18,9 @@@
  
  /** Length of the output of our message digest. */
  #define DIGEST_LEN 20
 +/** Length of the output of our second (improved) message digests.  (For now
 + * this is just sha256, but any it can be any other 256-byte digest). */
 +#define DIGEST256_LEN 32
  /** Length of our symmetric cipher's keys. */
  #define CIPHER_KEY_LEN 16
  /** Length of our symmetric cipher's IV. */
@@@ -30,12 -27,9 +30,12 @@@
  /** Length of our DH keys. */
  #define DH_BYTES (1024/8)
  
 -/** Length of a message digest when encoded in base64 with trailing = signs
 - * removed. */
 +/** Length of a sha1 message digest when encoded in base64 with trailing =
 + * signs removed. */
  #define BASE64_DIGEST_LEN 27
 +/** Length of a sha256 message digest when encoded in base64 with trailing =
 + * signs removed. */
 +#define BASE64_DIGEST256_LEN 43
  
  /** Constants used to indicate no padding for public-key encryption */
  #define PK_NO_PADDING         60000
@@@ -54,26 -48,6 +54,26 @@@
  #define FINGERPRINT_LEN 49
  /** Length of hex encoding of SHA1 digest, not including final NUL. */
  #define HEX_DIGEST_LEN 40
 +/** Length of hex encoding of SHA256 digest, not including final NUL. */
 +#define HEX_DIGEST256_LEN 64
 +
 +typedef enum {
 +  DIGEST_SHA1 = 0,
 +  DIGEST_SHA256 = 1,
 +} digest_algorithm_t;
 +#define  N_DIGEST_ALGORITHMS (DIGEST_SHA256+1)
 +
 +/** A set of all the digests we know how to compute, taken on a single
 + * string.  Any digests that are shorter than 256 bits are right-padded
 + * with 0 bits.
 + *
 + * Note that this representation wastes 12 bytes for the SHA1 case, so
 + * don't use it for anything where we need to allocate a whole bunch at
 + * once.
 + **/
 +typedef struct {
 +  char d[N_DIGEST_ALGORITHMS][DIGEST256_LEN];
 +} digests_t;
  
  typedef struct crypto_pk_env_t crypto_pk_env_t;
  typedef struct crypto_cipher_env_t crypto_cipher_env_t;
@@@ -81,9 -55,7 +81,9 @@@ typedef struct crypto_digest_env_t cryp
  typedef struct crypto_dh_env_t crypto_dh_env_t;
  
  /* global state */
 -int crypto_global_init(int hardwareAccel);
 +int crypto_global_init(int hardwareAccel,
 +                       const char *accelName,
 +                       const char *accelPath);
  void crypto_thread_cleanup(void);
  int crypto_global_cleanup(void);
  
@@@ -99,9 -71,7 +99,9 @@@ crypto_cipher_env_t *crypto_new_cipher_
  void crypto_free_cipher_env(crypto_cipher_env_t *env);
  
  /* public key crypto */
 -int crypto_pk_generate_key(crypto_pk_env_t *env);
 +int crypto_pk_generate_key_with_bits(crypto_pk_env_t *env, int bits);
 +#define crypto_pk_generate_key(env)                     \
 +  crypto_pk_generate_key_with_bits((env), (PK_BYTES*8))
  
  int crypto_pk_read_private_key_from_filename(crypto_pk_env_t *env,
                                               const char *keyfile);
@@@ -112,7 -82,7 +112,7 @@@ int crypto_pk_write_private_key_to_stri
  int crypto_pk_read_public_key_from_string(crypto_pk_env_t *env,
                                            const char *src, size_t len);
  int crypto_pk_read_private_key_from_string(crypto_pk_env_t *env,
-                                            const char *s);
+                                            const char *s, ssize_t len);
  int crypto_pk_write_private_key_to_filename(crypto_pk_env_t *env,
                                              const char *fname);
  
@@@ -153,7 -123,7 +153,7 @@@ int crypto_pk_check_fingerprint_syntax(
  
  /* symmetric crypto */
  int crypto_cipher_generate_key(crypto_cipher_env_t *env);
 -int crypto_cipher_set_key(crypto_cipher_env_t *env, const char *key);
 +void crypto_cipher_set_key(crypto_cipher_env_t *env, const char *key);
  void crypto_cipher_generate_iv(char *iv_out);
  int crypto_cipher_set_iv(crypto_cipher_env_t *env, const char *iv);
  const char *crypto_cipher_get_key(crypto_cipher_env_t *env);
@@@ -173,15 -143,9 +173,15 @@@ int crypto_cipher_decrypt_with_iv(crypt
                                    char *to, size_t tolen,
                                    const char *from, size_t fromlen);
  
 -/* SHA-1 */
 +/* SHA-1 and other digests. */
  int crypto_digest(char *digest, const char *m, size_t len);
 +int crypto_digest256(char *digest, const char *m, size_t len,
 +                     digest_algorithm_t algorithm);
 +int crypto_digest_all(digests_t *ds_out, const char *m, size_t len);
 +const char *crypto_digest_algorithm_get_name(digest_algorithm_t alg);
 +int crypto_digest_algorithm_parse_name(const char *name);
  crypto_digest_env_t *crypto_new_digest_env(void);
 +crypto_digest_env_t *crypto_new_digest256_env(digest_algorithm_t algorithm);
  void crypto_free_digest_env(crypto_digest_env_t *digest);
  void crypto_digest_add_bytes(crypto_digest_env_t *digest, const char *data,
                               size_t len);
@@@ -200,7 -164,7 +200,7 @@@ int crypto_dh_get_bytes(crypto_dh_env_
  int crypto_dh_generate_public(crypto_dh_env_t *dh);
  int crypto_dh_get_public(crypto_dh_env_t *dh, char *pubkey_out,
                           size_t pubkey_out_len);
 -ssize_t crypto_dh_compute_secret(crypto_dh_env_t *dh,
 +ssize_t crypto_dh_compute_secret(int severity, crypto_dh_env_t *dh,
                               const char *pubkey, size_t pubkey_len,
                               char *secret_out, size_t secret_out_len);
  void crypto_dh_free(crypto_dh_env_t *dh);
@@@ -212,7 -176,6 +212,7 @@@ int crypto_seed_rng(int startup)
  int crypto_rand(char *to, size_t n);
  int crypto_rand_int(unsigned int max);
  uint64_t crypto_rand_uint64(uint64_t max);
 +double crypto_rand_double(void);
  
  char *crypto_random_hostname(int min_rand_len, int max_rand_len,
                               const char *prefix, const char *suffix);
@@@ -230,8 -193,6 +230,8 @@@ int base32_decode(char *dest, size_t de
  
  int digest_to_base64(char *d64, const char *digest);
  int digest_from_base64(char *digest, const char *d64);
 +int digest256_to_base64(char *d64, const char *digest);
 +int digest256_from_base64(char *digest, const char *d64);
  
  /** Length of RFC2440-style S2K specifier: the first 8 bytes are a salt, the
   * 9th describes how much iteration to do. */
diff --combined src/or/routerparse.c
index 66d024e,3778509..a6eef2d
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@@ -10,20 -10,7 +10,20 @@@
   **/
  
  #include "or.h"
 +#include "config.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "policies.h"
 +#include "rendcommon.h"
 +#include "router.h"
 +#include "routerlist.h"
  #include "memarea.h"
 +#include "microdesc.h"
 +#include "networkstatus.h"
 +#include "rephist.h"
 +#include "routerparse.h"
 +#undef log
 +#include <math.h>
  
  /****************************************************************************/
  
@@@ -68,7 -55,6 +68,7 @@@ typedef enum 
    K_S,
    K_V,
    K_W,
 +  K_M,
    K_EVENTDNS,
    K_EXTRA_INFO,
    K_EXTRA_INFO_DIGEST,
@@@ -76,31 -62,6 +76,31 @@@
    K_HIDDEN_SERVICE_DIR,
    K_ALLOW_SINGLE_HOP_EXITS,
  
 +  K_DIRREQ_END,
 +  K_DIRREQ_V2_IPS,
 +  K_DIRREQ_V3_IPS,
 +  K_DIRREQ_V2_REQS,
 +  K_DIRREQ_V3_REQS,
 +  K_DIRREQ_V2_SHARE,
 +  K_DIRREQ_V3_SHARE,
 +  K_DIRREQ_V2_RESP,
 +  K_DIRREQ_V3_RESP,
 +  K_DIRREQ_V2_DIR,
 +  K_DIRREQ_V3_DIR,
 +  K_DIRREQ_V2_TUN,
 +  K_DIRREQ_V3_TUN,
 +  K_ENTRY_END,
 +  K_ENTRY_IPS,
 +  K_CELL_END,
 +  K_CELL_PROCESSED,
 +  K_CELL_QUEUED,
 +  K_CELL_TIME,
 +  K_CELL_CIRCS,
 +  K_EXIT_END,
 +  K_EXIT_WRITTEN,
 +  K_EXIT_READ,
 +  K_EXIT_OPENED,
 +
    K_DIR_KEY_CERTIFICATE_VERSION,
    K_DIR_IDENTITY_KEY,
    K_DIR_KEY_PUBLISHED,
@@@ -117,18 -78,13 +117,18 @@@
  
    K_KNOWN_FLAGS,
    K_PARAMS,
 +  K_BW_WEIGHTS,
    K_VOTE_DIGEST,
    K_CONSENSUS_DIGEST,
 +  K_ADDITIONAL_DIGEST,
 +  K_ADDITIONAL_SIGNATURE,
    K_CONSENSUS_METHODS,
    K_CONSENSUS_METHOD,
    K_LEGACY_DIR_KEY,
 +  K_DIRECTORY_FOOTER,
  
    A_PURPOSE,
 +  A_LAST_LISTED,
    _A_UNKNOWN,
  
    R_RENDEZVOUS_SERVICE_DESCRIPTOR,
@@@ -166,7 -122,7 +166,7 @@@
   * type.
   *
   * This structure is only allocated in memareas; do not allocate it on
 - * the heap, or token_free() won't work.
 + * the heap, or token_clear() won't work.
   */
  typedef struct directory_token_t {
    directory_keyword tp;        /**< Type of the token. */
@@@ -302,31 -258,6 +302,31 @@@ static token_rule_t extrainfo_token_tab
    T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
    T01("read-history",        K_READ_HISTORY,        ARGS,    NO_OBJ ),
    T01("write-history",       K_WRITE_HISTORY,       ARGS,    NO_OBJ ),
 +  T01("dirreq-stats-end",    K_DIRREQ_END,          ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-ips",       K_DIRREQ_V2_IPS,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-ips",       K_DIRREQ_V3_IPS,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-reqs",      K_DIRREQ_V2_REQS,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-reqs",      K_DIRREQ_V3_REQS,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-share",     K_DIRREQ_V2_SHARE,     ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-share",     K_DIRREQ_V3_SHARE,     ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-resp",      K_DIRREQ_V2_RESP,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-resp",      K_DIRREQ_V3_RESP,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-direct-dl", K_DIRREQ_V2_DIR,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-direct-dl", K_DIRREQ_V3_DIR,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-tunneled-dl", K_DIRREQ_V2_TUN,     ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-tunneled-dl", K_DIRREQ_V3_TUN,     ARGS,    NO_OBJ ),
 +  T01("entry-stats-end",     K_ENTRY_END,           ARGS,    NO_OBJ ),
 +  T01("entry-ips",           K_ENTRY_IPS,           ARGS,    NO_OBJ ),
 +  T01("cell-stats-end",      K_CELL_END,            ARGS,    NO_OBJ ),
 +  T01("cell-processed-cells", K_CELL_PROCESSED,     ARGS,    NO_OBJ ),
 +  T01("cell-queued-cells",   K_CELL_QUEUED,         ARGS,    NO_OBJ ),
 +  T01("cell-time-in-queue",  K_CELL_TIME,           ARGS,    NO_OBJ ),
 +  T01("cell-circuits-per-decile", K_CELL_CIRCS,     ARGS,    NO_OBJ ),
 +  T01("exit-stats-end",      K_EXIT_END,            ARGS,    NO_OBJ ),
 +  T01("exit-kibibytes-written", K_EXIT_WRITTEN,     ARGS,    NO_OBJ ),
 +  T01("exit-kibibytes-read", K_EXIT_READ,           ARGS,    NO_OBJ ),
 +  T01("exit-streams-opened", K_EXIT_OPENED,         ARGS,    NO_OBJ ),
 +
    T1_START( "extra-info",          K_EXTRA_INFO,          GE(2),   NO_OBJ ),
  
    END_OF_TABLE
@@@ -336,11 -267,10 +336,11 @@@
   * documents. */
  static token_rule_t rtrstatus_token_table[] = {
    T01("p",                   K_P,               CONCAT_ARGS, NO_OBJ ),
 -  T1( "r",                   K_R,                   GE(8),   NO_OBJ ),
 +  T1( "r",                   K_R,                   GE(7),   NO_OBJ ),
    T1( "s",                   K_S,                   ARGS,    NO_OBJ ),
    T01("v",                   K_V,               CONCAT_ARGS, NO_OBJ ),
    T01("w",                   K_W,                   ARGS,    NO_OBJ ),
 +  T0N("m",                   K_M,               CONCAT_ARGS, NO_OBJ ),
    T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
    END_OF_TABLE
  };
@@@ -445,7 -375,7 +445,7 @@@ static token_rule_t client_keys_token_t
  
  /** List of tokens allowed in V3 networkstatus votes. */
  static token_rule_t networkstatus_token_table[] = {
 -  T1("network-status-version", K_NETWORK_STATUS_VERSION,
 +  T1_START("network-status-version", K_NETWORK_STATUS_VERSION,
                                                     GE(1),       NO_OBJ ),
    T1("vote-status",            K_VOTE_STATUS,      GE(1),       NO_OBJ ),
    T1("published",              K_PUBLISHED,        CONCAT_ARGS, NO_OBJ ),
@@@ -473,7 -403,7 +473,7 @@@
  
  /** List of tokens allowed in V3 networkstatus consensuses. */
  static token_rule_t networkstatus_consensus_token_table[] = {
 -  T1("network-status-version", K_NETWORK_STATUS_VERSION,
 +  T1_START("network-status-version", K_NETWORK_STATUS_VERSION,
                                                     GE(1),       NO_OBJ ),
    T1("vote-status",            K_VOTE_STATUS,      GE(1),       NO_OBJ ),
    T1("valid-after",            K_VALID_AFTER,      CONCAT_ARGS, NO_OBJ ),
@@@ -500,29 -430,17 +500,29 @@@
  /** List of tokens allowable in the footer of v1/v2 directory/networkstatus
   * footers. */
  static token_rule_t networkstatus_vote_footer_token_table[] = {
 -  T(  "directory-signature", K_DIRECTORY_SIGNATURE, GE(2),   NEED_OBJ ),
 +  T01("directory-footer",    K_DIRECTORY_FOOTER,    NO_ARGS,   NO_OBJ ),
 +  T01("bandwidth-weights",   K_BW_WEIGHTS,          ARGS,      NO_OBJ ),
 +  T(  "directory-signature", K_DIRECTORY_SIGNATURE, GE(2),     NEED_OBJ ),
    END_OF_TABLE
  };
  
  /** List of tokens allowable in detached networkstatus signature documents. */
  static token_rule_t networkstatus_detached_signature_token_table[] = {
    T1_START("consensus-digest", K_CONSENSUS_DIGEST, GE(1),       NO_OBJ ),
 +  T("additional-digest",       K_ADDITIONAL_DIGEST,GE(3),       NO_OBJ ),
    T1("valid-after",            K_VALID_AFTER,      CONCAT_ARGS, NO_OBJ ),
    T1("fresh-until",            K_FRESH_UNTIL,      CONCAT_ARGS, NO_OBJ ),
    T1("valid-until",            K_VALID_UNTIL,      CONCAT_ARGS, NO_OBJ ),
 -  T1N("directory-signature", K_DIRECTORY_SIGNATURE, GE(2),   NEED_OBJ ),
 +  T("additional-signature",  K_ADDITIONAL_SIGNATURE, GE(4),   NEED_OBJ ),
 +  T1N("directory-signature", K_DIRECTORY_SIGNATURE,  GE(2),   NEED_OBJ ),
 +  END_OF_TABLE
 +};
 +
 +static token_rule_t microdesc_token_table[] = {
 +  T1_START("onion-key",        K_ONION_KEY,        NO_ARGS,     NEED_KEY_1024),
 +  T01("family",                K_FAMILY,           ARGS,        NO_OBJ ),
 +  T01("p",                     K_P,                CONCAT_ARGS, NO_OBJ ),
 +  A01("@last-listed",          A_LAST_LISTED,      CONCAT_ARGS, NO_OBJ ),
    END_OF_TABLE
  };
  
@@@ -535,13 -453,9 +535,13 @@@ static addr_policy_t *router_parse_addr
  
  static int router_get_hash_impl(const char *s, size_t s_len, char *digest,
                                  const char *start_str, const char *end_str,
 -                                char end_char);
 -
 -static void token_free(directory_token_t *tok);
 +                                char end_char,
 +                                digest_algorithm_t alg);
 +static int router_get_hashes_impl(const char *s, size_t s_len,
 +                                  digests_t *digests,
 +                                  const char *start_str, const char *end_str,
 +                                  char end_char);
 +static void token_clear(directory_token_t *tok);
  static smartlist_t *find_all_exitpolicy(smartlist_t *s);
  static directory_token_t *_find_by_keyword(smartlist_t *s,
                                             directory_keyword keyword,
@@@ -565,7 -479,6 +565,7 @@@ static directory_token_t *get_next_toke
  #define CST_CHECK_AUTHORITY   (1<<0)
  #define CST_NO_CHECK_OBJTYPE  (1<<1)
  static int check_signature_token(const char *digest,
 +                                 ssize_t digest_len,
                                   directory_token_t *tok,
                                   crypto_pk_env_t *pkey,
                                   int flags,
@@@ -586,34 -499,6 +586,34 @@@ static int tor_version_same_series(tor_
  #define DUMP_AREA(a,name) STMT_NIL
  #endif
  
 +/** Last time we dumped a descriptor to disk. */
 +static time_t last_desc_dumped = 0;
 +
 +/** For debugging purposes, dump unparseable descriptor *<b>desc</b> of
 + * type *<b>type</b> to file $DATADIR/unparseable-desc. Do not write more
 + * than one descriptor to disk per minute. If there is already such a
 + * file in the data directory, overwrite it. */
 +static void
 +dump_desc(const char *desc, const char *type)
 +{
 +  time_t now = time(NULL);
 +  tor_assert(desc);
 +  tor_assert(type);
 +  if (!last_desc_dumped || last_desc_dumped + 60 < now) {
 +    char *debugfile = get_datadir_fname("unparseable-desc");
 +    size_t filelen = 50 + strlen(type) + strlen(desc);
 +    char *content = tor_malloc_zero(filelen);
 +    tor_snprintf(content, filelen, "Unable to parse descriptor of type "
 +                 "%s:\n%s", type, desc);
 +    write_str_to_file(debugfile, content, 0);
 +    log_info(LD_DIR, "Unable to parse descriptor of type %s. See file "
 +             "unparseable-desc in data directory for details.", type);
 +    tor_free(content);
 +    tor_free(debugfile);
 +    last_desc_dumped = now;
 +  }
 +}
 +
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the directory in
   * <b>s</b>.  Return 0 on success, -1 on failure.
   */
@@@ -621,8 -506,7 +621,8 @@@ in
  router_get_dir_hash(const char *s, char *digest)
  {
    return router_get_hash_impl(s, strlen(s), digest,
 -                              "signed-directory","\ndirectory-signature",'\n');
 +                              "signed-directory","\ndirectory-signature",'\n',
 +                              DIGEST_SHA1);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the first router in
@@@ -632,8 -516,7 +632,8 @@@ in
  router_get_router_hash(const char *s, size_t s_len, char *digest)
  {
    return router_get_hash_impl(s, s_len, digest,
 -                              "router ","\nrouter-signature", '\n');
 +                              "router ","\nrouter-signature", '\n',
 +                              DIGEST_SHA1);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the running-routers
@@@ -643,8 -526,7 +643,8 @@@ in
  router_get_runningrouters_hash(const char *s, char *digest)
  {
    return router_get_hash_impl(s, strlen(s), digest,
 -                              "network-status","\ndirectory-signature", '\n');
 +                              "network-status","\ndirectory-signature", '\n',
 +                              DIGEST_SHA1);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the network-status
@@@ -654,31 -536,18 +654,31 @@@ router_get_networkstatus_v2_hash(const 
  {
    return router_get_hash_impl(s, strlen(s), digest,
                                "network-status-version","\ndirectory-signature",
 -                              '\n');
 +                              '\n',
 +                              DIGEST_SHA1);
 +}
 +
 +/** Set <b>digests</b> to all the digests of the consensus document in
 + * <b>s</b> */
 +int
 +router_get_networkstatus_v3_hashes(const char *s, digests_t *digests)
 +{
 +  return router_get_hashes_impl(s,strlen(s),digests,
 +                                "network-status-version",
 +                                "\ndirectory-signature",
 +                                ' ');
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the network-status
   * string in <b>s</b>.  Return 0 on success, -1 on failure. */
  int
 -router_get_networkstatus_v3_hash(const char *s, char *digest)
 +router_get_networkstatus_v3_hash(const char *s, char *digest,
 +                                 digest_algorithm_t alg)
  {
    return router_get_hash_impl(s, strlen(s), digest,
                                "network-status-version",
                                "\ndirectory-signature",
 -                              ' ');
 +                              ' ', alg);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the extrainfo
@@@ -687,7 -556,7 +687,7 @@@ in
  router_get_extrainfo_hash(const char *s, char *digest)
  {
    return router_get_hash_impl(s, strlen(s), digest, "extra-info",
 -                              "\nrouter-signature",'\n');
 +                              "\nrouter-signature",'\n', DIGEST_SHA1);
  }
  
  /** Helper: used to generate signatures for routers, directories and
@@@ -699,17 -568,16 +699,17 @@@
   */
  int
  router_append_dirobj_signature(char *buf, size_t buf_len, const char *digest,
 -                               crypto_pk_env_t *private_key)
 +                               size_t digest_len, crypto_pk_env_t *private_key)
  {
    char *signature;
    size_t i, keysize;
 +  int siglen;
  
    keysize = crypto_pk_keysize(private_key);
    signature = tor_malloc(keysize);
 -  if (crypto_pk_private_sign(private_key, signature, keysize,
 -                             digest, DIGEST_LEN) < 0) {
 -
 +  siglen = crypto_pk_private_sign(private_key, signature, keysize,
 +                                  digest, digest_len);
 +  if (siglen < 0) {
      log_warn(LD_BUG,"Couldn't sign digest.");
      goto err;
    }
@@@ -717,7 -585,7 +717,7 @@@
      goto truncated;
  
    i = strlen(buf);
 -  if (base64_encode(buf+i, buf_len-i, signature, 128) < 0) {
 +  if (base64_encode(buf+i, buf_len-i, signature, siglen) < 0) {
      log_warn(LD_BUG,"couldn't base64-encode signature");
      goto err;
    }
@@@ -824,7 -692,7 +824,7 @@@ router_parse_directory(const char *str
    char digest[DIGEST_LEN];
    time_t published_on;
    int r;
 -  const char *end, *cp;
 +  const char *end, *cp, *str_dup = str;
    smartlist_t *tokens = NULL;
    crypto_pk_env_t *declared_key = NULL;
    memarea_t *area = memarea_new();
@@@ -860,11 -728,11 +860,11 @@@
    }
    declared_key = find_dir_signing_key(str, str+strlen(str));
    note_crypto_pk_op(VERIFY_DIR);
 -  if (check_signature_token(digest, tok, declared_key,
 +  if (check_signature_token(digest, DIGEST_LEN, tok, declared_key,
                              CST_CHECK_AUTHORITY, "directory")<0)
      goto err;
  
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_clear(tokens);
    memarea_clear(area);
  
@@@ -897,12 -765,11 +897,12 @@@
    r = 0;
    goto done;
   err:
 +  dump_desc(str_dup, "v1 directory");
    r = -1;
   done:
    if (declared_key) crypto_free_pk_env(declared_key);
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area) {
@@@ -924,7 -791,7 +924,7 @@@ router_parse_runningrouters(const char 
    int r = -1;
    crypto_pk_env_t *declared_key = NULL;
    smartlist_t *tokens = NULL;
 -  const char *eos = str + strlen(str);
 +  const char *eos = str + strlen(str), *str_dup = str;
    memarea_t *area = NULL;
  
    if (router_get_runningrouters_hash(str, digest)) {
@@@ -953,7 -820,7 +953,7 @@@
    }
    declared_key = find_dir_signing_key(str, eos);
    note_crypto_pk_op(VERIFY_DIR);
 -  if (check_signature_token(digest, tok, declared_key,
 +  if (check_signature_token(digest, DIGEST_LEN, tok, declared_key,
                              CST_CHECK_AUTHORITY, "running-routers")
        < 0)
      goto err;
@@@ -965,10 -832,9 +965,10 @@@
  
    r = 0;
   err:
 +  dump_desc(str_dup, "v1 running-routers");
    if (declared_key) crypto_free_pk_env(declared_key);
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area) {
@@@ -1018,7 -884,7 +1018,7 @@@ find_dir_signing_key(const char *str, c
    }
  
   done:
 -  if (tok) token_free(tok);
 +  if (tok) token_clear(tok);
    if (area) {
      DUMP_AREA(area, "dir-signing-key token");
      memarea_drop_all(area);
@@@ -1054,7 -920,6 +1054,7 @@@ dir_signing_key_is_trusted(crypto_pk_en
   */
  static int
  check_signature_token(const char *digest,
 +                      ssize_t digest_len,
                        directory_token_t *tok,
                        crypto_pk_env_t *pkey,
                        int flags,
@@@ -1087,14 -952,14 +1087,14 @@@
    signed_digest = tor_malloc(keysize);
    if (crypto_pk_public_checksig(pkey, signed_digest, keysize,
                                  tok->object_body, tok->object_size)
 -      != DIGEST_LEN) {
 +      < DIGEST_LEN) {
      log_warn(LD_DIR, "Error reading %s: invalid signature.", doctype);
      tor_free(signed_digest);
      return -1;
    }
  //  log_debug(LD_DIR,"Signed %s hash starts %s", doctype,
  //            hex_str(signed_digest,4));
 -  if (memcmp(digest, signed_digest, DIGEST_LEN)) {
 +  if (memcmp(digest, signed_digest, digest_len)) {
      log_warn(LD_DIR, "Error reading %s: signature does not match.", doctype);
      tor_free(signed_digest);
      return -1;
@@@ -1280,7 -1145,7 +1280,7 @@@ router_parse_entry_from_string(const ch
    smartlist_t *tokens = NULL, *exit_policy_tokens = NULL;
    directory_token_t *tok;
    struct in_addr in;
 -  const char *start_of_annotations, *cp;
 +  const char *start_of_annotations, *cp, *s_dup = s;
    size_t prepend_len = prepend_annotations ? strlen(prepend_annotations) : 0;
    int ok = 1;
    memarea_t *area = NULL;
@@@ -1558,7 -1423,7 +1558,7 @@@
      verified_digests = digestmap_new();
    digestmap_set(verified_digests, signed_digest, (void*)(uintptr_t)1);
  #endif
 -  if (check_signature_token(digest, tok, router->identity_pkey, 0,
 +  if (check_signature_token(digest, DIGEST_LEN, tok, router->identity_pkey, 0,
                              "router descriptor") < 0)
      goto err;
  
@@@ -1576,15 -1441,16 +1576,15 @@@
    goto done;
  
   err:
 +  dump_desc(s_dup, "router descriptor");
    routerinfo_free(router);
    router = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
 -  if (exit_policy_tokens) {
 -    smartlist_free(exit_policy_tokens);
 -  }
 +  smartlist_free(exit_policy_tokens);
    if (area) {
      DUMP_AREA(area, "routerinfo");
      memarea_drop_all(area);
@@@ -1609,7 -1475,6 +1609,7 @@@ extrainfo_parse_entry_from_string(cons
    crypto_pk_env_t *key = NULL;
    routerinfo_t *router = NULL;
    memarea_t *area = NULL;
 +  const char *s_dup = s;
  
    if (!end) {
      end = s + strlen(s);
@@@ -1684,8 -1549,7 +1684,8 @@@
  
    if (key) {
      note_crypto_pk_op(VERIFY_RTR);
 -    if (check_signature_token(digest, tok, key, 0, "extra-info") < 0)
 +    if (check_signature_token(digest, DIGEST_LEN, tok, key, 0,
 +                              "extra-info") < 0)
        goto err;
  
      if (router)
@@@ -1699,12 -1563,12 +1699,12 @@@
  
    goto done;
   err:
 -  if (extrainfo)
 -    extrainfo_free(extrainfo);
 +  dump_desc(s_dup, "extra-info descriptor");
 +  extrainfo_free(extrainfo);
    extrainfo = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area) {
@@@ -1728,7 -1592,6 +1728,7 @@@ authority_cert_parse_from_string(const 
    size_t len;
    int found;
    memarea_t *area = NULL;
 +  const char *s_dup = s;
  
    s = eat_whitespace(s);
    eos = strstr(s, "\ndir-key-certification");
@@@ -1753,7 -1616,7 +1753,7 @@@
      goto err;
    }
    if (router_get_hash_impl(s, strlen(s), digest, "dir-key-certificate-version",
 -                           "\ndir-key-certification", '\n') < 0)
 +                           "\ndir-key-certification", '\n', DIGEST_SHA1) < 0)
      goto err;
    tok = smartlist_get(tokens, 0);
    if (tok->tp != K_DIR_KEY_CERTIFICATE_VERSION || strcmp(tok->args[0], "3")) {
@@@ -1846,7 -1709,7 +1846,7 @@@
      }
    }
    if (!found) {
 -    if (check_signature_token(digest, tok, cert->identity_key, 0,
 +    if (check_signature_token(digest, DIGEST_LEN, tok, cert->identity_key, 0,
                                "key certificate")) {
        goto err;
      }
@@@ -1855,7 -1718,6 +1855,7 @@@
        /* XXXX Once all authorities generate cross-certified certificates,
         * make this field mandatory. */
        if (check_signature_token(cert->cache_info.identity_digest,
 +                                DIGEST_LEN,
                                  tok,
                                  cert->signing_key,
                                  CST_NO_CHECK_OBJTYPE,
@@@ -1875,7 -1737,7 +1875,7 @@@
    if (end_of_string) {
      *end_of_string = eat_whitespace(eos);
    }
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area) {
      DUMP_AREA(area, "authority cert");
@@@ -1883,9 -1745,8 +1883,9 @@@
    }
    return cert;
   err:
 +  dump_desc(s_dup, "authority cert");
    authority_cert_free(cert);
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area) {
      DUMP_AREA(area, "authority cert");
@@@ -1896,28 -1757,23 +1896,28 @@@
  
  /** Helper: given a string <b>s</b>, return the start of the next router-status
   * object (starting with "r " at the start of a line).  If none is found,
 - * return the start of the next directory signature.  If none is found, return
 - * the end of the string. */
 + * return the start of the directory footer, or the next directory signature.
 + * If none is found, return the end of the string. */
  static INLINE const char *
  find_start_of_next_routerstatus(const char *s)
  {
 -  const char *eos = strstr(s, "\nr ");
 -  if (eos) {
 -    const char *eos2 = tor_memstr(s, eos-s, "\ndirectory-signature");
 -    if (eos2 && eos2 < eos)
 -      return eos2;
 -    else
 -      return eos+1;
 -  } else {
 -    if ((eos = strstr(s, "\ndirectory-signature")))
 -      return eos+1;
 -    return s + strlen(s);
 -  }
 +  const char *eos, *footer, *sig;
 +  if ((eos = strstr(s, "\nr ")))
 +    ++eos;
 +  else
 +    eos = s + strlen(s);
 +
 +  footer = tor_memstr(s, eos-s, "\ndirectory-footer");
 +  sig = tor_memstr(s, eos-s, "\ndirectory-signature");
 +
 +  if (footer && sig)
 +    return MIN(footer, sig) + 1;
 +  else if (footer)
 +    return footer+1;
 +  else if (sig)
 +    return sig+1;
 +  else
 +    return eos;
  }
  
  /** Given a string at *<b>s</b>, containing a routerstatus object, and an
@@@ -1931,29 -1787,22 +1931,29 @@@
   * If <b>consensus_method</b> is nonzero, this routerstatus is part of a
   * consensus, and we should parse it according to the method used to
   * make that consensus.
 + *
 + * Parse according to the syntax used by the consensus flavor <b>flav</b>.
   **/
  static routerstatus_t *
  routerstatus_parse_entry_from_string(memarea_t *area,
                                       const char **s, smartlist_t *tokens,
                                       networkstatus_t *vote,
                                       vote_routerstatus_t *vote_rs,
 -                                     int consensus_method)
 +                                     int consensus_method,
 +                                     consensus_flavor_t flav)
  {
 -  const char *eos;
 +  const char *eos, *s_dup = *s;
    routerstatus_t *rs = NULL;
    directory_token_t *tok;
    char timebuf[ISO_TIME_LEN+1];
    struct in_addr in;
 +  int offset = 0;
    tor_assert(tokens);
    tor_assert(bool_eq(vote, vote_rs));
  
 +  if (!consensus_method)
 +    flav = FLAV_NS;
 +
    eos = find_start_of_next_routerstatus(*s);
  
    if (tokenize_string(area,*s, eos, tokens, rtrstatus_token_table,0)) {
@@@ -1965,15 -1814,7 +1965,15 @@@
      goto err;
    }
    tok = find_by_keyword(tokens, K_R);
 -  tor_assert(tok->n_args >= 8);
 +  tor_assert(tok->n_args >= 7);
 +  if (flav == FLAV_NS) {
 +    if (tok->n_args < 8) {
 +      log_warn(LD_DIR, "Too few arguments to r");
 +      goto err;
 +    }
 +  } else {
 +    offset = -1;
 +  }
    if (vote_rs) {
      rs = &vote_rs->status;
    } else {
@@@ -1994,34 -1835,29 +1994,34 @@@
      goto err;
    }
  
 -  if (digest_from_base64(rs->descriptor_digest, tok->args[2])) {
 -    log_warn(LD_DIR, "Error decoding descriptor digest %s",
 -             escaped(tok->args[2]));
 -    goto err;
 +  if (flav == FLAV_NS) {
 +    if (digest_from_base64(rs->descriptor_digest, tok->args[2])) {
 +      log_warn(LD_DIR, "Error decoding descriptor digest %s",
 +               escaped(tok->args[2]));
 +      goto err;
 +    }
    }
  
    if (tor_snprintf(timebuf, sizeof(timebuf), "%s %s",
 -                   tok->args[3], tok->args[4]) < 0 ||
 +                   tok->args[3+offset], tok->args[4+offset]) < 0 ||
        parse_iso_time(timebuf, &rs->published_on)<0) {
 -    log_warn(LD_DIR, "Error parsing time '%s %s'",
 -             tok->args[3], tok->args[4]);
 +    log_warn(LD_DIR, "Error parsing time '%s %s' [%d %d]",
 +             tok->args[3+offset], tok->args[4+offset],
 +             offset, (int)flav);
      goto err;
    }
  
 -  if (tor_inet_aton(tok->args[5], &in) == 0) {
 +  if (tor_inet_aton(tok->args[5+offset], &in) == 0) {
      log_warn(LD_DIR, "Error parsing router address in network-status %s",
 -             escaped(tok->args[5]));
 +             escaped(tok->args[5+offset]));
      goto err;
    }
    rs->addr = ntohl(in.s_addr);
  
 -  rs->or_port =(uint16_t) tor_parse_long(tok->args[6],10,0,65535,NULL,NULL);
 -  rs->dir_port = (uint16_t) tor_parse_long(tok->args[7],10,0,65535,NULL,NULL);
 +  rs->or_port = (uint16_t) tor_parse_long(tok->args[6+offset],
 +                                         10,0,65535,NULL,NULL);
 +  rs->dir_port = (uint16_t) tor_parse_long(tok->args[7+offset],
 +                                           10,0,65535,NULL,NULL);
  
    tok = find_opt_by_keyword(tokens, K_S);
    if (tok && vote) {
@@@ -2107,17 -1943,6 +2107,17 @@@
            goto err;
          }
          rs->has_bandwidth = 1;
 +      } else if (!strcmpstart(tok->args[i], "Measured=")) {
 +        int ok;
 +        rs->measured_bw =
 +            (uint32_t)tor_parse_ulong(strchr(tok->args[i], '=')+1,
 +                                      10, 0, UINT32_MAX, &ok, NULL);
 +        if (!ok) {
 +          log_warn(LD_DIR, "Invalid Measured Bandwidth %s",
 +                   escaped(tok->args[i]));
 +          goto err;
 +        }
 +        rs->has_measured_bw = 1;
        }
      }
    }
@@@ -2139,29 -1964,16 +2139,29 @@@
      rs->has_exitsummary = 1;
    }
  
 +  if (vote_rs) {
 +    SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, t) {
 +      if (t->tp == K_M && t->n_args) {
 +        vote_microdesc_hash_t *line =
 +          tor_malloc(sizeof(vote_microdesc_hash_t));
 +        line->next = vote_rs->microdesc;
 +        line->microdesc_hash_line = tor_strdup(t->args[0]);
 +        vote_rs->microdesc = line;
 +      }
 +    } SMARTLIST_FOREACH_END(t);
 +  }
 +
    if (!strcasecmp(rs->nickname, UNNAMED_ROUTER_NICKNAME))
      rs->is_named = 0;
  
    goto done;
   err:
 +  dump_desc(s_dup, "routerstatus entry");
    if (rs && !vote_rs)
      routerstatus_free(rs);
    rs = NULL;
   done:
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_clear(tokens);
    if (area) {
      DUMP_AREA(area, "routerstatus entry");
@@@ -2173,8 -1985,8 +2173,8 @@@
  }
  
  /** Helper to sort a smartlist of pointers to routerstatus_t */
 -static int
 -_compare_routerstatus_entries(const void **_a, const void **_b)
 +int
 +compare_routerstatus_entries(const void **_a, const void **_b)
  {
    const routerstatus_t *a = *_a, *b = *_b;
    return memcmp(a->identity_digest, b->identity_digest, DIGEST_LEN);
@@@ -2198,7 -2010,7 +2198,7 @@@ _free_duplicate_routerstatus_entry(voi
  networkstatus_v2_t *
  networkstatus_v2_parse_from_string(const char *s)
  {
 -  const char *eos;
 +  const char *eos, *s_dup = s;
    smartlist_t *tokens = smartlist_create();
    smartlist_t *footer_tokens = smartlist_create();
    networkstatus_v2_t *ns = NULL;
@@@ -2312,17 -2124,17 +2312,17 @@@
  
    ns->entries = smartlist_create();
    s = eos;
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_clear(tokens);
    memarea_clear(area);
    while (!strcmpstart(s, "r ")) {
      routerstatus_t *rs;
      if ((rs = routerstatus_parse_entry_from_string(area, &s, tokens,
 -                                                   NULL, NULL, 0)))
 +                                                   NULL, NULL, 0, 0)))
        smartlist_add(ns->entries, rs);
    }
 -  smartlist_sort(ns->entries, _compare_routerstatus_entries);
 -  smartlist_uniq(ns->entries, _compare_routerstatus_entries,
 +  smartlist_sort(ns->entries, compare_routerstatus_entries);
 +  smartlist_uniq(ns->entries, compare_routerstatus_entries,
                   _free_duplicate_routerstatus_entry);
  
    if (tokenize_string(area,s, NULL, footer_tokens, dir_footer_token_table,0)) {
@@@ -2341,19 -2153,19 +2341,19 @@@
    }
  
    note_crypto_pk_op(VERIFY_DIR);
 -  if (check_signature_token(ns_digest, tok, ns->signing_key, 0,
 +  if (check_signature_token(ns_digest, DIGEST_LEN, tok, ns->signing_key, 0,
                              "network-status") < 0)
      goto err;
  
    goto done;
   err:
 -  if (ns)
 -    networkstatus_v2_free(ns);
 +  dump_desc(s_dup, "v2 networkstatus");
 +  networkstatus_v2_free(ns);
    ns = NULL;
   done:
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
 -  SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(footer_tokens);
    if (area) {
      DUMP_AREA(area, "v2 networkstatus");
@@@ -2362,395 -2174,6 +2362,395 @@@
    return ns;
  }
  
 +/** Verify the bandwidth weights of a network status document */
 +int
 +networkstatus_verify_bw_weights(networkstatus_t *ns)
 +{
 +  int64_t weight_scale;
 +  int64_t G=0, M=0, E=0, D=0, T=0;
 +  double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed;
 +  double Gtotal=0, Mtotal=0, Etotal=0;
 +  const char *casename = NULL;
 +  int valid = 1;
 +
 +  weight_scale = networkstatus_get_param(ns, "bwweightscale", BW_WEIGHT_SCALE);
 +  Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1);
 +  Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1);
 +  Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1);
 +  Wmg = networkstatus_get_bw_weight(ns, "Wmg", -1);
 +  Wmm = networkstatus_get_bw_weight(ns, "Wmm", -1);
 +  Wme = networkstatus_get_bw_weight(ns, "Wme", -1);
 +  Wmd = networkstatus_get_bw_weight(ns, "Wmd", -1);
 +  Weg = networkstatus_get_bw_weight(ns, "Weg", -1);
 +  Wem = networkstatus_get_bw_weight(ns, "Wem", -1);
 +  Wee = networkstatus_get_bw_weight(ns, "Wee", -1);
 +  Wed = networkstatus_get_bw_weight(ns, "Wed", -1);
 +
 +  if (Wgg<0 || Wgm<0 || Wgd<0 || Wmg<0 || Wmm<0 || Wme<0 || Wmd<0 || Weg<0
 +          || Wem<0 || Wee<0 || Wed<0) {
 +    log_warn(LD_BUG, "No bandwidth weights produced in consensus!");
 +    return 0;
 +  }
 +
 +  // First, sanity check basic summing properties that hold for all cases
 +  // We use > 1 as the check for these because they are computed as integers.
 +  // Sometimes there are rounding errors.
 +  if (fabs(Wmm - weight_scale) > 1) {
 +    log_warn(LD_BUG, "Wmm=%lf != "I64_FORMAT,
 +             Wmm, I64_PRINTF_ARG(weight_scale));
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wem - Wee) > 1) {
 +    log_warn(LD_BUG, "Wem=%lf != Wee=%lf", Wem, Wee);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wgm - Wgg) > 1) {
 +    log_warn(LD_BUG, "Wgm=%lf != Wgg=%lf", Wgm, Wgg);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Weg - Wed) > 1) {
 +    log_warn(LD_BUG, "Wed=%lf != Weg=%lf", Wed, Weg);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wgg + Wmg - weight_scale) > 0.001*weight_scale) {
 +    log_warn(LD_BUG, "Wgg=%lf != "I64_FORMAT" - Wmg=%lf", Wgg,
 +             I64_PRINTF_ARG(weight_scale), Wmg);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wee + Wme - weight_scale) > 0.001*weight_scale) {
 +    log_warn(LD_BUG, "Wee=%lf != "I64_FORMAT" - Wme=%lf", Wee,
 +             I64_PRINTF_ARG(weight_scale), Wme);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wgd + Wmd + Wed - weight_scale) > 0.001*weight_scale) {
 +    log_warn(LD_BUG, "Wgd=%lf + Wmd=%lf + Wed=%lf != "I64_FORMAT,
 +             Wgd, Wmd, Wed, I64_PRINTF_ARG(weight_scale));
 +    valid = 0;
 +  }
 +
 +  Wgg /= weight_scale;
 +  Wgm /= weight_scale;
 +  Wgd /= weight_scale;
 +
 +  Wmg /= weight_scale;
 +  Wmm /= weight_scale;
 +  Wme /= weight_scale;
 +  Wmd /= weight_scale;
 +
 +  Weg /= weight_scale;
 +  Wem /= weight_scale;
 +  Wee /= weight_scale;
 +  Wed /= weight_scale;
 +
 +  // Then, gather G, M, E, D, T to determine case
 +  SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) {
 +    if (rs->has_bandwidth) {
 +      T += rs->bandwidth;
 +      if (rs->is_exit && rs->is_possible_guard) {
 +        D += rs->bandwidth;
 +        Gtotal += Wgd*rs->bandwidth;
 +        Mtotal += Wmd*rs->bandwidth;
 +        Etotal += Wed*rs->bandwidth;
 +      } else if (rs->is_exit) {
 +        E += rs->bandwidth;
 +        Mtotal += Wme*rs->bandwidth;
 +        Etotal += Wee*rs->bandwidth;
 +      } else if (rs->is_possible_guard) {
 +        G += rs->bandwidth;
 +        Gtotal += Wgg*rs->bandwidth;
 +        Mtotal += Wmg*rs->bandwidth;
 +      } else {
 +        M += rs->bandwidth;
 +        Mtotal += Wmm*rs->bandwidth;
 +      }
 +    } else {
 +      log_warn(LD_BUG, "Missing consensus bandwidth for router %s",
 +          rs->nickname);
 +    }
 +  } SMARTLIST_FOREACH_END(rs);
 +
 +  // Finally, check equality conditions depending upon case 1, 2 or 3
 +  // Full equality cases: 1, 3b
 +  // Partial equality cases: 2b (E=G), 3a (M=E)
 +  // Fully unknown: 2a
 +  if (3*E >= T && 3*G >= T) {
 +    // Case 1: Neither are scarce
 +    casename = "Case 1";
 +    if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) {
 +      log_warn(LD_DIR,
 +               "Bw Weight Failure for %s: Etotal %lf != Mtotal %lf. "
 +               "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +               " T="I64_FORMAT". "
 +               "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +               casename, Etotal, Mtotal,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +               Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +      valid = 0;
 +    }
 +    if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +      log_warn(LD_DIR,
 +               "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +               "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +               " T="I64_FORMAT". "
 +               "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +               casename, Etotal, Gtotal,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +               Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +      valid = 0;
 +    }
 +    if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) {
 +      log_warn(LD_DIR,
 +               "Bw Weight Failure for %s: Mtotal %lf != Gtotal %lf. "
 +               "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +               " T="I64_FORMAT". "
 +               "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +               casename, Mtotal, Gtotal,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +               Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +      valid = 0;
 +    }
 +  } else if (3*E < T && 3*G < T) {
 +    int64_t R = MIN(E, G);
 +    int64_t S = MAX(E, G);
 +    /*
 +     * Case 2: Both Guards and Exits are scarce
 +     * Balance D between E and G, depending upon
 +     * D capacity and scarcity. Devote no extra
 +     * bandwidth to middle nodes.
 +     */
 +    if (R+D < S) { // Subcase a
 +      double Rtotal, Stotal;
 +      if (E < G) {
 +        Rtotal = Etotal;
 +        Stotal = Gtotal;
 +      } else {
 +        Rtotal = Gtotal;
 +        Stotal = Etotal;
 +      }
 +      casename = "Case 2a";
 +      // Rtotal < Stotal
 +      if (Rtotal > Stotal) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Rtotal %lf > Stotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Rtotal, Stotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      // Rtotal < T/3
 +      if (3*Rtotal > T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Rtotal %lf > T "
 +                   I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                   " D="I64_FORMAT" T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Rtotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      // Stotal < T/3
 +      if (3*Stotal > T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Stotal %lf > T "
 +                   I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                   " D="I64_FORMAT" T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Stotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      // Mtotal > T/3
 +      if (3*Mtotal < T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Mtotal %lf < T "
 +                   I64_FORMAT". "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Mtotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +    } else { // Subcase b: R+D > S
 +      casename = "Case 2b";
 +
 +      /* Check the rare-M redirect case. */
 +      if (D != 0 && 3*M < T) {
 +        casename = "Case 2b (balanced)";
 +        if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Etotal %lf != Mtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Etotal, Mtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +        if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Etotal, Gtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +        if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Mtotal %lf != Gtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Mtotal, Gtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      } else {
 +        if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Etotal, Gtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      }
 +    }
 +  } else { // if (E < T/3 || G < T/3) {
 +    int64_t S = MIN(E, G);
 +    int64_t NS = MAX(E, G);
 +    if (3*(S+D) < T) { // Subcase a:
 +      double Stotal;
 +      double NStotal;
 +      if (G < E) {
 +        casename = "Case 3a (G scarce)";
 +        Stotal = Gtotal;
 +        NStotal = Etotal;
 +      } else { // if (G >= E) {
 +        casename = "Case 3a (E scarce)";
 +        NStotal = Gtotal;
 +        Stotal = Etotal;
 +      }
 +      // Stotal < T/3
 +      if (3*Stotal > T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Stotal %lf > T "
 +                   I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                   " D="I64_FORMAT" T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Stotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      if (NS >= M) {
 +        if (fabs(NStotal-Mtotal) > 0.01*MAX(NStotal,Mtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: NStotal %lf != Mtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, NStotal, Mtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      } else {
 +        // if NS < M, NStotal > T/3 because only one of G or E is scarce
 +        if (3*NStotal < T) {
 +          log_warn(LD_DIR,
 +                     "Bw Weight Failure for %s: 3*NStotal %lf < T "
 +                     I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT
 +                     " E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT". "
 +                     "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                     casename, NStotal*3, I64_PRINTF_ARG(T),
 +                     I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                     I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                     Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      }
 +    } else { // Subcase b: S+D >= T/3
 +      casename = "Case 3b";
 +      if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) {
 +        log_warn(LD_DIR,
 +                 "Bw Weight Failure for %s: Etotal %lf != Mtotal %lf. "
 +                 "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                 " T="I64_FORMAT". "
 +                 "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                 casename, Etotal, Mtotal,
 +                 I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                 I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                 Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +        log_warn(LD_DIR,
 +                 "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +                 "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                 " T="I64_FORMAT". "
 +                 "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                 casename, Etotal, Gtotal,
 +                 I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                 I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                 Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) {
 +        log_warn(LD_DIR,
 +                 "Bw Weight Failure for %s: Mtotal %lf != Gtotal %lf. "
 +                 "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                 " T="I64_FORMAT". "
 +                 "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                 casename, Mtotal, Gtotal,
 +                 I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                 I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                 Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +    }
 +  }
 +
 +  if (valid)
 +    log_notice(LD_DIR, "Bandwidth-weight %s is verified and valid.",
 +               casename);
 +
 +  return valid;
 +}
 +
  /** Parse a v3 networkstatus vote, opinion, or consensus (depending on
   * ns_type), from <b>s</b>, and return the result.  Return NULL on failure. */
  networkstatus_t *
@@@ -2761,21 -2184,19 +2761,21 @@@ networkstatus_parse_vote_from_string(co
    smartlist_t *rs_tokens = NULL, *footer_tokens = NULL;
    networkstatus_voter_info_t *voter = NULL;
    networkstatus_t *ns = NULL;
 -  char ns_digest[DIGEST_LEN];
 -  const char *cert, *end_of_header, *end_of_footer;
 +  digests_t ns_digests;
 +  const char *cert, *end_of_header, *end_of_footer, *s_dup = s;
    directory_token_t *tok;
    int ok;
    struct in_addr in;
    int i, inorder, n_signatures = 0;
    memarea_t *area = NULL, *rs_area = NULL;
 +  consensus_flavor_t flav = FLAV_NS;
 +
    tor_assert(s);
  
    if (eos_out)
      *eos_out = NULL;
  
 -  if (router_get_networkstatus_v3_hash(s, ns_digest)) {
 +  if (router_get_networkstatus_v3_hashes(s, &ns_digests)) {
      log_warn(LD_DIR, "Unable to compute digest of network-status");
      goto err;
    }
@@@ -2791,23 -2212,7 +2791,23 @@@
    }
  
    ns = tor_malloc_zero(sizeof(networkstatus_t));
 -  memcpy(ns->networkstatus_digest, ns_digest, DIGEST_LEN);
 +  memcpy(&ns->digests, &ns_digests, sizeof(ns_digests));
 +
 +  tok = find_by_keyword(tokens, K_NETWORK_STATUS_VERSION);
 +  tor_assert(tok);
 +  if (tok->n_args > 1) {
 +    int flavor = networkstatus_parse_flavor_name(tok->args[1]);
 +    if (flavor < 0) {
 +      log_warn(LD_DIR, "Can't parse document with unknown flavor %s",
 +               escaped(tok->args[2]));
 +      goto err;
 +    }
 +    ns->flavor = flav = flavor;
 +  }
 +  if (flav != FLAV_NS && ns_type != NS_TYPE_CONSENSUS) {
 +    log_warn(LD_DIR, "Flavor found on non-consenus networkstatus.");
 +    goto err;
 +  }
  
    if (ns_type != NS_TYPE_CONSENSUS) {
      const char *end_of_cert = NULL;
@@@ -2961,9 -2366,8 +2961,9 @@@
        if (voter)
          smartlist_add(ns->voters, voter);
        voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
 +      voter->sigs = smartlist_create();
        if (ns->type != NS_TYPE_CONSENSUS)
 -        memcpy(voter->vote_digest, ns_digest, DIGEST_LEN);
 +        memcpy(voter->vote_digest, ns_digests.d[DIGEST_SHA1], DIGEST_LEN);
  
        voter->nickname = tor_strdup(tok->args[0]);
        if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
@@@ -3055,7 -2459,7 +3055,7 @@@
      if (ns->type != NS_TYPE_CONSENSUS) {
        vote_routerstatus_t *rs = tor_malloc_zero(sizeof(vote_routerstatus_t));
        if (routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens, ns,
 -                                               rs, 0))
 +                                               rs, 0, 0))
          smartlist_add(ns->routerstatus_list, rs);
        else {
          tor_free(rs->version);
@@@ -3065,8 -2469,7 +3065,8 @@@
        routerstatus_t *rs;
        if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens,
                                                       NULL, NULL,
 -                                                     ns->consensus_method)))
 +                                                     ns->consensus_method,
 +                                                     flav)))
          smartlist_add(ns->routerstatus_list, rs);
      }
    }
@@@ -3099,73 -2502,14 +3099,73 @@@
      goto err;
    }
  
 -  SMARTLIST_FOREACH(footer_tokens, directory_token_t *, _tok,
    {
 +    int found_sig = 0;
 +    SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) {
 +      tok = _tok;
 +      if (tok->tp == K_DIRECTORY_SIGNATURE)
 +        found_sig = 1;
 +      else if (found_sig) {
 +        log_warn(LD_DIR, "Extraneous token after first directory-signature");
 +        goto err;
 +      }
 +    } SMARTLIST_FOREACH_END(_tok);
 +  }
 +
 +  if ((tok = find_opt_by_keyword(footer_tokens, K_DIRECTORY_FOOTER))) {
 +    if (tok != smartlist_get(footer_tokens, 0)) {
 +      log_warn(LD_DIR, "Misplaced directory-footer token");
 +      goto err;
 +    }
 +  }
 +
 +  tok = find_opt_by_keyword(footer_tokens, K_BW_WEIGHTS);
 +  if (tok) {
 +    ns->weight_params = smartlist_create();
 +    for (i = 0; i < tok->n_args; ++i) {
 +      int ok=0;
 +      char *eq = strchr(tok->args[i], '=');
 +      if (!eq) {
 +        log_warn(LD_DIR, "Bad element '%s' in weight params",
 +                 escaped(tok->args[i]));
 +        goto err;
 +      }
 +      tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL);
 +      if (!ok) {
 +        log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
 +        goto err;
 +      }
 +      smartlist_add(ns->weight_params, tor_strdup(tok->args[i]));
 +    }
 +  }
 +
 +  SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) {
      char declared_identity[DIGEST_LEN];
      networkstatus_voter_info_t *v;
 +    document_signature_t *sig;
 +    const char *id_hexdigest = NULL;
 +    const char *sk_hexdigest = NULL;
 +    digest_algorithm_t alg = DIGEST_SHA1;
      tok = _tok;
      if (tok->tp != K_DIRECTORY_SIGNATURE)
        continue;
      tor_assert(tok->n_args >= 2);
 +    if (tok->n_args == 2) {
 +      id_hexdigest = tok->args[0];
 +      sk_hexdigest = tok->args[1];
 +    } else {
 +      const char *algname = tok->args[0];
 +      int a;
 +      id_hexdigest = tok->args[1];
 +      sk_hexdigest = tok->args[2];
 +      a = crypto_digest_algorithm_parse_name(algname);
 +      if (a<0) {
 +        log_warn(LD_DIR, "Unknown digest algorithm %s; skipping",
 +                 escaped(algname));
 +        continue;
 +      }
 +      alg = a;
 +    }
  
      if (!tok->object_type ||
          strcmp(tok->object_type, "SIGNATURE") ||
@@@ -3174,11 -2518,11 +3174,11 @@@
        goto err;
      }
  
 -    if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
 +    if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
          base16_decode(declared_identity, sizeof(declared_identity),
 -                      tok->args[0], HEX_DIGEST_LEN) < 0) {
 +                      id_hexdigest, HEX_DIGEST_LEN) < 0) {
        log_warn(LD_DIR, "Error decoding declared identity %s in "
 -               "network-status vote.", escaped(tok->args[0]));
 +               "network-status vote.", escaped(id_hexdigest));
        goto err;
      }
      if (!(v = networkstatus_get_voter_by_id(ns, declared_identity))) {
@@@ -3186,15 -2530,11 +3186,15 @@@
                 "any declared directory source.");
        goto err;
      }
 -    if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
 -        base16_decode(v->signing_key_digest, sizeof(v->signing_key_digest),
 -                      tok->args[1], HEX_DIGEST_LEN) < 0) {
 -      log_warn(LD_DIR, "Error decoding declared digest %s in "
 -               "network-status vote.", escaped(tok->args[1]));
 +    sig = tor_malloc_zero(sizeof(document_signature_t));
 +    memcpy(sig->identity_digest, v->identity_digest, DIGEST_LEN);
 +    sig->alg = alg;
 +    if (strlen(sk_hexdigest) != HEX_DIGEST_LEN ||
 +        base16_decode(sig->signing_key_digest, sizeof(sig->signing_key_digest),
 +                      sk_hexdigest, HEX_DIGEST_LEN) < 0) {
 +      log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
 +               "network-status vote.", escaped(sk_hexdigest));
 +      tor_free(sig);
        goto err;
      }
  
@@@ -3203,49 -2543,35 +3203,49 @@@
                   DIGEST_LEN)) {
          log_warn(LD_DIR, "Digest mismatch between declared and actual on "
                   "network-status vote.");
 +        tor_free(sig);
          goto err;
        }
      }
  
 +    if (voter_get_sig_by_algorithm(v, sig->alg)) {
 +      /* We already parsed a vote with this algorithm from this voter. Use the
 +         first one. */
 +      log_fn(LOG_PROTOCOL_WARN, LD_DIR, "We received a networkstatus "
 +             "that contains two votes from the same voter with the same "
 +             "algorithm. Ignoring the second vote.");
 +      tor_free(sig);
 +      continue;
 +    }
 +
      if (ns->type != NS_TYPE_CONSENSUS) {
 -      if (check_signature_token(ns_digest, tok, ns->cert->signing_key, 0,
 -                                "network-status vote"))
 +      if (check_signature_token(ns_digests.d[DIGEST_SHA1], DIGEST_LEN,
 +                                tok, ns->cert->signing_key, 0,
 +                                "network-status vote")) {
 +        tor_free(sig);
          goto err;
 -      v->good_signature = 1;
 +      }
 +      sig->good_signature = 1;
      } else {
-       if (tok->object_size >= INT_MAX) {
 -      if (tok->object_size >= INT_MAX || tok->object_size >= SIZE_T_CEILING)
++      if (tok->object_size >= INT_MAX || tok->object_size >= SIZE_T_CEILING) {
 +        tor_free(sig);
          goto err;
 -      /* We already parsed a vote from this voter. Use the first one. */
 -      if (v->signature) {
 -        log_fn(LOG_PROTOCOL_WARN, LD_DIR, "We received a networkstatus "
 -                   "that contains two votes from the same voter. Ignoring "
 -                   "the second vote.");
 -        continue;
        }
 -
 -      v->signature = tor_memdup(tok->object_body, tok->object_size);
 -      v->signature_len = (int) tok->object_size;
 +      sig->signature = tor_memdup(tok->object_body, tok->object_size);
 +      sig->signature_len = (int) tok->object_size;
      }
 +    smartlist_add(v->sigs, sig);
 +
      ++n_signatures;
 -  });
 +  } SMARTLIST_FOREACH_END(_tok);
  
    if (! n_signatures) {
      log_warn(LD_DIR, "No signatures on networkstatus vote.");
      goto err;
 +  } else if (ns->type == NS_TYPE_VOTE && n_signatures != 1) {
 +    log_warn(LD_DIR, "Received more than one signature on a "
 +             "network-status vote.");
 +    goto err;
    }
  
    if (eos_out)
@@@ -3253,31 -2579,27 +3253,31 @@@
  
    goto done;
   err:
 -  if (ns)
 -    networkstatus_vote_free(ns);
 +  dump_desc(s_dup, "v3 networkstatus");
 +  networkstatus_vote_free(ns);
    ns = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (voter) {
 +    if (voter->sigs) {
 +      SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig,
 +                        document_signature_free(sig));
 +      smartlist_free(voter->sigs);
 +    }
      tor_free(voter->nickname);
      tor_free(voter->address);
      tor_free(voter->contact);
      tor_free(voter);
    }
    if (rs_tokens) {
 -    SMARTLIST_FOREACH(rs_tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(rs_tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(rs_tokens);
    }
    if (footer_tokens) {
 -    SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(footer_tokens);
    }
    if (area) {
@@@ -3290,35 -2612,6 +3290,35 @@@
    return ns;
  }
  
 +/** Return the digests_t that holds the digests of the
 + * <b>flavor_name</b>-flavored networkstatus according to the detached
 + * signatures document <b>sigs</b>, allocating a new digests_t as neeeded. */
 +static digests_t *
 +detached_get_digests(ns_detached_signatures_t *sigs, const char *flavor_name)
 +{
 +  digests_t *d = strmap_get(sigs->digests, flavor_name);
 +  if (!d) {
 +    d = tor_malloc_zero(sizeof(digests_t));
 +    strmap_set(sigs->digests, flavor_name, d);
 +  }
 +  return d;
 +}
 +
 +/** Return the list of signatures of the <b>flavor_name</b>-flavored
 + * networkstatus according to the detached signatures document <b>sigs</b>,
 + * allocating a new digests_t as neeeded. */
 +static smartlist_t *
 +detached_get_signatures(ns_detached_signatures_t *sigs,
 +                        const char *flavor_name)
 +{
 +  smartlist_t *sl = strmap_get(sigs->signatures, flavor_name);
 +  if (!sl) {
 +    sl = smartlist_create();
 +    strmap_set(sigs->signatures, flavor_name, sl);
 +  }
 +  return sl;
 +}
 +
  /** Parse a detached v3 networkstatus signature document between <b>s</b> and
   * <b>eos</b> and return the result.  Return -1 on failure. */
  ns_detached_signatures_t *
@@@ -3328,13 -2621,10 +3328,13 @@@ networkstatus_parse_detached_signatures
     * networkstatus_parse_vote_from_string(). */
    directory_token_t *tok;
    memarea_t *area = NULL;
 +  digests_t *digests;
  
    smartlist_t *tokens = smartlist_create();
    ns_detached_signatures_t *sigs =
      tor_malloc_zero(sizeof(ns_detached_signatures_t));
 +  sigs->digests = strmap_new();
 +  sigs->signatures = strmap_new();
  
    if (!eos)
      eos = s + strlen(s);
@@@ -3346,57 -2636,18 +3346,57 @@@
      goto err;
    }
  
 -  tok = find_by_keyword(tokens, K_CONSENSUS_DIGEST);
 -  if (strlen(tok->args[0]) != HEX_DIGEST_LEN) {
 -    log_warn(LD_DIR, "Wrong length on consensus-digest in detached "
 -             "networkstatus signatures");
 -    goto err;
 -  }
 -  if (base16_decode(sigs->networkstatus_digest, DIGEST_LEN,
 -                    tok->args[0], strlen(tok->args[0])) < 0) {
 -    log_warn(LD_DIR, "Bad encoding on on consensus-digest in detached "
 -             "networkstatus signatures");
 -    goto err;
 -  }
 +  /* Grab all the digest-like tokens. */
 +  SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {
 +    const char *algname;
 +    digest_algorithm_t alg;
 +    const char *flavor;
 +    const char *hexdigest;
 +    size_t expected_length;
 +
 +    tok = _tok;
 +
 +    if (tok->tp == K_CONSENSUS_DIGEST) {
 +      algname = "sha1";
 +      alg = DIGEST_SHA1;
 +      flavor = "ns";
 +      hexdigest = tok->args[0];
 +    } else if (tok->tp == K_ADDITIONAL_DIGEST) {
 +      int a = crypto_digest_algorithm_parse_name(tok->args[1]);
 +      if (a<0) {
 +        log_warn(LD_DIR, "Unrecognized algorithm name %s", tok->args[0]);
 +        continue;
 +      }
 +      alg = (digest_algorithm_t) a;
 +      flavor = tok->args[0];
 +      algname = tok->args[1];
 +      hexdigest = tok->args[2];
 +    } else {
 +      continue;
 +    }
 +
 +    expected_length =
 +      (alg == DIGEST_SHA1) ? HEX_DIGEST_LEN : HEX_DIGEST256_LEN;
 +
 +    if (strlen(hexdigest) != expected_length) {
 +      log_warn(LD_DIR, "Wrong length on consensus-digest in detached "
 +               "networkstatus signatures");
 +      goto err;
 +    }
 +    digests = detached_get_digests(sigs, flavor);
 +    tor_assert(digests);
 +    if (!tor_mem_is_zero(digests->d[alg], DIGEST256_LEN)) {
 +      log_warn(LD_DIR, "Multiple digests for %s with %s on detached "
 +               "signatures document", flavor, algname);
 +      continue;
 +    }
 +    if (base16_decode(digests->d[alg], DIGEST256_LEN,
 +                      hexdigest, strlen(hexdigest)) < 0) {
 +      log_warn(LD_DIR, "Bad encoding on consensus-digest in detached "
 +               "networkstatus signatures");
 +      goto err;
 +    }
 +  } SMARTLIST_FOREACH_END(_tok);
  
    tok = find_by_keyword(tokens, K_VALID_AFTER);
    if (parse_iso_time(tok->args[0], &sigs->valid_after)) {
@@@ -3416,102 -2667,57 +3416,102 @@@
      goto err;
    }
  
 -  sigs->signatures = smartlist_create();
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, _tok,
 -    {
 -      char id_digest[DIGEST_LEN];
 -      char sk_digest[DIGEST_LEN];
 -      networkstatus_voter_info_t *voter;
 +  SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {
 +    const char *id_hexdigest;
 +    const char *sk_hexdigest;
 +    const char *algname;
 +    const char *flavor;
 +    digest_algorithm_t alg;
 +
 +    char id_digest[DIGEST_LEN];
 +    char sk_digest[DIGEST_LEN];
 +    smartlist_t *siglist;
 +    document_signature_t *sig;
 +    int is_duplicate;
  
 -      tok = _tok;
 -      if (tok->tp != K_DIRECTORY_SIGNATURE)
 -        continue;
 +    tok = _tok;
 +    if (tok->tp == K_DIRECTORY_SIGNATURE) {
        tor_assert(tok->n_args >= 2);
 +      flavor = "ns";
 +      algname = "sha1";
 +      id_hexdigest = tok->args[0];
 +      sk_hexdigest = tok->args[1];
 +    } else if (tok->tp == K_ADDITIONAL_SIGNATURE) {
 +      tor_assert(tok->n_args >= 4);
 +      flavor = tok->args[0];
 +      algname = tok->args[1];
 +      id_hexdigest = tok->args[2];
 +      sk_hexdigest = tok->args[3];
 +    } else {
 +      continue;
 +    }
  
 -      if (!tok->object_type ||
 -          strcmp(tok->object_type, "SIGNATURE") ||
 -          tok->object_size < 128 || tok->object_size > 512) {
 -        log_warn(LD_DIR, "Bad object type or length on directory-signature");
 -        goto err;
 +    {
 +      int a = crypto_digest_algorithm_parse_name(algname);
 +      if (a<0) {
 +        log_warn(LD_DIR, "Unrecognized algorithm name %s", algname);
 +        continue;
        }
 +      alg = (digest_algorithm_t) a;
 +    }
  
 -      if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
 -          base16_decode(id_digest, sizeof(id_digest),
 -                        tok->args[0], HEX_DIGEST_LEN) < 0) {
 -        log_warn(LD_DIR, "Error decoding declared identity %s in "
 -                 "network-status vote.", escaped(tok->args[0]));
 -        goto err;
 -      }
 -      if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
 -          base16_decode(sk_digest, sizeof(sk_digest),
 -                        tok->args[1], HEX_DIGEST_LEN) < 0) {
 -        log_warn(LD_DIR, "Error decoding declared digest %s in "
 -                 "network-status vote.", escaped(tok->args[1]));
 -        goto err;
 -      }
 +    if (!tok->object_type ||
 +        strcmp(tok->object_type, "SIGNATURE") ||
 +        tok->object_size < 128 || tok->object_size > 512) {
 +      log_warn(LD_DIR, "Bad object type or length on directory-signature");
 +      goto err;
 +    }
  
 -      voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
 -      memcpy(voter->identity_digest, id_digest, DIGEST_LEN);
 -      memcpy(voter->signing_key_digest, sk_digest, DIGEST_LEN);
 -      if (tok->object_size >= INT_MAX || tok->object_size >= SIZE_T_CEILING)
 -        goto err;
 -      voter->signature = tor_memdup(tok->object_body, tok->object_size);
 -      voter->signature_len = (int) tok->object_size;
 +    if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
 +        base16_decode(id_digest, sizeof(id_digest),
 +                      id_hexdigest, HEX_DIGEST_LEN) < 0) {
 +      log_warn(LD_DIR, "Error decoding declared identity %s in "
 +               "network-status vote.", escaped(id_hexdigest));
 +      goto err;
 +    }
 +    if (strlen(sk_hexdigest) != HEX_DIGEST_LEN ||
 +        base16_decode(sk_digest, sizeof(sk_digest),
 +                      sk_hexdigest, HEX_DIGEST_LEN) < 0) {
 +      log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
 +               "network-status vote.", escaped(sk_hexdigest));
 +      goto err;
 +    }
  
 -      smartlist_add(sigs->signatures, voter);
 +    siglist = detached_get_signatures(sigs, flavor);
 +    is_duplicate = 0;
 +    SMARTLIST_FOREACH(siglist, document_signature_t *, s, {
 +      if (s->alg == alg &&
 +          !memcmp(id_digest, s->identity_digest, DIGEST_LEN) &&
 +          !memcmp(sk_digest, s->signing_key_digest, DIGEST_LEN)) {
 +        is_duplicate = 1;
 +      }
      });
 +    if (is_duplicate) {
 +      log_warn(LD_DIR, "Two signatures with identical keys and algorithm "
 +               "found.");
 +      continue;
 +    }
 +
 +    sig = tor_malloc_zero(sizeof(document_signature_t));
 +    sig->alg = alg;
 +    memcpy(sig->identity_digest, id_digest, DIGEST_LEN);
 +    memcpy(sig->signing_key_digest, sk_digest, DIGEST_LEN);
-     if (tok->object_size >= INT_MAX) {
++    if (tok->object_size >= INT_MAX || tok->object_size >= SIZE_T_CEILING) {
 +      tor_free(sig);
 +      goto err;
 +    }
 +    sig->signature = tor_memdup(tok->object_body, tok->object_size);
 +    sig->signature_len = (int) tok->object_size;
 +
 +    smartlist_add(siglist, sig);
 +  } SMARTLIST_FOREACH_END(_tok);
  
    goto done;
   err:
    ns_detached_signatures_free(sigs);
    sigs = NULL;
   done:
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area) {
      DUMP_AREA(area, "detached signatures");
@@@ -3567,7 -2773,7 +3567,7 @@@ router_parse_addr_policy_item_from_stri
   err:
    r = NULL;
   done:
 -  token_free(tok);
 +  token_clear(tok);
    if (area) {
      DUMP_AREA(area, "policy item");
      memarea_drop_all(area);
@@@ -3690,8 -2896,9 +3690,8 @@@ assert_addr_policy_ok(smartlist_t *lst
  
  /** Free all resources allocated for <b>tok</b> */
  static void
 -token_free(directory_token_t *tok)
 +token_clear(directory_token_t *tok)
  {
 -  tor_assert(tok);
    if (tok->key)
      crypto_free_pk_env(tok->key);
  }
@@@ -3703,7 -2910,7 +3703,7 @@@
  
  #define RET_ERR(msg)                                               \
    STMT_BEGIN                                                       \
 -    if (tok) token_free(tok);                                      \
 +    if (tok) token_clear(tok);                                      \
      tok = ALLOC_ZERO(sizeof(directory_token_t));                   \
      tok->tp = _ERR;                                                \
      tok->error = STRDUP(msg);                                      \
@@@ -3814,6 -3021,10 +3814,10 @@@ static directory_token_t 
  get_next_token(memarea_t *area,
                 const char **s, const char *eos, token_rule_t *table)
  {
+   /** Reject any object at least this big; it is probably an overflow, an
+    * attack, a bug, or some other nonsense. */
+ #define MAX_UNPARSED_OBJECT_SIZE (128*1024)
+ 
    const char *next, *eol, *obstart;
    size_t obname_len;
    int i;
@@@ -3898,7 -3109,8 +3902,8 @@@
  
    obstart = *s; /* Set obstart to start of object spec */
    if (*s+16 >= eol || memchr(*s+11,'\0',eol-*s-16) || /* no short lines, */
-       strcmp_len(eol-5, "-----", 5)) {          /* nuls or invalid endings */
+       strcmp_len(eol-5, "-----", 5) ||           /* nuls or invalid endings */
+       (eol-*s) > MAX_UNPARSED_OBJECT_SIZE) {     /* name too long */
      RET_ERR("Malformed object: bad begin line");
    }
    tok->object_type = STRNDUP(*s+11, eol-*s-16);
@@@ -3923,13 -3135,16 +3928,16 @@@
      ebuf[sizeof(ebuf)-1] = '\0';
      RET_ERR(ebuf);
    }
+   if (next - *s > MAX_UNPARSED_OBJECT_SIZE)
+     RET_ERR("Couldn't parse object: missing footer or object much too big.");
+ 
    if (!strcmp(tok->object_type, "RSA PUBLIC KEY")) { /* If it's a public key */
      tok->key = crypto_new_pk_env();
      if (crypto_pk_read_public_key_from_string(tok->key, obstart, eol-obstart))
        RET_ERR("Couldn't parse public key.");
    } else if (!strcmp(tok->object_type, "RSA PRIVATE KEY")) { /* private key */
      tok->key = crypto_new_pk_env();
-     if (crypto_pk_read_private_key_from_string(tok->key, obstart))
+     if (crypto_pk_read_private_key_from_string(tok->key, obstart, eol-obstart))
        RET_ERR("Couldn't parse private key.");
    } else { /* If it's something else, try to base64-decode it */
      int r;
@@@ -3984,7 -3199,7 +3992,7 @@@ tokenize_string(memarea_t *area
      tok = get_next_token(area, s, end, table);
      if (tok->tp == _ERR) {
        log_warn(LD_DIR, "parse error: %s", tok->error);
 -      token_free(tok);
 +      token_clear(tok);
        return -1;
      }
      ++counts[tok->tp];
@@@ -4098,11 -3313,17 +4106,11 @@@ find_all_exitpolicy(smartlist_t *s
    return out;
  }
  
 -/** Compute the SHA-1 digest of the substring of <b>s</b> taken from the first
 - * occurrence of <b>start_str</b> through the first instance of c after the
 - * first subsequent occurrence of <b>end_str</b>; store the 20-byte result in
 - * <b>digest</b>; return 0 on success.
 - *
 - * If no such substring exists, return -1.
 - */
  static int
 -router_get_hash_impl(const char *s, size_t s_len, char *digest,
 +router_get_hash_impl_helper(const char *s, size_t s_len,
                              const char *start_str,
 -                            const char *end_str, char end_c)
 +                            const char *end_str, char end_c,
 +                            const char **start_out, const char **end_out)
  {
    const char *start, *end;
    start = tor_memstr(s, s_len, start_str);
@@@ -4129,214 -3350,14 +4137,214 @@@
    }
    ++end;
  
 -  if (crypto_digest(digest, start, end-start)) {
 -    log_warn(LD_BUG,"couldn't compute digest");
 +  *start_out = start;
 +  *end_out = end;
 +  return 0;
 +}
 +
 +/** Compute the digest of the substring of <b>s</b> taken from the first
 + * occurrence of <b>start_str</b> through the first instance of c after the
 + * first subsequent occurrence of <b>end_str</b>; store the 20-byte result in
 + * <b>digest</b>; return 0 on success.
 + *
 + * If no such substring exists, return -1.
 + */
 +static int
 +router_get_hash_impl(const char *s, size_t s_len, char *digest,
 +                     const char *start_str,
 +                     const char *end_str, char end_c,
 +                     digest_algorithm_t alg)
 +{
 +  const char *start=NULL, *end=NULL;
 +  if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,
 +                                  &start,&end)<0)
      return -1;
 +
 +  if (alg == DIGEST_SHA1) {
 +    if (crypto_digest(digest, start, end-start)) {
 +      log_warn(LD_BUG,"couldn't compute digest");
 +      return -1;
 +    }
 +  } else {
 +    if (crypto_digest256(digest, start, end-start, alg)) {
 +      log_warn(LD_BUG,"couldn't compute digest");
 +      return -1;
 +    }
    }
  
    return 0;
  }
  
 +/** As router_get_hash_impl, but compute all hashes. */
 +static int
 +router_get_hashes_impl(const char *s, size_t s_len, digests_t *digests,
 +                       const char *start_str,
 +                       const char *end_str, char end_c)
 +{
 +  const char *start=NULL, *end=NULL;
 +  if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,
 +                                  &start,&end)<0)
 +    return -1;
 +
 +  if (crypto_digest_all(digests, start, end-start)) {
 +    log_warn(LD_BUG,"couldn't compute digests");
 +    return -1;
 +  }
 +
 +  return 0;
 +}
 +
 +/** Assuming that s starts with a microdesc, return the start of the
 + * *NEXT* one.  Return NULL on "not found." */
 +static const char *
 +find_start_of_next_microdesc(const char *s, const char *eos)
 +{
 +  int started_with_annotations;
 +  s = eat_whitespace_eos(s, eos);
 +  if (!s)
 +    return NULL;
 +
 +#define CHECK_LENGTH() STMT_BEGIN \
 +    if (s+32 > eos)               \
 +      return NULL;                \
 +  STMT_END
 +
 +#define NEXT_LINE() STMT_BEGIN            \
 +    s = memchr(s, '\n', eos-s);           \
 +    if (!s || s+1 >= eos)                 \
 +      return NULL;                        \
 +    s++;                                  \
 +  STMT_END
 +
 +  CHECK_LENGTH();
 +
 +  started_with_annotations = (*s == '@');
 +
 +  if (started_with_annotations) {
 +    /* Start by advancing to the first non-annotation line. */
 +    while (*s == '@')
 +      NEXT_LINE();
 +  }
 +  CHECK_LENGTH();
 +
 +  /* Now we should be pointed at an onion-key line.  If we are, then skip
 +   * it. */
 +  if (!strcmpstart(s, "onion-key"))
 +    NEXT_LINE();
 +
 +  /* Okay, now we're pointed at the first line of the microdescriptor which is
 +     not an annotation or onion-key.  The next line that _is_ an annotation or
 +     onion-key is the start of the next microdescriptor. */
 +  while (s+32 < eos) {
 +    if (*s == '@' || !strcmpstart(s, "onion-key"))
 +      return s;
 +    NEXT_LINE();
 +  }
 +  return NULL;
 +
 +#undef CHECK_LENGTH
 +#undef NEXT_LINE
 +}
 +
 +/** Parse as many microdescriptors as are found from the string starting at
 + * <b>s</b> and ending at <b>eos</b>.  If allow_annotations is set, read any
 + * annotations we recognize and ignore ones we don't.  If <b>copy_body</b> is
 + * true, then strdup the bodies of the microdescriptors.  Return all newly
 + * parsed microdescriptors in a newly allocated smartlist_t. */
 +smartlist_t *
 +microdescs_parse_from_string(const char *s, const char *eos,
 +                             int allow_annotations, int copy_body)
 +{
 +  smartlist_t *tokens;
 +  smartlist_t *result;
 +  microdesc_t *md = NULL;
 +  memarea_t *area;
 +  const char *start = s;
 +  const char *start_of_next_microdesc;
 +  int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0;
 +
 +  directory_token_t *tok;
 +
 +  if (!eos)
 +    eos = s + strlen(s);
 +
 +  s = eat_whitespace_eos(s, eos);
 +  area = memarea_new();
 +  result = smartlist_create();
 +  tokens = smartlist_create();
 +
 +  while (s < eos) {
 +    start_of_next_microdesc = find_start_of_next_microdesc(s, eos);
 +    if (!start_of_next_microdesc)
 +      start_of_next_microdesc = eos;
 +
 +    if (tokenize_string(area, s, start_of_next_microdesc, tokens,
 +                        microdesc_token_table, flags)) {
 +      log_warn(LD_DIR, "Unparseable microdescriptor");
 +      goto next;
 +    }
 +
 +    md = tor_malloc_zero(sizeof(microdesc_t));
 +    {
 +      const char *cp = tor_memstr(s, start_of_next_microdesc-s,
 +                                  "onion-key");
 +      tor_assert(cp);
 +
 +      md->bodylen = start_of_next_microdesc - cp;
 +      if (copy_body)
 +        md->body = tor_strndup(cp, md->bodylen);
 +      else
 +        md->body = (char*)cp;
 +      md->off = cp - start;
 +    }
 +
 +    if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) {
 +      if (parse_iso_time(tok->args[0], &md->last_listed)) {
 +        log_warn(LD_DIR, "Bad last-listed time in microdescriptor");
 +        goto next;
 +      }
 +    }
 +
 +    tok = find_by_keyword(tokens, K_ONION_KEY);
 +    md->onion_pkey = tok->key;
 +    tok->key = NULL;
 +
 +    if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) {
 +      int i;
 +      md->family = smartlist_create();
 +      for (i=0;i<tok->n_args;++i) {
 +        if (!is_legal_nickname_or_hexdigest(tok->args[i])) {
 +          log_warn(LD_DIR, "Illegal nickname %s in family line",
 +                   escaped(tok->args[i]));
 +          goto next;
 +        }
 +        smartlist_add(md->family, tor_strdup(tok->args[i]));
 +      }
 +    }
 +
 +    if ((tok = find_opt_by_keyword(tokens, K_P))) {
 +      md->exitsummary = tor_strdup(tok->args[0]);
 +    }
 +
 +    crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256);
 +
 +    smartlist_add(result, md);
 +
 +    md = NULL;
 +  next:
 +    microdesc_free(md);
 +
 +    memarea_clear(area);
 +    smartlist_clear(tokens);
 +    s = start_of_next_microdesc;
 +  }
 +
 +  memarea_drop_all(area);
 +  smartlist_free(tokens);
 +
 +  return result;
 +}
 +
  /** Parse the Tor version of the platform string <b>platform</b>,
   * and compare it to the version in <b>cutoff</b>. Return 1 if
   * the router is at least as new as the cutoff, else return 0.
@@@ -4361,7 -3382,7 +4369,7 @@@ tor_version_as_new_as(const char *platf
    if (!*start) return 0;
    s = (char *)find_whitespace(start); /* also finds '\0', which is fine */
    s2 = (char*)eat_whitespace(s);
 -  if (!strcmpstart(s2, "(r"))
 +  if (!strcmpstart(s2, "(r") || !strcmpstart(s2, "(git-"))
      s = (char*)find_whitespace(s2);
  
    if ((size_t)(s-start+1) >= sizeof(tmp)) /* too big, no */
@@@ -4457,23 -3478,6 +4465,23 @@@ tor_version_parse(const char *s, tor_ve
    if (!strcmpstart(cp, "(r")) {
      cp += 2;
      out->svn_revision = (int) strtol(cp,&eos,10);
 +  } else if (!strcmpstart(cp, "(git-")) {
 +    char *close_paren = strchr(cp, ')');
 +    int hexlen;
 +    char digest[DIGEST_LEN];
 +    if (! close_paren)
 +      return -1;
 +    cp += 5;
 +    if (close_paren-cp > HEX_DIGEST_LEN)
 +      return -1;
 +    hexlen = (int)(close_paren-cp);
 +    memset(digest, 0, sizeof(digest));
 +    if ( hexlen == 0 || (hexlen % 2) == 1)
 +      return -1;
 +    if (base16_decode(digest, hexlen/2, cp, hexlen))
 +      return -1;
 +    memcpy(out->git_tag, digest, hexlen/2);
 +    out->git_tag_len = hexlen/2;
    }
  
    return 0;
@@@ -4499,14 -3503,8 +4507,14 @@@ tor_version_compare(tor_version_t *a, t
      return i;
    else if ((i = strcmp(a->status_tag, b->status_tag)))
      return i;
 +  else if ((i = a->svn_revision - b->svn_revision))
 +    return i;
 +  else if ((i = a->git_tag_len - b->git_tag_len))
 +    return i;
 +  else if (a->git_tag_len)
 +    return memcmp(a->git_tag, b->git_tag, a->git_tag_len);
    else
 -    return a->svn_revision - b->svn_revision;
 +    return 0;
  }
  
  /** Return true iff versions <b>a</b> and <b>b</b> belong to the same series.
@@@ -4595,7 -3593,7 +4603,7 @@@ rend_parse_v2_service_descriptor(rend_s
    /* Compute descriptor hash for later validation. */
    if (router_get_hash_impl(desc, strlen(desc), desc_hash,
                             "rendezvous-service-descriptor ",
 -                           "\nsignature", '\n') < 0) {
 +                           "\nsignature", '\n', DIGEST_SHA1) < 0) {
      log_warn(LD_REND, "Couldn't compute descriptor hash.");
      goto err;
    }
@@@ -4714,7 -3712,7 +4722,7 @@@
    /* Parse and verify signature. */
    tok = find_by_keyword(tokens, R_SIGNATURE);
    note_crypto_pk_op(VERIFY_RTR);
 -  if (check_signature_token(desc_hash, tok, result->pk, 0,
 +  if (check_signature_token(desc_hash, DIGEST_LEN, tok, result->pk, 0,
                              "v2 rendezvous service descriptor") < 0)
      goto err;
    /* Verify that descriptor ID belongs to public key and secret ID part. */
@@@ -4728,11 -3726,12 +4736,11 @@@
    }
    goto done;
   err:
 -  if (result)
 -    rend_service_descriptor_free(result);
 +  rend_service_descriptor_free(result);
    result = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area)
@@@ -4890,7 -3889,7 +4898,7 @@@ rend_parse_introduction_points(rend_ser
        eos = eos+1;
      tor_assert(eos <= intro_points_encoded+intro_points_encoded_size);
      /* Free tokens and clear token list. */
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_clear(tokens);
      memarea_clear(area);
      /* Tokenize string. */
@@@ -4963,7 -3962,7 +4971,7 @@@
  
   done:
    /* Free tokens and clear token list. */
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area)
      memarea_drop_all(area);
@@@ -5002,7 -4001,7 +5010,7 @@@ rend_parse_client_keys(strmap_t *parsed
      else
        eos = eos + 1;
      /* Free tokens and clear token list. */
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_clear(tokens);
      memarea_clear(area);
      /* Tokenize string. */
@@@ -5074,7 -4073,7 +5082,7 @@@
    result = -1;
   done:
    /* Free tokens and clear token list. */
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area)
      memarea_drop_all(area);
diff --combined src/test/test_dir.c
index c7c5a0b,0000000..e618150
mode 100644,000000..100644
--- a/src/test/test_dir.c
+++ b/src/test/test_dir.c
@@@ -1,1317 -1,0 +1,1317 @@@
 +/* Copyright (c) 2001-2004, Roger Dingledine.
 + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
 + * Copyright (c) 2007-2011, The Tor Project, Inc. */
 +/* See LICENSE for licensing information */
 +
 +#include "orconfig.h"
 +#define DIRSERV_PRIVATE
 +#define DIRVOTE_PRIVATE
 +#define ROUTER_PRIVATE
 +#include "or.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "networkstatus.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
 +#include "test.h"
 +
 +static void
 +test_dir_nicknames(void)
 +{
 +  test_assert( is_legal_nickname("a"));
 +  test_assert(!is_legal_nickname(""));
 +  test_assert(!is_legal_nickname("abcdefghijklmnopqrst")); /* 20 chars */
 +  test_assert(!is_legal_nickname("hyphen-")); /* bad char */
 +  test_assert( is_legal_nickname("abcdefghijklmnopqrs")); /* 19 chars */
 +  test_assert(!is_legal_nickname("$AAAAAAAA01234AAAAAAAAAAAAAAAAAAAAAAAAAAA"));
 +  /* valid */
 +  test_assert( is_legal_nickname_or_hexdigest(
 +                                 "$AAAAAAAA01234AAAAAAAAAAAAAAAAAAAAAAAAAAA"));
 +  test_assert( is_legal_nickname_or_hexdigest(
 +                         "$AAAAAAAA01234AAAAAAAAAAAAAAAAAAAAAAAAAAA=fred"));
 +  test_assert( is_legal_nickname_or_hexdigest(
 +                         "$AAAAAAAA01234AAAAAAAAAAAAAAAAAAAAAAAAAAA~fred"));
 +  /* too short */
 +  test_assert(!is_legal_nickname_or_hexdigest(
 +                                 "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
 +  /* illegal char */
 +  test_assert(!is_legal_nickname_or_hexdigest(
 +                                 "$AAAAAAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
 +  /* hex part too long */
 +  test_assert(!is_legal_nickname_or_hexdigest(
 +                         "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
 +  test_assert(!is_legal_nickname_or_hexdigest(
 +                         "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=fred"));
 +  /* Bad nickname */
 +  test_assert(!is_legal_nickname_or_hexdigest(
 +                         "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="));
 +  test_assert(!is_legal_nickname_or_hexdigest(
 +                         "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA~"));
 +  test_assert(!is_legal_nickname_or_hexdigest(
 +                       "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA~hyphen-"));
 +  test_assert(!is_legal_nickname_or_hexdigest(
 +                       "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA~"
 +                       "abcdefghijklmnoppqrst"));
 +  /* Bad extra char. */
 +  test_assert(!is_legal_nickname_or_hexdigest(
 +                         "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!"));
 +  test_assert(is_legal_nickname_or_hexdigest("xyzzy"));
 +  test_assert(is_legal_nickname_or_hexdigest("abcdefghijklmnopqrs"));
 +  test_assert(!is_legal_nickname_or_hexdigest("abcdefghijklmnopqrst"));
 + done:
 +  ;
 +}
 +
 +/** Run unit tests for router descriptor generation logic. */
 +static void
 +test_dir_formats(void)
 +{
 +  char buf[8192], buf2[8192];
 +  char platform[256];
 +  char fingerprint[FINGERPRINT_LEN+1];
 +  char *pk1_str = NULL, *pk2_str = NULL, *pk3_str = NULL, *cp;
 +  size_t pk1_str_len, pk2_str_len, pk3_str_len;
 +  routerinfo_t *r1=NULL, *r2=NULL;
 +  crypto_pk_env_t *pk1 = NULL, *pk2 = NULL, *pk3 = NULL;
 +  routerinfo_t *rp1 = NULL;
 +  addr_policy_t *ex1, *ex2;
 +  routerlist_t *dir1 = NULL, *dir2 = NULL;
 +
 +  pk1 = pk_generate(0);
 +  pk2 = pk_generate(1);
 +  pk3 = pk_generate(2);
 +
 +  test_assert(pk1 && pk2 && pk3);
 +
 +  get_platform_str(platform, sizeof(platform));
 +  r1 = tor_malloc_zero(sizeof(routerinfo_t));
 +  r1->address = tor_strdup("18.244.0.1");
 +  r1->addr = 0xc0a80001u; /* 192.168.0.1 */
 +  r1->cache_info.published_on = 0;
 +  r1->or_port = 9000;
 +  r1->dir_port = 9003;
 +  r1->onion_pkey = crypto_pk_dup_key(pk1);
 +  r1->identity_pkey = crypto_pk_dup_key(pk2);
 +  r1->bandwidthrate = 1000;
 +  r1->bandwidthburst = 5000;
 +  r1->bandwidthcapacity = 10000;
 +  r1->exit_policy = NULL;
 +  r1->nickname = tor_strdup("Magri");
 +  r1->platform = tor_strdup(platform);
 +
 +  ex1 = tor_malloc_zero(sizeof(addr_policy_t));
 +  ex2 = tor_malloc_zero(sizeof(addr_policy_t));
 +  ex1->policy_type = ADDR_POLICY_ACCEPT;
 +  tor_addr_from_ipv4h(&ex1->addr, 0);
 +  ex1->maskbits = 0;
 +  ex1->prt_min = ex1->prt_max = 80;
 +  ex2->policy_type = ADDR_POLICY_REJECT;
 +  tor_addr_from_ipv4h(&ex2->addr, 18<<24);
 +  ex2->maskbits = 8;
 +  ex2->prt_min = ex2->prt_max = 24;
 +  r2 = tor_malloc_zero(sizeof(routerinfo_t));
 +  r2->address = tor_strdup("1.1.1.1");
 +  r2->addr = 0x0a030201u; /* 10.3.2.1 */
 +  r2->platform = tor_strdup(platform);
 +  r2->cache_info.published_on = 5;
 +  r2->or_port = 9005;
 +  r2->dir_port = 0;
 +  r2->onion_pkey = crypto_pk_dup_key(pk2);
 +  r2->identity_pkey = crypto_pk_dup_key(pk1);
 +  r2->bandwidthrate = r2->bandwidthburst = r2->bandwidthcapacity = 3000;
 +  r2->exit_policy = smartlist_create();
 +  smartlist_add(r2->exit_policy, ex2);
 +  smartlist_add(r2->exit_policy, ex1);
 +  r2->nickname = tor_strdup("Fred");
 +
 +  test_assert(!crypto_pk_write_public_key_to_string(pk1, &pk1_str,
 +                                                    &pk1_str_len));
 +  test_assert(!crypto_pk_write_public_key_to_string(pk2 , &pk2_str,
 +                                                    &pk2_str_len));
 +  test_assert(!crypto_pk_write_public_key_to_string(pk3 , &pk3_str,
 +                                                    &pk3_str_len));
 +
 +  memset(buf, 0, 2048);
 +  test_assert(router_dump_router_to_string(buf, 2048, r1, pk2)>0);
 +
 +  strlcpy(buf2, "router Magri 18.244.0.1 9000 0 9003\n"
 +          "platform Tor "VERSION" on ", sizeof(buf2));
 +  strlcat(buf2, get_uname(), sizeof(buf2));
 +  strlcat(buf2, "\n"
 +          "opt protocols Link 1 2 Circuit 1\n"
 +          "published 1970-01-01 00:00:00\n"
 +          "opt fingerprint ", sizeof(buf2));
 +  test_assert(!crypto_pk_get_fingerprint(pk2, fingerprint, 1));
 +  strlcat(buf2, fingerprint, sizeof(buf2));
 +  strlcat(buf2, "\nuptime 0\n"
 +  /* XXX the "0" above is hard-coded, but even if we made it reflect
 +   * uptime, that still wouldn't make it right, because the two
 +   * descriptors might be made on different seconds... hm. */
 +         "bandwidth 1000 5000 10000\n"
 +          "onion-key\n", sizeof(buf2));
 +  strlcat(buf2, pk1_str, sizeof(buf2));
 +  strlcat(buf2, "signing-key\n", sizeof(buf2));
 +  strlcat(buf2, pk2_str, sizeof(buf2));
 +  strlcat(buf2, "opt hidden-service-dir\n", sizeof(buf2));
 +  strlcat(buf2, "reject *:*\nrouter-signature\n", sizeof(buf2));
 +  buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same
 +                             * twice */
 +
 +  test_streq(buf, buf2);
 +
 +  test_assert(router_dump_router_to_string(buf, 2048, r1, pk2)>0);
 +  cp = buf;
 +  rp1 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL);
 +  test_assert(rp1);
 +  test_streq(rp1->address, r1->address);
 +  test_eq(rp1->or_port, r1->or_port);
 +  //test_eq(rp1->dir_port, r1->dir_port);
 +  test_eq(rp1->bandwidthrate, r1->bandwidthrate);
 +  test_eq(rp1->bandwidthburst, r1->bandwidthburst);
 +  test_eq(rp1->bandwidthcapacity, r1->bandwidthcapacity);
 +  test_assert(crypto_pk_cmp_keys(rp1->onion_pkey, pk1) == 0);
 +  test_assert(crypto_pk_cmp_keys(rp1->identity_pkey, pk2) == 0);
 +  //test_assert(rp1->exit_policy == NULL);
 +
 +#if 0
 +  /* XXX Once we have exit policies, test this again. XXX */
 +  strlcpy(buf2, "router tor.tor.tor 9005 0 0 3000\n", sizeof(buf2));
 +  strlcat(buf2, pk2_str, sizeof(buf2));
 +  strlcat(buf2, "signing-key\n", sizeof(buf2));
 +  strlcat(buf2, pk1_str, sizeof(buf2));
 +  strlcat(buf2, "accept *:80\nreject 18.*:24\n\n", sizeof(buf2));
 +  test_assert(router_dump_router_to_string(buf, 2048, &r2, pk2)>0);
 +  test_streq(buf, buf2);
 +
 +  cp = buf;
 +  rp2 = router_parse_entry_from_string(&cp,1);
 +  test_assert(rp2);
 +  test_streq(rp2->address, r2.address);
 +  test_eq(rp2->or_port, r2.or_port);
 +  test_eq(rp2->dir_port, r2.dir_port);
 +  test_eq(rp2->bandwidth, r2.bandwidth);
 +  test_assert(crypto_pk_cmp_keys(rp2->onion_pkey, pk2) == 0);
 +  test_assert(crypto_pk_cmp_keys(rp2->identity_pkey, pk1) == 0);
 +  test_eq(rp2->exit_policy->policy_type, EXIT_POLICY_ACCEPT);
 +  test_streq(rp2->exit_policy->string, "accept *:80");
 +  test_streq(rp2->exit_policy->address, "*");
 +  test_streq(rp2->exit_policy->port, "80");
 +  test_eq(rp2->exit_policy->next->policy_type, EXIT_POLICY_REJECT);
 +  test_streq(rp2->exit_policy->next->string, "reject 18.*:24");
 +  test_streq(rp2->exit_policy->next->address, "18.*");
 +  test_streq(rp2->exit_policy->next->port, "24");
 +  test_assert(rp2->exit_policy->next->next == NULL);
 +
 +  /* Okay, now for the directories. */
 +  {
 +    fingerprint_list = smartlist_create();
 +    crypto_pk_get_fingerprint(pk2, buf, 1);
 +    add_fingerprint_to_dir("Magri", buf, fingerprint_list);
 +    crypto_pk_get_fingerprint(pk1, buf, 1);
 +    add_fingerprint_to_dir("Fred", buf, fingerprint_list);
 +  }
 +
 +  {
 +  char d[DIGEST_LEN];
 +  const char *m;
 +  /* XXXX NM re-enable. */
 +  /* Make sure routers aren't too far in the past any more. */
 +  r1->cache_info.published_on = time(NULL);
 +  r2->cache_info.published_on = time(NULL)-3*60*60;
 +  test_assert(router_dump_router_to_string(buf, 2048, r1, pk2)>0);
 +  test_eq(dirserv_add_descriptor(buf,&m,""), ROUTER_ADDED_NOTIFY_GENERATOR);
 +  test_assert(router_dump_router_to_string(buf, 2048, r2, pk1)>0);
 +  test_eq(dirserv_add_descriptor(buf,&m,""), ROUTER_ADDED_NOTIFY_GENERATOR);
 +  get_options()->Nickname = tor_strdup("DirServer");
 +  test_assert(!dirserv_dump_directory_to_string(&cp,pk3, 0));
 +  crypto_pk_get_digest(pk3, d);
 +  test_assert(!router_parse_directory(cp));
 +  test_eq(2, smartlist_len(dir1->routers));
 +  tor_free(cp);
 +  }
 +#endif
 +  dirserv_free_fingerprint_list();
 +
 + done:
 +  if (r1)
 +    routerinfo_free(r1);
 +  if (r2)
 +    routerinfo_free(r2);
 +
 +  tor_free(pk1_str);
 +  tor_free(pk2_str);
 +  tor_free(pk3_str);
 +  if (pk1) crypto_free_pk_env(pk1);
 +  if (pk2) crypto_free_pk_env(pk2);
 +  if (pk3) crypto_free_pk_env(pk3);
 +  if (rp1) routerinfo_free(rp1);
 +  tor_free(dir1); /* XXXX And more !*/
 +  tor_free(dir2); /* And more !*/
 +}
 +
 +static void
 +test_dir_versions(void)
 +{
 +  tor_version_t ver1;
 +
 +  /* Try out version parsing functionality */
 +  test_eq(0, tor_version_parse("0.3.4pre2-cvs", &ver1));
 +  test_eq(0, ver1.major);
 +  test_eq(3, ver1.minor);
 +  test_eq(4, ver1.micro);
 +  test_eq(VER_PRE, ver1.status);
 +  test_eq(2, ver1.patchlevel);
 +  test_eq(0, tor_version_parse("0.3.4rc1", &ver1));
 +  test_eq(0, ver1.major);
 +  test_eq(3, ver1.minor);
 +  test_eq(4, ver1.micro);
 +  test_eq(VER_RC, ver1.status);
 +  test_eq(1, ver1.patchlevel);
 +  test_eq(0, tor_version_parse("1.3.4", &ver1));
 +  test_eq(1, ver1.major);
 +  test_eq(3, ver1.minor);
 +  test_eq(4, ver1.micro);
 +  test_eq(VER_RELEASE, ver1.status);
 +  test_eq(0, ver1.patchlevel);
 +  test_eq(0, tor_version_parse("1.3.4.999", &ver1));
 +  test_eq(1, ver1.major);
 +  test_eq(3, ver1.minor);
 +  test_eq(4, ver1.micro);
 +  test_eq(VER_RELEASE, ver1.status);
 +  test_eq(999, ver1.patchlevel);
 +  test_eq(0, tor_version_parse("0.1.2.4-alpha", &ver1));
 +  test_eq(0, ver1.major);
 +  test_eq(1, ver1.minor);
 +  test_eq(2, ver1.micro);
 +  test_eq(4, ver1.patchlevel);
 +  test_eq(VER_RELEASE, ver1.status);
 +  test_streq("alpha", ver1.status_tag);
 +  test_eq(0, tor_version_parse("0.1.2.4", &ver1));
 +  test_eq(0, ver1.major);
 +  test_eq(1, ver1.minor);
 +  test_eq(2, ver1.micro);
 +  test_eq(4, ver1.patchlevel);
 +  test_eq(VER_RELEASE, ver1.status);
 +  test_streq("", ver1.status_tag);
 +
 +#define tt_versionstatus_op(vs1, op, vs2)                               \
 +  tt_assert_test_type(vs1,vs2,#vs1" "#op" "#vs2,version_status_t,       \
 +                      (_val1 op _val2),"%d")
 +#define test_v_i_o(val, ver, lst)                                       \
 +  tt_versionstatus_op(val, ==, tor_version_is_obsolete(ver, lst))
 +
 +  /* make sure tor_version_is_obsolete() works */
 +  test_v_i_o(VS_OLD, "0.0.1", "Tor 0.0.2");
 +  test_v_i_o(VS_OLD, "0.0.1", "0.0.2, Tor 0.0.3");
 +  test_v_i_o(VS_OLD, "0.0.1", "0.0.2,Tor 0.0.3");
 +  test_v_i_o(VS_OLD, "0.0.1","0.0.3,BetterTor 0.0.1");
 +  test_v_i_o(VS_RECOMMENDED, "0.0.2", "Tor 0.0.2,Tor 0.0.3");
 +  test_v_i_o(VS_NEW_IN_SERIES, "0.0.2", "Tor 0.0.2pre1,Tor 0.0.3");
 +  test_v_i_o(VS_OLD, "0.0.2", "Tor 0.0.2.1,Tor 0.0.3");
 +  test_v_i_o(VS_NEW, "0.1.0", "Tor 0.0.2,Tor 0.0.3");
 +  test_v_i_o(VS_RECOMMENDED, "0.0.7rc2", "0.0.7,Tor 0.0.7rc2,Tor 0.0.8");
 +  test_v_i_o(VS_OLD, "0.0.5.0", "0.0.5.1-cvs");
 +  test_v_i_o(VS_NEW_IN_SERIES, "0.0.5.1-cvs", "0.0.5, 0.0.6");
 +  /* Not on list, but newer than any in same series. */
 +  test_v_i_o(VS_NEW_IN_SERIES, "0.1.0.3",
 +             "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0");
 +  /* Series newer than any on list. */
 +  test_v_i_o(VS_NEW, "0.1.2.3", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0");
 +  /* Series older than any on list. */
 +  test_v_i_o(VS_OLD, "0.0.1.3", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0");
 +  /* Not on list, not newer than any on same series. */
 +  test_v_i_o(VS_UNRECOMMENDED, "0.1.0.1",
 +             "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0");
 +  /* On list, not newer than any on same series. */
 +  test_v_i_o(VS_UNRECOMMENDED,
 +             "0.1.0.1", "Tor 0.1.0.2,Tor 0.0.9.5,Tor 0.1.1.0");
 +  test_eq(0, tor_version_as_new_as("Tor 0.0.5", "0.0.9pre1-cvs"));
 +  test_eq(1, tor_version_as_new_as(
 +          "Tor 0.0.8 on Darwin 64-121-192-100.c3-0."
 +          "sfpo-ubr1.sfrn-sfpo.ca.cable.rcn.com Power Macintosh",
 +          "0.0.8rc2"));
 +  test_eq(0, tor_version_as_new_as(
 +          "Tor 0.0.8 on Darwin 64-121-192-100.c3-0."
 +          "sfpo-ubr1.sfrn-sfpo.ca.cable.rcn.com Power Macintosh", "0.0.8.2"));
 +
 +  /* Now try svn revisions. */
 +  test_eq(1, tor_version_as_new_as("Tor 0.2.1.0-dev (r100)",
 +                                   "Tor 0.2.1.0-dev (r99)"));
 +  test_eq(1, tor_version_as_new_as("Tor 0.2.1.0-dev (r100) on Banana Jr",
 +                                   "Tor 0.2.1.0-dev (r99) on Hal 9000"));
 +  test_eq(1, tor_version_as_new_as("Tor 0.2.1.0-dev (r100)",
 +                                   "Tor 0.2.1.0-dev on Colossus"));
 +  test_eq(0, tor_version_as_new_as("Tor 0.2.1.0-dev (r99)",
 +                                   "Tor 0.2.1.0-dev (r100)"));
 +  test_eq(0, tor_version_as_new_as("Tor 0.2.1.0-dev (r99) on MCP",
 +                                   "Tor 0.2.1.0-dev (r100) on AM"));
 +  test_eq(0, tor_version_as_new_as("Tor 0.2.1.0-dev",
 +                                   "Tor 0.2.1.0-dev (r99)"));
 +  test_eq(1, tor_version_as_new_as("Tor 0.2.1.1",
 +                                   "Tor 0.2.1.0-dev (r99)"));
 +
 +  /* Now try git revisions */
 +  test_eq(0, tor_version_parse("0.5.6.7 (git-ff00ff)", &ver1));
 +  test_eq(0, ver1.major);
 +  test_eq(5, ver1.minor);
 +  test_eq(6, ver1.micro);
 +  test_eq(7, ver1.patchlevel);
 +  test_eq(3, ver1.git_tag_len);
 +  test_memeq(ver1.git_tag, "\xff\x00\xff", 3);
 +  test_eq(-1, tor_version_parse("0.5.6.7 (git-ff00xx)", &ver1));
 +  test_eq(-1, tor_version_parse("0.5.6.7 (git-ff00fff)", &ver1));
 +  test_eq(0, tor_version_parse("0.5.6.7 (git ff00fff)", &ver1));
 + done:
 +  ;
 +}
 +
 +/** Run unit tests for directory fp_pair functions. */
 +static void
 +test_dir_fp_pairs(void)
 +{
 +  smartlist_t *sl = smartlist_create();
 +  fp_pair_t *pair;
 +
 +  dir_split_resource_into_fingerprint_pairs(
 +       /* Two pairs, out of order, with one duplicate. */
 +       "73656372657420646174612E0000000000FFFFFF-"
 +       "557365204145532d32353620696e73746561642e+"
 +       "73656372657420646174612E0000000000FFFFFF-"
 +       "557365204145532d32353620696e73746561642e+"
 +       "48657861646563696d616c2069736e277420736f-"
 +       "676f6f6420666f7220686964696e6720796f7572.z", sl);
 +
 +  test_eq(smartlist_len(sl), 2);
 +  pair = smartlist_get(sl, 0);
 +  test_memeq(pair->first,  "Hexadecimal isn't so", DIGEST_LEN);
 +  test_memeq(pair->second, "good for hiding your", DIGEST_LEN);
 +  pair = smartlist_get(sl, 1);
 +  test_memeq(pair->first,  "secret data.\0\0\0\0\0\xff\xff\xff", DIGEST_LEN);
 +  test_memeq(pair->second, "Use AES-256 instead.", DIGEST_LEN);
 +
 + done:
 +  SMARTLIST_FOREACH(sl, fp_pair_t *, pair, tor_free(pair));
 +  smartlist_free(sl);
 +}
 +
 +static void
 +test_dir_split_fps(void *testdata)
 +{
 +  smartlist_t *sl = smartlist_create();
 +  char *mem_op_hex_tmp = NULL;
 +  (void)testdata;
 +
 +  /* Some example hex fingerprints and their base64 equivalents */
 +#define HEX1 "Fe0daff89127389bc67558691231234551193EEE"
 +#define HEX2 "Deadbeef99999991111119999911111111f00ba4"
 +#define HEX3 "b33ff00db33ff00db33ff00db33ff00db33ff00d"
 +#define HEX256_1 \
 +    "f3f3f3f3fbbbbf3f3f3f3fbbbf3f3f3f3fbbbbf3f3f3f3fbbbf3f3f3f3fbbbbf"
 +#define HEX256_2 \
 +    "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccCCc"
 +#define HEX256_3 \
 +    "0123456789ABCdef0123456789ABCdef0123456789ABCdef0123456789ABCdef"
 +#define B64_1 "/g2v+JEnOJvGdVhpEjEjRVEZPu4"
 +#define B64_2 "3q2+75mZmZERERmZmRERERHwC6Q"
 +#define B64_3 "sz/wDbM/8A2zP/ANsz/wDbM/8A0"
 +#define B64_256_1 "8/Pz8/u7vz8/Pz+7vz8/Pz+7u/Pz8/P7u/Pz8/P7u78"
 +#define B64_256_2 "zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMw"
 +#define B64_256_3 "ASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8"
 +
 +  /* no flags set */
 +  dir_split_resource_into_fingerprints("A+C+B", sl, NULL, 0);
 +  tt_int_op(smartlist_len(sl), ==, 3);
 +  tt_str_op(smartlist_get(sl, 0), ==, "A");
 +  tt_str_op(smartlist_get(sl, 1), ==, "C");
 +  tt_str_op(smartlist_get(sl, 2), ==, "B");
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_clear(sl);
 +
 +  /* uniq strings. */
 +  dir_split_resource_into_fingerprints("A+C+B+A+B+B", sl, NULL, DSR_SORT_UNIQ);
 +  tt_int_op(smartlist_len(sl), ==, 3);
 +  tt_str_op(smartlist_get(sl, 0), ==, "A");
 +  tt_str_op(smartlist_get(sl, 1), ==, "B");
 +  tt_str_op(smartlist_get(sl, 2), ==, "C");
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_clear(sl);
 +
 +  /* Decode hex. */
 +  dir_split_resource_into_fingerprints(HEX1"+"HEX2, sl, NULL, DSR_HEX);
 +  tt_int_op(smartlist_len(sl), ==, 2);
 +  test_mem_op_hex(smartlist_get(sl, 0), ==, HEX1);
 +  test_mem_op_hex(smartlist_get(sl, 1), ==, HEX2);
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_clear(sl);
 +
 +  /* decode hex and drop weirdness. */
 +  dir_split_resource_into_fingerprints(HEX1"+bogus+"HEX2"+"HEX256_1,
 +                                       sl, NULL, DSR_HEX);
 +  tt_int_op(smartlist_len(sl), ==, 2);
 +  test_mem_op_hex(smartlist_get(sl, 0), ==, HEX1);
 +  test_mem_op_hex(smartlist_get(sl, 1), ==, HEX2);
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_clear(sl);
 +
 +  /* Decode long hex */
 +  dir_split_resource_into_fingerprints(HEX256_1"+"HEX256_2"+"HEX2"+"HEX256_3,
 +                                       sl, NULL, DSR_HEX|DSR_DIGEST256);
 +  tt_int_op(smartlist_len(sl), ==, 3);
 +  test_mem_op_hex(smartlist_get(sl, 0), ==, HEX256_1);
 +  test_mem_op_hex(smartlist_get(sl, 1), ==, HEX256_2);
 +  test_mem_op_hex(smartlist_get(sl, 2), ==, HEX256_3);
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_clear(sl);
 +
 +  /* Decode hex and sort. */
 +  dir_split_resource_into_fingerprints(HEX1"+"HEX2"+"HEX3"+"HEX2,
 +                                       sl, NULL, DSR_HEX|DSR_SORT_UNIQ);
 +  tt_int_op(smartlist_len(sl), ==, 3);
 +  test_mem_op_hex(smartlist_get(sl, 0), ==, HEX3);
 +  test_mem_op_hex(smartlist_get(sl, 1), ==, HEX2);
 +  test_mem_op_hex(smartlist_get(sl, 2), ==, HEX1);
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_clear(sl);
 +
 +  /* Decode long hex and sort */
 +  dir_split_resource_into_fingerprints(HEX256_1"+"HEX256_2"+"HEX256_3
 +                                       "+"HEX256_1,
 +                                       sl, NULL,
 +                                       DSR_HEX|DSR_DIGEST256|DSR_SORT_UNIQ);
 +  tt_int_op(smartlist_len(sl), ==, 3);
 +  test_mem_op_hex(smartlist_get(sl, 0), ==, HEX256_3);
 +  test_mem_op_hex(smartlist_get(sl, 1), ==, HEX256_2);
 +  test_mem_op_hex(smartlist_get(sl, 2), ==, HEX256_1);
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_clear(sl);
 +
 +  /* Decode base64 */
 +  dir_split_resource_into_fingerprints(B64_1"-"B64_2, sl, NULL, DSR_BASE64);
 +  tt_int_op(smartlist_len(sl), ==, 2);
 +  test_mem_op_hex(smartlist_get(sl, 0), ==, HEX1);
 +  test_mem_op_hex(smartlist_get(sl, 1), ==, HEX2);
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_clear(sl);
 +
 +  /* Decode long base64 */
 +  dir_split_resource_into_fingerprints(B64_256_1"-"B64_256_2,
 +                                       sl, NULL, DSR_BASE64|DSR_DIGEST256);
 +  tt_int_op(smartlist_len(sl), ==, 2);
 +  test_mem_op_hex(smartlist_get(sl, 0), ==, HEX256_1);
 +  test_mem_op_hex(smartlist_get(sl, 1), ==, HEX256_2);
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_clear(sl);
 +
 +  dir_split_resource_into_fingerprints(B64_256_1,
 +                                       sl, NULL, DSR_BASE64|DSR_DIGEST256);
 +  tt_int_op(smartlist_len(sl), ==, 1);
 +  test_mem_op_hex(smartlist_get(sl, 0), ==, HEX256_1);
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_clear(sl);
 +
 + done:
 +  SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
 +  smartlist_free(sl);
 +  tor_free(mem_op_hex_tmp);
 +}
 +
 +static void
 +test_dir_measured_bw(void)
 +{
 +  measured_bw_line_t mbwl;
 +  int i;
 +  const char *lines_pass[] = {
 +    "node_id=$557365204145532d32353620696e73746561642e bw=1024\n",
 +    "node_id=$557365204145532d32353620696e73746561642e\t  bw=1024 \n",
 +    " node_id=$557365204145532d32353620696e73746561642e  bw=1024\n",
 +    "\tnoise\tnode_id=$557365204145532d32353620696e73746561642e  "
 +                "bw=1024 junk=007\n",
 +    "misc=junk node_id=$557365204145532d32353620696e73746561642e  "
 +                "bw=1024 junk=007\n",
 +    "end"
 +  };
 +  const char *lines_fail[] = {
 +    /* Test possible python stupidity on input */
 +    "node_id=None bw=1024\n",
 +    "node_id=$None bw=1024\n",
 +    "node_id=$557365204145532d32353620696e73746561642e bw=None\n",
 +    "node_id=$557365204145532d32353620696e73746561642e bw=1024.0\n",
 +    "node_id=$557365204145532d32353620696e73746561642e bw=.1024\n",
 +    "node_id=$557365204145532d32353620696e73746561642e bw=1.024\n",
 +    "node_id=$557365204145532d32353620696e73746561642e bw=1024 bw=0\n",
 +    "node_id=$557365204145532d32353620696e73746561642e bw=1024 bw=None\n",
 +    "node_id=$557365204145532d32353620696e73746561642e bw=-1024\n",
 +    /* Test incomplete writes due to race conditions, partial copies, etc */
 +    "node_i",
 +    "node_i\n",
 +    "node_id=",
 +    "node_id=\n",
 +    "node_id=$557365204145532d32353620696e73746561642e bw=",
 +    "node_id=$557365204145532d32353620696e73746561642e bw=1024",
 +    "node_id=$557365204145532d32353620696e73746561642e bw=\n",
 +    "node_id=$557365204145532d32353620696e7374",
 +    "node_id=$557365204145532d32353620696e7374\n",
 +    "",
 +    "\n",
 +    " \n ",
 +    " \n\n",
 +    /* Test assorted noise */
 +    " node_id= ",
 +    "node_id==$557365204145532d32353620696e73746561642e bw==1024\n",
 +    "node_id=$55736520414552d32353620696e73746561642e bw=1024\n",
 +    "node_id=557365204145532d32353620696e73746561642e bw=1024\n",
 +    "node_id= $557365204145532d32353620696e73746561642e bw=0.23\n",
 +    "end"
 +  };
 +
 +  for (i = 0; strcmp(lines_fail[i], "end"); i++) {
 +    //fprintf(stderr, "Testing: %s\n", lines_fail[i]);
 +    test_assert(measured_bw_line_parse(&mbwl, lines_fail[i]) == -1);
 +  }
 +
 +  for (i = 0; strcmp(lines_pass[i], "end"); i++) {
 +    //fprintf(stderr, "Testing: %s %d\n", lines_pass[i], TOR_ISSPACE('\n'));
 +    test_assert(measured_bw_line_parse(&mbwl, lines_pass[i]) == 0);
 +    test_assert(mbwl.bw == 1024);
 +    test_assert(strcmp(mbwl.node_hex,
 +                "557365204145532d32353620696e73746561642e") == 0);
 +  }
 +
 + done:
 +  return;
 +}
 +
 +static void
 +test_dir_param_voting(void)
 +{
 +  networkstatus_t vote1, vote2, vote3, vote4;
 +  smartlist_t *votes = smartlist_create();
 +  char *res = NULL;
 +
 +  /* dirvote_compute_params only looks at the net_params field of the votes,
 +     so that's all we need to set.
 +   */
 +  memset(&vote1, 0, sizeof(vote1));
 +  memset(&vote2, 0, sizeof(vote2));
 +  memset(&vote3, 0, sizeof(vote3));
 +  memset(&vote4, 0, sizeof(vote4));
 +  vote1.net_params = smartlist_create();
 +  vote2.net_params = smartlist_create();
 +  vote3.net_params = smartlist_create();
 +  vote4.net_params = smartlist_create();
 +  smartlist_split_string(vote1.net_params,
 +                         "ab=90 abcd=20 cw=50 x-yz=-99", NULL, 0, 0);
 +  smartlist_split_string(vote2.net_params,
 +                         "ab=27 cw=5 x-yz=88", NULL, 0, 0);
 +  smartlist_split_string(vote3.net_params,
 +                         "abcd=20 c=60 cw=500 x-yz=-9 zzzzz=101", NULL, 0, 0);
 +  smartlist_split_string(vote4.net_params,
 +                         "ab=900 abcd=200 c=1 cw=51 x-yz=100", NULL, 0, 0);
 +  test_eq(100, networkstatus_get_param(&vote4, "x-yz", 50));
 +  test_eq(222, networkstatus_get_param(&vote4, "foobar", 222));
 +
 +  smartlist_add(votes, &vote1);
 +  smartlist_add(votes, &vote2);
 +  smartlist_add(votes, &vote3);
 +  smartlist_add(votes, &vote4);
 +
 +  res = dirvote_compute_params(votes);
 +  test_streq(res,
 +             "ab=90 abcd=20 c=1 cw=50 x-yz=-9 zzzzz=101");
 +
 + done:
 +  tor_free(res);
 +  SMARTLIST_FOREACH(vote1.net_params, char *, cp, tor_free(cp));
 +  SMARTLIST_FOREACH(vote2.net_params, char *, cp, tor_free(cp));
 +  SMARTLIST_FOREACH(vote3.net_params, char *, cp, tor_free(cp));
 +  SMARTLIST_FOREACH(vote4.net_params, char *, cp, tor_free(cp));
 +  smartlist_free(vote1.net_params);
 +  smartlist_free(vote2.net_params);
 +  smartlist_free(vote3.net_params);
 +  smartlist_free(vote4.net_params);
 +  smartlist_free(votes);
 +
 +  return;
 +}
 +
 +extern const char AUTHORITY_CERT_1[];
 +extern const char AUTHORITY_SIGNKEY_1[];
 +extern const char AUTHORITY_CERT_2[];
 +extern const char AUTHORITY_SIGNKEY_2[];
 +extern const char AUTHORITY_CERT_3[];
 +extern const char AUTHORITY_SIGNKEY_3[];
 +
 +/** Helper: Test that two networkstatus_voter_info_t do in fact represent the
 + * same voting authority, and that they do in fact have all the same
 + * information. */
 +static void
 +test_same_voter(networkstatus_voter_info_t *v1,
 +                networkstatus_voter_info_t *v2)
 +{
 +  test_streq(v1->nickname, v2->nickname);
 +  test_memeq(v1->identity_digest, v2->identity_digest, DIGEST_LEN);
 +  test_streq(v1->address, v2->address);
 +  test_eq(v1->addr, v2->addr);
 +  test_eq(v1->dir_port, v2->dir_port);
 +  test_eq(v1->or_port, v2->or_port);
 +  test_streq(v1->contact, v2->contact);
 +  test_memeq(v1->vote_digest, v2->vote_digest, DIGEST_LEN);
 + done:
 +  ;
 +}
 +
 +/** Helper: Make a new routerinfo containing the right information for a
 + * given vote_routerstatus_t. */
 +static routerinfo_t *
 +generate_ri_from_rs(const vote_routerstatus_t *vrs)
 +{
 +  routerinfo_t *r;
 +  const routerstatus_t *rs = &vrs->status;
 +  static time_t published = 0;
 +
 +  r = tor_malloc_zero(sizeof(routerinfo_t));
 +  memcpy(r->cache_info.identity_digest, rs->identity_digest, DIGEST_LEN);
 +  memcpy(r->cache_info.signed_descriptor_digest, rs->descriptor_digest,
 +         DIGEST_LEN);
 +  r->cache_info.do_not_cache = 1;
 +  r->cache_info.routerlist_index = -1;
 +  r->cache_info.signed_descriptor_body =
 +    tor_strdup("123456789012345678901234567890123");
 +  r->cache_info.signed_descriptor_len =
 +    strlen(r->cache_info.signed_descriptor_body);
 +  r->exit_policy = smartlist_create();
 +  r->cache_info.published_on = ++published + time(NULL);
 +  return r;
 +}
 +
 +/** Helper: get a detached signatures document for one or two
 + * consensuses. */
 +static char *
 +get_detached_sigs(networkstatus_t *ns, networkstatus_t *ns2)
 +{
 +  char *r;
 +  smartlist_t *sl;
 +  tor_assert(ns && ns->flavor == FLAV_NS);
 +  sl = smartlist_create();
 +  smartlist_add(sl,ns);
 +  if (ns2)
 +    smartlist_add(sl,ns2);
 +  r = networkstatus_get_detached_signatures(sl);
 +  smartlist_free(sl);
 +  return r;
 +}
 +
 +/** Run unit tests for generating and parsing V3 consensus networkstatus
 + * documents. */
 +static void
 +test_dir_v3_networkstatus(void)
 +{
 +  authority_cert_t *cert1=NULL, *cert2=NULL, *cert3=NULL;
 +  crypto_pk_env_t *sign_skey_1=NULL, *sign_skey_2=NULL, *sign_skey_3=NULL;
 +  crypto_pk_env_t *sign_skey_leg1=NULL;
 +  const char *msg=NULL;
 +
 +  time_t now = time(NULL);
 +  networkstatus_voter_info_t *voter;
 +  document_signature_t *sig;
 +  networkstatus_t *vote=NULL, *v1=NULL, *v2=NULL, *v3=NULL, *con=NULL,
 +    *con_md=NULL;
 +  vote_routerstatus_t *vrs;
 +  routerstatus_t *rs;
 +  char *v1_text=NULL, *v2_text=NULL, *v3_text=NULL, *consensus_text=NULL, *cp;
 +  smartlist_t *votes = smartlist_create();
 +
 +  /* For generating the two other consensuses. */
 +  char *detached_text1=NULL, *detached_text2=NULL;
 +  char *consensus_text2=NULL, *consensus_text3=NULL;
 +  char *consensus_text_md2=NULL, *consensus_text_md3=NULL;
 +  char *consensus_text_md=NULL;
 +  networkstatus_t *con2=NULL, *con_md2=NULL, *con3=NULL, *con_md3=NULL;
 +  ns_detached_signatures_t *dsig1=NULL, *dsig2=NULL;
 +
 +  /* Parse certificates and keys. */
 +  cert1 = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL);
 +  test_assert(cert1);
 +  test_assert(cert1->is_cross_certified);
 +  cert2 = authority_cert_parse_from_string(AUTHORITY_CERT_2, NULL);
 +  test_assert(cert2);
 +  cert3 = authority_cert_parse_from_string(AUTHORITY_CERT_3, NULL);
 +  test_assert(cert3);
 +  sign_skey_1 = crypto_new_pk_env();
 +  sign_skey_2 = crypto_new_pk_env();
 +  sign_skey_3 = crypto_new_pk_env();
 +  sign_skey_leg1 = pk_generate(4);
 +
 +  test_assert(!crypto_pk_read_private_key_from_string(sign_skey_1,
-                                                       AUTHORITY_SIGNKEY_1));
++                                                      AUTHORITY_SIGNKEY_1, -1));
 +  test_assert(!crypto_pk_read_private_key_from_string(sign_skey_2,
-                                                       AUTHORITY_SIGNKEY_2));
++                                                      AUTHORITY_SIGNKEY_2, -1));
 +  test_assert(!crypto_pk_read_private_key_from_string(sign_skey_3,
-                                                       AUTHORITY_SIGNKEY_3));
++                                                      AUTHORITY_SIGNKEY_3, -1));
 +
 +  test_assert(!crypto_pk_cmp_keys(sign_skey_1, cert1->signing_key));
 +  test_assert(!crypto_pk_cmp_keys(sign_skey_2, cert2->signing_key));
 +
 +  /*
 +   * Set up a vote; generate it; try to parse it.
 +   */
 +  vote = tor_malloc_zero(sizeof(networkstatus_t));
 +  vote->type = NS_TYPE_VOTE;
 +  vote->published = now;
 +  vote->valid_after = now+1000;
 +  vote->fresh_until = now+2000;
 +  vote->valid_until = now+3000;
 +  vote->vote_seconds = 100;
 +  vote->dist_seconds = 200;
 +  vote->supported_methods = smartlist_create();
 +  smartlist_split_string(vote->supported_methods, "1 2 3", NULL, 0, -1);
 +  vote->client_versions = tor_strdup("0.1.2.14,0.1.2.15");
 +  vote->server_versions = tor_strdup("0.1.2.14,0.1.2.15,0.1.2.16");
 +  vote->known_flags = smartlist_create();
 +  smartlist_split_string(vote->known_flags,
 +                     "Authority Exit Fast Guard Running Stable V2Dir Valid",
 +                     0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
 +  vote->voters = smartlist_create();
 +  voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
 +  voter->nickname = tor_strdup("Voter1");
 +  voter->address = tor_strdup("1.2.3.4");
 +  voter->addr = 0x01020304;
 +  voter->dir_port = 80;
 +  voter->or_port = 9000;
 +  voter->contact = tor_strdup("voter@xxxxxxxxxxx");
 +  crypto_pk_get_digest(cert1->identity_key, voter->identity_digest);
 +  smartlist_add(vote->voters, voter);
 +  vote->cert = authority_cert_dup(cert1);
 +  vote->net_params = smartlist_create();
 +  smartlist_split_string(vote->net_params, "circuitwindow=101 foo=990",
 +                         NULL, 0, 0);
 +  vote->routerstatus_list = smartlist_create();
 +  /* add the first routerstatus. */
 +  vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
 +  rs = &vrs->status;
 +  vrs->version = tor_strdup("0.1.2.14");
 +  rs->published_on = now-1500;
 +  strlcpy(rs->nickname, "router2", sizeof(rs->nickname));
 +  memset(rs->identity_digest, 3, DIGEST_LEN);
 +  memset(rs->descriptor_digest, 78, DIGEST_LEN);
 +  rs->addr = 0x99008801;
 +  rs->or_port = 443;
 +  rs->dir_port = 8000;
 +  /* all flags but running cleared */
 +  rs->is_running = 1;
 +  smartlist_add(vote->routerstatus_list, vrs);
 +  test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0);
 +
 +  /* add the second routerstatus. */
 +  vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
 +  rs = &vrs->status;
 +  vrs->version = tor_strdup("0.2.0.5");
 +  rs->published_on = now-1000;
 +  strlcpy(rs->nickname, "router1", sizeof(rs->nickname));
 +  memset(rs->identity_digest, 5, DIGEST_LEN);
 +  memset(rs->descriptor_digest, 77, DIGEST_LEN);
 +  rs->addr = 0x99009901;
 +  rs->or_port = 443;
 +  rs->dir_port = 0;
 +  rs->is_exit = rs->is_stable = rs->is_fast = rs->is_running =
 +    rs->is_valid = rs->is_v2_dir = rs->is_possible_guard = 1;
 +  smartlist_add(vote->routerstatus_list, vrs);
 +  test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0);
 +
 +  /* add the third routerstatus. */
 +  vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
 +  rs = &vrs->status;
 +  vrs->version = tor_strdup("0.1.0.3");
 +  rs->published_on = now-1000;
 +  strlcpy(rs->nickname, "router3", sizeof(rs->nickname));
 +  memset(rs->identity_digest, 33, DIGEST_LEN);
 +  memset(rs->descriptor_digest, 79, DIGEST_LEN);
 +  rs->addr = 0xAA009901;
 +  rs->or_port = 400;
 +  rs->dir_port = 9999;
 +  rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast =
 +    rs->is_running = rs->is_valid = rs->is_v2_dir = rs->is_possible_guard = 1;
 +  smartlist_add(vote->routerstatus_list, vrs);
 +  test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0);
 +
 +  /* add a fourth routerstatus that is not running. */
 +  vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
 +  rs = &vrs->status;
 +  vrs->version = tor_strdup("0.1.6.3");
 +  rs->published_on = now-1000;
 +  strlcpy(rs->nickname, "router4", sizeof(rs->nickname));
 +  memset(rs->identity_digest, 34, DIGEST_LEN);
 +  memset(rs->descriptor_digest, 47, DIGEST_LEN);
 +  rs->addr = 0xC0000203;
 +  rs->or_port = 500;
 +  rs->dir_port = 1999;
 +  /* Running flag (and others) cleared */
 +  smartlist_add(vote->routerstatus_list, vrs);
 +  test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0);
 +
 +  /* dump the vote and try to parse it. */
 +  v1_text = format_networkstatus_vote(sign_skey_1, vote);
 +  test_assert(v1_text);
 +  v1 = networkstatus_parse_vote_from_string(v1_text, NULL, NS_TYPE_VOTE);
 +  test_assert(v1);
 +
 +  /* Make sure the parsed thing was right. */
 +  test_eq(v1->type, NS_TYPE_VOTE);
 +  test_eq(v1->published, vote->published);
 +  test_eq(v1->valid_after, vote->valid_after);
 +  test_eq(v1->fresh_until, vote->fresh_until);
 +  test_eq(v1->valid_until, vote->valid_until);
 +  test_eq(v1->vote_seconds, vote->vote_seconds);
 +  test_eq(v1->dist_seconds, vote->dist_seconds);
 +  test_streq(v1->client_versions, vote->client_versions);
 +  test_streq(v1->server_versions, vote->server_versions);
 +  test_assert(v1->voters && smartlist_len(v1->voters));
 +  voter = smartlist_get(v1->voters, 0);
 +  test_streq(voter->nickname, "Voter1");
 +  test_streq(voter->address, "1.2.3.4");
 +  test_eq(voter->addr, 0x01020304);
 +  test_eq(voter->dir_port, 80);
 +  test_eq(voter->or_port, 9000);
 +  test_streq(voter->contact, "voter@xxxxxxxxxxx");
 +  test_assert(v1->cert);
 +  test_assert(!crypto_pk_cmp_keys(sign_skey_1, v1->cert->signing_key));
 +  cp = smartlist_join_strings(v1->known_flags, ":", 0, NULL);
 +  test_streq(cp, "Authority:Exit:Fast:Guard:Running:Stable:V2Dir:Valid");
 +  tor_free(cp);
 +  test_eq(smartlist_len(v1->routerstatus_list), 4);
 +  /* Check the first routerstatus. */
 +  vrs = smartlist_get(v1->routerstatus_list, 0);
 +  rs = &vrs->status;
 +  test_streq(vrs->version, "0.1.2.14");
 +  test_eq(rs->published_on, now-1500);
 +  test_streq(rs->nickname, "router2");
 +  test_memeq(rs->identity_digest,
 +             "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3",
 +             DIGEST_LEN);
 +  test_memeq(rs->descriptor_digest, "NNNNNNNNNNNNNNNNNNNN", DIGEST_LEN);
 +  test_eq(rs->addr, 0x99008801);
 +  test_eq(rs->or_port, 443);
 +  test_eq(rs->dir_port, 8000);
 +  test_eq(vrs->flags, U64_LITERAL(16)); // no flags except "running"
 +  /* Check the second routerstatus. */
 +  vrs = smartlist_get(v1->routerstatus_list, 1);
 +  rs = &vrs->status;
 +  test_streq(vrs->version, "0.2.0.5");
 +  test_eq(rs->published_on, now-1000);
 +  test_streq(rs->nickname, "router1");
 +  test_memeq(rs->identity_digest,
 +             "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5",
 +             DIGEST_LEN);
 +  test_memeq(rs->descriptor_digest, "MMMMMMMMMMMMMMMMMMMM", DIGEST_LEN);
 +  test_eq(rs->addr, 0x99009901);
 +  test_eq(rs->or_port, 443);
 +  test_eq(rs->dir_port, 0);
 +  test_eq(vrs->flags, U64_LITERAL(254)); // all flags except "authority."
 +
 +  {
 +    measured_bw_line_t mbw;
 +    memset(mbw.node_id, 33, sizeof(mbw.node_id));
 +    mbw.bw = 1024;
 +    test_assert(measured_bw_line_apply(&mbw,
 +                v1->routerstatus_list) == 1);
 +    vrs = smartlist_get(v1->routerstatus_list, 2);
 +    test_assert(vrs->status.has_measured_bw &&
 +                vrs->status.measured_bw == 1024);
 +  }
 +
 +  /* Generate second vote. It disagrees on some of the times,
 +   * and doesn't list versions, and knows some crazy flags */
 +  vote->published = now+1;
 +  vote->fresh_until = now+3005;
 +  vote->dist_seconds = 300;
 +  authority_cert_free(vote->cert);
 +  vote->cert = authority_cert_dup(cert2);
 +  vote->net_params = smartlist_create();
 +  smartlist_split_string(vote->net_params, "bar=2000000000 circuitwindow=20",
 +                         NULL, 0, 0);
 +  tor_free(vote->client_versions);
 +  tor_free(vote->server_versions);
 +  voter = smartlist_get(vote->voters, 0);
 +  tor_free(voter->nickname);
 +  tor_free(voter->address);
 +  voter->nickname = tor_strdup("Voter2");
 +  voter->address = tor_strdup("2.3.4.5");
 +  voter->addr = 0x02030405;
 +  crypto_pk_get_digest(cert2->identity_key, voter->identity_digest);
 +  smartlist_add(vote->known_flags, tor_strdup("MadeOfCheese"));
 +  smartlist_add(vote->known_flags, tor_strdup("MadeOfTin"));
 +  smartlist_sort_strings(vote->known_flags);
 +  vrs = smartlist_get(vote->routerstatus_list, 2);
 +  smartlist_del_keeporder(vote->routerstatus_list, 2);
 +  tor_free(vrs->version);
 +  tor_free(vrs);
 +  vrs = smartlist_get(vote->routerstatus_list, 0);
 +  vrs->status.is_fast = 1;
 +  /* generate and parse. */
 +  v2_text = format_networkstatus_vote(sign_skey_2, vote);
 +  test_assert(v2_text);
 +  v2 = networkstatus_parse_vote_from_string(v2_text, NULL, NS_TYPE_VOTE);
 +  test_assert(v2);
 +  /* Check that flags come out right.*/
 +  cp = smartlist_join_strings(v2->known_flags, ":", 0, NULL);
 +  test_streq(cp, "Authority:Exit:Fast:Guard:MadeOfCheese:MadeOfTin:"
 +             "Running:Stable:V2Dir:Valid");
 +  tor_free(cp);
 +  vrs = smartlist_get(v2->routerstatus_list, 1);
 +  /* 1023 - authority(1) - madeofcheese(16) - madeoftin(32) */
 +  test_eq(vrs->flags, U64_LITERAL(974));
 +
 +  /* Generate the third vote. */
 +  vote->published = now;
 +  vote->fresh_until = now+2003;
 +  vote->dist_seconds = 250;
 +  authority_cert_free(vote->cert);
 +  vote->cert = authority_cert_dup(cert3);
 +  vote->net_params = smartlist_create();
 +  smartlist_split_string(vote->net_params, "circuitwindow=80 foo=660",
 +                         NULL, 0, 0);
 +  smartlist_add(vote->supported_methods, tor_strdup("4"));
 +  vote->client_versions = tor_strdup("0.1.2.14,0.1.2.17");
 +  vote->server_versions = tor_strdup("0.1.2.10,0.1.2.15,0.1.2.16");
 +  voter = smartlist_get(vote->voters, 0);
 +  tor_free(voter->nickname);
 +  tor_free(voter->address);
 +  voter->nickname = tor_strdup("Voter3");
 +  voter->address = tor_strdup("3.4.5.6");
 +  voter->addr = 0x03040506;
 +  crypto_pk_get_digest(cert3->identity_key, voter->identity_digest);
 +  /* This one has a legacy id. */
 +  memset(voter->legacy_id_digest, (int)'A', DIGEST_LEN);
 +  vrs = smartlist_get(vote->routerstatus_list, 0);
 +  smartlist_del_keeporder(vote->routerstatus_list, 0);
 +  tor_free(vrs->version);
 +  tor_free(vrs);
 +  vrs = smartlist_get(vote->routerstatus_list, 0);
 +  memset(vrs->status.descriptor_digest, (int)'Z', DIGEST_LEN);
 +  test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0);
 +
 +  v3_text = format_networkstatus_vote(sign_skey_3, vote);
 +  test_assert(v3_text);
 +
 +  v3 = networkstatus_parse_vote_from_string(v3_text, NULL, NS_TYPE_VOTE);
 +  test_assert(v3);
 +
 +  /* Compute a consensus as voter 3. */
 +  smartlist_add(votes, v3);
 +  smartlist_add(votes, v1);
 +  smartlist_add(votes, v2);
 +  consensus_text = networkstatus_compute_consensus(votes, 3,
 +                                                   cert3->identity_key,
 +                                                   sign_skey_3,
 +                                                   "AAAAAAAAAAAAAAAAAAAA",
 +                                                   sign_skey_leg1,
 +                                                   FLAV_NS);
 +  test_assert(consensus_text);
 +  con = networkstatus_parse_vote_from_string(consensus_text, NULL,
 +                                             NS_TYPE_CONSENSUS);
 +  test_assert(con);
 +  //log_notice(LD_GENERAL, "<<%s>>\n<<%s>>\n<<%s>>\n",
 +  //           v1_text, v2_text, v3_text);
 +  consensus_text_md = networkstatus_compute_consensus(votes, 3,
 +                                                   cert3->identity_key,
 +                                                   sign_skey_3,
 +                                                   "AAAAAAAAAAAAAAAAAAAA",
 +                                                   sign_skey_leg1,
 +                                                   FLAV_MICRODESC);
 +  test_assert(consensus_text_md);
 +  con_md = networkstatus_parse_vote_from_string(consensus_text_md, NULL,
 +                                                NS_TYPE_CONSENSUS);
 +  test_assert(con_md);
 +  test_eq(con_md->flavor, FLAV_MICRODESC);
 +
 +  /* Check consensus contents. */
 +  test_assert(con->type == NS_TYPE_CONSENSUS);
 +  test_eq(con->published, 0); /* this field only appears in votes. */
 +  test_eq(con->valid_after, now+1000);
 +  test_eq(con->fresh_until, now+2003); /* median */
 +  test_eq(con->valid_until, now+3000);
 +  test_eq(con->vote_seconds, 100);
 +  test_eq(con->dist_seconds, 250); /* median */
 +  test_streq(con->client_versions, "0.1.2.14");
 +  test_streq(con->server_versions, "0.1.2.15,0.1.2.16");
 +  cp = smartlist_join_strings(v2->known_flags, ":", 0, NULL);
 +  test_streq(cp, "Authority:Exit:Fast:Guard:MadeOfCheese:MadeOfTin:"
 +             "Running:Stable:V2Dir:Valid");
 +  tor_free(cp);
 +  cp = smartlist_join_strings(con->net_params, ":", 0, NULL);
 +  test_streq(cp, "bar=2000000000:circuitwindow=80:foo=660");
 +  tor_free(cp);
 +
 +  test_eq(4, smartlist_len(con->voters)); /*3 voters, 1 legacy key.*/
 +  /* The voter id digests should be in this order. */
 +  test_assert(memcmp(cert2->cache_info.identity_digest,
 +                     cert1->cache_info.identity_digest,DIGEST_LEN)<0);
 +  test_assert(memcmp(cert1->cache_info.identity_digest,
 +                     cert3->cache_info.identity_digest,DIGEST_LEN)<0);
 +  test_same_voter(smartlist_get(con->voters, 1),
 +                  smartlist_get(v2->voters, 0));
 +  test_same_voter(smartlist_get(con->voters, 2),
 +                  smartlist_get(v1->voters, 0));
 +  test_same_voter(smartlist_get(con->voters, 3),
 +                  smartlist_get(v3->voters, 0));
 +
 +  test_assert(!con->cert);
 +  test_eq(2, smartlist_len(con->routerstatus_list));
 +  /* There should be two listed routers: one with identity 3, one with
 +   * identity 5. */
 +  /* This one showed up in 2 digests. */
 +  rs = smartlist_get(con->routerstatus_list, 0);
 +  test_memeq(rs->identity_digest,
 +             "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3",
 +             DIGEST_LEN);
 +  test_memeq(rs->descriptor_digest, "NNNNNNNNNNNNNNNNNNNN", DIGEST_LEN);
 +  test_assert(!rs->is_authority);
 +  test_assert(!rs->is_exit);
 +  test_assert(!rs->is_fast);
 +  test_assert(!rs->is_possible_guard);
 +  test_assert(!rs->is_stable);
 +  test_assert(rs->is_running); /* If it wasn't running it wouldn't be here */
 +  test_assert(!rs->is_v2_dir);
 +  test_assert(!rs->is_valid);
 +  test_assert(!rs->is_named);
 +  /* XXXX check version */
 +
 +  rs = smartlist_get(con->routerstatus_list, 1);
 +  /* This one showed up in 3 digests. Twice with ID 'M', once with 'Z'.  */
 +  test_memeq(rs->identity_digest,
 +             "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5",
 +             DIGEST_LEN);
 +  test_streq(rs->nickname, "router1");
 +  test_memeq(rs->descriptor_digest, "MMMMMMMMMMMMMMMMMMMM", DIGEST_LEN);
 +  test_eq(rs->published_on, now-1000);
 +  test_eq(rs->addr, 0x99009901);
 +  test_eq(rs->or_port, 443);
 +  test_eq(rs->dir_port, 0);
 +  test_assert(!rs->is_authority);
 +  test_assert(rs->is_exit);
 +  test_assert(rs->is_fast);
 +  test_assert(rs->is_possible_guard);
 +  test_assert(rs->is_stable);
 +  test_assert(rs->is_running);
 +  test_assert(rs->is_v2_dir);
 +  test_assert(rs->is_valid);
 +  test_assert(!rs->is_named);
 +  /* XXXX check version */
 +
 +  /* Check signatures.  the first voter is a pseudo-entry with a legacy key.
 +   * The second one hasn't signed.  The fourth one has signed: validate it. */
 +  voter = smartlist_get(con->voters, 1);
 +  test_eq(smartlist_len(voter->sigs), 0);
 +
 +  voter = smartlist_get(con->voters, 3);
 +  test_eq(smartlist_len(voter->sigs), 1);
 +  sig = smartlist_get(voter->sigs, 0);
 +  test_assert(sig->signature);
 +  test_assert(!sig->good_signature);
 +  test_assert(!sig->bad_signature);
 +
 +  test_assert(!networkstatus_check_document_signature(con, sig, cert3));
 +  test_assert(sig->signature);
 +  test_assert(sig->good_signature);
 +  test_assert(!sig->bad_signature);
 +
 +  {
 +    const char *msg=NULL;
 +    /* Compute the other two signed consensuses. */
 +    smartlist_shuffle(votes);
 +    consensus_text2 = networkstatus_compute_consensus(votes, 3,
 +                                                      cert2->identity_key,
 +                                                      sign_skey_2, NULL,NULL,
 +                                                      FLAV_NS);
 +    consensus_text_md2 = networkstatus_compute_consensus(votes, 3,
 +                                                      cert2->identity_key,
 +                                                      sign_skey_2, NULL,NULL,
 +                                                      FLAV_MICRODESC);
 +    smartlist_shuffle(votes);
 +    consensus_text3 = networkstatus_compute_consensus(votes, 3,
 +                                                      cert1->identity_key,
 +                                                      sign_skey_1, NULL,NULL,
 +                                                      FLAV_NS);
 +    consensus_text_md3 = networkstatus_compute_consensus(votes, 3,
 +                                                      cert1->identity_key,
 +                                                      sign_skey_1, NULL,NULL,
 +                                                      FLAV_MICRODESC);
 +    test_assert(consensus_text2);
 +    test_assert(consensus_text3);
 +    test_assert(consensus_text_md2);
 +    test_assert(consensus_text_md3);
 +    con2 = networkstatus_parse_vote_from_string(consensus_text2, NULL,
 +                                                NS_TYPE_CONSENSUS);
 +    con3 = networkstatus_parse_vote_from_string(consensus_text3, NULL,
 +                                                NS_TYPE_CONSENSUS);
 +    con_md2 = networkstatus_parse_vote_from_string(consensus_text_md2, NULL,
 +                                                NS_TYPE_CONSENSUS);
 +    con_md3 = networkstatus_parse_vote_from_string(consensus_text_md3, NULL,
 +                                                NS_TYPE_CONSENSUS);
 +    test_assert(con2);
 +    test_assert(con3);
 +    test_assert(con_md2);
 +    test_assert(con_md3);
 +
 +    /* All three should have the same digest. */
 +    test_memeq(&con->digests, &con2->digests, sizeof(digests_t));
 +    test_memeq(&con->digests, &con3->digests, sizeof(digests_t));
 +
 +    test_memeq(&con_md->digests, &con_md2->digests, sizeof(digests_t));
 +    test_memeq(&con_md->digests, &con_md3->digests, sizeof(digests_t));
 +
 +    /* Extract a detached signature from con3. */
 +    detached_text1 = get_detached_sigs(con3, con_md3);
 +    tor_assert(detached_text1);
 +    /* Try to parse it. */
 +    dsig1 = networkstatus_parse_detached_signatures(detached_text1, NULL);
 +    tor_assert(dsig1);
 +
 +    /* Are parsed values as expected? */
 +    test_eq(dsig1->valid_after, con3->valid_after);
 +    test_eq(dsig1->fresh_until, con3->fresh_until);
 +    test_eq(dsig1->valid_until, con3->valid_until);
 +    {
 +      digests_t *dsig_digests = strmap_get(dsig1->digests, "ns");
 +      test_assert(dsig_digests);
 +      test_memeq(dsig_digests->d[DIGEST_SHA1], con3->digests.d[DIGEST_SHA1],
 +                 DIGEST_LEN);
 +      dsig_digests = strmap_get(dsig1->digests, "microdesc");
 +      test_assert(dsig_digests);
 +      test_memeq(dsig_digests->d[DIGEST_SHA256],
 +                 con_md3->digests.d[DIGEST_SHA256],
 +                 DIGEST256_LEN);
 +    }
 +    {
 +      smartlist_t *dsig_signatures = strmap_get(dsig1->signatures, "ns");
 +      test_assert(dsig_signatures);
 +      test_eq(1, smartlist_len(dsig_signatures));
 +      sig = smartlist_get(dsig_signatures, 0);
 +      test_memeq(sig->identity_digest, cert1->cache_info.identity_digest,
 +                 DIGEST_LEN);
 +      test_eq(sig->alg, DIGEST_SHA1);
 +
 +      dsig_signatures = strmap_get(dsig1->signatures, "microdesc");
 +      test_assert(dsig_signatures);
 +      test_eq(1, smartlist_len(dsig_signatures));
 +      sig = smartlist_get(dsig_signatures, 0);
 +      test_memeq(sig->identity_digest, cert1->cache_info.identity_digest,
 +                 DIGEST_LEN);
 +      test_eq(sig->alg, DIGEST_SHA256);
 +    }
 +
 +    /* Try adding it to con2. */
 +    detached_text2 = get_detached_sigs(con2,con_md2);
 +    test_eq(1, networkstatus_add_detached_signatures(con2, dsig1, &msg));
 +    tor_free(detached_text2);
 +    test_eq(1, networkstatus_add_detached_signatures(con_md2, dsig1, &msg));
 +    tor_free(detached_text2);
 +    detached_text2 = get_detached_sigs(con2,con_md2);
 +    //printf("\n<%s>\n", detached_text2);
 +    dsig2 = networkstatus_parse_detached_signatures(detached_text2, NULL);
 +    test_assert(dsig2);
 +    /*
 +    printf("\n");
 +    SMARTLIST_FOREACH(dsig2->signatures, networkstatus_voter_info_t *, vi, {
 +        char hd[64];
 +        base16_encode(hd, sizeof(hd), vi->identity_digest, DIGEST_LEN);
 +        printf("%s\n", hd);
 +      });
 +    */
 +    test_eq(2,
 +            smartlist_len((smartlist_t*)strmap_get(dsig2->signatures, "ns")));
 +    test_eq(2,
 +            smartlist_len((smartlist_t*)strmap_get(dsig2->signatures,
 +                                                   "microdesc")));
 +
 +    /* Try adding to con2 twice; verify that nothing changes. */
 +    test_eq(0, networkstatus_add_detached_signatures(con2, dsig1, &msg));
 +
 +    /* Add to con. */
 +    test_eq(2, networkstatus_add_detached_signatures(con, dsig2, &msg));
 +    /* Check signatures */
 +    voter = smartlist_get(con->voters, 1);
 +    sig = smartlist_get(voter->sigs, 0);
 +    test_assert(sig);
 +    test_assert(!networkstatus_check_document_signature(con, sig, cert2));
 +    voter = smartlist_get(con->voters, 2);
 +    sig = smartlist_get(voter->sigs, 0);
 +    test_assert(sig);
 +    test_assert(!networkstatus_check_document_signature(con, sig, cert1));
 +  }
 +
 + done:
 +  smartlist_free(votes);
 +  tor_free(v1_text);
 +  tor_free(v2_text);
 +  tor_free(v3_text);
 +  tor_free(consensus_text);
 +  tor_free(consensus_text_md);
 +
 +  if (vote)
 +    networkstatus_vote_free(vote);
 +  if (v1)
 +    networkstatus_vote_free(v1);
 +  if (v2)
 +    networkstatus_vote_free(v2);
 +  if (v3)
 +    networkstatus_vote_free(v3);
 +  if (con)
 +    networkstatus_vote_free(con);
 +  if (con_md)
 +    networkstatus_vote_free(con_md);
 +  if (sign_skey_1)
 +    crypto_free_pk_env(sign_skey_1);
 +  if (sign_skey_2)
 +    crypto_free_pk_env(sign_skey_2);
 +  if (sign_skey_3)
 +    crypto_free_pk_env(sign_skey_3);
 +  if (sign_skey_leg1)
 +    crypto_free_pk_env(sign_skey_leg1);
 +  if (cert1)
 +    authority_cert_free(cert1);
 +  if (cert2)
 +    authority_cert_free(cert2);
 +  if (cert3)
 +    authority_cert_free(cert3);
 +
 +  tor_free(consensus_text2);
 +  tor_free(consensus_text3);
 +  tor_free(consensus_text_md2);
 +  tor_free(consensus_text_md3);
 +  tor_free(detached_text1);
 +  tor_free(detached_text2);
 +  if (con2)
 +    networkstatus_vote_free(con2);
 +  if (con3)
 +    networkstatus_vote_free(con3);
 +  if (con_md2)
 +    networkstatus_vote_free(con_md2);
 +  if (con_md3)
 +    networkstatus_vote_free(con_md3);
 +  if (dsig1)
 +    ns_detached_signatures_free(dsig1);
 +  if (dsig2)
 +    ns_detached_signatures_free(dsig2);
 +}
 +
 +#define DIR_LEGACY(name)                                                   \
 +  { #name, legacy_test_helper, 0, &legacy_setup, test_dir_ ## name }
 +
 +#define DIR(name)                               \
 +  { #name, test_dir_##name, 0, NULL, NULL }
 +
 +struct testcase_t dir_tests[] = {
 +  DIR_LEGACY(nicknames),
 +  DIR_LEGACY(formats),
 +  DIR_LEGACY(versions),
 +  DIR_LEGACY(fp_pairs),
 +  DIR(split_fps),
 +  DIR_LEGACY(measured_bw),
 +  DIR_LEGACY(param_voting),
 +  DIR_LEGACY(v3_networkstatus),
 +  END_OF_TESTCASES
 +};
 +