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

[tor-commits] [tor/master] extract networkstatus parsing to its own file.



commit 4201203845fa563a7f7410609267b96f2c319720
Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Date:   Mon Oct 1 10:44:59 2018 -0500

    extract networkstatus parsing to its own file.
---
 src/core/include.am                  |    2 +
 src/feature/dirauth/bwauth.c         |    2 +-
 src/feature/dirauth/dirvote.c        |   33 +-
 src/feature/dirauth/guardfraction.c  |    2 +-
 src/feature/dircache/consdiffmgr.c   |    2 +-
 src/feature/dircommon/consdiff.c     |    2 +-
 src/feature/dirparse/ns_parse.c      | 1684 ++++++++++++++++++++++++++++++++++
 src/feature/dirparse/ns_parse.h      |   45 +
 src/feature/dirparse/routerparse.c   | 1646 +--------------------------------
 src/feature/dirparse/routerparse.h   |   33 +-
 src/feature/nodelist/networkstatus.c |    3 +-
 src/test/fuzz/fuzz_consensus.c       |    2 +-
 src/test/fuzz/fuzz_vrs.c             |    4 +-
 src/test/test_consdiffmgr.c          |    2 +-
 src/test/test_dir.c                  |    2 +
 src/test/test_dir_common.c           |    1 +
 src/test/test_guardfraction.c        |    4 +-
 src/test/test_routerlist.c           |    1 +
 18 files changed, 1767 insertions(+), 1703 deletions(-)

diff --git a/src/core/include.am b/src/core/include.am
index 6815f327f..1cecd8ba7 100644
--- a/src/core/include.am
+++ b/src/core/include.am
@@ -75,6 +75,7 @@ LIBTOR_APP_A_SOURCES = 				\
 	src/feature/dircommon/voting_schedule.c	\
 	src/feature/dirparse/authcert_parse.c	\
 	src/feature/dirparse/microdesc_parse.c	\
+	src/feature/dirparse/ns_parse.c		\
 	src/feature/dirparse/parsecommon.c	\
 	src/feature/dirparse/routerparse.c	\
 	src/feature/dirparse/sigcommon.c	\
@@ -298,6 +299,7 @@ noinst_HEADERS +=					\
 	src/feature/dirparse/authcert_members.i		\
 	src/feature/dirparse/authcert_parse.h		\
 	src/feature/dirparse/microdesc_parse.h		\
+	src/feature/dirparse/ns_parse.h			\
 	src/feature/dirparse/parsecommon.h		\
 	src/feature/dirparse/routerparse.h		\
 	src/feature/dirparse/sigcommon.h		\
diff --git a/src/feature/dirauth/bwauth.c b/src/feature/dirauth/bwauth.c
index ec23f629c..29e0c9096 100644
--- a/src/feature/dirauth/bwauth.c
+++ b/src/feature/dirauth/bwauth.c
@@ -15,7 +15,7 @@
 #include "app/config/config.h"
 #include "feature/nodelist/networkstatus.h"
 #include "feature/nodelist/routerlist.h"
-#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/ns_parse.h"
 
 #include "feature/nodelist/routerinfo_st.h"
 #include "feature/nodelist/vote_routerstatus_st.h"
diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c
index 0322c9cae..80a5b5473 100644
--- a/src/feature/dirauth/dirvote.c
+++ b/src/feature/dirauth/dirvote.c
@@ -6,32 +6,33 @@
 #define DIRVOTE_PRIVATE
 #include "core/or/or.h"
 #include "app/config/config.h"
+#include "core/or/policies.h"
+#include "core/or/protover.h"
+#include "core/or/tor_version_st.h"
+#include "core/or/versions.h"
+#include "feature/dirauth/bwauth.h"
 #include "feature/dirauth/dircollate.h"
 #include "feature/dirauth/dsigs_parse.h"
+#include "feature/dirauth/guardfraction.h"
 #include "feature/dirauth/recommend_pkg.h"
 #include "feature/dirauth/voteflags.h"
-#include "feature/dircommon/directory.h"
-#include "feature/dirclient/dirclient.h"
-#include "feature/dirauth/bwauth.h"
 #include "feature/dircache/dirserv.h"
-#include "feature/dirauth/guardfraction.h"
+#include "feature/dirclient/dirclient.h"
+#include "feature/dircommon/directory.h"
+#include "feature/dirparse/microdesc_parse.h"
+#include "feature/dirparse/ns_parse.h"
+#include "feature/dirparse/parsecommon.h"
+#include "feature/dirparse/signing.h"
+#include "feature/nodelist/authcert.h"
+#include "feature/nodelist/dirlist.h"
+#include "feature/nodelist/fmt_routerstatus.h"
 #include "feature/nodelist/microdesc.h"
 #include "feature/nodelist/networkstatus.h"
 #include "feature/nodelist/nodelist.h"
-#include "feature/dirparse/parsecommon.h"
-#include "core/or/policies.h"
-#include "core/or/protover.h"
-#include "core/or/tor_version_st.h"
-#include "feature/stats/rephist.h"
+#include "feature/nodelist/routerlist.h"
 #include "feature/relay/router.h"
 #include "feature/relay/routerkeys.h"
-#include "feature/nodelist/authcert.h"
-#include "feature/nodelist/dirlist.h"
-#include "feature/nodelist/routerlist.h"
-#include "feature/dirparse/routerparse.h"
-#include "feature/dirparse/microdesc_parse.h"
-#include "feature/dirparse/signing.h"
-#include "feature/nodelist/fmt_routerstatus.h"
+#include "feature/stats/rephist.h"
 #include "feature/client/entrynodes.h" /* needed for guardfraction methods */
 #include "feature/nodelist/torcert.h"
 #include "feature/dircommon/voting_schedule.h"
diff --git a/src/feature/dirauth/guardfraction.c b/src/feature/dirauth/guardfraction.c
index 812cb7df2..1734d0a13 100644
--- a/src/feature/dirauth/guardfraction.c
+++ b/src/feature/dirauth/guardfraction.c
@@ -12,7 +12,7 @@
 #include "core/or/or.h"
 #include "feature/dirauth/guardfraction.h"
 #include "feature/nodelist/networkstatus.h"
-#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/ns_parse.h"
 
 #include "feature/nodelist/vote_routerstatus_st.h"
 
diff --git a/src/feature/dircache/consdiffmgr.c b/src/feature/dircache/consdiffmgr.c
index 7dc56f4f1..e79aad6ef 100644
--- a/src/feature/dircache/consdiffmgr.c
+++ b/src/feature/dircache/consdiffmgr.c
@@ -20,7 +20,7 @@
 #include "feature/dircache/consdiffmgr.h"
 #include "core/mainloop/cpuworker.h"
 #include "feature/nodelist/networkstatus.h"
-#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/ns_parse.h"
 #include "lib/evloop/compat_libevent.h"
 #include "lib/evloop/workqueue.h"
 #include "lib/compress/compress.h"
diff --git a/src/feature/dircommon/consdiff.c b/src/feature/dircommon/consdiff.c
index 944ddd552..f8ced8503 100644
--- a/src/feature/dircommon/consdiff.c
+++ b/src/feature/dircommon/consdiff.c
@@ -41,7 +41,7 @@
 #include "core/or/or.h"
 #include "feature/dircommon/consdiff.h"
 #include "lib/memarea/memarea.h"
-#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/ns_parse.h"
 
 static const char* ns_diff_version = "network-status-diff-version 1";
 static const char* hash_token = "hash";
diff --git a/src/feature/dirparse/ns_parse.c b/src/feature/dirparse/ns_parse.c
new file mode 100644
index 000000000..e356c0c11
--- /dev/null
+++ b/src/feature/dirparse/ns_parse.c
@@ -0,0 +1,1684 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file routerparse.c
+ * \brief Code to parse and validate consensus documents and votes.
+ */
+
+#define NS_PARSE_PRIVATE
+
+#include "core/or/or.h"
+#include "app/config/config.h"
+#include "feature/client/entrynodes.h"
+#include "feature/dirauth/dirvote.h"
+#include "feature/dirparse/authcert_parse.h"
+#include "feature/dirparse/ns_parse.h"
+#include "feature/dirparse/parsecommon.h"
+#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/sigcommon.h"
+#include "feature/dirparse/unparseable.h"
+#include "feature/hs_common/shared_random_client.h"
+#include "feature/nodelist/authcert.h"
+#include "feature/nodelist/describe.h"
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/nickname.h"
+#include "lib/crypt_ops/crypto_format.h"
+#include "lib/memarea/memarea.h"
+
+#include "feature/dirauth/vote_microdesc_hash_st.h"
+#include "feature/nodelist/authority_cert_st.h"
+#include "feature/nodelist/document_signature_st.h"
+#include "feature/nodelist/networkstatus_st.h"
+#include "feature/nodelist/networkstatus_voter_info_st.h"
+#include "feature/nodelist/vote_routerstatus_st.h"
+
+#undef log
+#include <math.h>
+
+/** List of tokens recognized in the body part of v3 networkstatus
+ * documents. */
+static token_rule_t rtrstatus_token_table[] = {
+  T01("p",                   K_P,               CONCAT_ARGS, NO_OBJ ),
+  T1( "r",                   K_R,                   GE(7),   NO_OBJ ),
+  T0N("a",                   K_A,                   GE(1),   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("id",                  K_ID,                  GE(2),   NO_OBJ ),
+  T01("pr",                  K_PROTO,           CONCAT_ARGS, NO_OBJ ),
+  T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
+  END_OF_TABLE
+};
+
+/** List of tokens recognized in V3 networkstatus votes. */
+static token_rule_t networkstatus_token_table[] = {
+  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 ),
+  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 ),
+  T1("voting-delay",           K_VOTING_DELAY,     GE(2),       NO_OBJ ),
+  T1("known-flags",            K_KNOWN_FLAGS,      ARGS,        NO_OBJ ),
+  T01("params",                K_PARAMS,           ARGS,        NO_OBJ ),
+  T( "fingerprint",            K_FINGERPRINT,      CONCAT_ARGS, NO_OBJ ),
+  T01("signing-ed25519",       K_SIGNING_CERT_ED,  NO_ARGS ,    NEED_OBJ ),
+  T01("shared-rand-participate",K_SR_FLAG,         NO_ARGS,     NO_OBJ ),
+  T0N("shared-rand-commit",    K_COMMIT,           GE(3),       NO_OBJ ),
+  T01("shared-rand-previous-value", K_PREVIOUS_SRV,EQ(2),       NO_OBJ ),
+  T01("shared-rand-current-value",  K_CURRENT_SRV, EQ(2),       NO_OBJ ),
+  T0N("package",               K_PACKAGE,          CONCAT_ARGS, NO_OBJ ),
+  T01("recommended-client-protocols", K_RECOMMENDED_CLIENT_PROTOCOLS,
+      CONCAT_ARGS, NO_OBJ ),
+  T01("recommended-relay-protocols", K_RECOMMENDED_RELAY_PROTOCOLS,
+      CONCAT_ARGS, NO_OBJ ),
+  T01("required-client-protocols",    K_REQUIRED_CLIENT_PROTOCOLS,
+      CONCAT_ARGS, NO_OBJ ),
+  T01("required-relay-protocols",    K_REQUIRED_RELAY_PROTOCOLS,
+      CONCAT_ARGS, NO_OBJ ),
+
+#include "feature/dirparse/authcert_members.i"
+
+  T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
+  T1( "contact",             K_CONTACT,         CONCAT_ARGS, NO_OBJ ),
+  T1( "dir-source",          K_DIR_SOURCE,      GE(6),       NO_OBJ ),
+  T01("legacy-dir-key",      K_LEGACY_DIR_KEY,  GE(1),       NO_OBJ ),
+  T1( "known-flags",         K_KNOWN_FLAGS,     CONCAT_ARGS, NO_OBJ ),
+  T01("client-versions",     K_CLIENT_VERSIONS, CONCAT_ARGS, NO_OBJ ),
+  T01("server-versions",     K_SERVER_VERSIONS, CONCAT_ARGS, NO_OBJ ),
+  T1( "consensus-methods",   K_CONSENSUS_METHODS, GE(1),     NO_OBJ ),
+
+  END_OF_TABLE
+};
+
+/** List of tokens recognized in V3 networkstatus consensuses. */
+static token_rule_t networkstatus_consensus_token_table[] = {
+  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 ),
+  T1("fresh-until",            K_FRESH_UNTIL,      CONCAT_ARGS, NO_OBJ ),
+  T1("valid-until",            K_VALID_UNTIL,      CONCAT_ARGS, NO_OBJ ),
+  T1("voting-delay",           K_VOTING_DELAY,     GE(2),       NO_OBJ ),
+
+  T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
+
+  T1N("dir-source",          K_DIR_SOURCE,          GE(6),   NO_OBJ ),
+  T1N("contact",             K_CONTACT,         CONCAT_ARGS, NO_OBJ ),
+  T1N("vote-digest",         K_VOTE_DIGEST,         GE(1),   NO_OBJ ),
+
+  T1( "known-flags",         K_KNOWN_FLAGS,     CONCAT_ARGS, NO_OBJ ),
+
+  T01("client-versions",     K_CLIENT_VERSIONS, CONCAT_ARGS, NO_OBJ ),
+  T01("server-versions",     K_SERVER_VERSIONS, CONCAT_ARGS, NO_OBJ ),
+  T01("consensus-method",    K_CONSENSUS_METHOD,    EQ(1),   NO_OBJ),
+  T01("params",                K_PARAMS,           ARGS,        NO_OBJ ),
+
+  T01("shared-rand-previous-value", K_PREVIOUS_SRV, EQ(2),   NO_OBJ ),
+  T01("shared-rand-current-value",  K_CURRENT_SRV,  EQ(2),   NO_OBJ ),
+
+  T01("recommended-client-protocols", K_RECOMMENDED_CLIENT_PROTOCOLS,
+      CONCAT_ARGS, NO_OBJ ),
+  T01("recommended-relay-protocols", K_RECOMMENDED_RELAY_PROTOCOLS,
+      CONCAT_ARGS, NO_OBJ ),
+  T01("required-client-protocols",    K_REQUIRED_CLIENT_PROTOCOLS,
+      CONCAT_ARGS, NO_OBJ ),
+  T01("required-relay-protocols",    K_REQUIRED_RELAY_PROTOCOLS,
+      CONCAT_ARGS, NO_OBJ ),
+
+  END_OF_TABLE
+};
+
+/** List of tokens recognized in the footer of v1 directory footers. */
+static token_rule_t networkstatus_vote_footer_token_table[] = {
+  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
+};
+
+/** Try to find the start and end of the signed portion of a networkstatus
+ * document in <b>s</b>. On success, set <b>start_out</b> to the first
+ * character of the document, and <b>end_out</b> to a position one after the
+ * final character of the signed document, and return 0.  On failure, return
+ * -1. */
+int
+router_get_networkstatus_v3_signed_boundaries(const char *s,
+                                              const char **start_out,
+                                              const char **end_out)
+{
+  return router_get_hash_impl_helper(s, strlen(s),
+                                     "network-status-version",
+                                     "\ndirectory-signature",
+                                     ' ', LOG_INFO,
+                                     start_out, end_out);
+}
+
+/** Set <b>digest_out</b> to the SHA3-256 digest of the signed portion of the
+ * networkstatus vote in <b>s</b> -- or of the entirety of <b>s</b> if no
+ * signed portion can be identified.  Return 0 on success, -1 on failure. */
+int
+router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out,
+                                           const char *s)
+{
+  const char *start, *end;
+  if (router_get_networkstatus_v3_signed_boundaries(s, &start, &end) < 0) {
+    start = s;
+    end = s + strlen(s);
+  }
+  tor_assert(start);
+  tor_assert(end);
+  return crypto_digest256((char*)digest_out, start, end-start,
+                          DIGEST_SHA3_256);
+}
+
+/** 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, common_digests_t *digests)
+{
+  return router_get_hashes_impl(s,strlen(s),digests,
+                                "network-status-version",
+                                "\ndirectory-signature",
+                                ' ');
+}
+
+/** 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 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, *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;
+}
+
+/** Parse the GuardFraction string from a consensus or vote.
+ *
+ *  If <b>vote</b> or <b>vote_rs</b> are set the document getting
+ *  parsed is a vote routerstatus. Otherwise it's a consensus. This is
+ *  the same semantic as in routerstatus_parse_entry_from_string(). */
+STATIC int
+routerstatus_parse_guardfraction(const char *guardfraction_str,
+                                 networkstatus_t *vote,
+                                 vote_routerstatus_t *vote_rs,
+                                 routerstatus_t *rs)
+{
+  int ok;
+  const char *end_of_header = NULL;
+  int is_consensus = !vote_rs;
+  uint32_t guardfraction;
+
+  tor_assert(bool_eq(vote, vote_rs));
+
+  /* If this info comes from a consensus, but we should't apply
+     guardfraction, just exit. */
+  if (is_consensus && !should_apply_guardfraction(NULL)) {
+    return 0;
+  }
+
+  end_of_header = strchr(guardfraction_str, '=');
+  if (!end_of_header) {
+    return -1;
+  }
+
+  guardfraction = (uint32_t)tor_parse_ulong(end_of_header+1,
+                                            10, 0, 100, &ok, NULL);
+  if (!ok) {
+    log_warn(LD_DIR, "Invalid GuardFraction %s", escaped(guardfraction_str));
+    return -1;
+  }
+
+  log_debug(LD_GENERAL, "[*] Parsed %s guardfraction '%s' for '%s'.",
+            is_consensus ? "consensus" : "vote",
+            guardfraction_str, rs->nickname);
+
+  if (!is_consensus) { /* We are parsing a vote */
+    vote_rs->status.guardfraction_percentage = guardfraction;
+    vote_rs->status.has_guardfraction = 1;
+  } else {
+    /* We are parsing a consensus. Only apply guardfraction to guards. */
+    if (rs->is_possible_guard) {
+      rs->guardfraction_percentage = guardfraction;
+      rs->has_guardfraction = 1;
+    } else {
+      log_warn(LD_BUG, "Got GuardFraction for non-guard %s. "
+               "This is not supposed to happen. Not applying. ", rs->nickname);
+    }
+  }
+
+  return 0;
+}
+
+/** Given a string at *<b>s</b>, containing a routerstatus object, and an
+ * empty smartlist at <b>tokens</b>, parse and return the first router status
+ * object in the string, and advance *<b>s</b> to just after the end of the
+ * router status.  Return NULL and advance *<b>s</b> on error.
+ *
+ * If <b>vote</b> and <b>vote_rs</b> are provided, don't allocate a fresh
+ * routerstatus but use <b>vote_rs</b> instead.
+ *
+ * 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,
+                                     consensus_flavor_t flav)
+{
+  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;
+  tor_assert(flav == FLAV_NS || flav == FLAV_MICRODESC);
+
+  eos = find_start_of_next_routerstatus(*s);
+
+  if (tokenize_string(area,*s, eos, tokens, rtrstatus_token_table,0)) {
+    log_warn(LD_DIR, "Error tokenizing router status");
+    goto err;
+  }
+  if (smartlist_len(tokens) < 1) {
+    log_warn(LD_DIR, "Impossibly short router status");
+    goto err;
+  }
+  tok = find_by_keyword(tokens, K_R);
+  tor_assert(tok->n_args >= 7); /* guaranteed by GE(7) in K_R setup */
+  if (flav == FLAV_NS) {
+    if (tok->n_args < 8) {
+      log_warn(LD_DIR, "Too few arguments to r");
+      goto err;
+    }
+  } else if (flav == FLAV_MICRODESC) {
+    offset = -1; /* There is no descriptor digest in an md consensus r line */
+  }
+
+  if (vote_rs) {
+    rs = &vote_rs->status;
+  } else {
+    rs = tor_malloc_zero(sizeof(routerstatus_t));
+  }
+
+  if (!is_legal_nickname(tok->args[0])) {
+    log_warn(LD_DIR,
+             "Invalid nickname %s in router status; skipping.",
+             escaped(tok->args[0]));
+    goto err;
+  }
+  strlcpy(rs->nickname, tok->args[0], sizeof(rs->nickname));
+
+  if (digest_from_base64(rs->identity_digest, tok->args[1])) {
+    log_warn(LD_DIR, "Error decoding identity digest %s",
+             escaped(tok->args[1]));
+    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+offset], tok->args[4+offset]) < 0 ||
+      parse_iso_time(timebuf, &rs->published_on)<0) {
+    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+offset], &in) == 0) {
+    log_warn(LD_DIR, "Error parsing router address in network-status %s",
+             escaped(tok->args[5+offset]));
+    goto err;
+  }
+  rs->addr = ntohl(in.s_addr);
+
+  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);
+
+  {
+    smartlist_t *a_lines = find_all_by_keyword(tokens, K_A);
+    if (a_lines) {
+      find_single_ipv6_orport(a_lines, &rs->ipv6_addr, &rs->ipv6_orport);
+      smartlist_free(a_lines);
+    }
+  }
+
+  tok = find_opt_by_keyword(tokens, K_S);
+  if (tok && vote) {
+    int i;
+    vote_rs->flags = 0;
+    for (i=0; i < tok->n_args; ++i) {
+      int p = smartlist_string_pos(vote->known_flags, tok->args[i]);
+      if (p >= 0) {
+        vote_rs->flags |= (UINT64_C(1)<<p);
+      } else {
+        log_warn(LD_DIR, "Flags line had a flag %s not listed in known_flags.",
+                 escaped(tok->args[i]));
+        goto err;
+      }
+    }
+  } else if (tok) {
+    /* This is a consensus, not a vote. */
+    int i;
+    for (i=0; i < tok->n_args; ++i) {
+      if (!strcmp(tok->args[i], "Exit"))
+        rs->is_exit = 1;
+      else if (!strcmp(tok->args[i], "Stable"))
+        rs->is_stable = 1;
+      else if (!strcmp(tok->args[i], "Fast"))
+        rs->is_fast = 1;
+      else if (!strcmp(tok->args[i], "Running"))
+        rs->is_flagged_running = 1;
+      else if (!strcmp(tok->args[i], "Named"))
+        rs->is_named = 1;
+      else if (!strcmp(tok->args[i], "Valid"))
+        rs->is_valid = 1;
+      else if (!strcmp(tok->args[i], "Guard"))
+        rs->is_possible_guard = 1;
+      else if (!strcmp(tok->args[i], "BadExit"))
+        rs->is_bad_exit = 1;
+      else if (!strcmp(tok->args[i], "Authority"))
+        rs->is_authority = 1;
+      else if (!strcmp(tok->args[i], "Unnamed") &&
+               consensus_method >= 2) {
+        /* Unnamed is computed right by consensus method 2 and later. */
+        rs->is_unnamed = 1;
+      } else if (!strcmp(tok->args[i], "HSDir")) {
+        rs->is_hs_dir = 1;
+      } else if (!strcmp(tok->args[i], "V2Dir")) {
+        rs->is_v2_dir = 1;
+      }
+    }
+    /* These are implied true by having been included in a consensus made
+     * with a given method */
+    rs->is_flagged_running = 1; /* Starting with consensus method 4. */
+    rs->is_valid = 1; /* Starting with consensus method 24. */
+  }
+  {
+    const char *protocols = NULL, *version = NULL;
+    if ((tok = find_opt_by_keyword(tokens, K_PROTO))) {
+      tor_assert(tok->n_args == 1);
+      protocols = tok->args[0];
+    }
+    if ((tok = find_opt_by_keyword(tokens, K_V))) {
+      tor_assert(tok->n_args == 1);
+      version = tok->args[0];
+      if (vote_rs) {
+        vote_rs->version = tor_strdup(tok->args[0]);
+      }
+    }
+
+    summarize_protover_flags(&rs->pv, protocols, version);
+  }
+
+  /* handle weighting/bandwidth info */
+  if ((tok = find_opt_by_keyword(tokens, K_W))) {
+    int i;
+    for (i=0; i < tok->n_args; ++i) {
+      if (!strcmpstart(tok->args[i], "Bandwidth=")) {
+        int ok;
+        rs->bandwidth_kb =
+          (uint32_t)tor_parse_ulong(strchr(tok->args[i], '=')+1,
+                                    10, 0, UINT32_MAX,
+                                    &ok, NULL);
+        if (!ok) {
+          log_warn(LD_DIR, "Invalid Bandwidth %s", escaped(tok->args[i]));
+          goto err;
+        }
+        rs->has_bandwidth = 1;
+      } else if (!strcmpstart(tok->args[i], "Measured=") && vote_rs) {
+        int ok;
+        vote_rs->measured_bw_kb =
+            (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;
+        }
+        vote_rs->has_measured_bw = 1;
+        vote->has_measured_bws = 1;
+      } else if (!strcmpstart(tok->args[i], "Unmeasured=1")) {
+        rs->bw_is_unmeasured = 1;
+      } else if (!strcmpstart(tok->args[i], "GuardFraction=")) {
+        if (routerstatus_parse_guardfraction(tok->args[i],
+                                             vote, vote_rs, rs) < 0) {
+          goto err;
+        }
+      }
+    }
+  }
+
+  /* parse exit policy summaries */
+  if ((tok = find_opt_by_keyword(tokens, K_P))) {
+    tor_assert(tok->n_args == 1);
+    if (strcmpstart(tok->args[0], "accept ") &&
+        strcmpstart(tok->args[0], "reject ")) {
+      log_warn(LD_DIR, "Unknown exit policy summary type %s.",
+               escaped(tok->args[0]));
+      goto err;
+    }
+    /* XXX weasel: parse this into ports and represent them somehow smart,
+     * maybe not here but somewhere on if we need it for the client.
+     * we should still parse it here to check it's valid tho.
+     */
+    rs->exitsummary = tor_strdup(tok->args[0]);
+    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;
+      }
+      if (t->tp == K_ID) {
+        tor_assert(t->n_args >= 2);
+        if (!strcmp(t->args[0], "ed25519")) {
+          vote_rs->has_ed25519_listing = 1;
+          if (strcmp(t->args[1], "none") &&
+              digest256_from_base64((char*)vote_rs->ed25519_id,
+                                    t->args[1])<0) {
+            log_warn(LD_DIR, "Bogus ed25519 key in networkstatus vote");
+            goto err;
+          }
+        }
+      }
+      if (t->tp == K_PROTO) {
+        tor_assert(t->n_args == 1);
+        vote_rs->protocols = tor_strdup(t->args[0]);
+      }
+    } SMARTLIST_FOREACH_END(t);
+  } else if (flav == FLAV_MICRODESC) {
+    tok = find_opt_by_keyword(tokens, K_M);
+    if (tok) {
+      tor_assert(tok->n_args);
+      if (digest256_from_base64(rs->descriptor_digest, tok->args[0])) {
+        log_warn(LD_DIR, "Error decoding microdescriptor digest %s",
+                 escaped(tok->args[0]));
+        goto err;
+      }
+    } else {
+      log_info(LD_BUG, "Found an entry in networkstatus with no "
+               "microdescriptor digest. (Router %s ($%s) at %s:%d.)",
+               rs->nickname, hex_str(rs->identity_digest, DIGEST_LEN),
+               fmt_addr32(rs->addr), rs->or_port);
+    }
+  }
+
+  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_clear(t));
+  smartlist_clear(tokens);
+  if (area) {
+    DUMP_AREA(area, "routerstatus entry");
+    memarea_clear(area);
+  }
+  *s = eos;
+
+  return rs;
+}
+
+int
+compare_vote_routerstatus_entries(const void **_a, const void **_b)
+{
+  const vote_routerstatus_t *a = *_a, *b = *_b;
+  return fast_memcmp(a->status.identity_digest, b->status.identity_digest,
+                     DIGEST_LEN);
+}
+
+/** Verify the bandwidth weights of a network status document */
+int
+networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method)
+{
+  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;
+  (void) consensus_method;
+
+  const int64_t weight_scale = networkstatus_get_weight_scale_param(ns);
+  tor_assert(weight_scale >= 1);
+  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=%f != %"PRId64,
+             Wmm, (weight_scale));
+    valid = 0;
+  }
+
+  if (fabs(Wem - Wee) > 1) {
+    log_warn(LD_BUG, "Wem=%f != Wee=%f", Wem, Wee);
+    valid = 0;
+  }
+
+  if (fabs(Wgm - Wgg) > 1) {
+    log_warn(LD_BUG, "Wgm=%f != Wgg=%f", Wgm, Wgg);
+    valid = 0;
+  }
+
+  if (fabs(Weg - Wed) > 1) {
+    log_warn(LD_BUG, "Wed=%f != Weg=%f", Wed, Weg);
+    valid = 0;
+  }
+
+  if (fabs(Wgg + Wmg - weight_scale) > 0.001*weight_scale) {
+    log_warn(LD_BUG, "Wgg=%f != %"PRId64" - Wmg=%f", Wgg,
+             (weight_scale), Wmg);
+    valid = 0;
+  }
+
+  if (fabs(Wee + Wme - weight_scale) > 0.001*weight_scale) {
+    log_warn(LD_BUG, "Wee=%f != %"PRId64" - Wme=%f", Wee,
+             (weight_scale), Wme);
+    valid = 0;
+  }
+
+  if (fabs(Wgd + Wmd + Wed - weight_scale) > 0.001*weight_scale) {
+    log_warn(LD_BUG, "Wgd=%f + Wmd=%f + Wed=%f != %"PRId64,
+             Wgd, Wmd, Wed, (weight_scale));
+    valid = 0;
+  }
+
+  Wgg /= weight_scale;
+  Wgm /= weight_scale; (void) Wgm; // unused from here on.
+  Wgd /= weight_scale;
+
+  Wmg /= weight_scale;
+  Wmm /= weight_scale;
+  Wme /= weight_scale;
+  Wmd /= weight_scale;
+
+  Weg /= weight_scale; (void) Weg; // unused from here on.
+  Wem /= weight_scale; (void) Wem; // unused from here on.
+  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) {
+    int is_exit = 0;
+    /* Bug #2203: Don't count bad exits as exits for balancing */
+    is_exit = rs->is_exit && !rs->is_bad_exit;
+    if (rs->has_bandwidth) {
+      T += rs->bandwidth_kb;
+      if (is_exit && rs->is_possible_guard) {
+        D += rs->bandwidth_kb;
+        Gtotal += Wgd*rs->bandwidth_kb;
+        Mtotal += Wmd*rs->bandwidth_kb;
+        Etotal += Wed*rs->bandwidth_kb;
+      } else if (is_exit) {
+        E += rs->bandwidth_kb;
+        Mtotal += Wme*rs->bandwidth_kb;
+        Etotal += Wee*rs->bandwidth_kb;
+      } else if (rs->is_possible_guard) {
+        G += rs->bandwidth_kb;
+        Gtotal += Wgg*rs->bandwidth_kb;
+        Mtotal += Wmg*rs->bandwidth_kb;
+      } else {
+        M += rs->bandwidth_kb;
+        Mtotal += Wmm*rs->bandwidth_kb;
+      }
+    } else {
+      log_warn(LD_BUG, "Missing consensus bandwidth for router %s",
+               routerstatus_describe(rs));
+    }
+  } 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 %f != Mtotal %f. "
+               "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+               " T=%"PRId64". "
+               "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+               casename, Etotal, Mtotal,
+               (G), (M), (E),
+               (D), (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 %f != Gtotal %f. "
+               "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+               " T=%"PRId64". "
+               "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+               casename, Etotal, Gtotal,
+               (G), (M), (E),
+               (D), (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 %f != Gtotal %f. "
+               "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+               " T=%"PRId64". "
+               "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+               casename, Mtotal, Gtotal,
+               (G), (M), (E),
+               (D), (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 %f > Stotal %f. "
+                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+                   " T=%"PRId64". "
+                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                   casename, Rtotal, Stotal,
+                   (G), (M), (E),
+                   (D), (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 %f > T "
+                   "%"PRId64". G=%"PRId64" M=%"PRId64" E=%"PRId64
+                   " D=%"PRId64" T=%"PRId64". "
+                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                   casename, Rtotal*3, (T),
+                   (G), (M), (E),
+                   (D), (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 %f > T "
+                   "%"PRId64". G=%"PRId64" M=%"PRId64" E=%"PRId64
+                   " D=%"PRId64" T=%"PRId64". "
+                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                   casename, Stotal*3, (T),
+                   (G), (M), (E),
+                   (D), (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 %f < T "
+                   "%"PRId64". "
+                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+                   " T=%"PRId64". "
+                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                   casename, Mtotal*3, (T),
+                   (G), (M), (E),
+                   (D), (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 %f != Mtotal %f. "
+                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+                   " T=%"PRId64". "
+                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                   casename, Etotal, Mtotal,
+                   (G), (M), (E),
+                   (D), (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 %f != Gtotal %f. "
+                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+                   " T=%"PRId64". "
+                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                   casename, Etotal, Gtotal,
+                   (G), (M), (E),
+                   (D), (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 %f != Gtotal %f. "
+                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+                   " T=%"PRId64". "
+                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                   casename, Mtotal, Gtotal,
+                   (G), (M), (E),
+                   (D), (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 %f != Gtotal %f. "
+                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+                   " T=%"PRId64". "
+                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                   casename, Etotal, Gtotal,
+                   (G), (M), (E),
+                   (D), (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 %f > T "
+                   "%"PRId64". G=%"PRId64" M=%"PRId64" E=%"PRId64
+                   " D=%"PRId64" T=%"PRId64". "
+                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                   casename, Stotal*3, (T),
+                   (G), (M), (E),
+                   (D), (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 %f != Mtotal %f. "
+                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+                   " T=%"PRId64". "
+                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                   casename, NStotal, Mtotal,
+                   (G), (M), (E),
+                   (D), (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 %f < T "
+                     "%"PRId64". G=%"PRId64" M=%"PRId64
+                     " E=%"PRId64" D=%"PRId64" T=%"PRId64". "
+                     "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                     casename, NStotal*3, (T),
+                     (G), (M), (E),
+                     (D), (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 %f != Mtotal %f. "
+                 "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+                 " T=%"PRId64". "
+                 "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                 casename, Etotal, Mtotal,
+                 (G), (M), (E),
+                 (D), (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 %f != Gtotal %f. "
+                 "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+                 " T=%"PRId64". "
+                 "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                 casename, Etotal, Gtotal,
+                 (G), (M), (E),
+                 (D), (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 %f != Gtotal %f. "
+                 "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+                 " T=%"PRId64". "
+                 "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
+                 casename, Mtotal, Gtotal,
+                 (G), (M), (E),
+                 (D), (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;
+}
+
+/** Check if a shared random value of type <b>srv_type</b> is in
+ *  <b>tokens</b>. If there is, parse it and set it to <b>srv_out</b>. Return
+ *  -1 on failure, 0 on success. The resulting srv is allocated on the heap and
+ *  it's the responsibility of the caller to free it. */
+static int
+extract_one_srv(smartlist_t *tokens, directory_keyword srv_type,
+                sr_srv_t **srv_out)
+{
+  int ret = -1;
+  directory_token_t *tok;
+  sr_srv_t *srv = NULL;
+  smartlist_t *chunks;
+
+  tor_assert(tokens);
+
+  chunks = smartlist_new();
+  tok = find_opt_by_keyword(tokens, srv_type);
+  if (!tok) {
+    /* That's fine, no SRV is allowed. */
+    ret = 0;
+    goto end;
+  }
+  for (int i = 0; i < tok->n_args; i++) {
+    smartlist_add(chunks, tok->args[i]);
+  }
+  srv = sr_parse_srv(chunks);
+  if (srv == NULL) {
+    log_warn(LD_DIR, "SR: Unparseable SRV %s", escaped(tok->object_body));
+    goto end;
+  }
+  /* All is good. */
+  *srv_out = srv;
+  ret = 0;
+ end:
+  smartlist_free(chunks);
+  return ret;
+}
+
+/** Extract any shared random values found in <b>tokens</b> and place them in
+ *  the networkstatus <b>ns</b>. */
+static void
+extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens)
+{
+  const char *voter_identity;
+  networkstatus_voter_info_t *voter;
+
+  tor_assert(ns);
+  tor_assert(tokens);
+  /* Can be only one of them else code flow. */
+  tor_assert(ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_CONSENSUS);
+
+  if (ns->type == NS_TYPE_VOTE) {
+    voter = smartlist_get(ns->voters, 0);
+    tor_assert(voter);
+    voter_identity = hex_str(voter->identity_digest,
+                             sizeof(voter->identity_digest));
+  } else {
+    /* Consensus has multiple voters so no specific voter. */
+    voter_identity = "consensus";
+  }
+
+  /* We extract both, and on error everything is stopped because it means
+   * the vote is malformed for the shared random value(s). */
+  if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) {
+    log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s",
+             voter_identity);
+    /* Maybe we have a chance with the current SRV so let's try it anyway. */
+  }
+  if (extract_one_srv(tokens, K_CURRENT_SRV, &ns->sr_info.current_srv) < 0) {
+    log_warn(LD_DIR, "SR: Unable to parse current SRV from %s",
+             voter_identity);
+  }
+}
+
+/** 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 *
+networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
+                                     networkstatus_type_t ns_type)
+{
+  smartlist_t *tokens = smartlist_new();
+  smartlist_t *rs_tokens = NULL, *footer_tokens = NULL;
+  networkstatus_voter_info_t *voter = NULL;
+  networkstatus_t *ns = NULL;
+  common_digests_t ns_digests;
+  uint8_t sha3_as_signed[DIGEST256_LEN];
+  const char *cert, *end_of_header, *end_of_footer, *s_dup = s;
+  directory_token_t *tok;
+  struct in_addr in;
+  int i, inorder, n_signatures = 0;
+  memarea_t *area = NULL, *rs_area = NULL;
+  consensus_flavor_t flav = FLAV_NS;
+  char *last_kwd=NULL;
+
+  tor_assert(s);
+
+  if (eos_out)
+    *eos_out = NULL;
+
+  if (router_get_networkstatus_v3_hashes(s, &ns_digests) ||
+      router_get_networkstatus_v3_sha3_as_signed(sha3_as_signed, s)<0) {
+    log_warn(LD_DIR, "Unable to compute digest of network-status");
+    goto err;
+  }
+
+  area = memarea_new();
+  end_of_header = find_start_of_next_routerstatus(s);
+  if (tokenize_string(area, s, end_of_header, tokens,
+                      (ns_type == NS_TYPE_CONSENSUS) ?
+                      networkstatus_consensus_token_table :
+                      networkstatus_token_table, 0)) {
+    log_warn(LD_DIR, "Error tokenizing network-status header");
+    goto err;
+  }
+
+  ns = tor_malloc_zero(sizeof(networkstatus_t));
+  memcpy(&ns->digests, &ns_digests, sizeof(ns_digests));
+  memcpy(&ns->digest_sha3_as_signed, sha3_as_signed, sizeof(sha3_as_signed));
+
+  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[1]));
+      goto err;
+    }
+    ns->flavor = flav = flavor;
+  }
+  if (flav != FLAV_NS && ns_type != NS_TYPE_CONSENSUS) {
+    log_warn(LD_DIR, "Flavor found on non-consensus networkstatus.");
+    goto err;
+  }
+
+  if (ns_type != NS_TYPE_CONSENSUS) {
+    const char *end_of_cert = NULL;
+    if (!(cert = strstr(s, "\ndir-key-certificate-version")))
+      goto err;
+    ++cert;
+    ns->cert = authority_cert_parse_from_string(cert, &end_of_cert);
+    if (!ns->cert || !end_of_cert || end_of_cert > end_of_header)
+      goto err;
+  }
+
+  tok = find_by_keyword(tokens, K_VOTE_STATUS);
+  tor_assert(tok->n_args);
+  if (!strcmp(tok->args[0], "vote")) {
+    ns->type = NS_TYPE_VOTE;
+  } else if (!strcmp(tok->args[0], "consensus")) {
+    ns->type = NS_TYPE_CONSENSUS;
+  } else if (!strcmp(tok->args[0], "opinion")) {
+    ns->type = NS_TYPE_OPINION;
+  } else {
+    log_warn(LD_DIR, "Unrecognized vote status %s in network-status",
+             escaped(tok->args[0]));
+    goto err;
+  }
+  if (ns_type != ns->type) {
+    log_warn(LD_DIR, "Got the wrong kind of v3 networkstatus.");
+    goto err;
+  }
+
+  if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_OPINION) {
+    tok = find_by_keyword(tokens, K_PUBLISHED);
+    if (parse_iso_time(tok->args[0], &ns->published))
+      goto err;
+
+    ns->supported_methods = smartlist_new();
+    tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHODS);
+    if (tok) {
+      for (i=0; i < tok->n_args; ++i)
+        smartlist_add_strdup(ns->supported_methods, tok->args[i]);
+    } else {
+      smartlist_add_strdup(ns->supported_methods, "1");
+    }
+  } else {
+    tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHOD);
+    if (tok) {
+      int num_ok;
+      ns->consensus_method = (int)tor_parse_long(tok->args[0], 10, 1, INT_MAX,
+                                                 &num_ok, NULL);
+      if (!num_ok)
+        goto err;
+    } else {
+      ns->consensus_method = 1;
+    }
+  }
+
+  if ((tok = find_opt_by_keyword(tokens, K_RECOMMENDED_CLIENT_PROTOCOLS)))
+    ns->recommended_client_protocols = tor_strdup(tok->args[0]);
+  if ((tok = find_opt_by_keyword(tokens, K_RECOMMENDED_RELAY_PROTOCOLS)))
+    ns->recommended_relay_protocols = tor_strdup(tok->args[0]);
+  if ((tok = find_opt_by_keyword(tokens, K_REQUIRED_CLIENT_PROTOCOLS)))
+    ns->required_client_protocols = tor_strdup(tok->args[0]);
+  if ((tok = find_opt_by_keyword(tokens, K_REQUIRED_RELAY_PROTOCOLS)))
+    ns->required_relay_protocols = tor_strdup(tok->args[0]);
+
+  tok = find_by_keyword(tokens, K_VALID_AFTER);
+  if (parse_iso_time(tok->args[0], &ns->valid_after))
+    goto err;
+
+  tok = find_by_keyword(tokens, K_FRESH_UNTIL);
+  if (parse_iso_time(tok->args[0], &ns->fresh_until))
+    goto err;
+
+  tok = find_by_keyword(tokens, K_VALID_UNTIL);
+  if (parse_iso_time(tok->args[0], &ns->valid_until))
+    goto err;
+
+  tok = find_by_keyword(tokens, K_VOTING_DELAY);
+  tor_assert(tok->n_args >= 2);
+  {
+    int ok;
+    ns->vote_seconds =
+      (int) tor_parse_long(tok->args[0], 10, 0, INT_MAX, &ok, NULL);
+    if (!ok)
+      goto err;
+    ns->dist_seconds =
+      (int) tor_parse_long(tok->args[1], 10, 0, INT_MAX, &ok, NULL);
+    if (!ok)
+      goto err;
+  }
+  if (ns->valid_after +
+      (get_options()->TestingTorNetwork ?
+       MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL) > ns->fresh_until) {
+    log_warn(LD_DIR, "Vote/consensus freshness interval is too short");
+    goto err;
+  }
+  if (ns->valid_after +
+      (get_options()->TestingTorNetwork ?
+       MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL)*2 > ns->valid_until) {
+    log_warn(LD_DIR, "Vote/consensus liveness interval is too short");
+    goto err;
+  }
+  if (ns->vote_seconds < MIN_VOTE_SECONDS) {
+    log_warn(LD_DIR, "Vote seconds is too short");
+    goto err;
+  }
+  if (ns->dist_seconds < MIN_DIST_SECONDS) {
+    log_warn(LD_DIR, "Dist seconds is too short");
+    goto err;
+  }
+
+  if ((tok = find_opt_by_keyword(tokens, K_CLIENT_VERSIONS))) {
+    ns->client_versions = tor_strdup(tok->args[0]);
+  }
+  if ((tok = find_opt_by_keyword(tokens, K_SERVER_VERSIONS))) {
+    ns->server_versions = tor_strdup(tok->args[0]);
+  }
+
+  {
+    smartlist_t *package_lst = find_all_by_keyword(tokens, K_PACKAGE);
+    ns->package_lines = smartlist_new();
+    if (package_lst) {
+      SMARTLIST_FOREACH(package_lst, directory_token_t *, t,
+                    smartlist_add_strdup(ns->package_lines, t->args[0]));
+    }
+    smartlist_free(package_lst);
+  }
+
+  tok = find_by_keyword(tokens, K_KNOWN_FLAGS);
+  ns->known_flags = smartlist_new();
+  inorder = 1;
+  for (i = 0; i < tok->n_args; ++i) {
+    smartlist_add_strdup(ns->known_flags, tok->args[i]);
+    if (i>0 && strcmp(tok->args[i-1], tok->args[i])>= 0) {
+      log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]);
+      inorder = 0;
+    }
+  }
+  if (!inorder) {
+    log_warn(LD_DIR, "known-flags not in order");
+    goto err;
+  }
+  if (ns->type != NS_TYPE_CONSENSUS &&
+      smartlist_len(ns->known_flags) > MAX_KNOWN_FLAGS_IN_VOTE) {
+    /* If we allowed more than 64 flags in votes, then parsing them would make
+     * us invoke undefined behavior whenever we used 1<<flagnum to do a
+     * bit-shift. This is only for votes and opinions: consensus users don't
+     * care about flags they don't recognize, and so don't build a bitfield
+     * for them. */
+    log_warn(LD_DIR, "Too many known-flags in consensus vote or opinion");
+    goto err;
+  }
+
+  tok = find_opt_by_keyword(tokens, K_PARAMS);
+  if (tok) {
+    int any_dups = 0;
+    inorder = 1;
+    ns->net_params = smartlist_new();
+    for (i = 0; i < tok->n_args; ++i) {
+      int ok=0;
+      char *eq = strchr(tok->args[i], '=');
+      size_t eq_pos;
+      if (!eq) {
+        log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
+        goto err;
+      }
+      eq_pos = eq-tok->args[i];
+      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;
+      }
+      if (i > 0 && strcmp(tok->args[i-1], tok->args[i]) >= 0) {
+        log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]);
+        inorder = 0;
+      }
+      if (last_kwd && eq_pos == strlen(last_kwd) &&
+          fast_memeq(last_kwd, tok->args[i], eq_pos)) {
+        log_warn(LD_DIR, "Duplicate value for %s parameter",
+                 escaped(tok->args[i]));
+        any_dups = 1;
+      }
+      tor_free(last_kwd);
+      last_kwd = tor_strndup(tok->args[i], eq_pos);
+      smartlist_add_strdup(ns->net_params, tok->args[i]);
+    }
+    if (!inorder) {
+      log_warn(LD_DIR, "params not in order");
+      goto err;
+    }
+    if (any_dups) {
+      log_warn(LD_DIR, "Duplicate in parameters");
+      goto err;
+    }
+  }
+
+  ns->voters = smartlist_new();
+
+  SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {
+    tok = _tok;
+    if (tok->tp == K_DIR_SOURCE) {
+      tor_assert(tok->n_args >= 6);
+
+      if (voter)
+        smartlist_add(ns->voters, voter);
+      voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
+      voter->sigs = smartlist_new();
+      if (ns->type != NS_TYPE_CONSENSUS)
+        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 ||
+          base16_decode(voter->identity_digest, sizeof(voter->identity_digest),
+                        tok->args[1], HEX_DIGEST_LEN)
+                        != sizeof(voter->identity_digest)) {
+        log_warn(LD_DIR, "Error decoding identity digest %s in "
+                 "network-status document.", escaped(tok->args[1]));
+        goto err;
+      }
+      if (ns->type != NS_TYPE_CONSENSUS &&
+          tor_memneq(ns->cert->cache_info.identity_digest,
+                 voter->identity_digest, DIGEST_LEN)) {
+        log_warn(LD_DIR,"Mismatch between identities in certificate and vote");
+        goto err;
+      }
+      if (ns->type != NS_TYPE_CONSENSUS) {
+        if (authority_cert_is_blacklisted(ns->cert)) {
+          log_warn(LD_DIR, "Rejecting vote signature made with blacklisted "
+                   "signing key %s",
+                   hex_str(ns->cert->signing_key_digest, DIGEST_LEN));
+          goto err;
+        }
+      }
+      voter->address = tor_strdup(tok->args[2]);
+      if (!tor_inet_aton(tok->args[3], &in)) {
+        log_warn(LD_DIR, "Error decoding IP address %s in network-status.",
+                 escaped(tok->args[3]));
+        goto err;
+      }
+      voter->addr = ntohl(in.s_addr);
+      int ok;
+      voter->dir_port = (uint16_t)
+        tor_parse_long(tok->args[4], 10, 0, 65535, &ok, NULL);
+      if (!ok)
+        goto err;
+      voter->or_port = (uint16_t)
+        tor_parse_long(tok->args[5], 10, 0, 65535, &ok, NULL);
+      if (!ok)
+        goto err;
+    } else if (tok->tp == K_CONTACT) {
+      if (!voter || voter->contact) {
+        log_warn(LD_DIR, "contact element is out of place.");
+        goto err;
+      }
+      voter->contact = tor_strdup(tok->args[0]);
+    } else if (tok->tp == K_VOTE_DIGEST) {
+      tor_assert(ns->type == NS_TYPE_CONSENSUS);
+      tor_assert(tok->n_args >= 1);
+      if (!voter || ! tor_digest_is_zero(voter->vote_digest)) {
+        log_warn(LD_DIR, "vote-digest element is out of place.");
+        goto err;
+      }
+      if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
+        base16_decode(voter->vote_digest, sizeof(voter->vote_digest),
+                      tok->args[0], HEX_DIGEST_LEN)
+                      != sizeof(voter->vote_digest)) {
+        log_warn(LD_DIR, "Error decoding vote digest %s in "
+                 "network-status consensus.", escaped(tok->args[0]));
+        goto err;
+      }
+    }
+  } SMARTLIST_FOREACH_END(_tok);
+  if (voter) {
+    smartlist_add(ns->voters, voter);
+    voter = NULL;
+  }
+  if (smartlist_len(ns->voters) == 0) {
+    log_warn(LD_DIR, "Missing dir-source elements in a networkstatus.");
+    goto err;
+  } else if (ns->type != NS_TYPE_CONSENSUS && smartlist_len(ns->voters) != 1) {
+    log_warn(LD_DIR, "Too many dir-source elements in a vote networkstatus.");
+    goto err;
+  }
+
+  if (ns->type != NS_TYPE_CONSENSUS &&
+      (tok = find_opt_by_keyword(tokens, K_LEGACY_DIR_KEY))) {
+    int bad = 1;
+    if (strlen(tok->args[0]) == HEX_DIGEST_LEN) {
+      networkstatus_voter_info_t *voter_0 = smartlist_get(ns->voters, 0);
+      if (base16_decode(voter_0->legacy_id_digest, DIGEST_LEN,
+                        tok->args[0], HEX_DIGEST_LEN) != DIGEST_LEN)
+        bad = 1;
+      else
+        bad = 0;
+    }
+    if (bad) {
+      log_warn(LD_DIR, "Invalid legacy key digest %s on vote.",
+               escaped(tok->args[0]));
+    }
+  }
+
+  /* If this is a vote document, check if information about the shared
+     randomness protocol is included, and extract it. */
+  if (ns->type == NS_TYPE_VOTE) {
+    dirvote_parse_sr_commits(ns, tokens);
+  }
+  /* For both a vote and consensus, extract the shared random values. */
+  if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_CONSENSUS) {
+    extract_shared_random_srvs(ns, tokens);
+  }
+
+  /* Parse routerstatus lines. */
+  rs_tokens = smartlist_new();
+  rs_area = memarea_new();
+  s = end_of_header;
+  ns->routerstatus_list = smartlist_new();
+
+  while (!strcmpstart(s, "r ")) {
+    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, 0)) {
+        smartlist_add(ns->routerstatus_list, rs);
+      } else {
+        vote_routerstatus_free(rs);
+      }
+    } else {
+      routerstatus_t *rs;
+      if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens,
+                                                     NULL, NULL,
+                                                     ns->consensus_method,
+                                                     flav))) {
+        /* Use exponential-backoff scheduling when downloading microdescs */
+        smartlist_add(ns->routerstatus_list, rs);
+      }
+    }
+  }
+  for (i = 1; i < smartlist_len(ns->routerstatus_list); ++i) {
+    routerstatus_t *rs1, *rs2;
+    if (ns->type != NS_TYPE_CONSENSUS) {
+      vote_routerstatus_t *a = smartlist_get(ns->routerstatus_list, i-1);
+      vote_routerstatus_t *b = smartlist_get(ns->routerstatus_list, i);
+      rs1 = &a->status; rs2 = &b->status;
+    } else {
+      rs1 = smartlist_get(ns->routerstatus_list, i-1);
+      rs2 = smartlist_get(ns->routerstatus_list, i);
+    }
+    if (fast_memcmp(rs1->identity_digest, rs2->identity_digest, DIGEST_LEN)
+        >= 0) {
+      log_warn(LD_DIR, "Networkstatus entries not sorted by identity digest");
+      goto err;
+    }
+  }
+  if (ns_type != NS_TYPE_CONSENSUS) {
+    digest256map_t *ed_id_map = digest256map_new();
+    SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, vote_routerstatus_t *,
+                            vrs) {
+      if (! vrs->has_ed25519_listing ||
+          tor_mem_is_zero((const char *)vrs->ed25519_id, DIGEST256_LEN))
+        continue;
+      if (digest256map_get(ed_id_map, vrs->ed25519_id) != NULL) {
+        log_warn(LD_DIR, "Vote networkstatus ed25519 identities were not "
+                 "unique");
+        digest256map_free(ed_id_map, NULL);
+        goto err;
+      }
+      digest256map_set(ed_id_map, vrs->ed25519_id, (void*)1);
+    } SMARTLIST_FOREACH_END(vrs);
+    digest256map_free(ed_id_map, NULL);
+  }
+
+  /* Parse footer; check signature. */
+  footer_tokens = smartlist_new();
+  if ((end_of_footer = strstr(s, "\nnetwork-status-version ")))
+    ++end_of_footer;
+  else
+    end_of_footer = s + strlen(s);
+  if (tokenize_string(area,s, end_of_footer, footer_tokens,
+                      networkstatus_vote_footer_token_table, 0)) {
+    log_warn(LD_DIR, "Error tokenizing network-status vote footer.");
+    goto err;
+  }
+
+  {
+    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_new();
+    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_strdup(ns->weight_params, 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") ||
+        tok->object_size < 128 || tok->object_size > 512) {
+      log_warn(LD_DIR, "Bad object type or length on directory-signature");
+      goto err;
+    }
+
+    if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
+        base16_decode(declared_identity, sizeof(declared_identity),
+                      id_hexdigest, HEX_DIGEST_LEN)
+                      != sizeof(declared_identity)) {
+      log_warn(LD_DIR, "Error decoding declared identity %s in "
+               "network-status document.", escaped(id_hexdigest));
+      goto err;
+    }
+    if (!(v = networkstatus_get_voter_by_id(ns, declared_identity))) {
+      log_warn(LD_DIR, "ID on signature on network-status document does "
+               "not match any declared directory source.");
+      goto err;
+    }
+    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)
+                      != sizeof(sig->signing_key_digest)) {
+      log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
+               "network-status document.", escaped(sk_hexdigest));
+      tor_free(sig);
+      goto err;
+    }
+
+    if (ns->type != NS_TYPE_CONSENSUS) {
+      if (tor_memneq(declared_identity, ns->cert->cache_info.identity_digest,
+                 DIGEST_LEN)) {
+        log_warn(LD_DIR, "Digest mismatch between declared and actual on "
+                 "network-status vote.");
+        tor_free(sig);
+        goto err;
+      }
+    }
+
+    if (networkstatus_get_voter_sig_by_alg(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 signatures from the same voter with the same "
+             "algorithm. Ignoring the second signature.");
+      tor_free(sig);
+      continue;
+    }
+
+    if (ns->type != NS_TYPE_CONSENSUS) {
+      if (check_signature_token(ns_digests.d[DIGEST_SHA1], DIGEST_LEN,
+                                tok, ns->cert->signing_key, 0,
+                                "network-status document")) {
+        tor_free(sig);
+        goto err;
+      }
+      sig->good_signature = 1;
+    } else {
+      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(v->sigs, sig);
+
+    ++n_signatures;
+  } SMARTLIST_FOREACH_END(_tok);
+
+  if (! n_signatures) {
+    log_warn(LD_DIR, "No signatures on networkstatus document.");
+    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)
+    *eos_out = end_of_footer;
+
+  goto done;
+ err:
+  dump_desc(s_dup, "v3 networkstatus");
+  networkstatus_vote_free(ns);
+  ns = NULL;
+ done:
+  if (tokens) {
+    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_clear(t));
+    smartlist_free(rs_tokens);
+  }
+  if (footer_tokens) {
+    SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t));
+    smartlist_free(footer_tokens);
+  }
+  if (area) {
+    DUMP_AREA(area, "v3 networkstatus");
+    memarea_drop_all(area);
+  }
+  if (rs_area)
+    memarea_drop_all(rs_area);
+  tor_free(last_kwd);
+
+  return ns;
+}
diff --git a/src/feature/dirparse/ns_parse.h b/src/feature/dirparse/ns_parse.h
new file mode 100644
index 000000000..22438d73a
--- /dev/null
+++ b/src/feature/dirparse/ns_parse.h
@@ -0,0 +1,45 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file ns_parse.h
+ * \brief Header file for ns_parse.c.
+ **/
+
+#ifndef TOR_NS_PARSE_H
+#define TOR_NS_PARSE_H
+
+int router_get_networkstatus_v3_hashes(const char *s,
+                                       common_digests_t *digests);
+int router_get_networkstatus_v3_signed_boundaries(const char *s,
+                                                  const char **start_out,
+                                                  const char **end_out);
+int router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out,
+                                               const char *s);
+int compare_vote_routerstatus_entries(const void **_a, const void **_b);
+
+int networkstatus_verify_bw_weights(networkstatus_t *ns, int);
+enum networkstatus_type_t;
+networkstatus_t *networkstatus_parse_vote_from_string(const char *s,
+                                           const char **eos_out,
+                                           enum networkstatus_type_t ns_type);
+
+#ifdef NS_PARSE_PRIVATE
+STATIC int routerstatus_parse_guardfraction(const char *guardfraction_str,
+                                            networkstatus_t *vote,
+                                            vote_routerstatus_t *vote_rs,
+                                            routerstatus_t *rs);
+struct memarea_t;
+STATIC routerstatus_t *routerstatus_parse_entry_from_string(
+                                     struct memarea_t *area,
+                                     const char **s, smartlist_t *tokens,
+                                     networkstatus_t *vote,
+                                     vote_routerstatus_t *vote_rs,
+                                     int consensus_method,
+                                     consensus_flavor_t flav);
+#endif
+
+#endif
diff --git a/src/feature/dirparse/routerparse.c b/src/feature/dirparse/routerparse.c
index daeb67c91..956e8f3ea 100644
--- a/src/feature/dirparse/routerparse.c
+++ b/src/feature/dirparse/routerparse.c
@@ -193,110 +193,6 @@ static token_rule_t extrainfo_token_table[] = {
   END_OF_TABLE
 };
 
-/** List of tokens recognized in the body part of v3 networkstatus
- * documents. */
-static token_rule_t rtrstatus_token_table[] = {
-  T01("p",                   K_P,               CONCAT_ARGS, NO_OBJ ),
-  T1( "r",                   K_R,                   GE(7),   NO_OBJ ),
-  T0N("a",                   K_A,                   GE(1),   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("id",                  K_ID,                  GE(2),   NO_OBJ ),
-  T01("pr",                  K_PROTO,           CONCAT_ARGS, NO_OBJ ),
-  T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
-  END_OF_TABLE
-};
-
-/** List of tokens recognized in V3 networkstatus votes. */
-static token_rule_t networkstatus_token_table[] = {
-  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 ),
-  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 ),
-  T1("voting-delay",           K_VOTING_DELAY,     GE(2),       NO_OBJ ),
-  T1("known-flags",            K_KNOWN_FLAGS,      ARGS,        NO_OBJ ),
-  T01("params",                K_PARAMS,           ARGS,        NO_OBJ ),
-  T( "fingerprint",            K_FINGERPRINT,      CONCAT_ARGS, NO_OBJ ),
-  T01("signing-ed25519",       K_SIGNING_CERT_ED,  NO_ARGS ,    NEED_OBJ ),
-  T01("shared-rand-participate",K_SR_FLAG,         NO_ARGS,     NO_OBJ ),
-  T0N("shared-rand-commit",    K_COMMIT,           GE(3),       NO_OBJ ),
-  T01("shared-rand-previous-value", K_PREVIOUS_SRV,EQ(2),       NO_OBJ ),
-  T01("shared-rand-current-value",  K_CURRENT_SRV, EQ(2),       NO_OBJ ),
-  T0N("package",               K_PACKAGE,          CONCAT_ARGS, NO_OBJ ),
-  T01("recommended-client-protocols", K_RECOMMENDED_CLIENT_PROTOCOLS,
-      CONCAT_ARGS, NO_OBJ ),
-  T01("recommended-relay-protocols", K_RECOMMENDED_RELAY_PROTOCOLS,
-      CONCAT_ARGS, NO_OBJ ),
-  T01("required-client-protocols",    K_REQUIRED_CLIENT_PROTOCOLS,
-      CONCAT_ARGS, NO_OBJ ),
-  T01("required-relay-protocols",    K_REQUIRED_RELAY_PROTOCOLS,
-      CONCAT_ARGS, NO_OBJ ),
-
-#include "feature/dirparse/authcert_members.i"
-
-  T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
-  T1( "contact",             K_CONTACT,         CONCAT_ARGS, NO_OBJ ),
-  T1( "dir-source",          K_DIR_SOURCE,      GE(6),       NO_OBJ ),
-  T01("legacy-dir-key",      K_LEGACY_DIR_KEY,  GE(1),       NO_OBJ ),
-  T1( "known-flags",         K_KNOWN_FLAGS,     CONCAT_ARGS, NO_OBJ ),
-  T01("client-versions",     K_CLIENT_VERSIONS, CONCAT_ARGS, NO_OBJ ),
-  T01("server-versions",     K_SERVER_VERSIONS, CONCAT_ARGS, NO_OBJ ),
-  T1( "consensus-methods",   K_CONSENSUS_METHODS, GE(1),     NO_OBJ ),
-
-  END_OF_TABLE
-};
-
-/** List of tokens recognized in V3 networkstatus consensuses. */
-static token_rule_t networkstatus_consensus_token_table[] = {
-  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 ),
-  T1("fresh-until",            K_FRESH_UNTIL,      CONCAT_ARGS, NO_OBJ ),
-  T1("valid-until",            K_VALID_UNTIL,      CONCAT_ARGS, NO_OBJ ),
-  T1("voting-delay",           K_VOTING_DELAY,     GE(2),       NO_OBJ ),
-
-  T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
-
-  T1N("dir-source",          K_DIR_SOURCE,          GE(6),   NO_OBJ ),
-  T1N("contact",             K_CONTACT,         CONCAT_ARGS, NO_OBJ ),
-  T1N("vote-digest",         K_VOTE_DIGEST,         GE(1),   NO_OBJ ),
-
-  T1( "known-flags",         K_KNOWN_FLAGS,     CONCAT_ARGS, NO_OBJ ),
-
-  T01("client-versions",     K_CLIENT_VERSIONS, CONCAT_ARGS, NO_OBJ ),
-  T01("server-versions",     K_SERVER_VERSIONS, CONCAT_ARGS, NO_OBJ ),
-  T01("consensus-method",    K_CONSENSUS_METHOD,    EQ(1),   NO_OBJ),
-  T01("params",                K_PARAMS,           ARGS,        NO_OBJ ),
-
-  T01("shared-rand-previous-value", K_PREVIOUS_SRV, EQ(2),   NO_OBJ ),
-  T01("shared-rand-current-value",  K_CURRENT_SRV,  EQ(2),   NO_OBJ ),
-
-  T01("recommended-client-protocols", K_RECOMMENDED_CLIENT_PROTOCOLS,
-      CONCAT_ARGS, NO_OBJ ),
-  T01("recommended-relay-protocols", K_RECOMMENDED_RELAY_PROTOCOLS,
-      CONCAT_ARGS, NO_OBJ ),
-  T01("required-client-protocols",    K_REQUIRED_CLIENT_PROTOCOLS,
-      CONCAT_ARGS, NO_OBJ ),
-  T01("required-relay-protocols",    K_REQUIRED_RELAY_PROTOCOLS,
-      CONCAT_ARGS, NO_OBJ ),
-
-  END_OF_TABLE
-};
-
-/** List of tokens recognized in the footer of v1 directory footers. */
-static token_rule_t networkstatus_vote_footer_token_table[] = {
-  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
-};
-
 #undef T
 
 /* static function prototypes */
@@ -318,52 +214,6 @@ router_get_router_hash(const char *s, size_t s_len, char *digest)
                               DIGEST_SHA1);
 }
 
-/** Try to find the start and end of the signed portion of a networkstatus
- * document in <b>s</b>. On success, set <b>start_out</b> to the first
- * character of the document, and <b>end_out</b> to a position one after the
- * final character of the signed document, and return 0.  On failure, return
- * -1. */
-int
-router_get_networkstatus_v3_signed_boundaries(const char *s,
-                                              const char **start_out,
-                                              const char **end_out)
-{
-  return router_get_hash_impl_helper(s, strlen(s),
-                                     "network-status-version",
-                                     "\ndirectory-signature",
-                                     ' ', LOG_INFO,
-                                     start_out, end_out);
-}
-
-/** Set <b>digest_out</b> to the SHA3-256 digest of the signed portion of the
- * networkstatus vote in <b>s</b> -- or of the entirety of <b>s</b> if no
- * signed portion can be identified.  Return 0 on success, -1 on failure. */
-int
-router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out,
-                                           const char *s)
-{
-  const char *start, *end;
-  if (router_get_networkstatus_v3_signed_boundaries(s, &start, &end) < 0) {
-    start = s;
-    end = s + strlen(s);
-  }
-  tor_assert(start);
-  tor_assert(end);
-  return crypto_digest256((char*)digest_out, start, end-start,
-                          DIGEST_SHA3_256);
-}
-
-/** 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, common_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 <b>s_len</b>-byte
  * extrainfo string at <b>s</b>.  Return 0 on success, -1 on failure. */
 int
@@ -1351,93 +1201,10 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
   return extrainfo;
 }
 
-/** 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 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, *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;
-}
-
-/** Parse the GuardFraction string from a consensus or vote.
- *
- *  If <b>vote</b> or <b>vote_rs</b> are set the document getting
- *  parsed is a vote routerstatus. Otherwise it's a consensus. This is
- *  the same semantic as in routerstatus_parse_entry_from_string(). */
-STATIC int
-routerstatus_parse_guardfraction(const char *guardfraction_str,
-                                 networkstatus_t *vote,
-                                 vote_routerstatus_t *vote_rs,
-                                 routerstatus_t *rs)
-{
-  int ok;
-  const char *end_of_header = NULL;
-  int is_consensus = !vote_rs;
-  uint32_t guardfraction;
-
-  tor_assert(bool_eq(vote, vote_rs));
-
-  /* If this info comes from a consensus, but we should't apply
-     guardfraction, just exit. */
-  if (is_consensus && !should_apply_guardfraction(NULL)) {
-    return 0;
-  }
-
-  end_of_header = strchr(guardfraction_str, '=');
-  if (!end_of_header) {
-    return -1;
-  }
-
-  guardfraction = (uint32_t)tor_parse_ulong(end_of_header+1,
-                                            10, 0, 100, &ok, NULL);
-  if (!ok) {
-    log_warn(LD_DIR, "Invalid GuardFraction %s", escaped(guardfraction_str));
-    return -1;
-  }
-
-  log_debug(LD_GENERAL, "[*] Parsed %s guardfraction '%s' for '%s'.",
-            is_consensus ? "consensus" : "vote",
-            guardfraction_str, rs->nickname);
-
-  if (!is_consensus) { /* We are parsing a vote */
-    vote_rs->status.guardfraction_percentage = guardfraction;
-    vote_rs->status.has_guardfraction = 1;
-  } else {
-    /* We are parsing a consensus. Only apply guardfraction to guards. */
-    if (rs->is_possible_guard) {
-      rs->guardfraction_percentage = guardfraction;
-      rs->has_guardfraction = 1;
-    } else {
-      log_warn(LD_BUG, "Got GuardFraction for non-guard %s. "
-               "This is not supposed to happen. Not applying. ", rs->nickname);
-    }
-  }
-
-  return 0;
-}
-
 /** Summarize the protocols listed in <b>protocols</b> into <b>out</b>,
  * falling back or correcting them based on <b>version</b> as appropriate.
  */
-STATIC void
+void
 summarize_protover_flags(protover_summary_flags_t *out,
                          const char *protocols,
                          const char *version)
@@ -1478,1417 +1245,6 @@ summarize_protover_flags(protover_summary_flags_t *out,
   }
 }
 
-/** Given a string at *<b>s</b>, containing a routerstatus object, and an
- * empty smartlist at <b>tokens</b>, parse and return the first router status
- * object in the string, and advance *<b>s</b> to just after the end of the
- * router status.  Return NULL and advance *<b>s</b> on error.
- *
- * If <b>vote</b> and <b>vote_rs</b> are provided, don't allocate a fresh
- * routerstatus but use <b>vote_rs</b> instead.
- *
- * 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,
-                                     consensus_flavor_t flav)
-{
-  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;
-  tor_assert(flav == FLAV_NS || flav == FLAV_MICRODESC);
-
-  eos = find_start_of_next_routerstatus(*s);
-
-  if (tokenize_string(area,*s, eos, tokens, rtrstatus_token_table,0)) {
-    log_warn(LD_DIR, "Error tokenizing router status");
-    goto err;
-  }
-  if (smartlist_len(tokens) < 1) {
-    log_warn(LD_DIR, "Impossibly short router status");
-    goto err;
-  }
-  tok = find_by_keyword(tokens, K_R);
-  tor_assert(tok->n_args >= 7); /* guaranteed by GE(7) in K_R setup */
-  if (flav == FLAV_NS) {
-    if (tok->n_args < 8) {
-      log_warn(LD_DIR, "Too few arguments to r");
-      goto err;
-    }
-  } else if (flav == FLAV_MICRODESC) {
-    offset = -1; /* There is no descriptor digest in an md consensus r line */
-  }
-
-  if (vote_rs) {
-    rs = &vote_rs->status;
-  } else {
-    rs = tor_malloc_zero(sizeof(routerstatus_t));
-  }
-
-  if (!is_legal_nickname(tok->args[0])) {
-    log_warn(LD_DIR,
-             "Invalid nickname %s in router status; skipping.",
-             escaped(tok->args[0]));
-    goto err;
-  }
-  strlcpy(rs->nickname, tok->args[0], sizeof(rs->nickname));
-
-  if (digest_from_base64(rs->identity_digest, tok->args[1])) {
-    log_warn(LD_DIR, "Error decoding identity digest %s",
-             escaped(tok->args[1]));
-    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+offset], tok->args[4+offset]) < 0 ||
-      parse_iso_time(timebuf, &rs->published_on)<0) {
-    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+offset], &in) == 0) {
-    log_warn(LD_DIR, "Error parsing router address in network-status %s",
-             escaped(tok->args[5+offset]));
-    goto err;
-  }
-  rs->addr = ntohl(in.s_addr);
-
-  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);
-
-  {
-    smartlist_t *a_lines = find_all_by_keyword(tokens, K_A);
-    if (a_lines) {
-      find_single_ipv6_orport(a_lines, &rs->ipv6_addr, &rs->ipv6_orport);
-      smartlist_free(a_lines);
-    }
-  }
-
-  tok = find_opt_by_keyword(tokens, K_S);
-  if (tok && vote) {
-    int i;
-    vote_rs->flags = 0;
-    for (i=0; i < tok->n_args; ++i) {
-      int p = smartlist_string_pos(vote->known_flags, tok->args[i]);
-      if (p >= 0) {
-        vote_rs->flags |= (UINT64_C(1)<<p);
-      } else {
-        log_warn(LD_DIR, "Flags line had a flag %s not listed in known_flags.",
-                 escaped(tok->args[i]));
-        goto err;
-      }
-    }
-  } else if (tok) {
-    /* This is a consensus, not a vote. */
-    int i;
-    for (i=0; i < tok->n_args; ++i) {
-      if (!strcmp(tok->args[i], "Exit"))
-        rs->is_exit = 1;
-      else if (!strcmp(tok->args[i], "Stable"))
-        rs->is_stable = 1;
-      else if (!strcmp(tok->args[i], "Fast"))
-        rs->is_fast = 1;
-      else if (!strcmp(tok->args[i], "Running"))
-        rs->is_flagged_running = 1;
-      else if (!strcmp(tok->args[i], "Named"))
-        rs->is_named = 1;
-      else if (!strcmp(tok->args[i], "Valid"))
-        rs->is_valid = 1;
-      else if (!strcmp(tok->args[i], "Guard"))
-        rs->is_possible_guard = 1;
-      else if (!strcmp(tok->args[i], "BadExit"))
-        rs->is_bad_exit = 1;
-      else if (!strcmp(tok->args[i], "Authority"))
-        rs->is_authority = 1;
-      else if (!strcmp(tok->args[i], "Unnamed") &&
-               consensus_method >= 2) {
-        /* Unnamed is computed right by consensus method 2 and later. */
-        rs->is_unnamed = 1;
-      } else if (!strcmp(tok->args[i], "HSDir")) {
-        rs->is_hs_dir = 1;
-      } else if (!strcmp(tok->args[i], "V2Dir")) {
-        rs->is_v2_dir = 1;
-      }
-    }
-    /* These are implied true by having been included in a consensus made
-     * with a given method */
-    rs->is_flagged_running = 1; /* Starting with consensus method 4. */
-    rs->is_valid = 1; /* Starting with consensus method 24. */
-  }
-  {
-    const char *protocols = NULL, *version = NULL;
-    if ((tok = find_opt_by_keyword(tokens, K_PROTO))) {
-      tor_assert(tok->n_args == 1);
-      protocols = tok->args[0];
-    }
-    if ((tok = find_opt_by_keyword(tokens, K_V))) {
-      tor_assert(tok->n_args == 1);
-      version = tok->args[0];
-      if (vote_rs) {
-        vote_rs->version = tor_strdup(tok->args[0]);
-      }
-    }
-
-    summarize_protover_flags(&rs->pv, protocols, version);
-  }
-
-  /* handle weighting/bandwidth info */
-  if ((tok = find_opt_by_keyword(tokens, K_W))) {
-    int i;
-    for (i=0; i < tok->n_args; ++i) {
-      if (!strcmpstart(tok->args[i], "Bandwidth=")) {
-        int ok;
-        rs->bandwidth_kb =
-          (uint32_t)tor_parse_ulong(strchr(tok->args[i], '=')+1,
-                                    10, 0, UINT32_MAX,
-                                    &ok, NULL);
-        if (!ok) {
-          log_warn(LD_DIR, "Invalid Bandwidth %s", escaped(tok->args[i]));
-          goto err;
-        }
-        rs->has_bandwidth = 1;
-      } else if (!strcmpstart(tok->args[i], "Measured=") && vote_rs) {
-        int ok;
-        vote_rs->measured_bw_kb =
-            (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;
-        }
-        vote_rs->has_measured_bw = 1;
-        vote->has_measured_bws = 1;
-      } else if (!strcmpstart(tok->args[i], "Unmeasured=1")) {
-        rs->bw_is_unmeasured = 1;
-      } else if (!strcmpstart(tok->args[i], "GuardFraction=")) {
-        if (routerstatus_parse_guardfraction(tok->args[i],
-                                             vote, vote_rs, rs) < 0) {
-          goto err;
-        }
-      }
-    }
-  }
-
-  /* parse exit policy summaries */
-  if ((tok = find_opt_by_keyword(tokens, K_P))) {
-    tor_assert(tok->n_args == 1);
-    if (strcmpstart(tok->args[0], "accept ") &&
-        strcmpstart(tok->args[0], "reject ")) {
-      log_warn(LD_DIR, "Unknown exit policy summary type %s.",
-               escaped(tok->args[0]));
-      goto err;
-    }
-    /* XXX weasel: parse this into ports and represent them somehow smart,
-     * maybe not here but somewhere on if we need it for the client.
-     * we should still parse it here to check it's valid tho.
-     */
-    rs->exitsummary = tor_strdup(tok->args[0]);
-    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;
-      }
-      if (t->tp == K_ID) {
-        tor_assert(t->n_args >= 2);
-        if (!strcmp(t->args[0], "ed25519")) {
-          vote_rs->has_ed25519_listing = 1;
-          if (strcmp(t->args[1], "none") &&
-              digest256_from_base64((char*)vote_rs->ed25519_id,
-                                    t->args[1])<0) {
-            log_warn(LD_DIR, "Bogus ed25519 key in networkstatus vote");
-            goto err;
-          }
-        }
-      }
-      if (t->tp == K_PROTO) {
-        tor_assert(t->n_args == 1);
-        vote_rs->protocols = tor_strdup(t->args[0]);
-      }
-    } SMARTLIST_FOREACH_END(t);
-  } else if (flav == FLAV_MICRODESC) {
-    tok = find_opt_by_keyword(tokens, K_M);
-    if (tok) {
-      tor_assert(tok->n_args);
-      if (digest256_from_base64(rs->descriptor_digest, tok->args[0])) {
-        log_warn(LD_DIR, "Error decoding microdescriptor digest %s",
-                 escaped(tok->args[0]));
-        goto err;
-      }
-    } else {
-      log_info(LD_BUG, "Found an entry in networkstatus with no "
-               "microdescriptor digest. (Router %s ($%s) at %s:%d.)",
-               rs->nickname, hex_str(rs->identity_digest, DIGEST_LEN),
-               fmt_addr32(rs->addr), rs->or_port);
-    }
-  }
-
-  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_clear(t));
-  smartlist_clear(tokens);
-  if (area) {
-    DUMP_AREA(area, "routerstatus entry");
-    memarea_clear(area);
-  }
-  *s = eos;
-
-  return rs;
-}
-
-int
-compare_vote_routerstatus_entries(const void **_a, const void **_b)
-{
-  const vote_routerstatus_t *a = *_a, *b = *_b;
-  return fast_memcmp(a->status.identity_digest, b->status.identity_digest,
-                     DIGEST_LEN);
-}
-
-/** Verify the bandwidth weights of a network status document */
-int
-networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method)
-{
-  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;
-  (void) consensus_method;
-
-  const int64_t weight_scale = networkstatus_get_weight_scale_param(ns);
-  tor_assert(weight_scale >= 1);
-  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=%f != %"PRId64,
-             Wmm, (weight_scale));
-    valid = 0;
-  }
-
-  if (fabs(Wem - Wee) > 1) {
-    log_warn(LD_BUG, "Wem=%f != Wee=%f", Wem, Wee);
-    valid = 0;
-  }
-
-  if (fabs(Wgm - Wgg) > 1) {
-    log_warn(LD_BUG, "Wgm=%f != Wgg=%f", Wgm, Wgg);
-    valid = 0;
-  }
-
-  if (fabs(Weg - Wed) > 1) {
-    log_warn(LD_BUG, "Wed=%f != Weg=%f", Wed, Weg);
-    valid = 0;
-  }
-
-  if (fabs(Wgg + Wmg - weight_scale) > 0.001*weight_scale) {
-    log_warn(LD_BUG, "Wgg=%f != %"PRId64" - Wmg=%f", Wgg,
-             (weight_scale), Wmg);
-    valid = 0;
-  }
-
-  if (fabs(Wee + Wme - weight_scale) > 0.001*weight_scale) {
-    log_warn(LD_BUG, "Wee=%f != %"PRId64" - Wme=%f", Wee,
-             (weight_scale), Wme);
-    valid = 0;
-  }
-
-  if (fabs(Wgd + Wmd + Wed - weight_scale) > 0.001*weight_scale) {
-    log_warn(LD_BUG, "Wgd=%f + Wmd=%f + Wed=%f != %"PRId64,
-             Wgd, Wmd, Wed, (weight_scale));
-    valid = 0;
-  }
-
-  Wgg /= weight_scale;
-  Wgm /= weight_scale; (void) Wgm; // unused from here on.
-  Wgd /= weight_scale;
-
-  Wmg /= weight_scale;
-  Wmm /= weight_scale;
-  Wme /= weight_scale;
-  Wmd /= weight_scale;
-
-  Weg /= weight_scale; (void) Weg; // unused from here on.
-  Wem /= weight_scale; (void) Wem; // unused from here on.
-  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) {
-    int is_exit = 0;
-    /* Bug #2203: Don't count bad exits as exits for balancing */
-    is_exit = rs->is_exit && !rs->is_bad_exit;
-    if (rs->has_bandwidth) {
-      T += rs->bandwidth_kb;
-      if (is_exit && rs->is_possible_guard) {
-        D += rs->bandwidth_kb;
-        Gtotal += Wgd*rs->bandwidth_kb;
-        Mtotal += Wmd*rs->bandwidth_kb;
-        Etotal += Wed*rs->bandwidth_kb;
-      } else if (is_exit) {
-        E += rs->bandwidth_kb;
-        Mtotal += Wme*rs->bandwidth_kb;
-        Etotal += Wee*rs->bandwidth_kb;
-      } else if (rs->is_possible_guard) {
-        G += rs->bandwidth_kb;
-        Gtotal += Wgg*rs->bandwidth_kb;
-        Mtotal += Wmg*rs->bandwidth_kb;
-      } else {
-        M += rs->bandwidth_kb;
-        Mtotal += Wmm*rs->bandwidth_kb;
-      }
-    } else {
-      log_warn(LD_BUG, "Missing consensus bandwidth for router %s",
-               routerstatus_describe(rs));
-    }
-  } 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 %f != Mtotal %f. "
-               "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-               " T=%"PRId64". "
-               "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-               casename, Etotal, Mtotal,
-               (G), (M), (E),
-               (D), (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 %f != Gtotal %f. "
-               "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-               " T=%"PRId64". "
-               "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-               casename, Etotal, Gtotal,
-               (G), (M), (E),
-               (D), (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 %f != Gtotal %f. "
-               "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-               " T=%"PRId64". "
-               "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-               casename, Mtotal, Gtotal,
-               (G), (M), (E),
-               (D), (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 %f > Stotal %f. "
-                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-                   " T=%"PRId64". "
-                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                   casename, Rtotal, Stotal,
-                   (G), (M), (E),
-                   (D), (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 %f > T "
-                   "%"PRId64". G=%"PRId64" M=%"PRId64" E=%"PRId64
-                   " D=%"PRId64" T=%"PRId64". "
-                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                   casename, Rtotal*3, (T),
-                   (G), (M), (E),
-                   (D), (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 %f > T "
-                   "%"PRId64". G=%"PRId64" M=%"PRId64" E=%"PRId64
-                   " D=%"PRId64" T=%"PRId64". "
-                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                   casename, Stotal*3, (T),
-                   (G), (M), (E),
-                   (D), (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 %f < T "
-                   "%"PRId64". "
-                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-                   " T=%"PRId64". "
-                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                   casename, Mtotal*3, (T),
-                   (G), (M), (E),
-                   (D), (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 %f != Mtotal %f. "
-                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-                   " T=%"PRId64". "
-                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                   casename, Etotal, Mtotal,
-                   (G), (M), (E),
-                   (D), (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 %f != Gtotal %f. "
-                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-                   " T=%"PRId64". "
-                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                   casename, Etotal, Gtotal,
-                   (G), (M), (E),
-                   (D), (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 %f != Gtotal %f. "
-                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-                   " T=%"PRId64". "
-                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                   casename, Mtotal, Gtotal,
-                   (G), (M), (E),
-                   (D), (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 %f != Gtotal %f. "
-                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-                   " T=%"PRId64". "
-                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                   casename, Etotal, Gtotal,
-                   (G), (M), (E),
-                   (D), (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 %f > T "
-                   "%"PRId64". G=%"PRId64" M=%"PRId64" E=%"PRId64
-                   " D=%"PRId64" T=%"PRId64". "
-                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                   casename, Stotal*3, (T),
-                   (G), (M), (E),
-                   (D), (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 %f != Mtotal %f. "
-                   "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-                   " T=%"PRId64". "
-                   "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                   casename, NStotal, Mtotal,
-                   (G), (M), (E),
-                   (D), (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 %f < T "
-                     "%"PRId64". G=%"PRId64" M=%"PRId64
-                     " E=%"PRId64" D=%"PRId64" T=%"PRId64". "
-                     "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                     casename, NStotal*3, (T),
-                     (G), (M), (E),
-                     (D), (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 %f != Mtotal %f. "
-                 "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-                 " T=%"PRId64". "
-                 "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                 casename, Etotal, Mtotal,
-                 (G), (M), (E),
-                 (D), (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 %f != Gtotal %f. "
-                 "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-                 " T=%"PRId64". "
-                 "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                 casename, Etotal, Gtotal,
-                 (G), (M), (E),
-                 (D), (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 %f != Gtotal %f. "
-                 "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
-                 " T=%"PRId64". "
-                 "Wgg=%f Wgd=%f Wmg=%f Wme=%f Wmd=%f Wee=%f Wed=%f",
-                 casename, Mtotal, Gtotal,
-                 (G), (M), (E),
-                 (D), (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;
-}
-
-/** Check if a shared random value of type <b>srv_type</b> is in
- *  <b>tokens</b>. If there is, parse it and set it to <b>srv_out</b>. Return
- *  -1 on failure, 0 on success. The resulting srv is allocated on the heap and
- *  it's the responsibility of the caller to free it. */
-static int
-extract_one_srv(smartlist_t *tokens, directory_keyword srv_type,
-                sr_srv_t **srv_out)
-{
-  int ret = -1;
-  directory_token_t *tok;
-  sr_srv_t *srv = NULL;
-  smartlist_t *chunks;
-
-  tor_assert(tokens);
-
-  chunks = smartlist_new();
-  tok = find_opt_by_keyword(tokens, srv_type);
-  if (!tok) {
-    /* That's fine, no SRV is allowed. */
-    ret = 0;
-    goto end;
-  }
-  for (int i = 0; i < tok->n_args; i++) {
-    smartlist_add(chunks, tok->args[i]);
-  }
-  srv = sr_parse_srv(chunks);
-  if (srv == NULL) {
-    log_warn(LD_DIR, "SR: Unparseable SRV %s", escaped(tok->object_body));
-    goto end;
-  }
-  /* All is good. */
-  *srv_out = srv;
-  ret = 0;
- end:
-  smartlist_free(chunks);
-  return ret;
-}
-
-/** Extract any shared random values found in <b>tokens</b> and place them in
- *  the networkstatus <b>ns</b>. */
-static void
-extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens)
-{
-  const char *voter_identity;
-  networkstatus_voter_info_t *voter;
-
-  tor_assert(ns);
-  tor_assert(tokens);
-  /* Can be only one of them else code flow. */
-  tor_assert(ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_CONSENSUS);
-
-  if (ns->type == NS_TYPE_VOTE) {
-    voter = smartlist_get(ns->voters, 0);
-    tor_assert(voter);
-    voter_identity = hex_str(voter->identity_digest,
-                             sizeof(voter->identity_digest));
-  } else {
-    /* Consensus has multiple voters so no specific voter. */
-    voter_identity = "consensus";
-  }
-
-  /* We extract both, and on error everything is stopped because it means
-   * the vote is malformed for the shared random value(s). */
-  if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) {
-    log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s",
-             voter_identity);
-    /* Maybe we have a chance with the current SRV so let's try it anyway. */
-  }
-  if (extract_one_srv(tokens, K_CURRENT_SRV, &ns->sr_info.current_srv) < 0) {
-    log_warn(LD_DIR, "SR: Unable to parse current SRV from %s",
-             voter_identity);
-  }
-}
-
-/** 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 *
-networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
-                                     networkstatus_type_t ns_type)
-{
-  smartlist_t *tokens = smartlist_new();
-  smartlist_t *rs_tokens = NULL, *footer_tokens = NULL;
-  networkstatus_voter_info_t *voter = NULL;
-  networkstatus_t *ns = NULL;
-  common_digests_t ns_digests;
-  uint8_t sha3_as_signed[DIGEST256_LEN];
-  const char *cert, *end_of_header, *end_of_footer, *s_dup = s;
-  directory_token_t *tok;
-  struct in_addr in;
-  int i, inorder, n_signatures = 0;
-  memarea_t *area = NULL, *rs_area = NULL;
-  consensus_flavor_t flav = FLAV_NS;
-  char *last_kwd=NULL;
-
-  tor_assert(s);
-
-  if (eos_out)
-    *eos_out = NULL;
-
-  if (router_get_networkstatus_v3_hashes(s, &ns_digests) ||
-      router_get_networkstatus_v3_sha3_as_signed(sha3_as_signed, s)<0) {
-    log_warn(LD_DIR, "Unable to compute digest of network-status");
-    goto err;
-  }
-
-  area = memarea_new();
-  end_of_header = find_start_of_next_routerstatus(s);
-  if (tokenize_string(area, s, end_of_header, tokens,
-                      (ns_type == NS_TYPE_CONSENSUS) ?
-                      networkstatus_consensus_token_table :
-                      networkstatus_token_table, 0)) {
-    log_warn(LD_DIR, "Error tokenizing network-status header");
-    goto err;
-  }
-
-  ns = tor_malloc_zero(sizeof(networkstatus_t));
-  memcpy(&ns->digests, &ns_digests, sizeof(ns_digests));
-  memcpy(&ns->digest_sha3_as_signed, sha3_as_signed, sizeof(sha3_as_signed));
-
-  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[1]));
-      goto err;
-    }
-    ns->flavor = flav = flavor;
-  }
-  if (flav != FLAV_NS && ns_type != NS_TYPE_CONSENSUS) {
-    log_warn(LD_DIR, "Flavor found on non-consensus networkstatus.");
-    goto err;
-  }
-
-  if (ns_type != NS_TYPE_CONSENSUS) {
-    const char *end_of_cert = NULL;
-    if (!(cert = strstr(s, "\ndir-key-certificate-version")))
-      goto err;
-    ++cert;
-    ns->cert = authority_cert_parse_from_string(cert, &end_of_cert);
-    if (!ns->cert || !end_of_cert || end_of_cert > end_of_header)
-      goto err;
-  }
-
-  tok = find_by_keyword(tokens, K_VOTE_STATUS);
-  tor_assert(tok->n_args);
-  if (!strcmp(tok->args[0], "vote")) {
-    ns->type = NS_TYPE_VOTE;
-  } else if (!strcmp(tok->args[0], "consensus")) {
-    ns->type = NS_TYPE_CONSENSUS;
-  } else if (!strcmp(tok->args[0], "opinion")) {
-    ns->type = NS_TYPE_OPINION;
-  } else {
-    log_warn(LD_DIR, "Unrecognized vote status %s in network-status",
-             escaped(tok->args[0]));
-    goto err;
-  }
-  if (ns_type != ns->type) {
-    log_warn(LD_DIR, "Got the wrong kind of v3 networkstatus.");
-    goto err;
-  }
-
-  if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_OPINION) {
-    tok = find_by_keyword(tokens, K_PUBLISHED);
-    if (parse_iso_time(tok->args[0], &ns->published))
-      goto err;
-
-    ns->supported_methods = smartlist_new();
-    tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHODS);
-    if (tok) {
-      for (i=0; i < tok->n_args; ++i)
-        smartlist_add_strdup(ns->supported_methods, tok->args[i]);
-    } else {
-      smartlist_add_strdup(ns->supported_methods, "1");
-    }
-  } else {
-    tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHOD);
-    if (tok) {
-      int num_ok;
-      ns->consensus_method = (int)tor_parse_long(tok->args[0], 10, 1, INT_MAX,
-                                                 &num_ok, NULL);
-      if (!num_ok)
-        goto err;
-    } else {
-      ns->consensus_method = 1;
-    }
-  }
-
-  if ((tok = find_opt_by_keyword(tokens, K_RECOMMENDED_CLIENT_PROTOCOLS)))
-    ns->recommended_client_protocols = tor_strdup(tok->args[0]);
-  if ((tok = find_opt_by_keyword(tokens, K_RECOMMENDED_RELAY_PROTOCOLS)))
-    ns->recommended_relay_protocols = tor_strdup(tok->args[0]);
-  if ((tok = find_opt_by_keyword(tokens, K_REQUIRED_CLIENT_PROTOCOLS)))
-    ns->required_client_protocols = tor_strdup(tok->args[0]);
-  if ((tok = find_opt_by_keyword(tokens, K_REQUIRED_RELAY_PROTOCOLS)))
-    ns->required_relay_protocols = tor_strdup(tok->args[0]);
-
-  tok = find_by_keyword(tokens, K_VALID_AFTER);
-  if (parse_iso_time(tok->args[0], &ns->valid_after))
-    goto err;
-
-  tok = find_by_keyword(tokens, K_FRESH_UNTIL);
-  if (parse_iso_time(tok->args[0], &ns->fresh_until))
-    goto err;
-
-  tok = find_by_keyword(tokens, K_VALID_UNTIL);
-  if (parse_iso_time(tok->args[0], &ns->valid_until))
-    goto err;
-
-  tok = find_by_keyword(tokens, K_VOTING_DELAY);
-  tor_assert(tok->n_args >= 2);
-  {
-    int ok;
-    ns->vote_seconds =
-      (int) tor_parse_long(tok->args[0], 10, 0, INT_MAX, &ok, NULL);
-    if (!ok)
-      goto err;
-    ns->dist_seconds =
-      (int) tor_parse_long(tok->args[1], 10, 0, INT_MAX, &ok, NULL);
-    if (!ok)
-      goto err;
-  }
-  if (ns->valid_after +
-      (get_options()->TestingTorNetwork ?
-       MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL) > ns->fresh_until) {
-    log_warn(LD_DIR, "Vote/consensus freshness interval is too short");
-    goto err;
-  }
-  if (ns->valid_after +
-      (get_options()->TestingTorNetwork ?
-       MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL)*2 > ns->valid_until) {
-    log_warn(LD_DIR, "Vote/consensus liveness interval is too short");
-    goto err;
-  }
-  if (ns->vote_seconds < MIN_VOTE_SECONDS) {
-    log_warn(LD_DIR, "Vote seconds is too short");
-    goto err;
-  }
-  if (ns->dist_seconds < MIN_DIST_SECONDS) {
-    log_warn(LD_DIR, "Dist seconds is too short");
-    goto err;
-  }
-
-  if ((tok = find_opt_by_keyword(tokens, K_CLIENT_VERSIONS))) {
-    ns->client_versions = tor_strdup(tok->args[0]);
-  }
-  if ((tok = find_opt_by_keyword(tokens, K_SERVER_VERSIONS))) {
-    ns->server_versions = tor_strdup(tok->args[0]);
-  }
-
-  {
-    smartlist_t *package_lst = find_all_by_keyword(tokens, K_PACKAGE);
-    ns->package_lines = smartlist_new();
-    if (package_lst) {
-      SMARTLIST_FOREACH(package_lst, directory_token_t *, t,
-                    smartlist_add_strdup(ns->package_lines, t->args[0]));
-    }
-    smartlist_free(package_lst);
-  }
-
-  tok = find_by_keyword(tokens, K_KNOWN_FLAGS);
-  ns->known_flags = smartlist_new();
-  inorder = 1;
-  for (i = 0; i < tok->n_args; ++i) {
-    smartlist_add_strdup(ns->known_flags, tok->args[i]);
-    if (i>0 && strcmp(tok->args[i-1], tok->args[i])>= 0) {
-      log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]);
-      inorder = 0;
-    }
-  }
-  if (!inorder) {
-    log_warn(LD_DIR, "known-flags not in order");
-    goto err;
-  }
-  if (ns->type != NS_TYPE_CONSENSUS &&
-      smartlist_len(ns->known_flags) > MAX_KNOWN_FLAGS_IN_VOTE) {
-    /* If we allowed more than 64 flags in votes, then parsing them would make
-     * us invoke undefined behavior whenever we used 1<<flagnum to do a
-     * bit-shift. This is only for votes and opinions: consensus users don't
-     * care about flags they don't recognize, and so don't build a bitfield
-     * for them. */
-    log_warn(LD_DIR, "Too many known-flags in consensus vote or opinion");
-    goto err;
-  }
-
-  tok = find_opt_by_keyword(tokens, K_PARAMS);
-  if (tok) {
-    int any_dups = 0;
-    inorder = 1;
-    ns->net_params = smartlist_new();
-    for (i = 0; i < tok->n_args; ++i) {
-      int ok=0;
-      char *eq = strchr(tok->args[i], '=');
-      size_t eq_pos;
-      if (!eq) {
-        log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
-        goto err;
-      }
-      eq_pos = eq-tok->args[i];
-      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;
-      }
-      if (i > 0 && strcmp(tok->args[i-1], tok->args[i]) >= 0) {
-        log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]);
-        inorder = 0;
-      }
-      if (last_kwd && eq_pos == strlen(last_kwd) &&
-          fast_memeq(last_kwd, tok->args[i], eq_pos)) {
-        log_warn(LD_DIR, "Duplicate value for %s parameter",
-                 escaped(tok->args[i]));
-        any_dups = 1;
-      }
-      tor_free(last_kwd);
-      last_kwd = tor_strndup(tok->args[i], eq_pos);
-      smartlist_add_strdup(ns->net_params, tok->args[i]);
-    }
-    if (!inorder) {
-      log_warn(LD_DIR, "params not in order");
-      goto err;
-    }
-    if (any_dups) {
-      log_warn(LD_DIR, "Duplicate in parameters");
-      goto err;
-    }
-  }
-
-  ns->voters = smartlist_new();
-
-  SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {
-    tok = _tok;
-    if (tok->tp == K_DIR_SOURCE) {
-      tor_assert(tok->n_args >= 6);
-
-      if (voter)
-        smartlist_add(ns->voters, voter);
-      voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
-      voter->sigs = smartlist_new();
-      if (ns->type != NS_TYPE_CONSENSUS)
-        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 ||
-          base16_decode(voter->identity_digest, sizeof(voter->identity_digest),
-                        tok->args[1], HEX_DIGEST_LEN)
-                        != sizeof(voter->identity_digest)) {
-        log_warn(LD_DIR, "Error decoding identity digest %s in "
-                 "network-status document.", escaped(tok->args[1]));
-        goto err;
-      }
-      if (ns->type != NS_TYPE_CONSENSUS &&
-          tor_memneq(ns->cert->cache_info.identity_digest,
-                 voter->identity_digest, DIGEST_LEN)) {
-        log_warn(LD_DIR,"Mismatch between identities in certificate and vote");
-        goto err;
-      }
-      if (ns->type != NS_TYPE_CONSENSUS) {
-        if (authority_cert_is_blacklisted(ns->cert)) {
-          log_warn(LD_DIR, "Rejecting vote signature made with blacklisted "
-                   "signing key %s",
-                   hex_str(ns->cert->signing_key_digest, DIGEST_LEN));
-          goto err;
-        }
-      }
-      voter->address = tor_strdup(tok->args[2]);
-      if (!tor_inet_aton(tok->args[3], &in)) {
-        log_warn(LD_DIR, "Error decoding IP address %s in network-status.",
-                 escaped(tok->args[3]));
-        goto err;
-      }
-      voter->addr = ntohl(in.s_addr);
-      int ok;
-      voter->dir_port = (uint16_t)
-        tor_parse_long(tok->args[4], 10, 0, 65535, &ok, NULL);
-      if (!ok)
-        goto err;
-      voter->or_port = (uint16_t)
-        tor_parse_long(tok->args[5], 10, 0, 65535, &ok, NULL);
-      if (!ok)
-        goto err;
-    } else if (tok->tp == K_CONTACT) {
-      if (!voter || voter->contact) {
-        log_warn(LD_DIR, "contact element is out of place.");
-        goto err;
-      }
-      voter->contact = tor_strdup(tok->args[0]);
-    } else if (tok->tp == K_VOTE_DIGEST) {
-      tor_assert(ns->type == NS_TYPE_CONSENSUS);
-      tor_assert(tok->n_args >= 1);
-      if (!voter || ! tor_digest_is_zero(voter->vote_digest)) {
-        log_warn(LD_DIR, "vote-digest element is out of place.");
-        goto err;
-      }
-      if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
-        base16_decode(voter->vote_digest, sizeof(voter->vote_digest),
-                      tok->args[0], HEX_DIGEST_LEN)
-                      != sizeof(voter->vote_digest)) {
-        log_warn(LD_DIR, "Error decoding vote digest %s in "
-                 "network-status consensus.", escaped(tok->args[0]));
-        goto err;
-      }
-    }
-  } SMARTLIST_FOREACH_END(_tok);
-  if (voter) {
-    smartlist_add(ns->voters, voter);
-    voter = NULL;
-  }
-  if (smartlist_len(ns->voters) == 0) {
-    log_warn(LD_DIR, "Missing dir-source elements in a networkstatus.");
-    goto err;
-  } else if (ns->type != NS_TYPE_CONSENSUS && smartlist_len(ns->voters) != 1) {
-    log_warn(LD_DIR, "Too many dir-source elements in a vote networkstatus.");
-    goto err;
-  }
-
-  if (ns->type != NS_TYPE_CONSENSUS &&
-      (tok = find_opt_by_keyword(tokens, K_LEGACY_DIR_KEY))) {
-    int bad = 1;
-    if (strlen(tok->args[0]) == HEX_DIGEST_LEN) {
-      networkstatus_voter_info_t *voter_0 = smartlist_get(ns->voters, 0);
-      if (base16_decode(voter_0->legacy_id_digest, DIGEST_LEN,
-                        tok->args[0], HEX_DIGEST_LEN) != DIGEST_LEN)
-        bad = 1;
-      else
-        bad = 0;
-    }
-    if (bad) {
-      log_warn(LD_DIR, "Invalid legacy key digest %s on vote.",
-               escaped(tok->args[0]));
-    }
-  }
-
-  /* If this is a vote document, check if information about the shared
-     randomness protocol is included, and extract it. */
-  if (ns->type == NS_TYPE_VOTE) {
-    dirvote_parse_sr_commits(ns, tokens);
-  }
-  /* For both a vote and consensus, extract the shared random values. */
-  if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_CONSENSUS) {
-    extract_shared_random_srvs(ns, tokens);
-  }
-
-  /* Parse routerstatus lines. */
-  rs_tokens = smartlist_new();
-  rs_area = memarea_new();
-  s = end_of_header;
-  ns->routerstatus_list = smartlist_new();
-
-  while (!strcmpstart(s, "r ")) {
-    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, 0)) {
-        smartlist_add(ns->routerstatus_list, rs);
-      } else {
-        vote_routerstatus_free(rs);
-      }
-    } else {
-      routerstatus_t *rs;
-      if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens,
-                                                     NULL, NULL,
-                                                     ns->consensus_method,
-                                                     flav))) {
-        /* Use exponential-backoff scheduling when downloading microdescs */
-        smartlist_add(ns->routerstatus_list, rs);
-      }
-    }
-  }
-  for (i = 1; i < smartlist_len(ns->routerstatus_list); ++i) {
-    routerstatus_t *rs1, *rs2;
-    if (ns->type != NS_TYPE_CONSENSUS) {
-      vote_routerstatus_t *a = smartlist_get(ns->routerstatus_list, i-1);
-      vote_routerstatus_t *b = smartlist_get(ns->routerstatus_list, i);
-      rs1 = &a->status; rs2 = &b->status;
-    } else {
-      rs1 = smartlist_get(ns->routerstatus_list, i-1);
-      rs2 = smartlist_get(ns->routerstatus_list, i);
-    }
-    if (fast_memcmp(rs1->identity_digest, rs2->identity_digest, DIGEST_LEN)
-        >= 0) {
-      log_warn(LD_DIR, "Networkstatus entries not sorted by identity digest");
-      goto err;
-    }
-  }
-  if (ns_type != NS_TYPE_CONSENSUS) {
-    digest256map_t *ed_id_map = digest256map_new();
-    SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, vote_routerstatus_t *,
-                            vrs) {
-      if (! vrs->has_ed25519_listing ||
-          tor_mem_is_zero((const char *)vrs->ed25519_id, DIGEST256_LEN))
-        continue;
-      if (digest256map_get(ed_id_map, vrs->ed25519_id) != NULL) {
-        log_warn(LD_DIR, "Vote networkstatus ed25519 identities were not "
-                 "unique");
-        digest256map_free(ed_id_map, NULL);
-        goto err;
-      }
-      digest256map_set(ed_id_map, vrs->ed25519_id, (void*)1);
-    } SMARTLIST_FOREACH_END(vrs);
-    digest256map_free(ed_id_map, NULL);
-  }
-
-  /* Parse footer; check signature. */
-  footer_tokens = smartlist_new();
-  if ((end_of_footer = strstr(s, "\nnetwork-status-version ")))
-    ++end_of_footer;
-  else
-    end_of_footer = s + strlen(s);
-  if (tokenize_string(area,s, end_of_footer, footer_tokens,
-                      networkstatus_vote_footer_token_table, 0)) {
-    log_warn(LD_DIR, "Error tokenizing network-status vote footer.");
-    goto err;
-  }
-
-  {
-    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_new();
-    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_strdup(ns->weight_params, 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") ||
-        tok->object_size < 128 || tok->object_size > 512) {
-      log_warn(LD_DIR, "Bad object type or length on directory-signature");
-      goto err;
-    }
-
-    if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
-        base16_decode(declared_identity, sizeof(declared_identity),
-                      id_hexdigest, HEX_DIGEST_LEN)
-                      != sizeof(declared_identity)) {
-      log_warn(LD_DIR, "Error decoding declared identity %s in "
-               "network-status document.", escaped(id_hexdigest));
-      goto err;
-    }
-    if (!(v = networkstatus_get_voter_by_id(ns, declared_identity))) {
-      log_warn(LD_DIR, "ID on signature on network-status document does "
-               "not match any declared directory source.");
-      goto err;
-    }
-    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)
-                      != sizeof(sig->signing_key_digest)) {
-      log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
-               "network-status document.", escaped(sk_hexdigest));
-      tor_free(sig);
-      goto err;
-    }
-
-    if (ns->type != NS_TYPE_CONSENSUS) {
-      if (tor_memneq(declared_identity, ns->cert->cache_info.identity_digest,
-                 DIGEST_LEN)) {
-        log_warn(LD_DIR, "Digest mismatch between declared and actual on "
-                 "network-status vote.");
-        tor_free(sig);
-        goto err;
-      }
-    }
-
-    if (networkstatus_get_voter_sig_by_alg(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 signatures from the same voter with the same "
-             "algorithm. Ignoring the second signature.");
-      tor_free(sig);
-      continue;
-    }
-
-    if (ns->type != NS_TYPE_CONSENSUS) {
-      if (check_signature_token(ns_digests.d[DIGEST_SHA1], DIGEST_LEN,
-                                tok, ns->cert->signing_key, 0,
-                                "network-status document")) {
-        tor_free(sig);
-        goto err;
-      }
-      sig->good_signature = 1;
-    } else {
-      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(v->sigs, sig);
-
-    ++n_signatures;
-  } SMARTLIST_FOREACH_END(_tok);
-
-  if (! n_signatures) {
-    log_warn(LD_DIR, "No signatures on networkstatus document.");
-    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)
-    *eos_out = end_of_footer;
-
-  goto done;
- err:
-  dump_desc(s_dup, "v3 networkstatus");
-  networkstatus_vote_free(ns);
-  ns = NULL;
- done:
-  if (tokens) {
-    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_clear(t));
-    smartlist_free(rs_tokens);
-  }
-  if (footer_tokens) {
-    SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t));
-    smartlist_free(footer_tokens);
-  }
-  if (area) {
-    DUMP_AREA(area, "v3 networkstatus");
-    memarea_drop_all(area);
-  }
-  if (rs_area)
-    memarea_drop_all(rs_area);
-  tor_free(last_kwd);
-
-  return ns;
-}
-
 /** Parse the addr policy in the string <b>s</b> and return it.  If
  * assume_action is nonnegative, then insert its action (ADDR_POLICY_ACCEPT or
  * ADDR_POLICY_REJECT) for items that specify no action.
diff --git a/src/feature/dirparse/routerparse.h b/src/feature/dirparse/routerparse.h
index eb0ca880f..80c974e06 100644
--- a/src/feature/dirparse/routerparse.h
+++ b/src/feature/dirparse/routerparse.h
@@ -14,16 +14,7 @@
 
 #include "core/or/versions.h"
 
-enum networkstatus_type_t;
-
 int router_get_router_hash(const char *s, size_t s_len, char *digest);
-int router_get_networkstatus_v3_hashes(const char *s,
-                                       common_digests_t *digests);
-int router_get_networkstatus_v3_signed_boundaries(const char *s,
-                                                  const char **start_out,
-                                                  const char **end_out);
-int router_get_networkstatus_v3_sha3_as_signed(uint8_t *digest_out,
-                                               const char *s);
 int router_get_extrainfo_hash(const char *s, size_t s_len, char *digest);
 
 int router_parse_list_from_string(const char **s, const char *eos,
@@ -46,12 +37,6 @@ extrainfo_t *extrainfo_parse_entry_from_string(const char *s, const char *end,
 MOCK_DECL(addr_policy_t *, router_parse_addr_policy_item_from_string,
          (const char *s, int assume_action, int *malformed_list));
 
-int compare_vote_routerstatus_entries(const void **_a, const void **_b);
-int networkstatus_verify_bw_weights(networkstatus_t *ns, int);
-networkstatus_t *networkstatus_parse_vote_from_string(const char *s,
-                                           const char **eos_out,
-                                           enum networkstatus_type_t ns_type);
-
 int find_single_ipv6_orport(const smartlist_t *list,
                             tor_addr_t *addr_out,
                             uint16_t *port_out);
@@ -59,23 +44,9 @@ int find_single_ipv6_orport(const smartlist_t *list,
 void routerparse_init(void);
 void routerparse_free_all(void);
 
-#ifdef ROUTERPARSE_PRIVATE
-STATIC int routerstatus_parse_guardfraction(const char *guardfraction_str,
-                                            networkstatus_t *vote,
-                                            vote_routerstatus_t *vote_rs,
-                                            routerstatus_t *rs);
-struct memarea_t;
-STATIC routerstatus_t *routerstatus_parse_entry_from_string(
-                                     struct memarea_t *area,
-                                     const char **s, smartlist_t *tokens,
-                                     networkstatus_t *vote,
-                                     vote_routerstatus_t *vote_rs,
-                                     int consensus_method,
-                                     consensus_flavor_t flav);
-STATIC void summarize_protover_flags(protover_summary_flags_t *out,
-                                     const char *protocols,
+void summarize_protover_flags(protover_summary_flags_t *out,
+                                    const char *protocols,
                                      const char *version);
-#endif /* defined(ROUTERPARSE_PRIVATE) */
 
 #define ED_DESC_SIGNATURE_PREFIX "Tor router descriptor signature v1"
 
diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c
index d214e6470..de2451b79 100644
--- a/src/feature/nodelist/networkstatus.c
+++ b/src/feature/nodelist/networkstatus.c
@@ -53,6 +53,7 @@
 #include "core/or/protover.h"
 #include "core/or/relay.h"
 #include "core/or/scheduler.h"
+#include "core/or/versions.h"
 #include "feature/client/bridges.h"
 #include "feature/client/entrynodes.h"
 #include "feature/client/transports.h"
@@ -64,7 +65,7 @@
 #include "feature/dirclient/dlstatus.h"
 #include "feature/dircommon/directory.h"
 #include "feature/dircommon/voting_schedule.h"
-#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/ns_parse.h"
 #include "feature/hibernate/hibernate.h"
 #include "feature/nodelist/authcert.h"
 #include "feature/nodelist/dirlist.h"
diff --git a/src/test/fuzz/fuzz_consensus.c b/src/test/fuzz/fuzz_consensus.c
index 64507c67e..b56702a65 100644
--- a/src/test/fuzz/fuzz_consensus.c
+++ b/src/test/fuzz/fuzz_consensus.c
@@ -2,7 +2,7 @@
 /* See LICENSE for licensing information */
 #define SIGCOMMON_PRIVATE
 #include "core/or/or.h"
-#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/ns_parse.h"
 #include "feature/dirparse/sigcommon.h"
 #include "feature/dirparse/unparseable.h"
 #include "feature/nodelist/networkstatus.h"
diff --git a/src/test/fuzz/fuzz_vrs.c b/src/test/fuzz/fuzz_vrs.c
index 8fb6e6ef2..0b869aa5c 100644
--- a/src/test/fuzz/fuzz_vrs.c
+++ b/src/test/fuzz/fuzz_vrs.c
@@ -1,9 +1,9 @@
 /* Copyright (c) 2016-2018, The Tor Project, Inc. */
 /* See LICENSE for licensing information */
-#define ROUTERPARSE_PRIVATE
+#define NS_PARSE_PRIVATE
 #define NETWORKSTATUS_PRIVATE
 #include "core/or/or.h"
-#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/ns_parse.h"
 #include "feature/dirparse/unparseable.h"
 #include "lib/memarea/memarea.h"
 #include "feature/nodelist/microdesc.h"
diff --git a/src/test/test_consdiffmgr.c b/src/test/test_consdiffmgr.c
index dc05c351b..6c451da68 100644
--- a/src/test/test_consdiffmgr.c
+++ b/src/test/test_consdiffmgr.c
@@ -11,7 +11,7 @@
 #include "core/mainloop/cpuworker.h"
 #include "lib/crypt_ops/crypto_rand.h"
 #include "feature/nodelist/networkstatus.h"
-#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/ns_parse.h"
 #include "lib/evloop/workqueue.h"
 #include "lib/compress/compress.h"
 #include "lib/encoding/confline.h"
diff --git a/src/test/test_dir.c b/src/test/test_dir.c
index 59464022b..3b5d6dc3d 100644
--- a/src/test/test_dir.c
+++ b/src/test/test_dir.c
@@ -16,6 +16,7 @@
 #define DLSTATUS_PRIVATE
 #define HIBERNATE_PRIVATE
 #define NETWORKSTATUS_PRIVATE
+#define NS_PARSE_PRIVATE
 #define NODE_SELECT_PRIVATE
 #define RELAY_PRIVATE
 #define ROUTERLIST_PRIVATE
@@ -54,6 +55,7 @@
 #include "feature/nodelist/node_select.h"
 #include "feature/nodelist/routerlist.h"
 #include "feature/dirparse/authcert_parse.h"
+#include "feature/dirparse/ns_parse.h"
 #include "feature/dirparse/routerparse.h"
 #include "feature/dirparse/unparseable.h"
 #include "feature/nodelist/routerset.h"
diff --git a/src/test/test_dir_common.c b/src/test/test_dir_common.c
index 5a9659ad4..86d283894 100644
--- a/src/test/test_dir_common.c
+++ b/src/test/test_dir_common.c
@@ -11,6 +11,7 @@
 #include "feature/nodelist/nodelist.h"
 #include "feature/nodelist/routerlist.h"
 #include "feature/dirparse/authcert_parse.h"
+#include "feature/dirparse/ns_parse.h"
 #include "test/test_dir_common.h"
 #include "feature/dircommon/voting_schedule.h"
 
diff --git a/src/test/test_guardfraction.c b/src/test/test_guardfraction.c
index c7266be66..d6f4cd63f 100644
--- a/src/test/test_guardfraction.c
+++ b/src/test/test_guardfraction.c
@@ -2,15 +2,15 @@
 /* See LICENSE for licensing information */
 
 #define GUARDFRACTION_PRIVATE
-#define ROUTERPARSE_PRIVATE
 #define NETWORKSTATUS_PRIVATE
+#define NS_PARSE_PRIVATE
 
 #include "orconfig.h"
 #include "core/or/or.h"
 #include "app/config/config.h"
 #include "feature/dirauth/guardfraction.h"
 #include "feature/client/entrynodes.h"
-#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/ns_parse.h"
 #include "feature/nodelist/networkstatus.h"
 
 #include "feature/nodelist/networkstatus_st.h"
diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c
index d4606c227..b386ad529 100644
--- a/src/test/test_routerlist.c
+++ b/src/test/test_routerlist.c
@@ -35,6 +35,7 @@
 #include "feature/nodelist/routerset.h"
 #include "feature/dirparse/authcert_parse.h"
 #include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/ns_parse.h"
 #include "feature/dirauth/shared_random.h"
 #include "app/config/statefile.h"
 



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits