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

arma at torproject.org arma at torproject.org
Sat Jan 15 22:31:46 UTC 2011


commit f1de329e784e76dbb27cde148522a6c490e7cf63
Merge: e895919 1a07348
Author: Nick Mathewson <nickm at torproject.org>
Date:   Mon Jan 3 11:51:17 2011 -0500

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

 LICENSE                  |    2 +-
 Makefile.am              |    2 +-
 contrib/bundle.nsi       |    2 +-
 contrib/netinst.nsi      |    2 +-
 contrib/polipo/README    |    4 ++--
 src/common/address.c     |    2 +-
 src/common/address.h     |    2 +-
 src/common/aes.c         |    2 +-
 src/common/aes.h         |    2 +-
 src/common/compat.c      |    2 +-
 src/common/compat.h      |    2 +-
 src/common/container.c   |    2 +-
 src/common/container.h   |    2 +-
 src/common/crypto.c      |    2 +-
 src/common/crypto.h      |    2 +-
 src/common/ht.h          |    2 +-
 src/common/log.c         |    2 +-
 src/common/memarea.c     |    2 +-
 src/common/memarea.h     |    2 +-
 src/common/mempool.c     |    2 +-
 src/common/mempool.h     |    2 +-
 src/common/torgzip.c     |    2 +-
 src/common/torgzip.h     |    2 +-
 src/common/torint.h      |    2 +-
 src/common/torlog.h      |    2 +-
 src/common/tortls.c      |    2 +-
 src/common/tortls.h      |    2 +-
 src/common/util.c        |    2 +-
 src/common/util.h        |    2 +-
 src/or/buffers.c         |    2 +-
 src/or/circuitbuild.c    |    2 +-
 src/or/circuitlist.c     |    2 +-
 src/or/circuituse.c      |    2 +-
 src/or/command.c         |    2 +-
 src/or/config.c          |    4 ++--
 src/or/connection.c      |    2 +-
 src/or/connection_edge.c |    2 +-
 src/or/connection_or.c   |    2 +-
 src/or/control.c         |    2 +-
 src/or/cpuworker.c       |    2 +-
 src/or/directory.c       |    2 +-
 src/or/dirserv.c         |    2 +-
 src/or/dirvote.c         |    2 +-
 src/or/dns.c             |    2 +-
 src/or/dnsserv.c         |    2 +-
 src/or/eventdns_tor.h    |    2 +-
 src/or/geoip.c           |    2 +-
 src/or/hibernate.c       |    2 +-
 src/or/main.c            |    2 +-
 src/or/networkstatus.c   |    2 +-
 src/or/ntmain.c          |    2 +-
 src/or/onion.c           |    2 +-
 src/or/or.h              |    2 +-
 src/or/policies.c        |    2 +-
 src/or/reasons.c         |    2 +-
 src/or/relay.c           |    2 +-
 src/or/rendclient.c      |    2 +-
 src/or/rendcommon.c      |    2 +-
 src/or/rendmid.c         |    2 +-
 src/or/rendservice.c     |    2 +-
 src/or/rephist.c         |    2 +-
 src/or/router.c          |    2 +-
 src/or/routerlist.c      |    2 +-
 src/or/routerparse.c     |    2 +-
 src/or/tor_main.c        |    2 +-
 src/test/test_data.c     |    2 +-
 src/tools/tor-gencert.c  |    2 +-
 src/tools/tor-resolve.c  |    2 +-
 68 files changed, 70 insertions(+), 70 deletions(-)

diff --combined Makefile.am
index d03c71f,4ded744..eb61de7
--- a/Makefile.am
+++ b/Makefile.am
@@@ -1,6 -1,7 +1,6 @@@
 -# $Id$
  # Copyright (c) 2001-2004, Roger Dingledine
  # Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson
- # Copyright (c) 2007-2010, The Tor Project, Inc.
+ # Copyright (c) 2007-2011, The Tor Project, Inc.
  # See LICENSE for licensing information
  
  AUTOMAKE_OPTIONS = foreign
@@@ -10,7 -11,7 +10,7 @@@ SUBDIRS = src doc contri
  
  DIST_SUBDIRS = src doc contrib
  
 -EXTRA_DIST = INSTALL README AUTHORS LICENSE ChangeLog          \
 +EXTRA_DIST = INSTALL README LICENSE ChangeLog          \
               ReleaseNotes tor.spec tor.spec.in
  
  #install-data-local:
@@@ -34,22 -35,39 +34,22 @@@ dist-rpm
  	mv $$RPM_BUILD_DIR/RPMS/* .;                            \
  	rm -rf $$RPM_BUILD_DIR
  
 -dist-osx:
 -	@if [ "x$(prefix)" != 'x/Library/Tor' ]; then \
 -	  echo "Configure with --prefix=/Library/Tor, please"; \
 -	  exit 1; \
 -	fi; \
 -	if [ "x$(bindir)" != 'x/Library/Tor' ]; then \
 -	  echo "Configure with --bindir=/Library/Tor, please"; \
 -	  exit 1; \
 -	fi; \
 -	if [ "x$(sysconfdir)" != 'x/Library' ]; then \
 -	  echo "Configure with --sysconfdir=/Library, please"; \
 -	  exit 1; \
 -	fi; \
 -	if [ "x$(CONFDIR)" != 'x/Library/Tor' ]; then \
 -	  echo "Configure with CONFDIR=/Library/Tor, please"; \
 -	fi
 -	$(MAKE) all
 -	VERSION=$(VERSION) sh ./contrib/osx/package.sh
 -
  dist: check
  
  doxygen:
  	doxygen && cd doc/doxygen/latex && make
  
 -test:
 -	./src/or/test
 +test: all
 +	./src/test/test
  
 -# Avoid strlcpy.c, strlcat.c, tree.h
 +# Avoid strlcpy.c, strlcat.c, aes.c, OpenBSD_malloc_Linux.c, sha256.c,
 +# eventdns.[hc], tinytest*.[ch]
  check-spaces:
  	./contrib/checkSpace.pl -C                    \
  	        src/common/*.h                        \
  		src/common/[^asO]*.c src/common/address.c \
 -		src/or/[^et]*.[ch] src/or/t*.c src/or/eventdns_tor.h
 +		src/or/[^e]*.[ch] src/or/eventdns_tor.h \
 +		src/test/test*.[ch] src/tools/*.[ch]
  
  check-docs:
  	./contrib/checkOptionDocs.pl
diff --combined contrib/polipo/README
index 98deec3,be3d4b6..1110ca2
--- a/contrib/polipo/README
+++ b/contrib/polipo/README
@@@ -1,19 -1,19 +1,19 @@@
- Copyright 2007-2008 Andrew Lewman
- Copyright 2009-2010 The Tor Project
+ Copyright 2007-2008, Andrew Lewman
+ Copyright 2009-2011, The Tor Project
  
  ----------------
  General Comments
  ----------------
  
  These are some hacks for making polipo work and install a package native
 -to OSX or Windows.
 +to Windows.
  
  They need some work before they can be committed upstream:
 -  - Merge the three makefiles into one with specific builds such as "make
 -    dist-osx" or "make dist-win32"
 +  - Change the Makefile so it has a specific build such as "make
 +    dist-win32"
    - Configure the options for tor in polipo config, just leave them
      commented out for easy activation.
 -  - Work out better polipo config options for Tor.  
 +  - Work out better polipo config options for Tor.
  
  As always, I'm happy to accept patches.
  
@@@ -45,3 -45,12 +45,3 @@@ choose Compile NSIS Script.  You'll the
  The Polipo NSI installer assumes libgnurx-0.dll is in the same directory as polipo.exe.
  You'll need to copy libgnurx-0.dll into "./" in order to make the
  installation package.
 -
 ----------------------------------------------
 -OSX Universal Binary and Installation package
 ----------------------------------------------
 -1) Copy Makefile.osx over Makefile.
 -2) Run 'make'.
 -3) Copy the contents of this directory into a directory named "contrib".
 -4) Run './contrib/package.sh'
 -5) You should have a Polipo-version.dmg ready for installation.
diff --combined src/common/address.c
index c35f04c,82a7096..0e57528
--- a/src/common/address.c
+++ b/src/common/address.c
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003-2004, Roger Dingledine
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -12,7 -12,7 +12,7 @@@
  #include "compat.h"
  #include "util.h"
  #include "address.h"
 -#include "log.h"
 +#include "torlog.h"
  
  #ifdef MS_WINDOWS
  #include <process.h>
@@@ -726,7 -726,7 +726,7 @@@ tor_addr_is_loopback(const tor_addr_t *
  }
  
  /** Set <b>dest</b> to equal the IPv4 address in <b>v4addr</b> (given in
 - * network order. */
 + * network order). */
  void
  tor_addr_from_ipv4n(tor_addr_t *dest, uint32_t v4addr)
  {
@@@ -760,8 -760,6 +760,8 @@@ tor_addr_from_in6(tor_addr_t *dest, con
  void
  tor_addr_copy(tor_addr_t *dest, const tor_addr_t *src)
  {
 +  if (src == dest)
 +    return;
    tor_assert(src);
    tor_assert(dest);
    memcpy(dest, src, sizeof(tor_addr_t));
@@@ -914,6 -912,13 +914,6 @@@ tor_dup_addr(const tor_addr_t *addr
    return tor_strdup(buf);
  }
  
 -/** Copy the address in <b>src</b> to <b>dest</b> */
 -void
 -tor_addr_assign(tor_addr_t *dest, const tor_addr_t *src)
 -{
 -  memcpy(dest, src, sizeof(tor_addr_t));
 -}
 -
  /** Return a string representing the address <b>addr</b>.  This string is
   * statically allocated, and must not be freed.  Each call to
   * <b>fmt_addr</b> invalidates the last result of the function.  This
diff --combined src/common/address.h
index 6116bb4,cb24387..371c6da
--- a/src/common/address.h
+++ b/src/common/address.h
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003-2004, Roger Dingledine
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -107,6 -107,7 +107,6 @@@ tor_addr_eq_ipv4h(const tor_addr_t *a, 
  
  int tor_addr_lookup(const char *name, uint16_t family, tor_addr_t *addr_out);
  char *tor_dup_addr(const tor_addr_t *addr) ATTR_MALLOC;
 -void tor_addr_assign(tor_addr_t *dest, const tor_addr_t *src);
  const char *fmt_addr(const tor_addr_t *addr);
  int get_interface_address6(int severity, sa_family_t family, tor_addr_t *addr);
  
diff --combined src/common/aes.c
index a173283,7e332b0..39ab294
--- a/src/common/aes.c
+++ b/src/common/aes.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001, Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -21,7 -21,7 +21,7 @@@
  #include "compat.h"
  #include "aes.h"
  #include "util.h"
 -#include "log.h"
 +#include "torlog.h"
  
  /* We have 3 strategies for getting AES: Via OpenSSL's AES_encrypt function,
   * via OpenSSL's EVP_EncryptUpdate function, or via the built-in AES
@@@ -263,8 -263,7 +263,8 @@@ aes_set_key(aes_cnt_cipher_t *cipher, c
  void
  aes_free_cipher(aes_cnt_cipher_t *cipher)
  {
 -  tor_assert(cipher);
 +  if (!cipher)
 +    return;
  #ifdef USE_OPENSSL_EVP
    EVP_CIPHER_CTX_cleanup(&cipher->key);
  #endif
diff --combined src/common/compat.c
index 8ec4221,333ef5b..aa42514
--- a/src/common/compat.c
+++ b/src/common/compat.c
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003-2004, Roger Dingledine
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -88,7 -88,7 +88,7 @@@
  #include <sys/prctl.h>
  #endif
  
 -#include "log.h"
 +#include "torlog.h"
  #include "util.h"
  #include "container.h"
  #include "address.h"
@@@ -170,17 -170,12 +170,17 @@@ tor_munmap_file(tor_mmap_t *handle
  tor_mmap_t *
  tor_mmap_file(const char *filename)
  {
 +  TCHAR tfilename[MAX_PATH]= {0};
    tor_mmap_t *res = tor_malloc_zero(sizeof(tor_mmap_t));
    int empty = 0;
    res->file_handle = INVALID_HANDLE_VALUE;
    res->mmap_handle = NULL;
 -
 -  res->file_handle = CreateFile(filename,
 +#ifdef UNICODE
 +  mbstowcs(tfilename,filename,MAX_PATH);
 +#else
 +  strlcpy(tfilename,filename,MAX_PATH);
 +#endif
 +  res->file_handle = CreateFile(tfilename,
                                  GENERIC_READ, FILE_SHARE_READ,
                                  NULL,
                                  OPEN_EXISTING,
@@@ -313,100 -308,6 +313,100 @@@ tor_vsnprintf(char *str, size_t size, c
    return r;
  }
  
 +/**
 + * Portable asprintf implementation.  Does a printf() into a newly malloc'd
 + * string.  Sets *<b>strp</b> to this string, and returns its length (not
 + * including the terminating NUL character).
 + *
 + * You can treat this function as if its implementation were something like
 +   <pre>
 +     char buf[_INFINITY_];
 +     tor_snprintf(buf, sizeof(buf), fmt, args);
 +     *strp = tor_strdup(buf);
 +     return strlen(*strp):
 +   </pre>
 + * Where _INFINITY_ is an imaginary constant so big that any string can fit
 + * into it.
 + */
 +int
 +tor_asprintf(char **strp, const char *fmt, ...)
 +{
 +  int r;
 +  va_list args;
 +  va_start(args, fmt);
 +  r = tor_vasprintf(strp, fmt, args);
 +  va_end(args);
 +  if (!*strp || r < 0) {
 +    log_err(LD_BUG, "Internal error in asprintf");
 +    tor_assert(0);
 +  }
 +  return r;
 +}
 +
 +/**
 + * Portable vasprintf implementation.  Does a printf() into a newly malloc'd
 + * string.  Differs from regular vasprintf in the same ways that
 + * tor_asprintf() differs from regular asprintf.
 + */
 +int
 +tor_vasprintf(char **strp, const char *fmt, va_list args)
 +{
 +  /* use a temporary variable in case *strp is in args. */
 +  char *strp_tmp=NULL;
 +#ifdef HAVE_VASPRINTF
 +  /* If the platform gives us one, use it. */
 +  int r = vasprintf(&strp_tmp, fmt, args);
 +  if (r < 0)
 +    *strp = NULL;
 +  else
 +    *strp = strp_tmp;
 +  return r;
 +#elif defined(_MSC_VER)
 +  /* On Windows, _vsnprintf won't tell us the length of the string if it
 +   * overflows, so we need to use _vcsprintf to tell how much to allocate */
 +  int len, r;
 +  char *res;
 +  len = _vscprintf(fmt, args);
 +  if (len < 0) {
 +    *strp = NULL;
 +    return -1;
 +  }
 +  strp_tmp = tor_malloc(len + 1);
 +  r = _vsnprintf(strp_tmp, len+1, fmt, args);
 +  if (r != len) {
 +    tor_free(strp_tmp);
 +    *strp = NULL;
 +    return -1;
 +  }
 +  *strp = strp_tmp;
 +  return len;
 +#else
 +  /* Everywhere else, we have a decent vsnprintf that tells us how many
 +   * characters we need.  We give it a try on a short buffer first, since
 +   * it might be nice to avoid the second vsnprintf call.
 +   */
 +  char buf[128];
 +  int len, r;
 +  va_list tmp_args;
 +  va_copy(tmp_args, args);
 +  len = vsnprintf(buf, sizeof(buf), fmt, tmp_args);
 +  va_end(tmp_args);
 +  if (len < (int)sizeof(buf)) {
 +    *strp = tor_strdup(buf);
 +    return len;
 +  }
 +  strp_tmp = tor_malloc(len+1);
 +  r = vsnprintf(strp_tmp, len+1, fmt, args);
 +  if (r != len) {
 +    tor_free(strp_tmp);
 +    *strp = NULL;
 +    return -1;
 +  }
 +  *strp = strp_tmp;
 +  return len;
 +#endif
 +}
 +
  /** Given <b>hlen</b> bytes at <b>haystack</b> and <b>nlen</b> bytes at
   * <b>needle</b>, return a pointer to the first occurrence of the needle
   * within the haystack, or NULL if there is no such occurrence.
@@@ -498,37 -399,6 +498,37 @@@ const char TOR_TOLOWER_TABLE[256] = 
    240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,
  };
  
 +/** Implementation of strtok_r for platforms whose coders haven't figured out
 + * how to write one.  Hey guys!  You can use this code here for free! */
 +char *
 +tor_strtok_r_impl(char *str, const char *sep, char **lasts)
 +{
 +  char *cp, *start;
 +  if (str)
 +    start = cp = *lasts = str;
 +  else if (!*lasts)
 +    return NULL;
 +  else
 +    start = cp = *lasts;
 +
 +  tor_assert(*sep);
 +  if (sep[1]) {
 +    while (*cp && !strchr(sep, *cp))
 +      ++cp;
 +  } else {
 +    tor_assert(strlen(sep) == 1);
 +    cp = strchr(cp, *sep);
 +  }
 +
 +  if (!cp || !*cp) {
 +    *lasts = NULL;
 +  } else {
 +    *cp++ = '\0';
 +    *lasts = cp;
 +  }
 +  return start;
 +}
 +
  #ifdef MS_WINDOWS
  /** Take a filename and return a pointer to its final element.  This
   * function is called on __FILE__ to fix a MSVC nit where __FILE__
@@@ -580,8 -450,8 +580,8 @@@ get_uint32(const void *cp
    return v;
  }
  /**
 - * Read a 32-bit value beginning at <b>cp</b>.  Equivalent to
 - * *(uint32_t*)(cp), but will not cause segfaults on platforms that forbid
 + * Read a 64-bit value beginning at <b>cp</b>.  Equivalent to
 + * *(uint64_t*)(cp), but will not cause segfaults on platforms that forbid
   * unaligned memory access.
   */
  uint64_t
@@@ -1109,6 -979,9 +1109,6 @@@ set_max_file_descriptors(rlim_t limit, 
  #if defined(CYGWIN) || defined(__CYGWIN__)
    const char *platform = "Cygwin";
    const unsigned long MAX_CONNECTIONS = 3200;
 -#elif defined(IPHONE)
 -  const char *platform = "iPhone";
 -  const unsigned long MAX_CONNECTIONS = 9999;
  #elif defined(MS_WINDOWS)
    const char *platform = "Windows";
    const unsigned long MAX_CONNECTIONS = 15000;
@@@ -1680,30 -1553,6 +1680,30 @@@ tor_lookup_hostname(const char *name, u
    return -1;
  }
  
 +/** Initialize the insecure libc RNG. */
 +void
 +tor_init_weak_random(unsigned seed)
 +{
 +#ifdef MS_WINDOWS
 +  srand(seed);
 +#else
 +  srandom(seed);
 +#endif
 +}
 +
 +/** Return a randomly chosen value in the range 0..TOR_RAND_MAX.  This
 + * entropy will not be cryptographically strong; do not rely on it
 + * for anything an adversary should not be able to predict. */
 +long
 +tor_weak_random(void)
 +{
 +#ifdef MS_WINDOWS
 +  return rand();
 +#else
 +  return random();
 +#endif
 +}
 +
  /** Hold the result of our call to <b>uname</b>. */
  static char uname_result[256];
  /** True iff uname_result is set. */
@@@ -1729,14 -1578,13 +1729,14 @@@ get_uname(void
  #ifdef MS_WINDOWS
          OSVERSIONINFOEX info;
          int i;
 -        unsigned int leftover_mask;
          const char *plat = NULL;
          const char *extra = NULL;
 +        char acsd[MAX_PATH] = {0};
          static struct {
            unsigned major; unsigned minor; const char *version;
          } win_version_table[] = {
 -          { 6, 0, "Windows \"Longhorn\"" },
 +          { 6, 1, "Windows 7" },
 +          { 6, 0, "Windows Vista" },
            { 5, 2, "Windows Server 2003" },
            { 5, 1, "Windows XP" },
            { 5, 0, "Windows 2000" },
@@@ -1747,6 -1595,25 +1747,6 @@@
            { 3, 51, "Windows NT 3.51" },
            { 0, 0, NULL }
          };
 -#ifdef VER_SUITE_BACKOFFICE
 -        static struct {
 -          unsigned int mask; const char *str;
 -        } win_mask_table[] = {
 -          { VER_SUITE_BACKOFFICE,         " {backoffice}" },
 -          { VER_SUITE_BLADE,              " {\"blade\" (2003, web edition)}" },
 -          { VER_SUITE_DATACENTER,         " {datacenter}" },
 -          { VER_SUITE_ENTERPRISE,         " {enterprise}" },
 -          { VER_SUITE_EMBEDDEDNT,         " {embedded}" },
 -          { VER_SUITE_PERSONAL,           " {personal}" },
 -          { VER_SUITE_SINGLEUSERTS,
 -            " {terminal services, single user}" },
 -          { VER_SUITE_SMALLBUSINESS,      " {small business}" },
 -          { VER_SUITE_SMALLBUSINESS_RESTRICTED,
 -            " {small business, restricted}" },
 -          { VER_SUITE_TERMINAL,           " {terminal services}" },
 -          { 0, NULL },
 -        };
 -#endif
          memset(&info, 0, sizeof(info));
          info.dwOSVersionInfoSize = sizeof(info);
          if (! GetVersionEx((LPOSVERSIONINFO)&info)) {
@@@ -1755,19 -1622,14 +1755,19 @@@
            uname_result_is_set = 1;
            return uname_result;
          }
 +#ifdef UNICODE
 +        wcstombs(acsd, info.szCSDVersion, MAX_PATH);
 +#else
 +        strlcpy(acsd, info.szCSDVersion, sizeof(acsd));
 +#endif
          if (info.dwMajorVersion == 4 && info.dwMinorVersion == 0) {
            if (info.dwPlatformId == VER_PLATFORM_WIN32_NT)
              plat = "Windows NT 4.0";
            else
              plat = "Windows 95";
 -          if (info.szCSDVersion[1] == 'B')
 +          if (acsd[1] == 'B')
              extra = "OSR2 (B)";
 -          else if (info.szCSDVersion[1] == 'C')
 +          else if (acsd[1] == 'C')
              extra = "OSR2 (C)";
          } else {
            for (i=0; win_version_table[i].major>0; ++i) {
@@@ -1779,30 -1641,29 +1779,30 @@@
            }
          }
          if (plat && !strcmp(plat, "Windows 98")) {
 -          if (info.szCSDVersion[1] == 'A')
 +          if (acsd[1] == 'A')
              extra = "SE (A)";
 -          else if (info.szCSDVersion[1] == 'B')
 +          else if (acsd[1] == 'B')
              extra = "SE (B)";
          }
          if (plat) {
            if (!extra)
 -            extra = info.szCSDVersion;
 +            extra = acsd;
            tor_snprintf(uname_result, sizeof(uname_result), "%s %s",
                         plat, extra);
          } else {
            if (info.dwMajorVersion > 6 ||
 -              (info.dwMajorVersion==6 && info.dwMinorVersion>0))
 +              (info.dwMajorVersion==6 && info.dwMinorVersion>1))
              tor_snprintf(uname_result, sizeof(uname_result),
                        "Very recent version of Windows [major=%d,minor=%d] %s",
                        (int)info.dwMajorVersion,(int)info.dwMinorVersion,
 -                      info.szCSDVersion);
 +                      acsd);
            else
              tor_snprintf(uname_result, sizeof(uname_result),
                        "Unrecognized version of Windows [major=%d,minor=%d] %s",
                        (int)info.dwMajorVersion,(int)info.dwMinorVersion,
 -                      info.szCSDVersion);
 +                      acsd);
          }
 +#if !defined (WINCE)
  #ifdef VER_SUITE_BACKOFFICE
          if (info.wProductType == VER_NT_DOMAIN_CONTROLLER) {
            strlcat(uname_result, " [domain controller]", sizeof(uname_result));
@@@ -1811,7 -1672,18 +1811,7 @@@
          } else if (info.wProductType == VER_NT_WORKSTATION) {
            strlcat(uname_result, " [workstation]", sizeof(uname_result));
          }
 -        leftover_mask = info.wSuiteMask;
 -        for (i = 0; win_mask_table[i].mask; ++i) {
 -          if (info.wSuiteMask & win_mask_table[i].mask) {
 -            strlcat(uname_result, win_mask_table[i].str, sizeof(uname_result));
 -            leftover_mask &= ~win_mask_table[i].mask;
 -          }
 -        }
 -        if (leftover_mask) {
 -          size_t len = strlen(uname_result);
 -          tor_snprintf(uname_result+len, sizeof(uname_result)-len,
 -                       " {0x%x}", info.wSuiteMask);
 -        }
 +#endif
  #endif
  #else
          strlcpy(uname_result, "Unknown platform", sizeof(uname_result));
@@@ -1921,6 -1793,7 +1921,6 @@@ spawn_exit(void
     * call _exit, not exit, from child processes. */
    _exit(0);
  #endif
 -
  }
  
  /** Set *timeval to the current time of day.  On error, log and terminate.
@@@ -1940,15 -1813,8 +1940,15 @@@ tor_gettimeofday(struct timeval *timeva
      uint64_t ft_64;
      FILETIME ft_ft;
    } ft;
 +#if defined (WINCE)
 +  /* wince do not have GetSystemTimeAsFileTime */
 +  SYSTEMTIME stime;
 +  GetSystemTime(&stime);
 +  SystemTimeToFileTime(&stime,&ft.ft_ft);
 +#else
    /* number of 100-nsec units since Jan 1, 1601 */
    GetSystemTimeAsFileTime(&ft.ft_ft);
 +#endif
    if (ft.ft_64 < EPOCH_BIAS) {
      log_err(LD_GENERAL,"System time is before 1970; failing.");
      exit(1);
@@@ -2148,8 -2014,6 +2148,8 @@@ tor_mutex_new(void
  void
  tor_mutex_free(tor_mutex_t *m)
  {
 +  if (!m)
 +    return;
    tor_mutex_uninit(m);
    tor_free(m);
  }
@@@ -2177,8 -2041,7 +2177,8 @@@ tor_cond_new(void
  void
  tor_cond_free(tor_cond_t *cond)
  {
 -  tor_assert(cond);
 +  if (!cond)
 +    return;
    if (pthread_cond_destroy(&cond->cond)) {
      log_warn(LD_GENERAL,"Error freeing condition: %s", strerror(errno));
      return;
@@@ -2235,8 -2098,7 +2235,8 @@@ tor_cond_new(void
  void
  tor_cond_free(tor_cond_t *cond)
  {
 -  tor_assert(cond);
 +  if (!cond)
 +    return;
    DeleteCriticalSection(&cond->mutex);
    /* XXXX notify? */
    smartlist_free(cond->events);
@@@ -2312,89 -2174,6 +2312,89 @@@ tor_threads_init(void
  }
  #endif
  
 +#if defined(HAVE_MLOCKALL) && HAVE_DECL_MLOCKALL && defined(RLIMIT_MEMLOCK)
 +/** Attempt to raise the current and max rlimit to infinity for our process.
 + * This only needs to be done once and can probably only be done when we have
 + * not already dropped privileges.
 + */
 +static int
 +tor_set_max_memlock(void)
 +{
 +  /* Future consideration for Windows is probably SetProcessWorkingSetSize
 +   * This is similar to setting the memory rlimit of RLIMIT_MEMLOCK
 +   * http://msdn.microsoft.com/en-us/library/ms686234(VS.85).aspx
 +   */
 +
 +  struct rlimit limit;
 +
 +  /* RLIM_INFINITY is -1 on some platforms. */
 +  limit.rlim_cur = RLIM_INFINITY;
 +  limit.rlim_max = RLIM_INFINITY;
 +
 +  if (setrlimit(RLIMIT_MEMLOCK, &limit) == -1) {
 +    if (errno == EPERM) {
 +      log_warn(LD_GENERAL, "You appear to lack permissions to change memory "
 +                           "limits. Are you root?");
 +    }
 +    log_warn(LD_GENERAL, "Unable to raise RLIMIT_MEMLOCK: %s",
 +             strerror(errno));
 +    return -1;
 +  }
 +
 +  return 0;
 +}
 +#endif
 +
 +/** Attempt to lock all current and all future memory pages.
 + * This should only be called once and while we're privileged.
 + * Like mlockall() we return 0 when we're successful and -1 when we're not.
 + * Unlike mlockall() we return 1 if we've already attempted to lock memory.
 + */
 +int
 +tor_mlockall(void)
 +{
 +  static int memory_lock_attempted = 0;
 +
 +  if (memory_lock_attempted) {
 +    return 1;
 +  }
 +
 +  memory_lock_attempted = 1;
 +
 +  /*
 +   * Future consideration for Windows may be VirtualLock
 +   * VirtualLock appears to implement mlock() but not mlockall()
 +   *
 +   * http://msdn.microsoft.com/en-us/library/aa366895(VS.85).aspx
 +   */
 +
 +#if defined(HAVE_MLOCKALL) && HAVE_DECL_MLOCKALL && defined(RLIMIT_MEMLOCK)
 +  if (tor_set_max_memlock() == 0) {
 +    log_debug(LD_GENERAL, "RLIMIT_MEMLOCK is now set to RLIM_INFINITY.");
 +  }
 +
 +  if (mlockall(MCL_CURRENT|MCL_FUTURE) == 0) {
 +    log_info(LD_GENERAL, "Insecure OS paging is effectively disabled.");
 +    return 0;
 +  } else {
 +    if (errno == ENOSYS) {
 +      /* Apple - it's 2009! I'm looking at you. Grrr. */
 +      log_notice(LD_GENERAL, "It appears that mlockall() is not available on "
 +                             "your platform.");
 +    } else if (errno == EPERM) {
 +      log_notice(LD_GENERAL, "It appears that you lack the permissions to "
 +                             "lock memory. Are you root?");
 +    }
 +    log_notice(LD_GENERAL, "Unable to lock all current and future memory "
 +                           "pages: %s", strerror(errno));
 +    return -1;
 +  }
 +#else
 +  log_warn(LD_GENERAL, "Unable to lock memory pages. mlockall() unsupported?");
 +  return -1;
 +#endif
 +}
 +
  /** Identity of the "main" thread */
  static unsigned long main_thread_id = -1;
  
@@@ -2546,26 -2325,20 +2546,26 @@@ network_init(void
  char *
  format_win32_error(DWORD err)
  {
 -  LPVOID str = NULL;
 +  TCHAR *str = NULL;
    char *result;
  
    /* Somebody once decided that this interface was better than strerror(). */
 -  FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
 +  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                   FORMAT_MESSAGE_FROM_SYSTEM |
                   FORMAT_MESSAGE_IGNORE_INSERTS,
                   NULL, err,
                   MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
 -                 (LPTSTR) &str,
 +                (LPVOID)&str,
                   0, NULL);
  
    if (str) {
 -    result = tor_strdup((char*)str);
 +#ifdef UNICODE
 +    char abuf[1024] = {0};
 +    wcstombs(abuf,str,1024);
 +    result = tor_strdup(abuf);
 +#else
 +    result = tor_strdup(str);
 +#endif
      LocalFree(str); /* LocalFree != free() */
    } else {
      result = tor_strdup("<unformattable error>");
diff --combined src/common/compat.h
index 03e0686,f1d869a..01c94d5
--- a/src/common/compat.h
+++ b/src/common/compat.h
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003-2004, Roger Dingledine
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  #ifndef _TOR_COMPAT_H
@@@ -51,22 -51,6 +51,22 @@@
  #include <netinet6/in6.h>
  #endif
  
 +#if defined (WINCE)
 +#include <fcntl.h>
 +#include <io.h>
 +#include <math.h>
 +#include <projects.h>
 +#define snprintf _snprintf
 +/* this is not exported as W .... */
 +#define SHGetPathFromIDListW SHGetPathFromIDList
 +/* wcecompat has vasprintf */
 +#define HAVE_VASPRINTF
 +/* no service here */
 +#ifdef NT_SERVICE
 +#undef NT_SERVICE
 +#endif
 +#endif // WINCE
 +
  #ifndef NULL_REP_IS_ZERO_BYTES
  #error "It seems your platform does not represent NULL as zero. We can't cope."
  #endif
@@@ -193,8 -177,8 +193,8 @@@ extern INLINE double U64_TO_DBL(uint64_
  /* ===== String compatibility */
  #ifdef MS_WINDOWS
  /* Windows names string functions differently from most other platforms. */
 -#define strncasecmp strnicmp
 -#define strcasecmp stricmp
 +#define strncasecmp _strnicmp
 +#define strcasecmp _stricmp
  #endif
  #ifndef HAVE_STRLCAT
  size_t strlcat(char *dst, const char *src, size_t siz) ATTR_NONNULL((1,2));
@@@ -212,26 -196,18 +212,26 @@@ size_t strlcpy(char *dst, const char *s
  #define U64_SCANF_ARG(a) (a)
  /** Expands to a literal uint64_t-typed constant for the value <b>n</b>. */
  #define U64_LITERAL(n) (n ## ui64)
 +#define I64_PRINTF_ARG(a) (a)
 +#define I64_SCANF_ARG(a) (a)
 +#define I64_LITERAL(n) (n ## i64)
  #else
  #define U64_PRINTF_ARG(a) ((long long unsigned int)(a))
  #define U64_SCANF_ARG(a) ((long long unsigned int*)(a))
  #define U64_LITERAL(n) (n ## llu)
 +#define I64_PRINTF_ARG(a) ((long long signed int)(a))
 +#define I64_SCANF_ARG(a) ((long long signed int*)(a))
 +#define I64_LITERAL(n) (n ## ll)
  #endif
  
  #if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
  /** The formatting string used to put a uint64_t value in a printf() or
   * scanf() function.  See also U64_PRINTF_ARG and U64_SCANF_ARG. */
  #define U64_FORMAT "%I64u"
 +#define I64_FORMAT "%I64d"
  #else
  #define U64_FORMAT "%llu"
 +#define I64_FORMAT "%lld"
  #endif
  
  /** Represents an mmaped file. Allocated via tor_mmap_file; freed with
@@@ -259,10 -235,6 +259,10 @@@ int tor_snprintf(char *str, size_t size
  int tor_vsnprintf(char *str, size_t size, const char *format, va_list args)
    ATTR_NONNULL((1,3));
  
 +int tor_asprintf(char **strp, const char *fmt, ...)
 +  CHECK_PRINTF(2,3);
 +int tor_vasprintf(char **strp, const char *fmt, va_list args);
 +
  const void *tor_memmem(const void *haystack, size_t hlen, const void *needle,
                         size_t nlen)  ATTR_PURE ATTR_NONNULL((1,3));
  static const void *tor_memstr(const void *haystack, size_t hlen,
@@@ -295,13 -267,6 +295,13 @@@ extern const char TOR_TOLOWER_TABLE[]
  #define TOR_TOLOWER(c) (TOR_TOLOWER_TABLE[(uint8_t)c])
  #define TOR_TOUPPER(c) (TOR_TOUPPER_TABLE[(uint8_t)c])
  
 +char *tor_strtok_r_impl(char *str, const char *sep, char **lasts);
 +#ifdef HAVE_STRTOK_R
 +#define tor_strtok_r(str, sep, lasts) strtok_r(str, sep, lasts)
 +#else
 +#define tor_strtok_r(str, sep, lasts) tor_strtok_r_impl(str, sep, lasts)
 +#endif
 +
  #ifdef MS_WINDOWS
  #define _SHORT_FILE_ (tor_fix_source_file(__FILE__))
  const char *tor_fix_source_file(const char *fname);
@@@ -480,11 -445,6 +480,11 @@@ typedef enum 
    SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED = 0x08,
  } socks5_reply_status_t;
  
 +/* ===== Insecure rng */
 +void tor_init_weak_random(unsigned seed);
 +long tor_weak_random(void);
 +#define TOR_RAND_MAX (RAND_MAX)
 +
  /* ===== OS compatibility */
  const char *get_uname(void);
  
@@@ -542,8 -502,6 +542,8 @@@ typedef struct tor_mutex_t 
  #endif
  } tor_mutex_t;
  
 +int tor_mlockall(void);
 +
  #ifdef TOR_IS_MULTITHREADED
  tor_mutex_t *tor_mutex_new(void);
  void tor_mutex_init(tor_mutex_t *m);
@@@ -578,19 -536,6 +578,19 @@@ void tor_cond_signal_all(tor_cond_t *co
  #endif
  #endif
  
 +/** Macros for MIN/MAX.  Never use these when the arguments could have
 + * side-effects.
 + * {With GCC extensions we could probably define a safer MIN/MAX.  But
 + * depending on that safety would be dangerous, since not every platform
 + * has it.}
 + **/
 +#ifndef MAX
 +#define MAX(a,b) ( ((a)<(b)) ? (b) : (a) )
 +#endif
 +#ifndef MIN
 +#define MIN(a,b) ( ((a)>(b)) ? (b) : (a) )
 +#endif
 +
  /* Platform-specific helpers. */
  #ifdef MS_WINDOWS
  char *format_win32_error(DWORD err);
diff --combined src/common/container.c
index 72f3470,977d604..979e097
--- a/src/common/container.c
+++ b/src/common/container.c
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003-2004, Roger Dingledine
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -13,7 -13,7 +13,7 @@@
  
  #include "compat.h"
  #include "util.h"
 -#include "log.h"
 +#include "torlog.h"
  #include "container.h"
  #include "crypto.h"
  
@@@ -44,8 -44,7 +44,8 @@@ smartlist_create(void
  void
  smartlist_free(smartlist_t *sl)
  {
 -  tor_assert(sl != NULL);
 +  if (!sl)
 +    return;
    tor_free(sl->list);
    tor_free(sl);
  }
@@@ -460,42 -459,6 +460,42 @@@ smartlist_sort(smartlist_t *sl, int (*c
          (int (*)(const void *,const void*))compare);
  }
  
 +/** Given a smartlist <b>sl</b> sorted with the function <b>compare</b>,
 + * return the most frequent member in the list.  Break ties in favor of
 + * later elements.  If the list is empty, return NULL.
 + */
 +void *
 +smartlist_get_most_frequent(const smartlist_t *sl,
 +                            int (*compare)(const void **a, const void **b))
 +{
 +  const void *most_frequent = NULL;
 +  int most_frequent_count = 0;
 +
 +  const void *cur = NULL;
 +  int i, count=0;
 +
 +  if (!sl->num_used)
 +    return NULL;
 +  for (i = 0; i < sl->num_used; ++i) {
 +    const void *item = sl->list[i];
 +    if (cur && 0 == compare(&cur, &item)) {
 +      ++count;
 +    } else {
 +      if (cur && count >= most_frequent_count) {
 +        most_frequent = cur;
 +        most_frequent_count = count;
 +      }
 +      cur = item;
 +      count = 1;
 +    }
 +  }
 +  if (cur && count >= most_frequent_count) {
 +    most_frequent = cur;
 +    most_frequent_count = count;
 +  }
 +  return (void*)most_frequent;
 +}
 +
  /** Given a sorted smartlist <b>sl</b> and the comparison function used to
   * sort it, remove all duplicate members.  If free_fn is provided, calls
   * free_fn on each duplicate.  Otherwise, just removes them.  Preserves order.
@@@ -587,13 -550,6 +587,13 @@@ smartlist_sort_strings(smartlist_t *sl
    smartlist_sort(sl, _compare_string_ptrs);
  }
  
 +/** Return the most frequent string in the sorted list <b>sl</b> */
 +char *
 +smartlist_get_most_frequent_string(smartlist_t *sl)
 +{
 +  return smartlist_get_most_frequent(sl, _compare_string_ptrs);
 +}
 +
  /** Remove duplicate strings from a sorted list, and free them with tor_free().
   */
  void
@@@ -605,38 -561,6 +605,38 @@@ smartlist_uniq_strings(smartlist_t *sl
  /* Heap-based priority queue implementation for O(lg N) insert and remove.
   * Recall that the heap property is that, for every index I, h[I] <
   * H[LEFT_CHILD[I]] and h[I] < H[RIGHT_CHILD[I]].
 + *
 + * For us to remove items other than the topmost item, each item must store
 + * its own index within the heap.  When calling the pqueue functions, tell
 + * them about the offset of the field that stores the index within the item.
 + *
 + * Example:
 + *
 + *   typedef struct timer_t {
 + *     struct timeval tv;
 + *     int heap_index;
 + *   } timer_t;
 + *
 + *   static int compare(const void *p1, const void *p2) {
 + *     const timer_t *t1 = p1, *t2 = p2;
 + *     if (t1->tv.tv_sec < t2->tv.tv_sec) {
 + *        return -1;
 + *     } else if (t1->tv.tv_sec > t2->tv.tv_sec) {
 + *        return 1;
 + *     } else {
 + *        return t1->tv.tv_usec - t2->tv_usec;
 + *     }
 + *   }
 + *
 + *   void timer_heap_insert(smartlist_t *heap, timer_t *timer) {
 + *      smartlist_pqueue_add(heap, compare, STRUCT_OFFSET(timer_t, heap_index),
 + *         timer);
 + *   }
 + *
 + *   void timer_heap_pop(smartlist_t *heap) {
 + *      return smartlist_pqueue_pop(heap, compare,
 + *         STRUCT_OFFSET(timer_t, heap_index));
 + *   }
   */
  
  /* For a 1-indexed array, we would use LEFT_CHILD[x] = 2*x and RIGHT_CHILD[x]
@@@ -648,22 -572,12 +648,22 @@@
  #define RIGHT_CHILD(i) ( 2*(i) + 2 )
  #define PARENT(i)      ( ((i)-1) / 2 )
  
 +#define IDXP(p) ((int*)STRUCT_VAR_P(p, idx_field_offset))
 +
 +#define UPDATE_IDX(i)  do {                            \
 +    void *updated = sl->list[i];                       \
 +    *IDXP(updated) = i;                                \
 +  } while (0)
 +
 +#define IDX_OF_ITEM(p) (*IDXP(p))
 +
  /** Helper. <b>sl</b> may have at most one violation of the heap property:
   * the item at <b>idx</b> may be greater than one or both of its children.
   * Restore the heap property. */
  static INLINE void
  smartlist_heapify(smartlist_t *sl,
                    int (*compare)(const void *a, const void *b),
 +                  int idx_field_offset,
                    int idx)
  {
    while (1) {
@@@ -686,28 -600,21 +686,28 @@@
        void *tmp = sl->list[idx];
        sl->list[idx] = sl->list[best_idx];
        sl->list[best_idx] = tmp;
 +      UPDATE_IDX(idx);
 +      UPDATE_IDX(best_idx);
  
        idx = best_idx;
      }
    }
  }
  
 -/** Insert <b>item</b> into the heap stored in <b>sl</b>, where order
 - * is determined by <b>compare</b>. */
 +/** Insert <b>item</b> into the heap stored in <b>sl</b>, where order is
 + * determined by <b>compare</b> and the offset of the item in the heap is
 + * stored in an int-typed field at position <b>idx_field_offset</b> within
 + * item.
 + */
  void
  smartlist_pqueue_add(smartlist_t *sl,
                       int (*compare)(const void *a, const void *b),
 +                     int idx_field_offset,
                       void *item)
  {
    int idx;
    smartlist_add(sl,item);
 +  UPDATE_IDX(sl->num_used-1);
  
    for (idx = sl->num_used - 1; idx; ) {
      int parent = PARENT(idx);
@@@ -715,8 -622,6 +715,8 @@@
        void *tmp = sl->list[parent];
        sl->list[parent] = sl->list[idx];
        sl->list[idx] = tmp;
 +      UPDATE_IDX(parent);
 +      UPDATE_IDX(idx);
        idx = parent;
      } else {
        return;
@@@ -725,63 -630,32 +725,63 @@@
  }
  
  /** Remove and return the top-priority item from the heap stored in <b>sl</b>,
 - * where order is determined by <b>compare</b>.  <b>sl</b> must not be
 - * empty. */
 + * where order is determined by <b>compare</b> and the item's position is
 + * stored at position <b>idx_field_offset</b> within the item.  <b>sl</b> must
 + * not be empty. */
  void *
  smartlist_pqueue_pop(smartlist_t *sl,
 -                     int (*compare)(const void *a, const void *b))
 +                     int (*compare)(const void *a, const void *b),
 +                     int idx_field_offset)
  {
    void *top;
    tor_assert(sl->num_used);
  
    top = sl->list[0];
 +  *IDXP(top)=-1;
    if (--sl->num_used) {
      sl->list[0] = sl->list[sl->num_used];
 -    smartlist_heapify(sl, compare, 0);
 +    UPDATE_IDX(0);
 +    smartlist_heapify(sl, compare, idx_field_offset, 0);
    }
    return top;
  }
  
 +/** Remove the item <b>item</b> from the heap stored in <b>sl</b>,
 + * where order is determined by <b>compare</b> and the item's position is
 + * stored at position <b>idx_field_offset</b> within the item.  <b>sl</b> must
 + * not be empty. */
 +void
 +smartlist_pqueue_remove(smartlist_t *sl,
 +                        int (*compare)(const void *a, const void *b),
 +                        int idx_field_offset,
 +                        void *item)
 +{
 +  int idx = IDX_OF_ITEM(item);
 +  tor_assert(idx >= 0);
 +  tor_assert(sl->list[idx] == item);
 +  --sl->num_used;
 +  *IDXP(item) = -1;
 +  if (idx == sl->num_used) {
 +    return;
 +  } else {
 +    sl->list[idx] = sl->list[sl->num_used];
 +    UPDATE_IDX(idx);
 +    smartlist_heapify(sl, compare, idx_field_offset, idx);
 +  }
 +}
 +
  /** Assert that the heap property is correctly maintained by the heap stored
   * in <b>sl</b>, where order is determined by <b>compare</b>. */
  void
  smartlist_pqueue_assert_ok(smartlist_t *sl,
 -                           int (*compare)(const void *a, const void *b))
 +                           int (*compare)(const void *a, const void *b),
 +                           int idx_field_offset)
  {
    int i;
 -  for (i = sl->num_used - 1; i > 0; --i) {
 -    tor_assert(compare(sl->list[PARENT(i)], sl->list[i]) <= 0);
 +  for (i = sl->num_used - 1; i >= 0; --i) {
 +    if (i>0)
 +      tor_assert(compare(sl->list[PARENT(i)], sl->list[i]) <= 0);
 +    tor_assert(IDX_OF_ITEM(sl->list[i]) == i);
    }
  }
  
@@@ -807,37 -681,6 +807,37 @@@ smartlist_uniq_digests(smartlist_t *sl
    smartlist_uniq(sl, _compare_digests, _tor_free);
  }
  
 +/** Helper: compare two DIGEST256_LEN digests. */
 +static int
 +_compare_digests256(const void **_a, const void **_b)
 +{
 +  return memcmp((const char*)*_a, (const char*)*_b, DIGEST256_LEN);
 +}
 +
 +/** Sort the list of DIGEST256_LEN-byte digests into ascending order. */
 +void
 +smartlist_sort_digests256(smartlist_t *sl)
 +{
 +  smartlist_sort(sl, _compare_digests256);
 +}
 +
 +/** Return the most frequent member of the sorted list of DIGEST256_LEN
 + * digests in <b>sl</b> */
 +char *
 +smartlist_get_most_frequent_digest256(smartlist_t *sl)
 +{
 +  return smartlist_get_most_frequent(sl, _compare_digests256);
 +}
 +
 +/** Remove duplicate 256-bit digests from a sorted list, and free them with
 + * tor_free().
 + */
 +void
 +smartlist_uniq_digests256(smartlist_t *sl)
 +{
 +  smartlist_uniq(sl, _compare_digests256, _tor_free);
 +}
 +
  /** Helper: Declare an entry type and a map type to implement a mapping using
   * ht.h.  The map type will be called <b>maptype</b>.  The key part of each
   * entry is declared using the C declaration <b>keydecl</b>.  All functions
@@@ -1270,9 -1113,6 +1270,9 @@@ voi
  strmap_free(strmap_t *map, void (*free_val)(void*))
  {
    strmap_entry_t **ent, **next, *this;
 +  if (!map)
 +    return;
 +
    for (ent = HT_START(strmap_impl, &map->head); ent != NULL; ent = next) {
      this = *ent;
      next = HT_NEXT_RMV(strmap_impl, &map->head, ent);
@@@ -1294,8 -1134,6 +1294,8 @@@ voi
  digestmap_free(digestmap_t *map, void (*free_val)(void*))
  {
    digestmap_entry_t **ent, **next, *this;
 +  if (!map)
 +    return;
    for (ent = HT_START(digestmap_impl, &map->head); ent != NULL; ent = next) {
      this = *ent;
      next = HT_NEXT_RMV(digestmap_impl, &map->head, ent);
@@@ -1382,7 -1220,6 +1382,7 @@@ IMPLEMENT_ORDER_FUNC(find_nth_int, int
  IMPLEMENT_ORDER_FUNC(find_nth_time, time_t)
  IMPLEMENT_ORDER_FUNC(find_nth_double, double)
  IMPLEMENT_ORDER_FUNC(find_nth_uint32, uint32_t)
 +IMPLEMENT_ORDER_FUNC(find_nth_int32, int32_t)
  IMPLEMENT_ORDER_FUNC(find_nth_long, long)
  
  /** Return a newly allocated digestset_t, optimized to hold a total of
@@@ -1411,8 -1248,6 +1411,8 @@@ digestset_new(int max_elements
  void
  digestset_free(digestset_t *set)
  {
 +  if (!set)
 +    return;
    bitarray_free(set->ba);
    tor_free(set);
  }
diff --combined src/common/container.h
index 3568de0,2b9f964..768ba89
--- a/src/common/container.h
+++ b/src/common/container.h
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003-2004, Roger Dingledine
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  #ifndef _TOR_CONTAINER_H
@@@ -93,22 -93,13 +93,22 @@@ void smartlist_del_keeporder(smartlist_
  void smartlist_insert(smartlist_t *sl, int idx, void *val);
  void smartlist_sort(smartlist_t *sl,
                      int (*compare)(const void **a, const void **b));
 +void *smartlist_get_most_frequent(const smartlist_t *sl,
 +                    int (*compare)(const void **a, const void **b));
  void smartlist_uniq(smartlist_t *sl,
                      int (*compare)(const void **a, const void **b),
                      void (*free_fn)(void *elt));
 +
  void smartlist_sort_strings(smartlist_t *sl);
  void smartlist_sort_digests(smartlist_t *sl);
 +void smartlist_sort_digests256(smartlist_t *sl);
 +
 +char *smartlist_get_most_frequent_string(smartlist_t *sl);
 +char *smartlist_get_most_frequent_digest256(smartlist_t *sl);
 +
  void smartlist_uniq_strings(smartlist_t *sl);
  void smartlist_uniq_digests(smartlist_t *sl);
 +void smartlist_uniq_digests256(smartlist_t *sl);
  void *smartlist_bsearch(smartlist_t *sl, const void *key,
                          int (*compare)(const void *key, const void **member))
    ATTR_PURE;
@@@ -118,18 -109,11 +118,18 @@@ int smartlist_bsearch_idx(const smartli
  
  void smartlist_pqueue_add(smartlist_t *sl,
                            int (*compare)(const void *a, const void *b),
 +                          int idx_field_offset,
                            void *item);
  void *smartlist_pqueue_pop(smartlist_t *sl,
 -                           int (*compare)(const void *a, const void *b));
 +                           int (*compare)(const void *a, const void *b),
 +                           int idx_field_offset);
 +void smartlist_pqueue_remove(smartlist_t *sl,
 +                             int (*compare)(const void *a, const void *b),
 +                             int idx_field_offset,
 +                             void *item);
  void smartlist_pqueue_assert_ok(smartlist_t *sl,
 -                                int (*compare)(const void *a, const void *b));
 +                                int (*compare)(const void *a, const void *b),
 +                                int idx_field_offset);
  
  #define SPLIT_SKIP_SPACE   0x01
  #define SPLIT_IGNORE_BLANK 0x02
@@@ -643,7 -627,6 +643,7 @@@ void digestset_free(digestset_t* set)
  int find_nth_int(int *array, int n_elements, int nth);
  time_t find_nth_time(time_t *array, int n_elements, int nth);
  double find_nth_double(double *array, int n_elements, int nth);
 +int32_t find_nth_int32(int32_t *array, int n_elements, int nth);
  uint32_t find_nth_uint32(uint32_t *array, int n_elements, int nth);
  long find_nth_long(long *array, int n_elements, int nth);
  static INLINE int
@@@ -666,11 -649,6 +666,11 @@@ median_uint32(uint32_t *array, int n_el
  {
    return find_nth_uint32(array, n_elements, (n_elements-1)/2);
  }
 +static INLINE int32_t
 +median_int32(int32_t *array, int n_elements)
 +{
 +  return find_nth_int32(array, n_elements, (n_elements-1)/2);
 +}
  static INLINE long
  median_long(long *array, int n_elements)
  {
diff --combined src/common/crypto.c
index e030c56,3343980..7e4ec32
--- a/src/common/crypto.c
+++ b/src/common/crypto.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001, Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -27,7 -27,6 +27,7 @@@
  #include <openssl/rsa.h>
  #include <openssl/pem.h>
  #include <openssl/evp.h>
 +#include <openssl/engine.h>
  #include <openssl/rand.h>
  #include <openssl/opensslv.h>
  #include <openssl/bn.h>
@@@ -50,9 -49,9 +50,9 @@@
  
  #define CRYPTO_PRIVATE
  #include "crypto.h"
 -#include "log.h"
 +#include "../common/torlog.h"
  #include "aes.h"
 -#include "util.h"
 +#include "../common/util.h"
  #include "container.h"
  #include "compat.h"
  
@@@ -62,33 -61,6 +62,33 @@@
  
  #include <openssl/engine.h>
  
 +#ifdef ANDROID
 +/* Android's OpenSSL seems to have removed all of its Engine support. */
 +#define DISABLE_ENGINES
 +#endif
 +
 +#if OPENSSL_VERSION_NUMBER < 0x00908000l
 +/* On OpenSSL versions before 0.9.8, there is no working SHA256
 + * implementation, so we use Tom St Denis's nice speedy one, slightly adapted
 + * to our needs */
 +#define SHA256_CTX sha256_state
 +#define SHA256_Init sha256_init
 +#define SHA256_Update sha256_process
 +#define LTC_ARGCHK(x) tor_assert(x)
 +#include "sha256.c"
 +#define SHA256_Final(a,b) sha256_done(b,a)
 +
 +static unsigned char *
 +SHA256(const unsigned char *m, size_t len, unsigned char *d)
 +{
 +  SHA256_CTX ctx;
 +  SHA256_Init(&ctx);
 +  SHA256_Update(&ctx, m, len);
 +  SHA256_Final(d, &ctx);
 +  return d;
 +}
 +#endif
 +
  /** Macro: is k a valid RSA public or private key? */
  #define PUBLIC_KEY_OK(k) ((k) && (k)->key && (k)->key->n)
  /** Macro: is k a valid RSA private key? */
@@@ -122,7 -94,7 +122,7 @@@ struct crypto_dh_env_t 
  };
  
  static int setup_openssl_threading(void);
 -static int tor_check_dh_key(BIGNUM *bn);
 +static int tor_check_dh_key(int severity, BIGNUM *bn);
  
  /** Return the number of bytes added by padding method <b>padding</b>.
   */
@@@ -179,7 -151,6 +179,7 @@@ crypto_log_errors(int severity, const c
    }
  }
  
 +#ifndef DISABLE_ENGINES
  /** Log any OpenSSL engines we're using at NOTICE. */
  static void
  log_engine(const char *fn, ENGINE *e)
@@@ -194,82 -165,37 +194,82 @@@
      log(LOG_INFO, LD_CRYPTO, "Using default implementation for %s", fn);
    }
  }
 +#endif
 +
 +#ifndef DISABLE_ENGINES
 +/** Try to load an engine in a shared library via fully qualified path.
 + */
 +static ENGINE *
 +try_load_engine(const char *path, const char *engine)
 +{
 +  ENGINE *e = ENGINE_by_id("dynamic");
 +  if (e) {
 +    if (!ENGINE_ctrl_cmd_string(e, "ID", engine, 0) ||
 +        !ENGINE_ctrl_cmd_string(e, "DIR_LOAD", "2", 0) ||
 +        !ENGINE_ctrl_cmd_string(e, "DIR_ADD", path, 0) ||
 +        !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) {
 +      ENGINE_free(e);
 +      e = NULL;
 +    }
 +  }
 +  return e;
 +}
 +#endif
  
  /** Initialize the crypto library.  Return 0 on success, -1 on failure.
   */
  int
 -crypto_global_init(int useAccel)
 +crypto_global_init(int useAccel, const char *accelName, const char *accelDir)
  {
    if (!_crypto_global_initialized) {
      ERR_load_crypto_strings();
      OpenSSL_add_all_algorithms();
      _crypto_global_initialized = 1;
      setup_openssl_threading();
 -    /* XXX the below is a bug, since we can't know if we're supposed
 -     * to be using hardware acceleration or not. we should arrange
 -     * for this function to be called before init_keys. But make it
 -     * not complain loudly, at least until we make acceleration work. */
 -    if (useAccel < 0) {
 -      log_info(LD_CRYPTO, "Initializing OpenSSL via tor_tls_init().");
 -    }
      if (useAccel > 0) {
 +#ifdef DISABLE_ENGINES
 +      (void)accelName;
 +      (void)accelDir;
 +      log_warn(LD_CRYPTO, "No OpenSSL hardware acceleration support enabled.");
 +#else
 +      ENGINE *e = NULL;
 +
        log_info(LD_CRYPTO, "Initializing OpenSSL engine support.");
        ENGINE_load_builtin_engines();
 -      if (!ENGINE_register_all_complete())
 -        return -1;
 -
 -      /* XXXX make sure this isn't leaking. */
 +      ENGINE_register_all_complete();
 +
 +      if (accelName) {
 +        if (accelDir) {
 +          log_info(LD_CRYPTO, "Trying to load dynamic OpenSSL engine \"%s\""
 +                   " via path \"%s\".", accelName, accelDir);
 +          e = try_load_engine(accelName, accelDir);
 +        } else {
 +          log_info(LD_CRYPTO, "Initializing dynamic OpenSSL engine \"%s\""
 +                   " acceleration support.", accelName);
 +          e = ENGINE_by_id(accelName);
 +        }
 +        if (!e) {
 +          log_warn(LD_CRYPTO, "Unable to load dynamic OpenSSL engine \"%s\".",
 +                   accelName);
 +        } else {
 +          log_info(LD_CRYPTO, "Loaded dynamic OpenSSL engine \"%s\".",
 +                   accelName);
 +        }
 +      }
 +      if (e) {
 +        log_info(LD_CRYPTO, "Loaded OpenSSL hardware acceleration engine,"
 +                 " setting default ciphers.");
 +        ENGINE_set_default(e, ENGINE_METHOD_ALL);
 +      }
        log_engine("RSA", ENGINE_get_default_RSA());
        log_engine("DH", ENGINE_get_default_DH());
        log_engine("RAND", ENGINE_get_default_RAND());
        log_engine("SHA1", ENGINE_get_digest_engine(NID_sha1));
        log_engine("3DES", ENGINE_get_cipher_engine(NID_des_ede3_ecb));
        log_engine("AES", ENGINE_get_cipher_engine(NID_aes_128_ecb));
 +#endif
 +    } else {
 +      log_info(LD_CRYPTO, "NOT using OpenSSL engine support.");
      }
      return crypto_seed_rng(1);
    }
@@@ -291,11 -217,7 +291,11 @@@ crypto_global_cleanup(void
    EVP_cleanup();
    ERR_remove_state(0);
    ERR_free_strings();
 +
 +#ifndef DISABLE_ENGINES
    ENGINE_cleanup();
 +#endif
 +
    CONF_modules_unload(1);
    CRYPTO_cleanup_all_ex_data();
  #ifdef TOR_IS_MULTITHREADED
@@@ -337,8 -259,7 +337,8 @@@ _crypto_new_pk_env_evp_pkey(EVP_PKEY *p
    return _crypto_new_pk_env_rsa(rsa);
  }
  
 -/** Helper, used by tor-checkkey.c.  Return the RSA from a crypto_pk_env_t. */
 +/** Helper, used by tor-checkkey.c and tor-gencert.c.  Return the RSA from a
 + * crypto_pk_env_t. */
  RSA *
  _crypto_pk_env_get_rsa(crypto_pk_env_t *env)
  {
@@@ -400,12 -321,10 +400,12 @@@ crypto_new_pk_env(void
  void
  crypto_free_pk_env(crypto_pk_env_t *env)
  {
 -  tor_assert(env);
 +  if (!env)
 +    return;
  
    if (--env->refs > 0)
      return;
 +  tor_assert(env->refs == 0);
  
    if (env->key)
      RSA_free(env->key);
@@@ -428,7 -347,10 +428,7 @@@ crypto_create_init_cipher(const char *k
      return NULL;
    }
  
 -  if (crypto_cipher_set_key(crypto, key)) {
 -    crypto_log_errors(LOG_WARN, "setting symmetric key");
 -    goto error;
 -  }
 +  crypto_cipher_set_key(crypto, key);
  
    if (encrypt_mode)
      r = crypto_cipher_encrypt_init_cipher(crypto);
@@@ -462,8 -384,7 +462,8 @@@ crypto_new_cipher_env(void
  void
  crypto_free_cipher_env(crypto_cipher_env_t *env)
  {
 -  tor_assert(env);
 +  if (!env)
 +    return;
  
    tor_assert(env->cipher);
    aes_free_cipher(env->cipher);
@@@ -473,11 -394,11 +473,11 @@@
  
  /* public key crypto */
  
 -/** Generate a new public/private keypair in <b>env</b>.  Return 0 on
 - * success, -1 on failure.
 +/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>.
 + * Return 0 on success, -1 on failure.
   */
  int
 -crypto_pk_generate_key(crypto_pk_env_t *env)
 +crypto_pk_generate_key_with_bits(crypto_pk_env_t *env, int bits)
  {
    tor_assert(env);
  
@@@ -485,7 -406,7 +485,7 @@@
      RSA_free(env->key);
  #if OPENSSL_VERSION_NUMBER < 0x00908000l
    /* In OpenSSL 0.9.7, RSA_generate_key is all we have. */
 -  env->key = RSA_generate_key(PK_BYTES*8,65537, NULL, NULL);
 +  env->key = RSA_generate_key(bits, 65537, NULL, NULL);
  #else
    /* In OpenSSL 0.9.8, RSA_generate_key is deprecated. */
    {
@@@ -498,7 -419,7 +498,7 @@@
      r = RSA_new();
      if (!r)
        goto done;
 -    if (RSA_generate_key_ex(r, PK_BYTES*8, e, NULL) == -1)
 +    if (RSA_generate_key_ex(r, bits, e, NULL) == -1)
        goto done;
  
      env->key = r;
@@@ -780,25 -701,14 +780,25 @@@ crypto_pk_env_t 
  crypto_pk_copy_full(crypto_pk_env_t *env)
  {
    RSA *new_key;
 +  int privatekey = 0;
    tor_assert(env);
    tor_assert(env->key);
  
    if (PRIVATE_KEY_OK(env)) {
      new_key = RSAPrivateKey_dup(env->key);
 +    privatekey = 1;
    } else {
      new_key = RSAPublicKey_dup(env->key);
    }
 +  if (!new_key) {
 +    log_err(LD_CRYPTO, "Unable to duplicate a %s key: openssl failed.",
 +            privatekey?"private":"public");
 +    crypto_log_errors(LOG_ERR,
 +                      privatekey ? "Duplicating a private key" :
 +                      "Duplicating a public key");
 +    tor_fragile_assert();
 +    return NULL;
 +  }
  
    return _crypto_new_pk_env_rsa(new_key);
  }
@@@ -1268,14 -1178,19 +1268,14 @@@ crypto_cipher_generate_key(crypto_ciphe
  
  /** Set the symmetric key for the cipher in <b>env</b> to the first
   * CIPHER_KEY_LEN bytes of <b>key</b>. Does not initialize the cipher.
 - * Return 0 on success, -1 on failure.
   */
 -int
 +void
  crypto_cipher_set_key(crypto_cipher_env_t *env, const char *key)
  {
    tor_assert(env);
    tor_assert(key);
  
 -  if (!env->key)
 -    return -1;
 -
    memcpy(env->key, key, CIPHER_KEY_LEN);
 -  return 0;
  }
  
  /** Generate an initialization vector for our AES-CTR cipher; store it
@@@ -1452,69 -1367,9 +1452,69 @@@ crypto_digest(char *digest, const char 
    return (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL);
  }
  
 +int
 +crypto_digest256(char *digest, const char *m, size_t len,
 +                 digest_algorithm_t algorithm)
 +{
 +  tor_assert(m);
 +  tor_assert(digest);
 +  tor_assert(algorithm == DIGEST_SHA256);
 +  return (SHA256((const unsigned char*)m,len,(unsigned char*)digest) == NULL);
 +}
 +
 +/** Set the digests_t in <b>ds_out</b> to contain every digest on the
 + * <b>len</b> bytes in <b>m</b> that we know how to compute.  Return 0 on
 + * success, -1 on failure. */
 +int
 +crypto_digest_all(digests_t *ds_out, const char *m, size_t len)
 +{
 +  digest_algorithm_t i;
 +  tor_assert(ds_out);
 +  memset(ds_out, 0, sizeof(*ds_out));
 +  if (crypto_digest(ds_out->d[DIGEST_SHA1], m, len) < 0)
 +    return -1;
 +  for (i = DIGEST_SHA256; i < N_DIGEST_ALGORITHMS; ++i) {
 +    if (crypto_digest256(ds_out->d[i], m, len, i) < 0)
 +      return -1;
 +  }
 +  return 0;
 +}
 +
 +/** Return the name of an algorithm, as used in directory documents. */
 +const char *
 +crypto_digest_algorithm_get_name(digest_algorithm_t alg)
 +{
 +  switch (alg) {
 +    case DIGEST_SHA1:
 +      return "sha1";
 +    case DIGEST_SHA256:
 +      return "sha256";
 +    default:
 +      tor_fragile_assert();
 +      return "??unknown_digest??";
 +  }
 +}
 +
 +/** Given the name of a digest algorithm, return its integer value, or -1 if
 + * the name is not recognized. */
 +int
 +crypto_digest_algorithm_parse_name(const char *name)
 +{
 +  if (!strcmp(name, "sha1"))
 +    return DIGEST_SHA1;
 +  else if (!strcmp(name, "sha256"))
 +    return DIGEST_SHA256;
 +  else
 +    return -1;
 +}
 +
  /** Intermediate information about the digest of a stream of data. */
  struct crypto_digest_env_t {
 -  SHA_CTX d;
 +  union {
 +    SHA_CTX sha1;
 +    SHA256_CTX sha2;
 +  } d;
 +  digest_algorithm_t algorithm : 8;
  };
  
  /** Allocate and return a new digest object.
@@@ -1524,19 -1379,7 +1524,19 @@@ crypto_new_digest_env(void
  {
    crypto_digest_env_t *r;
    r = tor_malloc(sizeof(crypto_digest_env_t));
 -  SHA1_Init(&r->d);
 +  SHA1_Init(&r->d.sha1);
 +  r->algorithm = DIGEST_SHA1;
 +  return r;
 +}
 +
 +crypto_digest_env_t *
 +crypto_new_digest256_env(digest_algorithm_t algorithm)
 +{
 +  crypto_digest_env_t *r;
 +  tor_assert(algorithm == DIGEST_SHA256);
 +  r = tor_malloc(sizeof(crypto_digest_env_t));
 +  SHA256_Init(&r->d.sha2);
 +  r->algorithm = algorithm;
    return r;
  }
  
@@@ -1545,8 -1388,6 +1545,8 @@@
  void
  crypto_free_digest_env(crypto_digest_env_t *digest)
  {
 +  if (!digest)
 +    return;
    memset(digest, 0, sizeof(crypto_digest_env_t));
    tor_free(digest);
  }
@@@ -1559,51 -1400,30 +1559,51 @@@ crypto_digest_add_bytes(crypto_digest_e
  {
    tor_assert(digest);
    tor_assert(data);
 -  /* Using the SHA1_*() calls directly means we don't support doing
 -   * SHA1 in hardware. But so far the delay of getting the question
 +  /* Using the SHA*_*() calls directly means we don't support doing
 +   * SHA in hardware. But so far the delay of getting the question
     * to the hardware, and hearing the answer, is likely higher than
     * just doing it ourselves. Hashes are fast.
     */
 -  SHA1_Update(&digest->d, (void*)data, len);
 +  switch (digest->algorithm) {
 +    case DIGEST_SHA1:
 +      SHA1_Update(&digest->d.sha1, (void*)data, len);
 +      break;
 +    case DIGEST_SHA256:
 +      SHA256_Update(&digest->d.sha2, (void*)data, len);
 +      break;
 +    default:
 +      tor_fragile_assert();
 +      break;
 +  }
  }
  
  /** Compute the hash of the data that has been passed to the digest
   * object; write the first out_len bytes of the result to <b>out</b>.
 - * <b>out_len</b> must be \<= DIGEST_LEN.
 + * <b>out_len</b> must be \<= DIGEST256_LEN.
   */
  void
  crypto_digest_get_digest(crypto_digest_env_t *digest,
                           char *out, size_t out_len)
  {
 -  unsigned char r[DIGEST_LEN];
 -  SHA_CTX tmpctx;
 +  unsigned char r[DIGEST256_LEN];
 +  crypto_digest_env_t tmpenv;
    tor_assert(digest);
    tor_assert(out);
 -  tor_assert(out_len <= DIGEST_LEN);
 -  /* memcpy into a temporary ctx, since SHA1_Final clears the context */
 -  memcpy(&tmpctx, &digest->d, sizeof(SHA_CTX));
 -  SHA1_Final(r, &tmpctx);
 +  /* memcpy into a temporary ctx, since SHA*_Final clears the context */
 +  memcpy(&tmpenv, digest, sizeof(crypto_digest_env_t));
 +  switch (digest->algorithm) {
 +    case DIGEST_SHA1:
 +      tor_assert(out_len <= DIGEST_LEN);
 +      SHA1_Final(r, &tmpenv.d.sha1);
 +      break;
 +    case DIGEST_SHA256:
 +      tor_assert(out_len <= DIGEST256_LEN);
 +      SHA256_Final(r, &tmpenv.d.sha2);
 +      break;
 +    default:
 +      tor_fragile_assert();
 +      break;
 +  }
    memcpy(out, r, out_len);
    memset(r, 0, sizeof(r));
  }
@@@ -1739,7 -1559,7 +1739,7 @@@ crypto_dh_generate_public(crypto_dh_env
      crypto_log_errors(LOG_WARN, "generating DH key");
      return -1;
    }
 -  if (tor_check_dh_key(dh->dh->pub_key)<0) {
 +  if (tor_check_dh_key(LOG_WARN, dh->dh->pub_key)<0) {
      log_warn(LD_CRYPTO, "Weird! Our own DH key was invalid.  I guess once-in-"
               "the-universe chances really do happen.  Trying again.");
      /* Free and clear the keys, so OpenSSL will actually try again. */
@@@ -1786,7 -1606,7 +1786,7 @@@ crypto_dh_get_public(crypto_dh_env_t *d
   * See http://www.cl.cam.ac.uk/ftp/users/rja14/psandqs.ps.gz for some tips.
   */
  static int
 -tor_check_dh_key(BIGNUM *bn)
 +tor_check_dh_key(int severity, BIGNUM *bn)
  {
    BIGNUM *x;
    char *s;
@@@ -1797,13 -1617,13 +1797,13 @@@
      init_dh_param();
    BN_set_word(x, 1);
    if (BN_cmp(bn,x)<=0) {
 -    log_warn(LD_CRYPTO, "DH key must be at least 2.");
 +    log_fn(severity, LD_CRYPTO, "DH key must be at least 2.");
      goto err;
    }
    BN_copy(x,dh_param_p);
    BN_sub_word(x, 1);
    if (BN_cmp(bn,x)>=0) {
 -    log_warn(LD_CRYPTO, "DH key must be at most p-2.");
 +    log_fn(severity, LD_CRYPTO, "DH key must be at most p-2.");
      goto err;
    }
    BN_free(x);
@@@ -1811,7 -1631,7 +1811,7 @@@
   err:
    BN_free(x);
    s = BN_bn2hex(bn);
 -  log_warn(LD_CRYPTO, "Rejecting insecure DH key [%s]", s);
 +  log_fn(severity, LD_CRYPTO, "Rejecting insecure DH key [%s]", s);
    OPENSSL_free(s);
    return -1;
  }
@@@ -1829,7 -1649,7 +1829,7 @@@
   * where || is concatenation.)
   */
  ssize_t
 -crypto_dh_compute_secret(crypto_dh_env_t *dh,
 +crypto_dh_compute_secret(int severity, crypto_dh_env_t *dh,
                           const char *pubkey, size_t pubkey_len,
                           char *secret_out, size_t secret_bytes_out)
  {
@@@ -1844,9 -1664,9 +1844,9 @@@
    if (!(pubkey_bn = BN_bin2bn((const unsigned char*)pubkey,
                                (int)pubkey_len, NULL)))
      goto error;
 -  if (tor_check_dh_key(pubkey_bn)<0) {
 +  if (tor_check_dh_key(severity, pubkey_bn)<0) {
      /* Check for invalid public keys. */
 -    log_warn(LD_CRYPTO,"Rejected invalid g^x");
 +    log_fn(severity, LD_CRYPTO,"Rejected invalid g^x");
      goto error;
    }
    secret_tmp = tor_malloc(crypto_dh_get_bytes(dh));
@@@ -1918,8 -1738,7 +1918,8 @@@ crypto_expand_key_material(const char *
  void
  crypto_dh_free(crypto_dh_env_t *dh)
  {
 -  tor_assert(dh);
 +  if (!dh)
 +    return;
    tor_assert(dh->dh);
    DH_free(dh->dh);
    tor_free(dh);
@@@ -1944,14 -1763,6 +1944,14 @@@
      OPENSSL_VERSION_NUMBER <= 0x00907fffl) ||   \
     (OPENSSL_VERSION_NUMBER >= 0x0090803fl))
  
 +static void
 +seed_weak_rng(void)
 +{
 +  unsigned seed;
 +  crypto_rand((void*)&seed, sizeof(seed));
 +  tor_init_weak_random(seed);
 +}
 +
  /** Seed OpenSSL's random number generator with bytes from the operating
   * system.  <b>startup</b> should be true iff we have just started Tor and
   * have not yet allocated a bunch of fds.  Return 0 on success, -1 on failure.
@@@ -2002,7 -1813,6 +2002,7 @@@ crypto_seed_rng(int startup
    }
    RAND_seed(buf, sizeof(buf));
    memset(buf, 0, sizeof(buf));
 +  seed_weak_rng();
    return 0;
  #else
    for (i = 0; filenames[i]; ++i) {
@@@ -2019,7 -1829,6 +2019,7 @@@
      }
      RAND_seed(buf, (int)sizeof(buf));
      memset(buf, 0, sizeof(buf));
 +    seed_weak_rng();
      return 0;
    }
  
@@@ -2087,26 -1896,6 +2087,26 @@@ crypto_rand_uint64(uint64_t max
    }
  }
  
 +/** Return a pseudorandom double d, chosen uniformly from the range
 + * 0.0 <= d < 1.0.
 + */
 +double
 +crypto_rand_double(void)
 +{
 +  /* We just use an unsigned int here; we don't really care about getting
 +   * more than 32 bits of resolution */
 +  unsigned int uint;
 +  crypto_rand((char*)&uint, sizeof(uint));
 +#if SIZEOF_INT == 4
 +#define UINT_MAX_AS_DOUBLE 4294967296.0
 +#elif SIZEOF_INT == 8
 +#define UINT_MAX_AS_DOUBLE 1.8446744073709552e+19
 +#else
 +#error SIZEOF_INT is neither 4 nor 8
 +#endif
 +  return ((double)uint) / UINT_MAX_AS_DOUBLE;
 +}
 +
  /** Generate and return a new random hostname starting with <b>prefix</b>,
   * ending with <b>suffix</b>, and containing no less than
   * <b>min_rand_len</b> and no more than <b>max_rand_len</b> random base32
@@@ -2367,44 -2156,6 +2367,44 @@@ digest_from_base64(char *digest, const 
  #endif
  }
  
 +/** Base-64 encode DIGEST256_LINE bytes from <b>digest</b>, remove the
 + * trailing = and newline characters, and store the nul-terminated result in
 + * the first BASE64_DIGEST256_LEN+1 bytes of <b>d64</b>.  */
 +int
 +digest256_to_base64(char *d64, const char *digest)
 +{
 +  char buf[256];
 +  base64_encode(buf, sizeof(buf), digest, DIGEST256_LEN);
 +  buf[BASE64_DIGEST256_LEN] = '\0';
 +  memcpy(d64, buf, BASE64_DIGEST256_LEN+1);
 +  return 0;
 +}
 +
 +/** Given a base-64 encoded, nul-terminated digest in <b>d64</b> (without
 + * trailing newline or = characters), decode it and store the result in the
 + * first DIGEST256_LEN bytes at <b>digest</b>. */
 +int
 +digest256_from_base64(char *digest, const char *d64)
 +{
 +#ifdef USE_OPENSSL_BASE64
 +  char buf_in[BASE64_DIGEST256_LEN+3];
 +  char buf[256];
 +  if (strlen(d64) != BASE64_DIGEST256_LEN)
 +    return -1;
 +  memcpy(buf_in, d64, BASE64_DIGEST256_LEN);
 +  memcpy(buf_in+BASE64_DIGEST256_LEN, "=\n\0", 3);
 +  if (base64_decode(buf, sizeof(buf), buf_in, strlen(buf_in)) != DIGEST256_LEN)
 +    return -1;
 +  memcpy(digest, buf, DIGEST256_LEN);
 +  return 0;
 +#else
 +  if (base64_decode(digest, DIGEST256_LEN, d64, strlen(d64)) == DIGEST256_LEN)
 +    return 0;
 +  else
 +    return -1;
 +#endif
 +}
 +
  /** Implements base32 encoding as in rfc3548.  Limitation: Requires
   * that srclen*8 is a multiple of 5.
   */
diff --combined src/common/crypto.h
index a30e5bc,4fb06be..0801728
--- a/src/common/crypto.h
+++ b/src/common/crypto.h
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001, Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -18,9 -18,6 +18,9 @@@
  
  /** Length of the output of our message digest. */
  #define DIGEST_LEN 20
 +/** Length of the output of our second (improved) message digests.  (For now
 + * this is just sha256, but any it can be any other 256-byte digest). */
 +#define DIGEST256_LEN 32
  /** Length of our symmetric cipher's keys. */
  #define CIPHER_KEY_LEN 16
  /** Length of our symmetric cipher's IV. */
@@@ -30,12 -27,9 +30,12 @@@
  /** Length of our DH keys. */
  #define DH_BYTES (1024/8)
  
 -/** Length of a message digest when encoded in base64 with trailing = signs
 - * removed. */
 +/** Length of a sha1 message digest when encoded in base64 with trailing =
 + * signs removed. */
  #define BASE64_DIGEST_LEN 27
 +/** Length of a sha256 message digest when encoded in base64 with trailing =
 + * signs removed. */
 +#define BASE64_DIGEST256_LEN 43
  
  /** Constants used to indicate no padding for public-key encryption */
  #define PK_NO_PADDING         60000
@@@ -54,26 -48,6 +54,26 @@@
  #define FINGERPRINT_LEN 49
  /** Length of hex encoding of SHA1 digest, not including final NUL. */
  #define HEX_DIGEST_LEN 40
 +/** Length of hex encoding of SHA256 digest, not including final NUL. */
 +#define HEX_DIGEST256_LEN 64
 +
 +typedef enum {
 +  DIGEST_SHA1 = 0,
 +  DIGEST_SHA256 = 1,
 +} digest_algorithm_t;
 +#define  N_DIGEST_ALGORITHMS (DIGEST_SHA256+1)
 +
 +/** A set of all the digests we know how to compute, taken on a single
 + * string.  Any digests that are shorter than 256 bits are right-padded
 + * with 0 bits.
 + *
 + * Note that this representation wastes 12 bytes for the SHA1 case, so
 + * don't use it for anything where we need to allocate a whole bunch at
 + * once.
 + **/
 +typedef struct {
 +  char d[N_DIGEST_ALGORITHMS][DIGEST256_LEN];
 +} digests_t;
  
  typedef struct crypto_pk_env_t crypto_pk_env_t;
  typedef struct crypto_cipher_env_t crypto_cipher_env_t;
@@@ -81,9 -55,7 +81,9 @@@ typedef struct crypto_digest_env_t cryp
  typedef struct crypto_dh_env_t crypto_dh_env_t;
  
  /* global state */
 -int crypto_global_init(int hardwareAccel);
 +int crypto_global_init(int hardwareAccel,
 +                       const char *accelName,
 +                       const char *accelPath);
  void crypto_thread_cleanup(void);
  int crypto_global_cleanup(void);
  
@@@ -99,9 -71,7 +99,9 @@@ crypto_cipher_env_t *crypto_new_cipher_
  void crypto_free_cipher_env(crypto_cipher_env_t *env);
  
  /* public key crypto */
 -int crypto_pk_generate_key(crypto_pk_env_t *env);
 +int crypto_pk_generate_key_with_bits(crypto_pk_env_t *env, int bits);
 +#define crypto_pk_generate_key(env)                     \
 +  crypto_pk_generate_key_with_bits((env), (PK_BYTES*8))
  
  int crypto_pk_read_private_key_from_filename(crypto_pk_env_t *env,
                                               const char *keyfile);
@@@ -151,7 -121,7 +151,7 @@@ int crypto_pk_check_fingerprint_syntax(
  
  /* symmetric crypto */
  int crypto_cipher_generate_key(crypto_cipher_env_t *env);
 -int crypto_cipher_set_key(crypto_cipher_env_t *env, const char *key);
 +void crypto_cipher_set_key(crypto_cipher_env_t *env, const char *key);
  void crypto_cipher_generate_iv(char *iv_out);
  int crypto_cipher_set_iv(crypto_cipher_env_t *env, const char *iv);
  const char *crypto_cipher_get_key(crypto_cipher_env_t *env);
@@@ -171,15 -141,9 +171,15 @@@ int crypto_cipher_decrypt_with_iv(crypt
                                    char *to, size_t tolen,
                                    const char *from, size_t fromlen);
  
 -/* SHA-1 */
 +/* SHA-1 and other digests. */
  int crypto_digest(char *digest, const char *m, size_t len);
 +int crypto_digest256(char *digest, const char *m, size_t len,
 +                     digest_algorithm_t algorithm);
 +int crypto_digest_all(digests_t *ds_out, const char *m, size_t len);
 +const char *crypto_digest_algorithm_get_name(digest_algorithm_t alg);
 +int crypto_digest_algorithm_parse_name(const char *name);
  crypto_digest_env_t *crypto_new_digest_env(void);
 +crypto_digest_env_t *crypto_new_digest256_env(digest_algorithm_t algorithm);
  void crypto_free_digest_env(crypto_digest_env_t *digest);
  void crypto_digest_add_bytes(crypto_digest_env_t *digest, const char *data,
                               size_t len);
@@@ -198,7 -162,7 +198,7 @@@ int crypto_dh_get_bytes(crypto_dh_env_
  int crypto_dh_generate_public(crypto_dh_env_t *dh);
  int crypto_dh_get_public(crypto_dh_env_t *dh, char *pubkey_out,
                           size_t pubkey_out_len);
 -ssize_t crypto_dh_compute_secret(crypto_dh_env_t *dh,
 +ssize_t crypto_dh_compute_secret(int severity, crypto_dh_env_t *dh,
                               const char *pubkey, size_t pubkey_len,
                               char *secret_out, size_t secret_out_len);
  void crypto_dh_free(crypto_dh_env_t *dh);
@@@ -210,7 -174,6 +210,7 @@@ int crypto_seed_rng(int startup)
  int crypto_rand(char *to, size_t n);
  int crypto_rand_int(unsigned int max);
  uint64_t crypto_rand_uint64(uint64_t max);
 +double crypto_rand_double(void);
  
  char *crypto_random_hostname(int min_rand_len, int max_rand_len,
                               const char *prefix, const char *suffix);
@@@ -228,8 -191,6 +228,8 @@@ int base32_decode(char *dest, size_t de
  
  int digest_to_base64(char *d64, const char *digest);
  int digest_from_base64(char *digest, const char *d64);
 +int digest256_to_base64(char *d64, const char *digest);
 +int digest256_from_base64(char *digest, const char *d64);
  
  /** Length of RFC2440-style S2K specifier: the first 8 bytes are a salt, the
   * 9th describes how much iteration to do. */
diff --combined src/common/ht.h
index f598856,033cd89..0850c07
--- a/src/common/ht.h
+++ b/src/common/ht.h
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2002, Christopher Clark.
   * Copyright (c) 2005-2006, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See license at end. */
  
  /* Based on ideas by Christopher Clark and interfaces from Niels Provos. */
@@@ -42,10 -42,6 +42,10 @@@
  #define HT_SIZE(head)                           \
    ((head)->hth_n_entries)
  
 +/* Return memory usage for a hashtable (not counting the entries themselves) */
 +#define HT_MEM_USAGE(head)                         \
 +  (sizeof(*head) + (head)->hth_table_length * sizeof(void*))
 +
  #define HT_FIND(name, head, elm)     name##_HT_FIND((head), (elm))
  #define HT_INSERT(name, head, elm)   name##_HT_INSERT((head), (elm))
  #define HT_REPLACE(name, head, elm)  name##_HT_REPLACE((head), (elm))
diff --combined src/common/log.c
index 1324ff0,fe93bd7..2fef2cc
--- a/src/common/log.c
+++ b/src/common/log.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001, Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -33,9 -33,11 +33,9 @@@
  #include "compat.h"
  #include "util.h"
  #define LOG_PRIVATE
 -#include "log.h"
 +#include "torlog.h"
  #include "container.h"
  
 -#include <event.h>
 -
  #define TRUNCATED_STR "[...truncated]"
  #define TRUNCATED_STR_LEN 14
  
@@@ -81,7 -83,7 +81,7 @@@ should_log_function_name(log_domain_mas
        /* All debugging messages occur in interesting places. */
        return 1;
      case LOG_NOTICE:
 -  case LOG_WARN:
 +    case LOG_WARN:
      case LOG_ERR:
        /* We care about places where bugs occur. */
        return (domain == LD_BUG);
@@@ -103,17 -105,6 +103,17 @@@ static logfile_t *logfiles = NULL
  static int syslog_count = 0;
  #endif
  
 +/** Represents a log message that we are going to send to callback-driven
 + * loggers once we can do so in a non-reentrant way. */
 +typedef struct pending_cb_message_t {
 +  int severity;
 +  log_domain_mask_t domain;
 +  char *msg;
 +} pending_cb_message_t;
 +
 +/** Log messages waiting to be replayed onto callback-based logs */
 +static smartlist_t *pending_cb_messages = NULL;
 +
  #define LOCK_LOGS() STMT_BEGIN                                          \
    tor_mutex_acquire(&log_mutex);                                        \
    STMT_END
@@@ -272,7 -263,6 +272,7 @@@ logv(int severity, log_domain_mask_t do
    int formatted = 0;
    logfile_t *lf;
    char *end_of_prefix=NULL;
 +  int callbacks_deferred = 0;
  
    /* Call assert, not tor_assert, since tor_assert calls log on failure. */
    assert(format);
@@@ -280,10 -270,6 +280,10 @@@
     * interesting and hard to diagnose effects */
    assert(severity >= LOG_ERR && severity <= LOG_DEBUG);
    LOCK_LOGS();
 +
 +  if ((! (domain & LD_NOCB)) && smartlist_len(pending_cb_messages))
 +    flush_pending_log_callbacks();
 +
    lf = logfiles;
    while (lf) {
      if (! (lf->severities->masks[SEVERITY_MASK_IDX(severity)] & domain)) {
@@@ -294,6 -280,10 +294,6 @@@
        lf = lf->next;
        continue;
      }
 -    if (lf->callback && (domain & LD_NOCB)) {
 -      lf = lf->next;
 -      continue;
 -    }
      if (lf->seems_dead) {
        lf = lf->next;
        continue;
@@@ -328,19 -318,7 +328,19 @@@
        lf = lf->next;
        continue;
      } else if (lf->callback) {
 -      lf->callback(severity, domain, end_of_prefix);
 +      if (domain & LD_NOCB) {
 +        if (!callbacks_deferred) {
 +          pending_cb_message_t *msg = tor_malloc(sizeof(pending_cb_message_t));
 +          msg->severity = severity;
 +          msg->domain = domain;
 +          msg->msg = tor_strdup(end_of_prefix);
 +          smartlist_add(pending_cb_messages, msg);
 +
 +          callbacks_deferred = 1;
 +        }
 +      } else {
 +        lf->callback(severity, domain, end_of_prefix);
 +      }
        lf = lf->next;
        continue;
      }
@@@ -356,7 -334,7 +356,7 @@@
  
  /** Output a message to the log. */
  void
 -_log(int severity, log_domain_mask_t domain, const char *format, ...)
 +tor_log(int severity, log_domain_mask_t domain, const char *format, ...)
  {
    va_list ap;
    if (severity > _log_global_min_severity)
@@@ -454,8 -432,6 +454,8 @@@ _log_err(log_domain_mask_t domain, cons
  static void
  log_free(logfile_t *victim)
  {
 +  if (!victim)
 +    return;
    tor_free(victim->severities);
    tor_free(victim->filename);
    tor_free(victim);
@@@ -579,8 -555,6 +579,8 @@@ init_logging(void
      tor_mutex_init(&log_mutex);
      log_mutex_initialized = 1;
    }
 +  if (pending_cb_messages == NULL)
 +    pending_cb_messages = smartlist_create();
  }
  
  /** Add a log handler to receive messages during startup (before the real
@@@ -639,48 -613,6 +639,48 @@@ change_callback_log_severity(int loglev
    UNLOCK_LOGS();
  }
  
 +/** If there are any log messages that were genered with LD_NOCB waiting to
 + * be sent to callback-based loggers, send them now. */
 +void
 +flush_pending_log_callbacks(void)
 +{
 +  logfile_t *lf;
 +  smartlist_t *messages, *messages_tmp;
 +
 +  LOCK_LOGS();
 +  if (0 == smartlist_len(pending_cb_messages)) {
 +    UNLOCK_LOGS();
 +    return;
 +  }
 +
 +  messages = pending_cb_messages;
 +  pending_cb_messages = smartlist_create();
 +  do {
 +    SMARTLIST_FOREACH_BEGIN(messages, pending_cb_message_t *, msg) {
 +      const int severity = msg->severity;
 +      const int domain = msg->domain;
 +      for (lf = logfiles; lf; lf = lf->next) {
 +        if (! lf->callback || lf->seems_dead ||
 +            ! (lf->severities->masks[SEVERITY_MASK_IDX(severity)] & domain)) {
 +          continue;
 +        }
 +        lf->callback(severity, domain, msg->msg);
 +      }
 +      tor_free(msg->msg);
 +      tor_free(msg);
 +    } SMARTLIST_FOREACH_END(msg);
 +    smartlist_clear(messages);
 +
 +    messages_tmp = pending_cb_messages;
 +    pending_cb_messages = messages;
 +    messages = messages_tmp;
 +  } while (smartlist_len(messages));
 +
 +  smartlist_free(messages);
 +
 +  UNLOCK_LOGS();
 +}
 +
  /** Close any log handlers added by add_temp_log() or marked by
   * mark_logs_temp(). */
  void
@@@ -750,11 -682,13 +750,11 @@@ add_file_log(const log_severity_list_t 
    logfiles->needs_close = 1;
    lf = logfiles;
    _log_global_min_severity = get_min_log_level();
 -  UNLOCK_LOGS();
  
    if (log_tor_version(lf, 0) < 0) {
 -    LOCK_LOGS();
      delete_log(lf);
 -    UNLOCK_LOGS();
    }
 +  UNLOCK_LOGS();
  
    return 0;
  }
@@@ -817,7 -751,7 +817,7 @@@ log_level_to_string(int level
  static const char *domain_list[] = {
    "GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM",
    "HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV",
 -  "OR", "EDGE", "ACCT", "HIST", NULL
 +  "OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", NULL
  };
  
  /** Return a bitmask for the log domain for which <b>domain</b> is the name,
@@@ -995,6 -929,65 +995,6 @@@ switch_logs_debug(void
    UNLOCK_LOGS();
  }
  
 -#ifdef HAVE_EVENT_SET_LOG_CALLBACK
 -/** A string which, if it appears in a libevent log, should be ignored. */
 -static const char *suppress_msg = NULL;
 -/** Callback function passed to event_set_log() so we can intercept
 - * log messages from libevent. */
 -static void
 -libevent_logging_callback(int severity, const char *msg)
 -{
 -  char buf[1024];
 -  size_t n;
 -  if (suppress_msg && strstr(msg, suppress_msg))
 -    return;
 -  n = strlcpy(buf, msg, sizeof(buf));
 -  if (n && n < sizeof(buf) && buf[n-1] == '\n') {
 -    buf[n-1] = '\0';
 -  }
 -  switch (severity) {
 -    case _EVENT_LOG_DEBUG:
 -      log(LOG_DEBUG, LD_NOCB|LD_NET, "Message from libevent: %s", buf);
 -      break;
 -    case _EVENT_LOG_MSG:
 -      log(LOG_INFO, LD_NOCB|LD_NET, "Message from libevent: %s", buf);
 -      break;
 -    case _EVENT_LOG_WARN:
 -      log(LOG_WARN, LD_NOCB|LD_GENERAL, "Warning from libevent: %s", buf);
 -      break;
 -    case _EVENT_LOG_ERR:
 -      log(LOG_ERR, LD_NOCB|LD_GENERAL, "Error from libevent: %s", buf);
 -      break;
 -    default:
 -      log(LOG_WARN, LD_NOCB|LD_GENERAL, "Message [%d] from libevent: %s",
 -          severity, buf);
 -      break;
 -  }
 -}
 -/** Set hook to intercept log messages from libevent. */
 -void
 -configure_libevent_logging(void)
 -{
 -  event_set_log_callback(libevent_logging_callback);
 -}
 -/** Ignore any libevent log message that contains <b>msg</b>. */
 -void
 -suppress_libevent_log_msg(const char *msg)
 -{
 -  suppress_msg = msg;
 -}
 -#else
 -void
 -configure_libevent_logging(void)
 -{
 -}
 -void
 -suppress_libevent_log_msg(const char *msg)
 -{
 -  (void)msg;
 -}
 -#endif
 -
  #if 0
  static void
  dump_log_info(logfile_t *lf)
diff --combined src/common/memarea.c
index dac1543,ac26c5f..194deb8
--- a/src/common/memarea.c
+++ b/src/common/memarea.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Copyright (c) 2008-2010, The Tor Project, Inc. */
+ /* Copyright (c) 2008-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /** \file memarea.c
@@@ -11,11 -11,7 +11,11 @@@
  #include "memarea.h"
  #include "util.h"
  #include "compat.h"
 -#include "log.h"
 +#include "torlog.h"
 +
 +/** If true, we try to detect any attempts to write beyond the length of a
 + * memarea. */
 +#define USE_SENTINELS
  
  /** All returned pointers should be aligned to the nearest multiple of this
   * value. */
@@@ -29,24 -25,6 +29,24 @@@
  #error "void* is neither 4 nor 8 bytes long. I don't know how to align stuff."
  #endif
  
 +#ifdef USE_SENTINELS
 +#define SENTINEL_VAL 0x90806622u
 +#define SENTINEL_LEN sizeof(uint32_t)
 +#define SET_SENTINEL(chunk)                                     \
 +  STMT_BEGIN                                                    \
 +  set_uint32( &(chunk)->u.mem[chunk->mem_size], SENTINEL_VAL ); \
 +  STMT_END
 +#define CHECK_SENTINEL(chunk)                                           \
 +  STMT_BEGIN                                                            \
 +  uint32_t sent_val = get_uint32(&(chunk)->u.mem[chunk->mem_size]);     \
 +  tor_assert(sent_val == SENTINEL_VAL);                                 \
 +  STMT_END
 +#else
 +#define SENTINEL_LEN 0
 +#define SET_SENTINEL(chunk) STMT_NIL
 +#define CHECK_SENTINEL(chunk) STMT_NIL
 +#endif
 +
  /** Increment <b>ptr</b> until it is aligned to MEMAREA_ALIGN. */
  static INLINE void *
  realign_pointer(void *ptr)
@@@ -101,20 -79,15 +101,20 @@@ alloc_chunk(size_t sz, int freelist_ok
      freelist = res->next_chunk;
      res->next_chunk = NULL;
      --freelist_len;
 +    CHECK_SENTINEL(res);
      return res;
    } else {
      size_t chunk_size = freelist_ok ? CHUNK_SIZE : sz;
 -    memarea_chunk_t *res = tor_malloc_roundup(&chunk_size);
 +    memarea_chunk_t *res;
 +    chunk_size += SENTINEL_LEN;
 +    res = tor_malloc_roundup(&chunk_size);
      res->next_chunk = NULL;
 -    res->mem_size = chunk_size - CHUNK_HEADER_SIZE;
 +    res->mem_size = chunk_size - CHUNK_HEADER_SIZE - SENTINEL_LEN;
      res->next_mem = res->u.mem;
 -    tor_assert(res->next_mem+res->mem_size == ((char*)res)+chunk_size);
 +    tor_assert(res->next_mem+res->mem_size+SENTINEL_LEN ==
 +               ((char*)res)+chunk_size);
      tor_assert(realign_pointer(res->next_mem) == res->next_mem);
 +    SET_SENTINEL(res);
      return res;
    }
  }
@@@ -122,9 -95,8 +122,9 @@@
  /** Release <b>chunk</b> from a memarea, either by adding it to the freelist
   * or by freeing it if the freelist is already too big. */
  static void
 -chunk_free(memarea_chunk_t *chunk)
 +chunk_free_unchecked(memarea_chunk_t *chunk)
  {
 +  CHECK_SENTINEL(chunk);
    if (freelist_len < MAX_FREELIST_LEN) {
      ++freelist_len;
      chunk->next_chunk = freelist;
@@@ -152,7 -124,7 +152,7 @@@ memarea_drop_all(memarea_t *area
    memarea_chunk_t *chunk, *next;
    for (chunk = area->first; chunk; chunk = next) {
      next = chunk->next_chunk;
 -    chunk_free(chunk);
 +    chunk_free_unchecked(chunk);
    }
    area->first = NULL; /*fail fast on */
    tor_free(area);
@@@ -168,7 -140,7 +168,7 @@@ memarea_clear(memarea_t *area
    if (area->first->next_chunk) {
      for (chunk = area->first->next_chunk; chunk; chunk = next) {
        next = chunk->next_chunk;
 -      chunk_free(chunk);
 +      chunk_free_unchecked(chunk);
      }
      area->first->next_chunk = NULL;
    }
@@@ -211,7 -183,6 +211,7 @@@ memarea_alloc(memarea_t *area, size_t s
    memarea_chunk_t *chunk = area->first;
    char *result;
    tor_assert(chunk);
 +  CHECK_SENTINEL(chunk);
    tor_assert(sz < SIZE_T_CEILING);
    if (sz == 0)
      sz = 1;
@@@ -290,7 -261,6 +290,7 @@@ memarea_get_stats(memarea_t *area, size
    size_t a = 0, u = 0;
    memarea_chunk_t *chunk;
    for (chunk = area->first; chunk; chunk = chunk->next_chunk) {
 +    CHECK_SENTINEL(chunk);
      a += CHUNK_HEADER_SIZE + chunk->mem_size;
      tor_assert(chunk->next_mem >= chunk->u.mem);
      u += CHUNK_HEADER_SIZE + (chunk->next_mem - chunk->u.mem);
@@@ -307,7 -277,6 +307,7 @@@ memarea_assert_ok(memarea_t *area
    tor_assert(area->first);
  
    for (chunk = area->first; chunk; chunk = chunk->next_chunk) {
 +    CHECK_SENTINEL(chunk);
      tor_assert(chunk->next_mem >= chunk->u.mem);
      tor_assert(chunk->next_mem <=
            (char*) realign_pointer(chunk->u.mem+chunk->mem_size));
diff --combined src/common/mempool.c
index 9d019d6,9538a05..c444923
--- a/src/common/mempool.c
+++ b/src/common/mempool.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Copyright (c) 2007-2010, The Tor Project, Inc. */
+ /* Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  #if 1
  /* Tor dependencies */
@@@ -65,7 -65,7 +65,7 @@@
  #include "orconfig.h"
  #include "util.h"
  #include "compat.h"
 -#include "log.h"
 +#include "torlog.h"
  #define ALLOC(x) tor_malloc(x)
  #define FREE(x) tor_free(x)
  #define ASSERT(x) tor_assert(x)
diff --combined src/common/torgzip.c
index a247d6c,618b8b0..8c4dca9
--- a/src/common/torgzip.c
+++ b/src/common/torgzip.c
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -13,42 -13,20 +13,42 @@@
  #include <stdlib.h>
  #include <stdio.h>
  #include <assert.h>
 -#ifdef _MSC_VER
 -#include "..\..\contrib\zlib\zlib.h"
 -#else
 -#include <zlib.h>
 -#endif
  #include <string.h>
  #ifdef HAVE_NETINET_IN_H
  #include <netinet/in.h>
  #endif
  
 +#include "torint.h"
  #include "util.h"
 -#include "log.h"
 +#include "torlog.h"
  #include "torgzip.h"
  
 +/* zlib 1.2.4 and 1.2.5 do some "clever" things with macros.  Instead of
 +   saying "(defined(FOO) ? FOO : 0)" they like to say "FOO-0", on the theory
 +   that nobody will care if the compile outputs a no-such-identifier warning.
 +
 +   Sorry, but we like -Werror over here, so I guess we need to define these.
 +   I hope that zlib 1.2.6 doesn't break these too.
 +*/
 +#ifndef _LARGEFILE64_SOURCE
 +#define _LARGEFILE64_SOURCE 0
 +#endif
 +#ifndef _LFS64_LARGEFILE
 +#define _LFS64_LARGEFILE 0
 +#endif
 +#ifndef _FILE_OFFSET_BITS
 +#define _FILE_OFFSET_BITS 0
 +#endif
 +#ifndef off64_t
 +#define off64_t int64_t
 +#endif
 +
 +#ifdef _MSC_VER
 +#include "..\..\contrib\zlib\zlib.h"
 +#else
 +#include <zlib.h>
 +#endif
 +
  /** Set to 1 if zlib is a version that supports gzip; set to 0 if it doesn't;
   * set to -1 if we haven't checked yet. */
  static int gzip_is_supported = -1;
@@@ -187,7 -165,9 +187,7 @@@ tor_gzip_compress(char **out, size_t *o
      deflateEnd(stream);
      tor_free(stream);
    }
 -  if (*out) {
 -    tor_free(*out);
 -  }
 +  tor_free(*out);
    return -1;
  }
  
@@@ -443,8 -423,7 +443,8 @@@ tor_zlib_process(tor_zlib_state_t *stat
  void
  tor_zlib_free(tor_zlib_state_t *state)
  {
 -  tor_assert(state);
 +  if (!state)
 +    return;
  
    if (state->compress)
      deflateEnd(&state->stream);
diff --combined src/common/torint.h
index a57d501,4970dcd..f624bff
--- a/src/common/torint.h
+++ b/src/common/torint.h
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003, Roger Dingledine
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -117,10 -117,11 +117,10 @@@ typedef unsigned int uint32_t
  #ifndef INT32_MAX
  #define INT32_MAX 0x7fffffffu
  #endif
  #ifndef INT32_MIN
  #define INT32_MIN (-2147483647-1)
  #endif
 +#endif
  
  #if (SIZEOF_LONG == 4)
  #ifndef HAVE_INT32_T
diff --combined src/common/torlog.h
index 73c6906,0000000..f0be732
mode 100644,000000..100644
--- a/src/common/torlog.h
+++ b/src/common/torlog.h
@@@ -1,212 -1,0 +1,212 @@@
 +/* Copyright (c) 2001, Matej Pfajfar.
 + * Copyright (c) 2001-2004, Roger Dingledine.
 + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
++ * Copyright (c) 2007-2011, The Tor Project, Inc. */
 +/* See LICENSE for licensing information */
 +
 +/**
 + * \file log.h
 + *
 + * \brief Headers for log.c
 + **/
 +
 +#ifndef _TOR_LOG_H
 +
 +#include "compat.h"
 +
 +#ifdef HAVE_SYSLOG_H
 +#include <syslog.h>
 +#define LOG_WARN LOG_WARNING
 +#if LOG_DEBUG < LOG_ERR
 +#error "Your syslog.h thinks high numbers are more important.  " \
 +       "We aren't prepared to deal with that."
 +#endif
 +#else
 +/* Note: Syslog's logging code refers to priorities, with 0 being the most
 + * important.  Thus, all our comparisons needed to be reversed when we added
 + * syslog support.
 + *
 + * The upshot of this is that comments about log levels may be messed up: for
 + * "maximum severity" read "most severe" and "numerically *lowest* severity".
 + */
 +
 +/** Debug-level severity: for hyper-verbose messages of no interest to
 + * anybody but developers. */
 +#define LOG_DEBUG   7
 +/** Info-level severity: for messages that appear frequently during normal
 + * operation. */
 +#define LOG_INFO    6
 +/** Notice-level severity: for messages that appear infrequently
 + * during normal operation; that the user will probably care about;
 + * and that are not errors.
 + */
 +#define LOG_NOTICE  5
 +/** Warn-level severity: for messages that only appear when something has gone
 + * wrong. */
 +#define LOG_WARN    4
 +/** Error-level severity: for messages that only appear when something has gone
 + * very wrong, and the Tor process can no longer proceed. */
 +#define LOG_ERR     3
 +#endif
 +
 +/* Logging domains */
 +
 +/** Catch-all for miscellaneous events and fatal errors. */
 +#define LD_GENERAL  (1u<<0)
 +/** The cryptography subsystem. */
 +#define LD_CRYPTO   (1u<<1)
 +/** Networking. */
 +#define LD_NET      (1u<<2)
 +/** Parsing and acting on our configuration. */
 +#define LD_CONFIG   (1u<<3)
 +/** Reading and writing from the filesystem. */
 +#define LD_FS       (1u<<4)
 +/** Other servers' (non)compliance with the Tor protocol. */
 +#define LD_PROTOCOL (1u<<5)
 +/** Memory management. */
 +#define LD_MM       (1u<<6)
 +/** HTTP implementation. */
 +#define LD_HTTP     (1u<<7)
 +/** Application (socks) requests. */
 +#define LD_APP      (1u<<8)
 +/** Communication via the controller protocol. */
 +#define LD_CONTROL  (1u<<9)
 +/** Building, using, and managing circuits. */
 +#define LD_CIRC     (1u<<10)
 +/** Hidden services. */
 +#define LD_REND     (1u<<11)
 +/** Internal errors in this Tor process. */
 +#define LD_BUG      (1u<<12)
 +/** Learning and using information about Tor servers. */
 +#define LD_DIR      (1u<<13)
 +/** Learning and using information about Tor servers. */
 +#define LD_DIRSERV  (1u<<14)
 +/** Onion routing protocol. */
 +#define LD_OR       (1u<<15)
 +/** Generic edge-connection functionality. */
 +#define LD_EDGE     (1u<<16)
 +#define LD_EXIT     LD_EDGE
 +/** Bandwidth accounting. */
 +#define LD_ACCT     (1u<<17)
 +/** Router history */
 +#define LD_HIST     (1u<<18)
 +/** OR handshaking */
 +#define LD_HANDSHAKE (1u<<19)
 +/** Number of logging domains in the code. */
 +#define N_LOGGING_DOMAINS 20
 +
 +/** This log message is not safe to send to a callback-based logger
 + * immediately.  Used as a flag, not a log domain. */
 +#define LD_NOCB (1u<<31)
 +
 +typedef uint32_t log_domain_mask_t;
 +
 +/** Configures which severities are logged for each logging domain for a given
 + * log target. */
 +typedef struct log_severity_list_t {
 +  /** For each log severity, a bitmask of which domains a given logger is
 +   * logging. */
 +  log_domain_mask_t masks[LOG_DEBUG-LOG_ERR+1];
 +} log_severity_list_t;
 +
 +#ifdef LOG_PRIVATE
 +/** Given a severity, yields an index into log_severity_list_t.masks to use
 + * for that severity. */
 +#define SEVERITY_MASK_IDX(sev) ((sev) - LOG_ERR)
 +#endif
 +
 +/** Callback type used for add_callback_log. */
 +typedef void (*log_callback)(int severity, uint32_t domain, const char *msg);
 +
 +void init_logging(void);
 +int parse_log_level(const char *level);
 +const char *log_level_to_string(int level);
 +int parse_log_severity_config(const char **cfg,
 +                              log_severity_list_t *severity_out);
 +void set_log_severity_config(int minSeverity, int maxSeverity,
 +                             log_severity_list_t *severity_out);
 +void add_stream_log(const log_severity_list_t *severity, const char *name,
 +                    int fd);
 +int add_file_log(const log_severity_list_t *severity, const char *filename);
 +#ifdef HAVE_SYSLOG_H
 +int add_syslog_log(const log_severity_list_t *severity);
 +#endif
 +int add_callback_log(const log_severity_list_t *severity, log_callback cb);
 +int get_min_log_level(void);
 +void switch_logs_debug(void);
 +void logs_free_all(void);
 +void add_temp_log(int min_severity);
 +void close_temp_logs(void);
 +void rollback_log_changes(void);
 +void mark_logs_temp(void);
 +void change_callback_log_severity(int loglevelMin, int loglevelMax,
 +                                  log_callback cb);
 +void flush_pending_log_callbacks(void);
 +void log_set_application_name(const char *name);
 +
 +/* Outputs a message to stdout */
 +void tor_log(int severity, log_domain_mask_t domain, const char *format, ...)
 +  CHECK_PRINTF(3,4);
 +#define log tor_log /* hack it so we don't conflict with log() as much */
 +
 +#ifdef __GNUC__
 +extern int _log_global_min_severity;
 +
 +void _log_fn(int severity, log_domain_mask_t domain,
 +             const char *funcname, const char *format, ...)
 +  CHECK_PRINTF(4,5);
 +/** Log a message at level <b>severity</b>, using a pretty-printed version
 + * of the current function name. */
 +#define log_fn(severity, domain, args...)               \
 +  _log_fn(severity, domain, __PRETTY_FUNCTION__, args)
 +#define log_debug(domain, args...)                                      \
 +  STMT_BEGIN                                                            \
 +    if (PREDICT_UNLIKELY(_log_global_min_severity == LOG_DEBUG))        \
 +      _log_fn(LOG_DEBUG, domain, __PRETTY_FUNCTION__, args);            \
 +  STMT_END
 +#define log_info(domain, args...)                           \
 +  _log_fn(LOG_INFO, domain, __PRETTY_FUNCTION__, args)
 +#define log_notice(domain, args...)                         \
 +  _log_fn(LOG_NOTICE, domain, __PRETTY_FUNCTION__, args)
 +#define log_warn(domain, args...)                           \
 +  _log_fn(LOG_WARN, domain, __PRETTY_FUNCTION__, args)
 +#define log_err(domain, args...)                            \
 +  _log_fn(LOG_ERR, domain, __PRETTY_FUNCTION__, args)
 +
 +#else /* ! defined(__GNUC__) */
 +
 +void _log_fn(int severity, log_domain_mask_t domain, const char *format, ...);
 +void _log_debug(log_domain_mask_t domain, const char *format, ...);
 +void _log_info(log_domain_mask_t domain, const char *format, ...);
 +void _log_notice(log_domain_mask_t domain, const char *format, ...);
 +void _log_warn(log_domain_mask_t domain, const char *format, ...);
 +void _log_err(log_domain_mask_t domain, const char *format, ...);
 +
 +#if defined(_MSC_VER) && _MSC_VER < 1300
 +/* MSVC 6 and earlier don't have __func__, or even __LINE__. */
 +#define log_fn _log_fn
 +#define log_debug _log_debug
 +#define log_info _log_info
 +#define log_notice _log_notice
 +#define log_warn _log_warn
 +#define log_err _log_err
 +#else
 +/* We don't have GCC's varargs macros, so use a global variable to pass the
 + * function name to log_fn */
 +extern const char *_log_fn_function_name;
 +/* We abuse the comma operator here, since we can't use the standard
 + * do {...} while (0) trick to wrap this macro, since the macro can't take
 + * arguments. */
 +#define log_fn (_log_fn_function_name=__func__),_log_fn
 +#define log_debug (_log_fn_function_name=__func__),_log_debug
 +#define log_info (_log_fn_function_name=__func__),_log_info
 +#define log_notice (_log_fn_function_name=__func__),_log_notice
 +#define log_warn (_log_fn_function_name=__func__),_log_warn
 +#define log_err (_log_fn_function_name=__func__),_log_err
 +#endif
 +
 +#endif /* !GNUC */
 +
 +# define _TOR_LOG_H
 +#endif
 +
diff --combined src/common/tortls.c
index fa89412,1d597e2..9d22657
--- a/src/common/tortls.c
+++ b/src/common/tortls.c
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -16,10 -16,6 +16,10 @@@
  
  #include "orconfig.h"
  
 +#if defined (WINCE)
 +#include <WinSock2.h>
 +#endif
 +
  #include <assert.h>
  #ifdef MS_WINDOWS /*wrkard for dtls1.h >= 0.9.8m of "#include <winsock.h>"*/
   #define WIN32_WINNT 0x400
@@@ -49,7 -45,7 +49,7 @@@
  #include "crypto.h"
  #include "tortls.h"
  #include "util.h"
 -#include "log.h"
 +#include "torlog.h"
  #include "container.h"
  #include "ht.h"
  #include <string.h>
@@@ -86,9 -82,7 +86,9 @@@ static int use_unsafe_renegotiation_op 
   * SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION? */
  static int use_unsafe_renegotiation_flag = 0;
  
 -/** Structure holding the TLS state for a single connection. */
 +/** Holds a SSL_CTX object and related state used to configure TLS
 + * connections.
 + */
  typedef struct tor_tls_context_t {
    int refcnt;
    SSL_CTX *ctx;
@@@ -190,16 -184,10 +190,16 @@@ static X509* tor_tls_create_certificate
                                          const char *cname_sign,
                                          unsigned int lifetime);
  static void tor_tls_unblock_renegotiation(tor_tls_t *tls);
 -
 -/** Global tls context. We keep it here because nobody else needs to
 - * touch it. */
 -static tor_tls_context_t *global_tls_context = NULL;
 +static int tor_tls_context_init_one(tor_tls_context_t **ppcontext,
 +                                    crypto_pk_env_t *identity,
 +                                    unsigned int key_lifetime);
 +static tor_tls_context_t *tor_tls_context_new(crypto_pk_env_t *identity,
 +                                              unsigned int key_lifetime);
 +
 +/** Global TLS contexts. We keep them here because nobody else needs
 + * to touch them. */
 +static tor_tls_context_t *server_tls_context = NULL;
 +static tor_tls_context_t *client_tls_context = NULL;
  /** True iff tor_tls_init() has been called. */
  static int tls_library_is_initialized = 0;
  
@@@ -207,51 -195,30 +207,51 @@@
  #define _TOR_TLS_SYSCALL    (_MIN_TOR_TLS_ERROR_VAL - 2)
  #define _TOR_TLS_ZERORETURN (_MIN_TOR_TLS_ERROR_VAL - 1)
  
 +#include "tortls_states.h"
 +
 +/** Return the symbolic name of an OpenSSL state. */
 +static const char *
 +ssl_state_to_string(int ssl_state)
 +{
 +  static char buf[40];
 +  int i;
 +  for (i = 0; state_map[i].name; ++i) {
 +    if (state_map[i].state == ssl_state)
 +      return state_map[i].name;
 +  }
 +  tor_snprintf(buf, sizeof(buf), "Unknown state %d", ssl_state);
 +  return buf;
 +}
 +
  /** Log all pending tls errors at level <b>severity</b>.  Use
   * <b>doing</b> to describe our current activities.
   */
  static void
 -tls_log_errors(tor_tls_t *tls, int severity, const char *doing)
 +tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing)
  {
 +  const char *state = NULL;
 +  int st;
    unsigned long err;
    const char *msg, *lib, *func, *addr;
    addr = tls ? tls->address : NULL;
 +  st = (tls && tls->ssl) ? tls->ssl->state : -1;
    while ((err = ERR_get_error()) != 0) {
      msg = (const char*)ERR_reason_error_string(err);
      lib = (const char*)ERR_lib_error_string(err);
      func = (const char*)ERR_func_error_string(err);
 +    if (!state)
 +      state = (st>=0)?ssl_state_to_string(st):"---";
      if (!msg) msg = "(null)";
      if (!lib) lib = "(null)";
      if (!func) func = "(null)";
      if (doing) {
 -      log(severity, LD_NET, "TLS error while %s%s%s: %s (in %s:%s)",
 +      log(severity, domain, "TLS error while %s%s%s: %s (in %s:%s:%s)",
            doing, addr?" with ":"", addr?addr:"",
 -          msg, lib, func);
 +          msg, lib, func, state);
      } else {
 -      log(severity, LD_NET, "TLS error%s%s: %s (in %s:%s)",
 +      log(severity, domain, "TLS error%s%s: %s (in %s:%s:%s)",
            addr?" with ":"", addr?addr:"",
 -          msg, lib, func);
 +          msg, lib, func, state);
      }
    }
  }
@@@ -327,7 -294,7 +327,7 @@@ tor_tls_err_to_string(int err
   */
  static int
  tor_tls_get_error(tor_tls_t *tls, int r, int extra,
 -                  const char *doing, int severity)
 +                  const char *doing, int severity, int domain)
  {
    int err = SSL_get_error(tls->ssl, r);
    int tor_error = TOR_TLS_ERROR_MISC;
@@@ -342,28 -309,25 +342,28 @@@
        if (extra&CATCH_SYSCALL)
          return _TOR_TLS_SYSCALL;
        if (r == 0) {
 -        log(severity, LD_NET, "TLS error: unexpected close while %s", doing);
 +        log(severity, LD_NET, "TLS error: unexpected close while %s (%s)",
 +            doing, ssl_state_to_string(tls->ssl->state));
          tor_error = TOR_TLS_ERROR_IO;
        } else {
          int e = tor_socket_errno(tls->socket);
          log(severity, LD_NET,
 -            "TLS error: <syscall error while %s> (errno=%d: %s)",
 -            doing, e, tor_socket_strerror(e));
 +            "TLS error: <syscall error while %s> (errno=%d: %s; state=%s)",
 +            doing, e, tor_socket_strerror(e),
 +            ssl_state_to_string(tls->ssl->state));
          tor_error = tor_errno_to_tls_error(e);
        }
 -      tls_log_errors(tls, severity, doing);
 +      tls_log_errors(tls, severity, domain, doing);
        return tor_error;
      case SSL_ERROR_ZERO_RETURN:
        if (extra&CATCH_ZERO)
          return _TOR_TLS_ZERORETURN;
 -      log(severity, LD_NET, "TLS connection closed while %s", doing);
 -      tls_log_errors(tls, severity, doing);
 +      log(severity, LD_NET, "TLS connection closed while %s in state %s",
 +          doing, ssl_state_to_string(tls->ssl->state));
 +      tls_log_errors(tls, severity, domain, doing);
        return TOR_TLS_CLOSE;
      default:
 -      tls_log_errors(tls, severity, doing);
 +      tls_log_errors(tls, severity, domain, doing);
        return TOR_TLS_ERROR_MISC;
    }
  }
@@@ -377,10 -341,11 +377,10 @@@ tor_tls_init(void
      long version;
      SSL_library_init();
      SSL_load_error_strings();
  
      version = SSLeay();
  
 -    /* OpenSSL 0.9.8l introdeced SSL3_FLAGS_ALLOW_UNSAGE_LEGACY_RENEGOTIATION
 +    /* OpenSSL 0.9.8l introduced SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
       * here, but without thinking too hard about it: it turns out that the
       * flag in question needed to be set at the last minute, and that it
       * conflicted with an existing flag number that had already been added
@@@ -399,8 -364,8 +399,8 @@@
       * leave their headers out of sync with their libraries.
       *
       * Yes, it _is_ almost as if the OpenSSL developers decided that no
 -     * program should be allowed to use renegotiation its first passed an
 -     * test of intelligence and determination.
 +     * program should be allowed to use renegotiation unless it first passed
 +     * a test of intelligence and determination.
       */
      if (version >= 0x009080c0L && version < 0x009080d0L) {
        log_notice(LD_GENERAL, "OpenSSL %s looks like version 0.9.8l; "
@@@ -435,15 -400,9 +435,15 @@@
  void
  tor_tls_free_all(void)
  {
 -  if (global_tls_context) {
 -    tor_tls_context_decref(global_tls_context);
 -    global_tls_context = NULL;
 +  if (server_tls_context) {
 +    tor_tls_context_t *ctx = server_tls_context;
 +    server_tls_context = NULL;
 +    tor_tls_context_decref(ctx);
 +  }
 +  if (client_tls_context) {
 +    tor_tls_context_t *ctx = client_tls_context;
 +    client_tls_context = NULL;
 +    tor_tls_context_decref(ctx);
    }
    if (!HT_EMPTY(&tlsmap_root)) {
      log_warn(LD_MM, "Still have entries in the tlsmap at shutdown.");
@@@ -552,7 -511,7 +552,7 @@@ tor_tls_create_certificate(crypto_pk_en
      x509 = NULL;
    }
   done:
 -  tls_log_errors(NULL, LOG_WARN, "generating certificate");
 +  tls_log_errors(NULL, LOG_WARN, LD_NET, "generating certificate");
    if (sign_pkey)
      EVP_PKEY_free(sign_pkey);
    if (pkey)
@@@ -569,9 -528,9 +569,9 @@@
    (TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":"           \
     TLS1_TXT_DHE_RSA_WITH_AES_128_SHA ":"           \
     SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA)
 -/* Note: for setting up your own private testing network with link crypto
 - * disabled, set the cipher lists to your cipher list to
 - * SSL3_TXT_RSA_NULL_SHA.  If you do this, you won't be able to communicate
 +/* Note: to set up your own private testing network with link crypto
 + * disabled, set your Tors' cipher list to
 + * (SSL3_TXT_RSA_NULL_SHA).  If you do this, you won't be able to communicate
   * with any of the "real" Tors, though. */
  
  #ifdef V2_HANDSHAKE_CLIENT
@@@ -630,97 -589,15 +630,97 @@@ tor_tls_context_incref(tor_tls_context_
    ++ctx->refcnt;
  }
  
 -/** Create a new TLS context for use with Tor TLS handshakes.
 - * <b>identity</b> should be set to the identity key used to sign the
 - * certificate, and <b>nickname</b> set to the nickname to use.
 +/** Create new global client and server TLS contexts.
 + *
 + * If <b>server_identity</b> is NULL, this will not generate a server
 + * TLS context. If <b>is_public_server</b> is non-zero, this will use
 + * the same TLS context for incoming and outgoing connections, and
 + * ignore <b>client_identity</b>. */
 +int
 +tor_tls_context_init(int is_public_server,
 +                     crypto_pk_env_t *client_identity,
 +                     crypto_pk_env_t *server_identity,
 +                     unsigned int key_lifetime)
 +{
 +  int rv1 = 0;
 +  int rv2 = 0;
 +
 +  if (is_public_server) {
 +    tor_tls_context_t *new_ctx;
 +    tor_tls_context_t *old_ctx;
 +
 +    tor_assert(server_identity != NULL);
 +
 +    rv1 = tor_tls_context_init_one(&server_tls_context,
 +                                   server_identity,
 +                                   key_lifetime);
 +
 +    if (rv1 >= 0) {
 +      new_ctx = server_tls_context;
 +      tor_tls_context_incref(new_ctx);
 +      old_ctx = client_tls_context;
 +      client_tls_context = new_ctx;
 +
 +      if (old_ctx != NULL) {
 +        tor_tls_context_decref(old_ctx);
 +      }
 +    }
 +  } else {
 +    if (server_identity != NULL) {
 +      rv1 = tor_tls_context_init_one(&server_tls_context,
 +                                     server_identity,
 +                                     key_lifetime);
 +    } else {
 +      tor_tls_context_t *old_ctx = server_tls_context;
 +      server_tls_context = NULL;
 +
 +      if (old_ctx != NULL) {
 +        tor_tls_context_decref(old_ctx);
 +      }
 +    }
 +
 +    rv2 = tor_tls_context_init_one(&client_tls_context,
 +                                   client_identity,
 +                                   key_lifetime);
 +  }
 +
 +  return MIN(rv1, rv2);
 +}
 +
 +/** Create a new global TLS context.
   *
   * You can call this function multiple times.  Each time you call it,
   * it generates new certificates; all new connections will use
   * the new SSL context.
   */
 -int
 +static int
 +tor_tls_context_init_one(tor_tls_context_t **ppcontext,
 +                         crypto_pk_env_t *identity,
 +                         unsigned int key_lifetime)
 +{
 +  tor_tls_context_t *new_ctx = tor_tls_context_new(identity,
 +                                                   key_lifetime);
 +  tor_tls_context_t *old_ctx = *ppcontext;
 +
 +  if (new_ctx != NULL) {
 +    *ppcontext = new_ctx;
 +
 +    /* Free the old context if one existed. */
 +    if (old_ctx != NULL) {
 +      /* This is safe even if there are open connections: we reference-
 +       * count tor_tls_context_t objects. */
 +      tor_tls_context_decref(old_ctx);
 +    }
 +  }
 +
 +  return ((new_ctx != NULL) ? 0 : -1);
 +}
 +
 +/** Create a new TLS context for use with Tor TLS handshakes.
 + * <b>identity</b> should be set to the identity key used to sign the
 + * certificate.
 + */
 +static tor_tls_context_t *
  tor_tls_context_new(crypto_pk_env_t *identity, unsigned int key_lifetime)
  {
    crypto_pk_env_t *rsa = NULL;
@@@ -815,15 -692,21 +815,15 @@@
                       always_accept_verify_cb);
    /* let us realloc bufs that we're writing from */
    SSL_CTX_set_mode(result->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
 -  /* Free the old context if one exists. */
 -  if (global_tls_context) {
 -    /* This is safe even if there are open connections: OpenSSL does
 -     * reference counting with SSL and SSL_CTX objects. */
 -    tor_tls_context_decref(global_tls_context);
 -  }
 -  global_tls_context = result;
 +
    if (rsa)
      crypto_free_pk_env(rsa);
    tor_free(nickname);
    tor_free(nn2);
 -  return 0;
 +  return result;
  
   error:
 -  tls_log_errors(NULL, LOG_WARN, "creating TLS context");
 +  tls_log_errors(NULL, LOG_WARN, LD_NET, "creating TLS context");
    tor_free(nickname);
    tor_free(nn2);
    if (pkey)
@@@ -836,7 -719,7 +836,7 @@@
      X509_free(cert);
    if (idcert)
      X509_free(idcert);
 -  return -1;
 +  return NULL;
  }
  
  #ifdef V2_HANDSHAKE_SERVER
@@@ -851,11 -734,11 +851,11 @@@ tor_tls_client_is_using_v2_ciphers(cons
    /* If we reached this point, we just got a client hello.  See if there is
     * a cipher list. */
    if (!(session = SSL_get_session((SSL *)ssl))) {
 -    log_warn(LD_NET, "No session on TLS?");
 +    log_info(LD_NET, "No session on TLS?");
      return 0;
    }
    if (!session->ciphers) {
 -    log_warn(LD_NET, "No ciphers on session");
 +    log_info(LD_NET, "No ciphers on session");
      return 0;
    }
    /* Now we need to see if there are any ciphers whose presence means we're
@@@ -867,7 -750,8 +867,7 @@@
          strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_256_SHA) &&
          strcmp(ciphername, SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA) &&
          strcmp(ciphername, "(NONE)")) {
 -      /* XXXX should be ld_debug */
 -      log_info(LD_NET, "Got a non-version-1 cipher called '%s'", ciphername);
 +      log_debug(LD_NET, "Got a non-version-1 cipher called '%s'", ciphername);
        // return 1;
        goto dump_list;
      }
@@@ -883,8 -767,8 +883,8 @@@
        smartlist_add(elts, (char*)ciphername);
      }
      s = smartlist_join_strings(elts, ":", 0, NULL);
 -    log_info(LD_NET, "Got a non-version-1 cipher list from %s.  It is: '%s'",
 -             address, s);
 +    log_debug(LD_NET, "Got a non-version-1 cipher list from %s.  It is: '%s'",
 +              address, s);
      tor_free(s);
      smartlist_free(elts);
    }
@@@ -1015,12 -899,10 +1015,12 @@@ tor_tls_new(int sock, int isServer
  {
    BIO *bio = NULL;
    tor_tls_t *result = tor_malloc_zero(sizeof(tor_tls_t));
 +  tor_tls_context_t *context = isServer ? server_tls_context :
 +    client_tls_context;
  
 -  tor_assert(global_tls_context); /* make sure somebody made it first */
 -  if (!(result->ssl = SSL_new(global_tls_context->ctx))) {
 -    tls_log_errors(NULL, LOG_WARN, "generating TLS context");
 +  tor_assert(context); /* make sure somebody made it first */
 +  if (!(result->ssl = SSL_new(context->ctx))) {
 +    tls_log_errors(NULL, LOG_WARN, LD_NET, "creating SSL object");
      tor_free(result);
      return NULL;
    }
@@@ -1036,7 -918,7 +1036,7 @@@
  
    if (!SSL_set_cipher_list(result->ssl,
                       isServer ? SERVER_CIPHER_LIST : CLIENT_CIPHER_LIST)) {
 -    tls_log_errors(NULL, LOG_WARN, "setting ciphers");
 +    tls_log_errors(NULL, LOG_WARN, LD_NET, "setting ciphers");
  #ifdef SSL_set_tlsext_host_name
      SSL_set_tlsext_host_name(result->ssl, NULL);
  #endif
@@@ -1049,7 -931,7 +1049,7 @@@
    result->socket = sock;
    bio = BIO_new_socket(sock, BIO_NOCLOSE);
    if (! bio) {
 -    tls_log_errors(NULL, LOG_WARN, "opening BIO");
 +    tls_log_errors(NULL, LOG_WARN, LD_NET, "opening BIO");
  #ifdef SSL_set_tlsext_host_name
      SSL_set_tlsext_host_name(result->ssl, NULL);
  #endif
@@@ -1059,8 -941,8 +1059,8 @@@
    }
    HT_INSERT(tlsmap, &tlsmap_root, result);
    SSL_set_bio(result->ssl, bio, bio);
 -  tor_tls_context_incref(global_tls_context);
 -  result->context = global_tls_context;
 +  tor_tls_context_incref(context);
 +  result->context = context;
    result->state = TOR_TLS_ST_HANDSHAKE;
    result->isServer = isServer;
    result->wantwrite_n = 0;
@@@ -1077,7 -959,7 +1077,7 @@@
  #endif
  
    /* Not expected to get called. */
 -  tls_log_errors(NULL, LOG_WARN, "generating TLS context");
 +  tls_log_errors(NULL, LOG_WARN, LD_NET, "creating tor_tls_t object");
    return result;
  }
  
@@@ -1156,9 -1038,7 +1156,9 @@@ voi
  tor_tls_free(tor_tls_t *tls)
  {
    tor_tls_t *removed;
 -  tor_assert(tls && tls->ssl);
 +  if (!tls)
 +    return;
 +  tor_assert(tls->ssl);
    removed = HT_REMOVE(tlsmap, &tlsmap_root, tls);
    if (!removed) {
      log_warn(LD_BUG, "Freeing a TLS that was not in the ssl->tls map.");
@@@ -1201,7 -1081,7 +1201,7 @@@ tor_tls_read(tor_tls_t *tls, char *cp, 
  #endif
      return r;
    }
 -  err = tor_tls_get_error(tls, r, CATCH_ZERO, "reading", LOG_DEBUG);
 +  err = tor_tls_get_error(tls, r, CATCH_ZERO, "reading", LOG_DEBUG, LD_NET);
    if (err == _TOR_TLS_ZERORETURN || err == TOR_TLS_CLOSE) {
      log_debug(LD_NET,"read returned r=%d; TLS is closed",r);
      tls->state = TOR_TLS_ST_CLOSED;
@@@ -1237,7 -1117,7 +1237,7 @@@ tor_tls_write(tor_tls_t *tls, const cha
      tls->wantwrite_n = 0;
    }
    r = SSL_write(tls->ssl, cp, (int)n);
 -  err = tor_tls_get_error(tls, r, 0, "writing", LOG_INFO);
 +  err = tor_tls_get_error(tls, r, 0, "writing", LOG_INFO, LD_NET);
    if (err == TOR_TLS_DONE) {
      return r;
    }
@@@ -1255,30 -1135,21 +1255,30 @@@ in
  tor_tls_handshake(tor_tls_t *tls)
  {
    int r;
 +  int oldstate;
    tor_assert(tls);
    tor_assert(tls->ssl);
    tor_assert(tls->state == TOR_TLS_ST_HANDSHAKE);
    check_no_tls_errors();
 +  oldstate = tls->ssl->state;
    if (tls->isServer) {
 +    log_debug(LD_HANDSHAKE, "About to call SSL_accept on %p (%s)", tls,
 +              ssl_state_to_string(tls->ssl->state));
      r = SSL_accept(tls->ssl);
    } else {
 +    log_debug(LD_HANDSHAKE, "About to call SSL_connect on %p (%s)", tls,
 +              ssl_state_to_string(tls->ssl->state));
      r = SSL_connect(tls->ssl);
    }
 +  if (oldstate != tls->ssl->state)
 +    log_debug(LD_HANDSHAKE, "After call, %p was in state %s",
 +              tls, ssl_state_to_string(tls->ssl->state));
    /* We need to call this here and not earlier, since OpenSSL has a penchant
     * for clearing its flags when you say accept or connect. */
    tor_tls_unblock_renegotiation(tls);
 -  r = tor_tls_get_error(tls,r,0, "handshaking", LOG_INFO);
 +  r = tor_tls_get_error(tls,r,0, "handshaking", LOG_INFO, LD_HANDSHAKE);
    if (ERR_peek_error() != 0) {
 -    tls_log_errors(tls, tls->isServer ? LOG_INFO : LOG_WARN,
 +    tls_log_errors(tls, tls->isServer ? LOG_INFO : LOG_WARN, LD_HANDSHAKE,
                     "handshaking");
      return TOR_TLS_ERROR_MISC;
    }
@@@ -1299,8 -1170,7 +1299,8 @@@
                     " get set. Fixing that.");
          }
          tls->wasV2Handshake = 1;
 -        log_debug(LD_NET, "Completed V2 TLS handshake with client; waiting "
 +        log_debug(LD_HANDSHAKE,
 +                  "Completed V2 TLS handshake with client; waiting "
                    "for renegotiation.");
        } else {
          tls->wasV2Handshake = 0;
@@@ -1312,13 -1182,10 +1312,13 @@@
        X509 *cert = SSL_get_peer_certificate(tls->ssl);
        STACK_OF(X509) *chain = SSL_get_peer_cert_chain(tls->ssl);
        int n_certs = sk_X509_num(chain);
 -      if (n_certs > 1 || (n_certs == 1 && cert != sk_X509_value(chain, 0)))
 +      if (n_certs > 1 || (n_certs == 1 && cert != sk_X509_value(chain, 0))) {
 +        log_debug(LD_HANDSHAKE, "Server sent back multiple certificates; it "
 +                  "looks like a v1 handshake on %p", tls);
          tls->wasV2Handshake = 0;
 -      else {
 -        log_debug(LD_NET, "Server sent back a single certificate; looks like "
 +      } else {
 +        log_debug(LD_HANDSHAKE,
 +                  "Server sent back a single certificate; looks like "
                    "a v2 handshake on %p.", tls);
          tls->wasV2Handshake = 1;
        }
@@@ -1326,7 -1193,7 +1326,7 @@@
          X509_free(cert);
  #endif
        if (SSL_set_cipher_list(tls->ssl, SERVER_CIPHER_LIST) == 0) {
 -        tls_log_errors(NULL, LOG_WARN, "re-setting ciphers");
 +        tls_log_errors(NULL, LOG_WARN, LD_HANDSHAKE, "re-setting ciphers");
          r = TOR_TLS_ERROR_MISC;
        }
      }
@@@ -1349,8 -1216,7 +1349,8 @@@ tor_tls_renegotiate(tor_tls_t *tls
    if (tls->state != TOR_TLS_ST_RENEGOTIATE) {
      int r = SSL_renegotiate(tls->ssl);
      if (r <= 0) {
 -      return tor_tls_get_error(tls, r, 0, "renegotiating", LOG_WARN);
 +      return tor_tls_get_error(tls, r, 0, "renegotiating", LOG_WARN,
 +                               LD_HANDSHAKE);
      }
      tls->state = TOR_TLS_ST_RENEGOTIATE;
    }
@@@ -1359,8 -1225,7 +1359,8 @@@
      tls->state = TOR_TLS_ST_OPEN;
      return TOR_TLS_DONE;
    } else
 -    return tor_tls_get_error(tls, r, 0, "renegotiating handshake", LOG_INFO);
 +    return tor_tls_get_error(tls, r, 0, "renegotiating handshake", LOG_INFO,
 +                             LD_HANDSHAKE);
  }
  
  /** Shut down an open tls connection <b>tls</b>.  When finished, returns
@@@ -1384,7 -1249,7 +1384,7 @@@ tor_tls_shutdown(tor_tls_t *tls
          r = SSL_read(tls->ssl, buf, 128);
        } while (r>0);
        err = tor_tls_get_error(tls, r, CATCH_ZERO, "reading to shut down",
 -                              LOG_INFO);
 +                              LOG_INFO, LD_NET);
        if (err == _TOR_TLS_ZERORETURN) {
          tls->state = TOR_TLS_ST_GOTCLOSE;
          /* fall through... */
@@@ -1400,7 -1265,7 +1400,7 @@@
        return TOR_TLS_DONE;
      }
      err = tor_tls_get_error(tls, r, CATCH_SYSCALL|CATCH_ZERO, "shutting down",
 -                            LOG_INFO);
 +                            LOG_INFO, LD_NET);
      if (err == _TOR_TLS_SYSCALL) {
        /* The underlying TCP connection closed while we were shutting down. */
        tls->state = TOR_TLS_ST_CLOSED;
@@@ -1432,7 -1297,7 +1432,7 @@@ tor_tls_peer_has_cert(tor_tls_t *tls
  {
    X509 *cert;
    cert = SSL_get_peer_certificate(tls->ssl);
 -  tls_log_errors(tls, LOG_WARN, "getting peer certificate");
 +  tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "getting peer certificate");
    if (!cert)
      return 0;
    X509_free(cert);
@@@ -1459,7 -1324,7 +1459,7 @@@ log_cert_lifetime(X509 *cert, const cha
      log_warn(LD_GENERAL, "Couldn't allocate BIO!"); goto end;
    }
    if (!(ASN1_TIME_print(bio, X509_get_notBefore(cert)))) {
 -    tls_log_errors(NULL, LOG_WARN, "printing certificate lifetime");
 +    tls_log_errors(NULL, LOG_WARN, LD_NET, "printing certificate lifetime");
      goto end;
    }
    BIO_get_mem_ptr(bio, &buf);
@@@ -1467,7 -1332,7 +1467,7 @@@
  
    (void)BIO_reset(bio);
    if (!(ASN1_TIME_print(bio, X509_get_notAfter(cert)))) {
 -    tls_log_errors(NULL, LOG_WARN, "printing certificate lifetime");
 +    tls_log_errors(NULL, LOG_WARN, LD_NET, "printing certificate lifetime");
      goto end;
    }
    BIO_get_mem_ptr(bio, &buf);
@@@ -1481,11 -1346,13 +1481,11 @@@
  
   end:
    /* Not expected to get invoked */
 -  tls_log_errors(NULL, LOG_WARN, "getting certificate lifetime");
 +  tls_log_errors(NULL, LOG_WARN, LD_NET, "getting certificate lifetime");
    if (bio)
      BIO_free(bio);
 -  if (s1)
 -    tor_free(s1);
 -  if (s2)
 -    tor_free(s2);
 +  tor_free(s1);
 +  tor_free(s2);
  }
  
  /** Helper function: try to extract a link certificate and an identity
@@@ -1553,7 -1420,7 +1553,7 @@@ tor_tls_verify(int severity, tor_tls_t 
    if (!(id_pkey = X509_get_pubkey(id_cert)) ||
        X509_verify(cert, id_pkey) <= 0) {
      log_fn(severity,LD_PROTOCOL,"X509_verify on cert and pkey returned <= 0");
 -    tls_log_errors(tls, severity,"verifying certificate");
 +    tls_log_errors(tls, severity, LD_HANDSHAKE, "verifying certificate");
      goto done;
    }
  
@@@ -1572,7 -1439,7 +1572,7 @@@
  
    /* This should never get invoked, but let's make sure in case OpenSSL
     * acts unexpectedly. */
 -  tls_log_errors(tls, LOG_WARN, "finishing tor_tls_verify");
 +  tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "finishing tor_tls_verify");
  
    return r;
  }
@@@ -1611,7 -1478,7 +1611,7 @@@ tor_tls_check_lifetime(tor_tls_t *tls, 
    if (cert)
      X509_free(cert);
    /* Not expected to get invoked */
 -  tls_log_errors(tls, LOG_WARN, "checking certificate lifetime");
 +  tls_log_errors(tls, LOG_WARN, LD_NET, "checking certificate lifetime");
  
    return r;
  }
@@@ -1679,7 -1546,7 +1679,7 @@@ _check_no_tls_errors(const char *fname
      return;
    log(LOG_WARN, LD_CRYPTO, "Unhandled OpenSSL errors found at %s:%d: ",
        tor_fix_source_file(fname), line);
 -  tls_log_errors(NULL, LOG_WARN, NULL);
 +  tls_log_errors(NULL, LOG_WARN, LD_NET, NULL);
  }
  
  /** Return true iff the initial TLS connection at <b>tls</b> did not use a v2
diff --combined src/common/tortls.h
index 955027b,9644b87..55fee81
--- a/src/common/tortls.h
+++ b/src/common/tortls.h
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003, Roger Dingledine
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  #ifndef _TOR_TORTLS_H
@@@ -50,10 -50,7 +50,10 @@@ typedef struct tor_tls_t tor_tls_t
  const char *tor_tls_err_to_string(int err);
  
  void tor_tls_free_all(void);
 -int tor_tls_context_new(crypto_pk_env_t *rsa, unsigned int key_lifetime);
 +int tor_tls_context_init(int is_public_server,
 +                         crypto_pk_env_t *client_identity,
 +                         crypto_pk_env_t *server_identity,
 +                         unsigned int key_lifetime);
  tor_tls_t *tor_tls_new(int sock, int is_server);
  void tor_tls_set_logged_address(tor_tls_t *tls, const char *address);
  void tor_tls_set_renegotiate_callback(tor_tls_t *tls,
diff --combined src/common/util.c
index d511872,2b1430c..4e85977
--- a/src/common/util.c
+++ b/src/common/util.c
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003, Roger Dingledine
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -15,8 -15,7 +15,8 @@@
  
  #include "orconfig.h"
  #include "util.h"
 -#include "log.h"
 +#include "torlog.h"
 +#undef log
  #include "crypto.h"
  #include "torint.h"
  #include "container.h"
@@@ -26,17 -25,11 +26,17 @@@
  #include <io.h>
  #include <direct.h>
  #include <process.h>
 +#include <tchar.h>
  #else
  #include <dirent.h>
  #include <pwd.h>
  #endif
  
 +/* math.h needs this on Linux */
 +#ifndef __USE_ISOC99
 +#define __USE_ISOC99 1
 +#endif
 +#include <math.h>
  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>
@@@ -292,7 -285,7 +292,7 @@@ tor_log_mallinfo(int severity
    struct mallinfo mi;
    memset(&mi, 0, sizeof(mi));
    mi = mallinfo();
 -  log(severity, LD_MM,
 +  tor_log(severity, LD_MM,
        "mallinfo() said: arena=%d, ordblks=%d, smblks=%d, hblks=%d, "
        "hblkhd=%d, usmblks=%d, fsmblks=%d, uordblks=%d, fordblks=%d, "
        "keepcost=%d",
@@@ -315,25 -308,6 +315,25 @@@
   * Math
   * ===== */
  
 +/**
 + * Returns the natural logarithm of d base 2.  We define this wrapper here so
 + * as to make it easier not to conflict with Tor's log() macro.
 + */
 +double
 +tor_mathlog(double d)
 +{
 +  return log(d);
 +}
 +
 +/** Return the long integer closest to d.  We define this wrapper here so
 + * that not all users of math.h need to use the right incancations to get
 + * the c99 functions. */
 +long
 +tor_lround(double d)
 +{
 +  return lround(d);
 +}
 +
  /** Returns floor(log2(u64)).  If u64 is 0, (incorrectly) returns 0. */
  int
  tor_log2(uint64_t u64)
@@@ -378,36 -352,6 +378,36 @@@ round_to_power_of_2(uint64_t u64
      return low;
  }
  
 +/** Return the lowest x such that x is at least <b>number</b>, and x modulo
 + * <b>divisor</b> == 0. */
 +unsigned
 +round_to_next_multiple_of(unsigned number, unsigned divisor)
 +{
 +  number += divisor - 1;
 +  number -= number % divisor;
 +  return number;
 +}
 +
 +/** Return the lowest x such that x is at least <b>number</b>, and x modulo
 + * <b>divisor</b> == 0. */
 +uint32_t
 +round_uint32_to_next_multiple_of(uint32_t number, uint32_t divisor)
 +{
 +  number += divisor - 1;
 +  number -= number % divisor;
 +  return number;
 +}
 +
 +/** Return the lowest x such that x is at least <b>number</b>, and x modulo
 + * <b>divisor</b> == 0. */
 +uint64_t
 +round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor)
 +{
 +  number += divisor - 1;
 +  number -= number % divisor;
 +  return number;
 +}
 +
  /* =====
   * String manipulation
   * ===== */
@@@ -690,29 -634,6 +690,29 @@@ find_whitespace_eos(const char *s, cons
    return s;
  }
  
 +/** Return the first occurrence of <b>needle</b> in <b>haystack</b> that
 + * occurs at the start of a line (that is, at the beginning of <b>haystack</b>
 + * or immediately after a newline).  Return NULL if no such string is found.
 + */
 +const char *
 +find_str_at_start_of_line(const char *haystack, const char *needle)
 +{
 +  size_t needle_len = strlen(needle);
 +
 +  do {
 +    if (!strncmp(haystack, needle, needle_len))
 +      return haystack;
 +
 +    haystack = strchr(haystack, '\n');
 +    if (!haystack)
 +      return NULL;
 +    else
 +      ++haystack;
 +  } while (*haystack);
 +
 +  return NULL;
 +}
 +
  /** Return true iff the 'len' bytes at 'mem' are all zero. */
  int
  tor_mem_is_zero(const char *mem, size_t len)
@@@ -740,13 -661,6 +740,13 @@@ tor_digest_is_zero(const char *digest
    return tor_mem_is_zero(digest, DIGEST_LEN);
  }
  
 +/** Return true iff the DIGEST256_LEN bytes in digest are all zero. */
 +int
 +tor_digest256_is_zero(const char *digest)
 +{
 +  return tor_mem_is_zero(digest, DIGEST256_LEN);
 +}
 +
  /* Helper: common code to check whether the result of a strtol or strtoul or
   * strtoll is correct. */
  #define CHECK_STRTOX_RESULT()                           \
@@@ -798,18 -712,7 +798,18 @@@ tor_parse_ulong(const char *s, int base
    CHECK_STRTOX_RESULT();
  }
  
 -/** As tor_parse_log, but return a unit64_t.  Only base 10 is guaranteed to
 +/** As tor_parse_long(), but return a double. */
 +double
 +tor_parse_double(const char *s, double min, double max, int *ok, char **next)
 +{
 +  char *endptr;
 +  double r;
 +
 +  r = strtod(s, &endptr);
 +  CHECK_STRTOX_RESULT();
 +}
 +
 +/** As tor_parse_long, but return a uint64_t.  Only base 10 is guaranteed to
   * work for now. */
  uint64_t
  tor_parse_uint64(const char *s, int base, uint64_t min,
@@@ -948,9 -851,6 +948,9 @@@ esc_for_log(const char *s
        case '\\':
        case '\"':
        case '\'':
 +      case '\r':
 +      case '\n':
 +      case '\t':
          len += 2;
          break;
        default:
@@@ -1012,7 -912,8 +1012,7 @@@ const char 
  escaped(const char *s)
  {
    static char *_escaped_val = NULL;
 -  if (_escaped_val)
 -    tor_free(_escaped_val);
 +  tor_free(_escaped_val);
  
    if (s)
      _escaped_val = esc_for_log(s);
@@@ -1099,42 -1000,6 +1099,42 @@@ wrap_string(smartlist_t *out, const cha
   * Time
   * ===== */
  
 +/**
 + * Converts struct timeval to a double value.
 + * Preserves microsecond precision, but just barely.
 + * Error is approx +/- 0.1 usec when dealing with epoch values.
 + */
 +double
 +tv_to_double(const struct timeval *tv)
 +{
 +  double conv = tv->tv_sec;
 +  conv += tv->tv_usec/1000000.0;
 +  return conv;
 +}
 +
 +/**
 + * Converts timeval to milliseconds.
 + */
 +int64_t
 +tv_to_msec(const struct timeval *tv)
 +{
 +  int64_t conv = ((int64_t)tv->tv_sec)*1000L;
 +  /* Round ghetto-style */
 +  conv += ((int64_t)tv->tv_usec+500)/1000L;
 +  return conv;
 +}
 +
 +/**
 + * Converts timeval to microseconds.
 + */
 +int64_t
 +tv_to_usec(const struct timeval *tv)
 +{
 +  int64_t conv = ((int64_t)tv->tv_sec)*1000000L;
 +  conv += tv->tv_usec;
 +  return conv;
 +}
 +
  /** Return the number of microseconds elapsed between *start and *end.
   */
  long
@@@ -1144,8 -1009,7 +1144,8 @@@ tv_udiff(const struct timeval *start, c
    long secdiff = end->tv_sec - start->tv_sec;
  
    if (labs(secdiff+1) > LONG_MAX/1000000) {
 -    log_warn(LD_GENERAL, "comparing times too far apart.");
 +    log_warn(LD_GENERAL, "comparing times on microsecond detail too far "
 +             "apart: %ld seconds", secdiff);
      return LONG_MAX;
    }
  
@@@ -1153,26 -1017,6 +1153,26 @@@
    return udiff;
  }
  
 +/** Return the number of milliseconds elapsed between *start and *end.
 + */
 +long
 +tv_mdiff(const struct timeval *start, const struct timeval *end)
 +{
 +  long mdiff;
 +  long secdiff = end->tv_sec - start->tv_sec;
 +
 +  if (labs(secdiff+1) > LONG_MAX/1000) {
 +    log_warn(LD_GENERAL, "comparing times on millisecond detail too far "
 +             "apart: %ld seconds", secdiff);
 +    return LONG_MAX;
 +  }
 +
 +  /* Subtract and round */
 +  mdiff = secdiff*1000L +
 +      ((long)end->tv_usec - (long)start->tv_usec + 500L) / 1000L;
 +  return mdiff;
 +}
 +
  /** Yield true iff <b>y</b> is a leap-year. */
  #define IS_LEAPYEAR(y) (!(y % 4) && ((y % 100) || !(y % 400)))
  /** Helper: Return the number of leap-days between Jan 1, y1 and Jan 1, y2. */
@@@ -1248,7 -1092,7 +1248,7 @@@ format_rfc1123_time(char *buf, time_t t
    memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3);
  }
  
 -/** Parse the the RFC1123 encoding of some time (in GMT) from <b>buf</b>,
 +/** Parse the RFC1123 encoding of some time (in GMT) from <b>buf</b>,
   * and store the result in *<b>t</b>.
   *
   * Return 0 on success, -1 on failure.
@@@ -1586,49 -1430,6 +1586,49 @@@ ftime_definitely_before(time_t now, tim
  }
  
  /* =====
 + * Rate limiting
 + * ===== */
 +
 +/** If the rate-limiter <b>lim</b> is ready at <b>now</b>, return the number
 + * of calls to rate_limit_is_ready (including this one!) since the last time
 + * rate_limit_is_ready returned nonzero.  Otherwise return 0. */
 +static int
 +rate_limit_is_ready(ratelim_t *lim, time_t now)
 +{
 +  if (lim->rate + lim->last_allowed <= now) {
 +    int res = lim->n_calls_since_last_time + 1;
 +    lim->last_allowed = now;
 +    lim->n_calls_since_last_time = 0;
 +    return res;
 +  } else {
 +    ++lim->n_calls_since_last_time;
 +    return 0;
 +  }
 +}
 +
 +/** If the rate-limiter <b>lim</b> is ready at <b>now</b>, return a newly
 + * allocated string indicating how many messages were suppressed, suitable to
 + * append to a log message.  Otherwise return NULL. */
 +char *
 +rate_limit_log(ratelim_t *lim, time_t now)
 +{
 +  int n;
 +  if ((n = rate_limit_is_ready(lim, now))) {
 +    if (n == 1) {
 +      return tor_strdup("");
 +    } else {
 +      char *cp=NULL;
 +      tor_asprintf(&cp,
 +                   " [%d similar message(s) suppressed in last %d seconds]",
 +                   n-1, lim->rate);
 +      return cp;
 +    }
 +  } else {
 +    return NULL;
 +  }
 +}
 +
 +/* =====
   * File helpers
   * ===== */
  
@@@ -1752,22 -1553,22 +1752,22 @@@ check_private_dir(const char *dirname, 
    tor_free(f);
    if (r) {
      if (errno != ENOENT) {
 -      log(LOG_WARN, LD_FS, "Directory %s cannot be read: %s", dirname,
 -          strerror(errno));
 +      log_warn(LD_FS, "Directory %s cannot be read: %s", dirname,
 +               strerror(errno));
        return -1;
      }
      if (check == CPD_NONE) {
 -      log(LOG_WARN, LD_FS, "Directory %s does not exist.", dirname);
 +      log_warn(LD_FS, "Directory %s does not exist.", dirname);
        return -1;
      } else if (check == CPD_CREATE) {
        log_info(LD_GENERAL, "Creating directory %s", dirname);
 -#ifdef MS_WINDOWS
 +#if defined (MS_WINDOWS) && !defined (WINCE)
        r = mkdir(dirname);
  #else
        r = mkdir(dirname, 0700);
  #endif
        if (r) {
 -        log(LOG_WARN, LD_FS, "Error creating directory %s: %s", dirname,
 +        log_warn(LD_FS, "Error creating directory %s: %s", dirname,
              strerror(errno));
          return -1;
        }
@@@ -1777,7 -1578,7 +1777,7 @@@
      return 0;
    }
    if (!(st.st_mode & S_IFDIR)) {
 -    log(LOG_WARN, LD_FS, "%s is not a directory", dirname);
 +    log_warn(LD_FS, "%s is not a directory", dirname);
      return -1;
    }
  #ifndef MS_WINDOWS
@@@ -1790,7 -1591,7 +1790,7 @@@
  
      pw = getpwuid(st.st_uid);
  
 -    log(LOG_WARN, LD_FS, "%s is not owned by this user (%s, %d) but by "
 +    log_warn(LD_FS, "%s is not owned by this user (%s, %d) but by "
          "%s (%d). Perhaps you are running Tor as the wrong user?",
                           dirname, process_ownername, (int)getuid(),
                           pw ? pw->pw_name : "<unknown>", (int)st.st_uid);
@@@ -1799,9 -1600,9 +1799,9 @@@
      return -1;
    }
    if (st.st_mode & 0077) {
 -    log(LOG_WARN, LD_FS, "Fixing permissions on directory %s", dirname);
 +    log_warn(LD_FS, "Fixing permissions on directory %s", dirname);
      if (chmod(dirname, 0700)) {
 -      log(LOG_WARN, LD_FS, "Could not chmod directory %s: %s", dirname,
 +      log_warn(LD_FS, "Could not chmod directory %s: %s", dirname,
            strerror(errno));
        return -1;
      } else {
@@@ -1832,13 -1633,12 +1832,13 @@@ write_str_to_file(const char *fname, co
  }
  
  /** Represents a file that we're writing to, with support for atomic commit:
 - * we can write into a a temporary file, and either remove the file on
 + * we can write into a temporary file, and either remove the file on
   * failure, or replace the original file on success. */
  struct open_file_t {
    char *tempname; /**< Name of the temporary file. */
    char *filename; /**< Name of the original file. */
 -  int rename_on_close; /**< Are we using the temporary file or not? */
 +  unsigned rename_on_close:1; /**< Are we using the temporary file or not? */
 +  unsigned binary:1; /**< Did we open in binary mode? */
    int fd; /**< fd for the open file. */
    FILE *stdio_file; /**< stdio wrapper for <b>fd</b>. */
  };
@@@ -1886,7 -1686,7 +1886,7 @@@ start_writing_to_file(const char *fname
    } else {
      open_name = new_file->tempname = tor_malloc(tempname_len);
      if (tor_snprintf(new_file->tempname, tempname_len, "%s.tmp", fname)<0) {
 -      log(LOG_WARN, LD_GENERAL, "Failed to generate filename");
 +      log_warn(LD_GENERAL, "Failed to generate filename");
        goto err;
      }
      /* We always replace an existing temporary file if there is one. */
@@@ -1894,12 -1694,9 +1894,12 @@@
      open_flags &= ~O_EXCL;
      new_file->rename_on_close = 1;
    }
 +  if (open_flags & O_BINARY)
 +    new_file->binary = 1;
  
 -  if ((new_file->fd = open(open_name, open_flags, mode)) < 0) {
 -    log(LOG_WARN, LD_FS, "Couldn't open \"%s\" (%s) for writing: %s",
 +  new_file->fd = open(open_name, open_flags, mode);
 +  if (new_file->fd < 0) {
 +    log_warn(LD_FS, "Couldn't open \"%s\" (%s) for writing: %s",
          open_name, fname, strerror(errno));
      goto err;
    }
@@@ -1935,8 -1732,7 +1935,8 @@@ fdopen_file(open_file_t *file_data
    if (file_data->stdio_file)
      return file_data->stdio_file;
    tor_assert(file_data->fd >= 0);
 -  if (!(file_data->stdio_file = fdopen(file_data->fd, "a"))) {
 +  if (!(file_data->stdio_file = fdopen(file_data->fd,
 +                                       file_data->binary?"ab":"a"))) {
      log_warn(LD_FS, "Couldn't fdopen \"%s\" [%d]: %s", file_data->filename,
               file_data->fd, strerror(errno));
    }
@@@ -2036,7 -1832,7 +2036,7 @@@ write_chunks_to_file_impl(const char *f
    {
      result = write_all(fd, chunk->bytes, chunk->len, 0);
      if (result < 0) {
 -      log(LOG_WARN, LD_FS, "Error writing to \"%s\": %s", fname,
 +      log_warn(LD_FS, "Error writing to \"%s\": %s", fname,
            strerror(errno));
        goto err;
      }
@@@ -2292,40 -2088,7 +2292,40 @@@ unescape_string(const char *s, char **r
  const char *
  parse_config_line_from_str(const char *line, char **key_out, char **value_out)
  {
 +  /* I believe the file format here is supposed to be:
 +     FILE = (EMPTYLINE | LINE)* (EMPTYLASTLINE | LASTLINE)?
 +
 +     EMPTYLASTLINE = SPACE* | COMMENT
 +     EMPTYLINE = EMPTYLASTLINE NL
 +     SPACE = ' ' | '\r' | '\t'
 +     COMMENT = '#' NOT-NL*
 +     NOT-NL = Any character except '\n'
 +     NL = '\n'
 +
 +     LASTLINE = SPACE* KEY SPACE* VALUES
 +     LINE = LASTLINE NL
 +     KEY = KEYCHAR+
 +     KEYCHAR = Any character except ' ', '\r', '\n', '\t', '#', "\"
 +
 +     VALUES = QUOTEDVALUE | NORMALVALUE
 +     QUOTEDVALUE = QUOTE QVITEM* QUOTE EOLSPACE?
 +     QUOTE = '"'
 +     QVCHAR = KEYCHAR | ESC ('n' | 't' | 'r' | '"' | ESC |'\'' | OCTAL | HEX)
 +     ESC = "\\"
 +     OCTAL = ODIGIT (ODIGIT ODIGIT?)?
 +     HEX = ('x' | 'X') HEXDIGIT HEXDIGIT
 +     ODIGIT = '0' .. '7'
 +     HEXDIGIT = '0'..'9' | 'a' .. 'f' | 'A' .. 'F'
 +     EOLSPACE = SPACE* COMMENT?
 +
 +     NORMALVALUE = (VALCHAR | ESC ESC_IGNORE | CONTINUATION)* EOLSPACE?
 +     VALCHAR = Any character except ESC, '#', and '\n'
 +     ESC_IGNORE = Any character except '#' or '\n'
 +     CONTINUATION = ESC NL ( COMMENT NL )*
 +   */
 +
    const char *key, *val, *cp;
 +  int continuation = 0;
  
    tor_assert(key_out);
    tor_assert(value_out);
@@@ -2349,10 -2112,9 +2349,10 @@@
      return line;
    }
  
 -  /* Skip until the next space. */
 +  /* Skip until the next space or \ followed by newline. */
    key = line;
 -  while (*line && !TOR_ISSPACE(*line) && *line != '#')
 +  while (*line && !TOR_ISSPACE(*line) && *line != '#' &&
 +         ! (line[0] == '\\' && line[1] == '\n'))
      ++line;
    *key_out = tor_strndup(key, line-key);
  
@@@ -2363,7 -2125,7 +2363,7 @@@
    val = line;
  
    /* Find the end of the line. */
 -  if (*line == '\"') {
 +  if (*line == '\"') { // XXX No continuation handling is done here
      if (!(line = unescape_string(line, value_out, NULL)))
         return NULL;
      while (*line == ' ' || *line == '\t')
@@@ -2371,53 -2133,18 +2371,53 @@@
      if (*line && *line != '#' && *line != '\n')
        return NULL;
    } else {
 -    while (*line && *line != '\n' && *line != '#')
 -      ++line;
 +    /* Look for the end of the line. */
 +    while (*line && *line != '\n' && (*line != '#' || continuation)) {
 +      if (*line == '\\' && line[1] == '\n') {
 +        continuation = 1;
 +        line += 2;
 +      } else if (*line == '#') {
 +        do {
 +          ++line;
 +        } while (*line && *line != '\n');
 +        if (*line == '\n')
 +          ++line;
 +      } else {
 +        ++line;
 +      }
 +    }
 +
      if (*line == '\n') {
        cp = line++;
      } else {
        cp = line;
      }
 +    /* Now back cp up to be the last nonspace character */
      while (cp>val && TOR_ISSPACE(*(cp-1)))
        --cp;
  
      tor_assert(cp >= val);
 +
 +    /* Now copy out and decode the value. */
      *value_out = tor_strndup(val, cp-val);
 +    if (continuation) {
 +      char *v_out, *v_in;
 +      v_out = v_in = *value_out;
 +      while (*v_in) {
 +        if (*v_in == '#') {
 +          do {
 +            ++v_in;
 +          } while (*v_in && *v_in != '\n');
 +          if (*v_in == '\n')
 +            ++v_in;
 +        } else if (v_in[0] == '\\' && v_in[1] == '\n') {
 +          v_in += 2;
 +        } else {
 +          *v_out++ = *v_in++;
 +        }
 +      }
 +      *v_out = '\0';
 +    }
    }
  
    if (*line == '#') {
@@@ -2436,22 -2163,19 +2436,22 @@@ char 
  expand_filename(const char *filename)
  {
    tor_assert(filename);
 +#ifdef MS_WINDOWS
 +  return tor_strdup(filename);
 +#else
    if (*filename == '~') {
 -    size_t len;
 -    char *home, *result;
 +    char *home, *result=NULL;
      const char *rest;
  
      if (filename[1] == '/' || filename[1] == '\0') {
        home = getenv("HOME");
        if (!home) {
          log_warn(LD_CONFIG, "Couldn't find $HOME environment variable while "
 -                 "expanding \"%s\"", filename);
 -        return NULL;
 +                 "expanding \"%s\"; defaulting to \"\".", filename);
 +        home = tor_strdup("");
 +      } else {
 +        home = tor_strdup(home);
        }
 -      home = tor_strdup(home);
        rest = strlen(filename)>=2?(filename+2):"";
      } else {
  #ifdef HAVE_PWD_H
@@@ -2478,19 -2202,21 +2478,19 @@@
      if (strlen(home)>1 && !strcmpend(home,PATH_SEPARATOR)) {
        home[strlen(home)-1] = '\0';
      }
 -    /* Plus one for /, plus one for NUL.
 -     * Round up to 16 in case we can't do math. */
 -    len = strlen(home)+strlen(rest)+16;
 -    result = tor_malloc(len);
 -    tor_snprintf(result,len,"%s"PATH_SEPARATOR"%s",home,rest);
 +    tor_asprintf(&result,"%s"PATH_SEPARATOR"%s",home,rest);
      tor_free(home);
      return result;
    } else {
      return tor_strdup(filename);
    }
 +#endif
  }
  
  #define MAX_SCANF_WIDTH 9999
  
 -/** DOCDOC */
 +/** Helper: given an ASCII-encoded decimal digit, return its numeric value.
 + * NOTE: requires that its input be in-bounds. */
  static int
  digit_to_num(char d)
  {
@@@ -2499,10 -2225,7 +2499,10 @@@
    return num;
  }
  
 -/** DOCDOC */
 +/** Helper: Read an unsigned int from *<b>bufp</b> of up to <b>width</b>
 + * characters.  (Handle arbitrary width if <b>width</b> is less than 0.)  On
 + * success, store the result in <b>out</b>, advance bufp to the next
 + * character, and return 0.  On failure, return -1. */
  static int
  scan_unsigned(const char **bufp, unsigned *out, int width)
  {
@@@ -2529,9 -2252,7 +2529,9 @@@
    return 0;
  }
  
 -/** DOCDOC */
 +/** Helper: copy up to <b>width</b> non-space characters from <b>bufp</b> to
 + * <b>out</b>.  Make sure <b>out</b> is nul-terminated. Advance <b>bufp</b>
 + * to the next non-space character or the EOS. */
  static int
  scan_string(const char **bufp, char *out, int width)
  {
@@@ -2620,12 -2341,7 +2620,12 @@@ tor_vsscanf(const char *buf, const cha
   * and store the results in the corresponding argument fields.  Differs from
   * sscanf in that it: Only handles %u and %Ns.  Does not handle arbitrarily
   * long widths. %u does not consume any space.  Is locale-independent.
 - * Returns -1 on malformed patterns. */
 + * Returns -1 on malformed patterns.
 + *
 + * (As with other locale-independent functions, we need this to parse data that
 + * is in ASCII without worrying that the C library's locale-handling will make
 + * miscellaneous characters look like numbers, spaces, and so on.)
 + */
  int
  tor_sscanf(const char *buf, const char *pattern, ...)
  {
@@@ -2646,32 -2362,20 +2646,32 @@@ tor_listdir(const char *dirname
    smartlist_t *result;
  #ifdef MS_WINDOWS
    char *pattern;
 +  TCHAR tpattern[MAX_PATH] = {0};
 +  char name[MAX_PATH] = {0};
    HANDLE handle;
    WIN32_FIND_DATA findData;
    size_t pattern_len = strlen(dirname)+16;
    pattern = tor_malloc(pattern_len);
    tor_snprintf(pattern, pattern_len, "%s\\*", dirname);
 -  if (INVALID_HANDLE_VALUE == (handle = FindFirstFile(pattern, &findData))) {
 +#ifdef UNICODE
 +  mbstowcs(tpattern,pattern,MAX_PATH);
 +#else
 +  strlcpy(tpattern, pattern, MAX_PATH);
 +#endif
 +  if (INVALID_HANDLE_VALUE == (handle = FindFirstFile(tpattern, &findData))) {
      tor_free(pattern);
      return NULL;
    }
    result = smartlist_create();
    while (1) {
 -    if (strcmp(findData.cFileName, ".") &&
 -        strcmp(findData.cFileName, "..")) {
 -      smartlist_add(result, tor_strdup(findData.cFileName));
 +#ifdef UNICODE
 +    wcstombs(name,findData.cFileName,MAX_PATH);
 +#else
 +    strlcpy(name,findData.cFileName,sizeof(name));
 +#endif
 +    if (strcmp(name, ".") &&
 +        strcmp(name, "..")) {
 +      smartlist_add(result, tor_strdup(name));
      }
      if (!FindNextFile(handle, &findData)) {
        DWORD err;
@@@ -2870,18 -2574,3 +2870,18 @@@ write_pidfile(char *filename
    }
  }
  
 +#ifdef MS_WINDOWS
 +HANDLE
 +load_windows_system_library(const TCHAR *library_name)
 +{
 +  TCHAR path[MAX_PATH];
 +  unsigned n;
 +  n = GetSystemDirectory(path, MAX_PATH);
 +  if (n == 0 || n + _tcslen(library_name) + 2 >= MAX_PATH)
 +    return 0;
 +  _tcscat(path, TEXT("\\"));
 +  _tcscat(path, library_name);
 +  return LoadLibrary(path);
 +}
 +#endif
 +
diff --combined src/common/util.h
index 833fd90,fcea9c5..3736237
--- a/src/common/util.h
+++ b/src/common/util.h
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003-2004, Roger Dingledine
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -43,7 -43,7 +43,7 @@@
   * stderr. */
  #define tor_assert(expr) STMT_BEGIN                                     \
      if (PREDICT_UNLIKELY(!(expr))) {                                    \
 -      log(LOG_ERR, LD_BUG, "%s:%d: %s: Assertion %s failed; aborting.", \
 +      log_err(LD_BUG, "%s:%d: %s: Assertion %s failed; aborting.",      \
            _SHORT_FILE_, __LINE__, __func__, #expr);                     \
        fprintf(stderr,"%s:%d %s: Assertion %s failed; aborting.\n",      \
                _SHORT_FILE_, __LINE__, __func__, #expr);                 \
@@@ -152,18 -152,8 +152,18 @@@ void tor_log_mallinfo(int severity)
  #define bool_neq(a,b) (!(a)!=!(b))
  
  /* Math functions */
 +double tor_mathlog(double d) ATTR_CONST;
 +long tor_lround(double d) ATTR_CONST;
  int tor_log2(uint64_t u64) ATTR_CONST;
  uint64_t round_to_power_of_2(uint64_t u64);
 +unsigned round_to_next_multiple_of(unsigned number, unsigned divisor);
 +uint32_t round_uint32_to_next_multiple_of(uint32_t number, uint32_t divisor);
 +uint64_t round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor);
 +
 +/* Compute the CEIL of <b>a</b> divided by <b>b</b>, for nonnegative <b>a</b>
 + * and positive <b>b</b>.  Works on integer types only. Not defined if a+b can
 + * overflow. */
 +#define CEIL_DIV(a,b) (((a)+(b)-1)/(b))
  
  /* String manipulation */
  
@@@ -189,8 -179,6 +189,8 @@@ long tor_parse_long(const char *s, int 
                      long max, int *ok, char **next);
  unsigned long tor_parse_ulong(const char *s, int base, unsigned long min,
                                unsigned long max, int *ok, char **next);
 +double tor_parse_double(const char *s, double min, double max, int *ok,
 +                        char **next);
  uint64_t tor_parse_uint64(const char *s, int base, uint64_t min,
                           uint64_t max, int *ok, char **next);
  const char *hex_str(const char *from, size_t fromlen) ATTR_NONNULL((1));
@@@ -200,11 -188,8 +200,11 @@@ const char *eat_whitespace_no_nl(const 
  const char *eat_whitespace_eos_no_nl(const char *s, const char *eos) ATTR_PURE;
  const char *find_whitespace(const char *s) ATTR_PURE;
  const char *find_whitespace_eos(const char *s, const char *eos) ATTR_PURE;
 +const char *find_str_at_start_of_line(const char *haystack, const char *needle)
 +  ATTR_PURE;
  int tor_mem_is_zero(const char *mem, size_t len) ATTR_PURE;
  int tor_digest_is_zero(const char *digest) ATTR_PURE;
 +int tor_digest256_is_zero(const char *digest) ATTR_PURE;
  char *esc_for_log(const char *string) ATTR_MALLOC;
  const char *escaped(const char *string);
  struct smartlist_t;
@@@ -222,11 -207,7 +222,11 @@@ void base16_encode(char *dest, size_t d
  int base16_decode(char *dest, size_t destlen, const char *src, size_t srclen);
  
  /* Time helpers */
 +double tv_to_double(const struct timeval *tv);
 +int64_t tv_to_msec(const struct timeval *tv);
 +int64_t tv_to_usec(const struct timeval *tv);
  long tv_udiff(const struct timeval *start, const struct timeval *end);
 +long tv_mdiff(const struct timeval *start, const struct timeval *end);
  time_t tor_timegm(struct tm *tm);
  #define RFC1123_TIME_LEN 29
  void format_rfc1123_time(char *buf, time_t t);
@@@ -257,33 -238,6 +257,33 @@@ int ftime_maybe_before(time_t now, time
  int ftime_definitely_after(time_t now, time_t when);
  int ftime_definitely_before(time_t now, time_t when);
  
 +/* Rate-limiter */
 +
 +/** A ratelim_t remembers how often an event is occurring, and how often
 + * it's allowed to occur.  Typical usage is something like:
 + *
 +   <pre>
 +    if (possibly_very_frequent_event()) {
 +      const int INTERVAL = 300;
 +      static ratelim_t warning_limit = RATELIM_INIT(INTERVAL);
 +      char *m;
 +      if ((m = rate_limit_log(&warning_limit, approx_time()))) {
 +        log_warn(LD_GENERAL, "The event occurred!%s", m);
 +        tor_free(m);
 +      }
 +    }
 +   </pre>
 + */
 +typedef struct ratelim_t {
 +  int rate;
 +  time_t last_allowed;
 +  int n_calls_since_last_time;
 +} ratelim_t;
 +
 +#define RATELIM_INIT(r) { (r), 0, 0 }
 +
 +char *rate_limit_log(ratelim_t *lim, time_t now);
 +
  /* File helpers */
  ssize_t write_all(int fd, const char *buf, size_t count, int isSocket);
  ssize_t read_all(int fd, char *buf, size_t count, int isSocket);
@@@ -340,11 -294,5 +340,11 @@@ void start_daemon(void)
  void finish_daemon(const char *desired_cwd);
  void write_pidfile(char *filename);
  
 +#ifdef MS_WINDOWS
 +HANDLE load_windows_system_library(const TCHAR *library_name);
 +#endif
 +
 +const char *libor_get_digests(void);
 +
  #endif
  
diff --combined src/or/buffers.c
index f84fb45,7103746..2a88382
--- a/src/or/buffers.c
+++ b/src/or/buffers.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -12,14 -12,6 +12,14 @@@
   **/
  #define BUFFERS_PRIVATE
  #include "or.h"
 +#include "buffers.h"
 +#include "config.h"
 +#include "connection_edge.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "reasons.h"
 +#include "../common/util.h"
 +#include "../common/torlog.h"
  #ifdef HAVE_UNISTD_H
  #include <unistd.h>
  #endif
@@@ -153,13 -145,10 +153,13 @@@ get_freelist(size_t alloc
  
  /** Deallocate a chunk or put it on a freelist */
  static void
 -chunk_free(chunk_t *chunk)
 +chunk_free_unchecked(chunk_t *chunk)
  {
 -  size_t alloc = CHUNK_ALLOC_SIZE(chunk->memlen);
 -  chunk_freelist_t *freelist = get_freelist(alloc);
 +  size_t alloc;
 +  chunk_freelist_t *freelist;
 +
 +  alloc = CHUNK_ALLOC_SIZE(chunk->memlen);
 +  freelist = get_freelist(alloc);
    if (freelist && freelist->cur_length < freelist->max_length) {
      chunk->next = freelist->head;
      freelist->head = chunk;
@@@ -204,7 -193,7 +204,7 @@@ chunk_new_with_alloc_size(size_t alloc
  }
  #else
  static void
 -chunk_free(chunk_t *chunk)
 +chunk_free_unchecked(chunk_t *chunk)
  {
    tor_free(chunk);
  }
@@@ -270,22 -259,13 +270,22 @@@ buf_shrink_freelists(int free_all
        int n_to_free = free_all ? freelists[i].cur_length :
          (freelists[i].lowest_length - slack);
        int n_to_skip = freelists[i].cur_length - n_to_free;
 +      int orig_length = freelists[i].cur_length;
        int orig_n_to_free = n_to_free, n_freed=0;
        int orig_n_to_skip = n_to_skip;
        int new_length = n_to_skip;
        chunk_t **chp = &freelists[i].head;
        chunk_t *chunk;
        while (n_to_skip) {
 -        tor_assert((*chp)->next);
 +        if (! (*chp)->next) {
 +          log_warn(LD_BUG, "I wanted to skip %d chunks in the freelist for "
 +                   "%d-byte chunks, but only found %d. (Length %d)",
 +                   orig_n_to_skip, (int)freelists[i].alloc_size,
 +                   orig_n_to_skip-n_to_skip, freelists[i].cur_length);
 +          assert_freelist_ok(&freelists[i]);
 +          goto done;
 +        }
 +        // tor_assert((*chp)->next);
          chp = &(*chp)->next;
          --n_to_skip;
        }
@@@ -310,15 -290,13 +310,15 @@@
        }
        // tor_assert(!n_to_free);
        freelists[i].cur_length = new_length;
 -      log_info(LD_MM, "Cleaned freelist for %d-byte chunks: kept %d, "
 -               "dropped %d.",
 -               (int)freelists[i].alloc_size, orig_n_to_skip, orig_n_to_free);
 +      log_info(LD_MM, "Cleaned freelist for %d-byte chunks: original "
 +               "length %d, kept %d, dropped %d.",
 +               (int)freelists[i].alloc_size, orig_length,
 +               orig_n_to_skip, orig_n_to_free);
      }
      freelists[i].lowest_length = freelists[i].cur_length;
      assert_freelist_ok(&freelists[i]);
    }
 + done:
    enable_control_logging();
  #else
    (void) free_all;
@@@ -426,7 -404,7 +426,7 @@@ buf_pullup(buf_t *buf, size_t bytes, in
        dest->next = src->next;
        if (buf->tail == src)
          buf->tail = dest;
 -      chunk_free(src);
 +      chunk_free_unchecked(src);
      } else {
        memcpy(CHUNK_WRITE_PTR(dest), src->data, n);
        dest->datalen += n;
@@@ -472,7 -450,7 +472,7 @@@ buf_remove_from_front(buf_t *buf, size_
        buf->head = victim->next;
        if (buf->tail == victim)
          buf->tail = NULL;
 -      chunk_free(victim);
 +      chunk_free_unchecked(victim);
      }
    }
    check();
@@@ -506,7 -484,7 +506,7 @@@ buf_clear(buf_t *buf
    buf->datalen = 0;
    for (chunk = buf->head; chunk; chunk = next) {
      next = chunk->next;
 -    chunk_free(chunk);
 +    chunk_free_unchecked(chunk);
    }
    buf->head = buf->tail = NULL;
  }
@@@ -545,8 -523,6 +545,8 @@@ buf_slack(const buf_t *buf
  void
  buf_free(buf_t *buf)
  {
 +  if (!buf)
 +    return;
    buf_clear(buf);
    buf->magic = 0xdeadbeef;
    tor_free(buf);
@@@ -1299,43 -1275,6 +1299,43 @@@ fetch_from_buf_http(buf_t *buf
    return 1;
  }
  
 +/**
 + * Wait this many seconds before warning the user about using SOCKS unsafely
 + * again (requires that WarnUnsafeSocks is turned on). */
 +#define SOCKS_WARN_INTERVAL 5
 +
 +/** Warn that the user application has made an unsafe socks request using
 + * protocol <b>socks_protocol</b> on port <b>port</b>.  Don't warn more than
 + * once per SOCKS_WARN_INTERVAL, unless <b>safe_socks</b> is set. */
 +static void
 +log_unsafe_socks_warning(int socks_protocol, const char *address,
 +                         uint16_t port, int safe_socks)
 +{
 +  static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL);
 +
 +  or_options_t *options = get_options();
 +  char *m = NULL;
 +  if (! options->WarnUnsafeSocks)
 +    return;
 +  if (safe_socks || (m = rate_limit_log(&socks_ratelim, approx_time()))) {
 +    log_warn(LD_APP,
 +             "Your application (using socks%d to port %d) is giving "
 +             "Tor only an IP address. Applications that do DNS resolves "
 +             "themselves may leak information. Consider using Socks4A "
 +             "(e.g. via privoxy or socat) instead. For more information, "
 +             "please see https://wiki.torproject.org/TheOnionRouter/"
 +             "TorFAQ#SOCKSAndDNS.%s%s",
 +             socks_protocol,
 +             (int)port,
 +             safe_socks ? " Rejecting." : "",
 +             m ? m : "");
 +    tor_free(m);
 +  }
 +  control_event_client_status(LOG_WARN,
 +                              "DANGEROUS_SOCKS PROTOCOL=SOCKS%d ADDRESS=%s:%d",
 +                              socks_protocol, address, (int)port);
 +}
 +
  /** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one
   * of the forms
   *  - socks4: "socksheader username\\0"
@@@ -1374,6 -1313,10 +1374,6 @@@ fetch_from_buf_socks(buf_t *buf, socks_
    char *next, *startaddr;
    struct in_addr in;
  
 -  /* If the user connects with socks4 or the wrong variant of socks5,
 -   * then log a warning to let him know that it might be unwise. */
 -  static int have_warned_about_unsafe_socks = 0;
 -
    if (buf->datalen < 2) /* version and another byte */
      return 0;
  
@@@ -1453,8 -1396,21 +1453,8 @@@
            req->port = ntohs(get_uint16(buf->head->data+4+addrlen));
            buf_remove_from_front(buf, 6+addrlen);
            if (req->command != SOCKS_COMMAND_RESOLVE_PTR &&
 -              !addressmap_have_mapping(req->address,0) &&
 -              !have_warned_about_unsafe_socks) {
 -            log_warn(LD_APP,
 -                "Your application (using socks5 to port %d) is giving "
 -                "Tor only an IP address. Applications that do DNS resolves "
 -                "themselves may leak information. Consider using Socks4A "
 -                "(e.g. via privoxy or socat) instead. For more information, "
 -                "please see http://wiki.noreply.org/noreply/TheOnionRouter/"
 -                "TorFAQ#SOCKSAndDNS.%s", req->port,
 -                safe_socks ? " Rejecting." : "");
 -            /*have_warned_about_unsafe_socks = 1;*/
 -                                      /*(for now, warn every time)*/
 -            control_event_client_status(LOG_WARN,
 -                          "DANGEROUS_SOCKS PROTOCOL=SOCKS5 ADDRESS=%s:%d",
 -                          req->address, req->port);
 +              !addressmap_have_mapping(req->address,0)) {
 +            log_unsafe_socks_warning(5, req->address, req->port, safe_socks);
              if (safe_socks)
                return -1;
            }
@@@ -1519,8 -1475,8 +1519,8 @@@
          return -1;
        }
  
 -      req->port = ntohs(*(uint16_t*)(buf->head->data+2));
 -      destip = ntohl(*(uint32_t*)(buf->head->data+4));
 +      req->port = ntohs(get_uint16(buf->head->data+2));
 +      destip = ntohl(get_uint32(buf->head->data+4));
        if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) {
          log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting.");
          return -1;
@@@ -1535,8 -1491,7 +1535,8 @@@
            return -1;
          }
          log_debug(LD_APP,
 -                  "socks4: successfully read destip (%s)", safe_str(tmpbuf));
 +                  "socks4: successfully read destip (%s)",
 +                  safe_str_client(tmpbuf));
          socks4_prot = socks4;
        }
  
@@@ -1554,9 -1509,20 +1554,9 @@@
  
        startaddr = NULL;
        if (socks4_prot != socks4a &&
 -          !addressmap_have_mapping(tmpbuf,0) &&
 -          !have_warned_about_unsafe_socks) {
 -        log_warn(LD_APP,
 -                 "Your application (using socks4 to port %d) is giving Tor "
 -                 "only an IP address. Applications that do DNS resolves "
 -                 "themselves may leak information. Consider using Socks4A "
 -                 "(e.g. via privoxy or socat) instead. For more information, "
 -                 "please see http://wiki.noreply.org/noreply/TheOnionRouter/"
 -                 "TorFAQ#SOCKSAndDNS.%s", req->port,
 -                 safe_socks ? " Rejecting." : "");
 -        /*have_warned_about_unsafe_socks = 1;*/  /*(for now, warn every time)*/
 -        control_event_client_status(LOG_WARN,
 -                        "DANGEROUS_SOCKS PROTOCOL=SOCKS4 ADDRESS=%s:%d",
 -                        tmpbuf, req->port);
 +          !addressmap_have_mapping(tmpbuf,0)) {
 +        log_unsafe_socks_warning(4, tmpbuf, req->port, safe_socks);
 +
          if (safe_socks)
            return -1;
        }
@@@ -1648,132 -1614,6 +1648,132 @@@
    }
  }
  
 +/** Inspect a reply from SOCKS server stored in <b>buf</b> according
 + * to <b>state</b>, removing the protocol data upon success. Return 0 on
 + * incomplete response, 1 on success and -1 on error, in which case
 + * <b>reason</b> is set to a descriptive message (free() when finished
 + * with it).
 + *
 + * As a special case, 2 is returned when user/pass is required
 + * during SOCKS5 handshake and user/pass is configured.
 + */
 +int
 +fetch_from_buf_socks_client(buf_t *buf, int state, char **reason)
 +{
 +  unsigned char *data;
 +  size_t addrlen;
 +
 +  if (buf->datalen < 2)
 +    return 0;
 +
 +  buf_pullup(buf, 128, 0);
 +  tor_assert(buf->head && buf->head->datalen >= 2);
 +
 +  data = (unsigned char *) buf->head->data;
 +
 +  switch (state) {
 +    case PROXY_SOCKS4_WANT_CONNECT_OK:
 +      /* Wait for the complete response */
 +      if (buf->head->datalen < 8)
 +        return 0;
 +
 +      if (data[1] != 0x5a) {
 +        *reason = tor_strdup(socks4_response_code_to_string(data[1]));
 +        return -1;
 +      }
 +
 +      /* Success */
 +      buf_remove_from_front(buf, 8);
 +      return 1;
 +
 +    case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE:
 +      /* we don't have any credentials */
 +      if (data[1] != 0x00) {
 +        *reason = tor_strdup("server doesn't support any of our "
 +                             "available authentication methods");
 +        return -1;
 +      }
 +
 +      log_info(LD_NET, "SOCKS 5 client: continuing without authentication");
 +      buf_clear(buf);
 +      return 1;
 +
 +    case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929:
 +      /* we have a username and password. return 1 if we can proceed without
 +       * providing authentication, or 2 otherwise. */
 +      switch (data[1]) {
 +        case 0x00:
 +          log_info(LD_NET, "SOCKS 5 client: we have auth details but server "
 +                            "doesn't require authentication.");
 +          buf_clear(buf);
 +          return 1;
 +        case 0x02:
 +          log_info(LD_NET, "SOCKS 5 client: need authentication.");
 +          buf_clear(buf);
 +          return 2;
 +        /* fall through */
 +      }
 +
 +      *reason = tor_strdup("server doesn't support any of our available "
 +                           "authentication methods");
 +      return -1;
 +
 +    case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK:
 +      /* handle server reply to rfc1929 authentication */
 +      if (data[1] != 0x00) {
 +        *reason = tor_strdup("authentication failed");
 +        return -1;
 +      }
 +
 +      log_info(LD_NET, "SOCKS 5 client: authentication successful.");
 +      buf_clear(buf);
 +      return 1;
 +
 +    case PROXY_SOCKS5_WANT_CONNECT_OK:
 +      /* response is variable length. BND.ADDR, etc, isn't needed
 +       * (don't bother with buf_pullup()), but make sure to eat all
 +       * the data used */
 +
 +      /* wait for address type field to arrive */
 +      if (buf->datalen < 4)
 +        return 0;
 +
 +      switch (data[3]) {
 +        case 0x01: /* ip4 */
 +          addrlen = 4;
 +          break;
 +        case 0x04: /* ip6 */
 +          addrlen = 16;
 +          break;
 +        case 0x03: /* fqdn (can this happen here?) */
 +          if (buf->datalen < 5)
 +            return 0;
 +          addrlen = 1 + data[4];
 +          break;
 +        default:
 +          *reason = tor_strdup("invalid response to connect request");
 +          return -1;
 +      }
 +
 +      /* wait for address and port */
 +      if (buf->datalen < 6 + addrlen)
 +        return 0;
 +
 +      if (data[1] != 0x00) {
 +        *reason = tor_strdup(socks5_response_code_to_string(data[1]));
 +        return -1;
 +      }
 +
 +      buf_remove_from_front(buf, 6 + addrlen);
 +      return 1;
 +  }
 +
 +  /* shouldn't get here... */
 +  tor_assert(0);
 +
 +  return -1;
 +}
 +
  /** Return 1 iff buf looks more like it has an (obsolete) v0 controller
   * command on it than any valid v1 controller command. */
  int
diff --combined src/or/circuitbuild.c
index 2fe750e,e86d3d4..d89de38
--- a/src/or/circuitbuild.c
+++ b/src/or/circuitbuild.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -9,45 -9,9 +9,45 @@@
   * \brief The actual details of building circuits.
   **/
  
 +#define CIRCUIT_PRIVATE
 +
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "directory.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "onion.h"
 +#include "policies.h"
 +#include "relay.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
 +#include "crypto.h"
 +#undef log
 +#include <math.h>
 +
 +#ifndef MIN
 +#define MIN(a,b) ((a)<(b)?(a):(b))
 +#endif
 +
 +#define CBT_BIN_TO_MS(bin) ((bin)*CBT_BIN_WIDTH + (CBT_BIN_WIDTH/2))
  
  /********* START VARIABLES **********/
 +/** Global list of circuit build times */
 +// FIXME: Add this as a member for entry_guard_t instead of global?
 +// Then we could do per-guard statistics, as guards are likely to
 +// vary in their own latency. The downside of this is that guards
 +// can change frequently, so we'd be building a lot more circuits
 +// most likely.
 +circuit_build_times_t circ_times;
  
  /** A global list of all circuits at this hop. */
  extern circuit_t *global_circuitlist;
@@@ -83,10 -47,6 +83,10 @@@ static smartlist_t *entry_guards = NULL
   * and those changes need to be flushed to disk. */
  static int entry_guards_dirty = 0;
  
 +/** If set, we're running the unit tests: we should avoid clobbering
 + * our state file or accessing get_options() or get_or_state() */
 +static int unit_tests = 0;
 +
  /********* END VARIABLES ************/
  
  static int circuit_deliver_create_cell(circuit_t *circ,
@@@ -99,1236 -59,6 +99,1236 @@@ static int onion_append_hop(crypt_path_
  
  static void entry_guards_changed(void);
  
 +static int
 +circuit_build_times_disabled(void)
 +{
 +  if (unit_tests) {
 +    return 0;
 +  } else {
 +    int consensus_disabled = networkstatus_get_param(NULL, "cbtdisabled",
 +                                                     0);
 +    int config_disabled = !get_options()->LearnCircuitBuildTimeout;
 +    int dirauth_disabled = get_options()->AuthoritativeDir;
 +    int state_disabled = (get_or_state()->LastWritten == -1);
 +
 +    if (consensus_disabled || config_disabled || dirauth_disabled ||
 +           state_disabled) {
 +      log_info(LD_CIRC,
 +               "CircuitBuildTime learning is disabled. "
 +               "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d",
 +               consensus_disabled, config_disabled, dirauth_disabled,
 +               state_disabled);
 +      return 1;
 +    } else {
 +      return 0;
 +    }
 +  }
 +}
 +
 +static int32_t
 +circuit_build_times_max_timeouts(void)
 +{
 +  int32_t num = networkstatus_get_param(NULL, "cbtmaxtimeouts",
 +          CBT_DEFAULT_MAX_RECENT_TIMEOUT_COUNT);
 +  return num;
 +}
 +
 +static int32_t
 +circuit_build_times_default_num_xm_modes(void)
 +{
 +  int32_t num = networkstatus_get_param(NULL, "cbtnummodes",
 +          CBT_DEFAULT_NUM_XM_MODES);
 +  return num;
 +}
 +
 +static int32_t
 +circuit_build_times_min_circs_to_observe(void)
 +{
 +  int32_t num = networkstatus_get_param(NULL, "cbtmincircs",
 +                CBT_DEFAULT_MIN_CIRCUITS_TO_OBSERVE);
 +  return num;
 +}
 +
 +/** Return true iff <b>cbt</b> has recorded enough build times that we
 + * want to start acting on the timeout it implies. */
 +int
 +circuit_build_times_enough_to_compute(circuit_build_times_t *cbt)
 +{
 +  return cbt->total_build_times >= circuit_build_times_min_circs_to_observe();
 +}
 +
 +double
 +circuit_build_times_quantile_cutoff(void)
 +{
 +  int32_t num = networkstatus_get_param(NULL, "cbtquantile",
 +                CBT_DEFAULT_QUANTILE_CUTOFF);
 +  return num/100.0;
 +}
 +
 +static double
 +circuit_build_times_close_quantile(void)
 +{
 +  int32_t num = networkstatus_get_param(NULL, "cbtclosequantile",
 +          CBT_DEFAULT_CLOSE_QUANTILE);
 +
 +  return num/100.0;
 +}
 +
 +static int32_t
 +circuit_build_times_test_frequency(void)
 +{
 +  int32_t num = networkstatus_get_param(NULL, "cbttestfreq",
 +                CBT_DEFAULT_TEST_FREQUENCY);
 +  return num;
 +}
 +
 +static int32_t
 +circuit_build_times_min_timeout(void)
 +{
 +  int32_t num = networkstatus_get_param(NULL, "cbtmintimeout",
 +                CBT_DEFAULT_TIMEOUT_MIN_VALUE);
 +  return num;
 +}
 +
 +int32_t
 +circuit_build_times_initial_timeout(void)
 +{
 +  int32_t num = networkstatus_get_param(NULL, "cbtinitialtimeout",
 +                CBT_DEFAULT_TIMEOUT_INITIAL_VALUE);
 +  return num;
 +}
 +
 +static int32_t
 +circuit_build_times_recent_circuit_count(void)
 +{
 +  int32_t num = networkstatus_get_param(NULL, "cbtrecentcount",
 +                CBT_DEFAULT_RECENT_CIRCUITS);
 +  return num;
 +}
 +
 +/**
 + * This function is called when we get a consensus update.
 + *
 + * It checks to see if we have changed any consensus parameters
 + * that require reallocation or discard of previous stats.
 + */
 +void
 +circuit_build_times_new_consensus_params(circuit_build_times_t *cbt,
 +                                         networkstatus_t *ns)
 +{
 +  int32_t num = networkstatus_get_param(ns, "cbtrecentcount",
 +                   CBT_DEFAULT_RECENT_CIRCUITS);
 +
 +  if (num > 0 && num != cbt->liveness.num_recent_circs) {
 +    int8_t *recent_circs;
 +    log_notice(LD_CIRC, "Changing recent timeout size from %d to %d",
 +               cbt->liveness.num_recent_circs, num);
 +
 +    tor_assert(cbt->liveness.timeouts_after_firsthop);
 +
 +    /*
 +     * Technically this is a circular array that we are reallocating
 +     * and memcopying. However, since it only consists of either 1s
 +     * or 0s, and is only used in a statistical test to determine when
 +     * we should discard our history after a sufficient number of 1's
 +     * have been reached, it is fine if order is not preserved or
 +     * elements are lost.
 +     *
 +     * cbtrecentcount should only be changing in cases of severe network
 +     * distress anyway, so memory correctness here is paramount over
 +     * doing acrobatics to preserve the array.
 +     */
 +    recent_circs = tor_malloc_zero(sizeof(int8_t)*num);
 +    memcpy(recent_circs, cbt->liveness.timeouts_after_firsthop,
 +           sizeof(int8_t)*MIN(num, cbt->liveness.num_recent_circs));
 +
 +    // Adjust the index if it needs it.
 +    if (num < cbt->liveness.num_recent_circs) {
 +      cbt->liveness.after_firsthop_idx = MIN(num-1,
 +              cbt->liveness.after_firsthop_idx);
 +    }
 +
 +    tor_free(cbt->liveness.timeouts_after_firsthop);
 +    cbt->liveness.timeouts_after_firsthop = recent_circs;
 +    cbt->liveness.num_recent_circs = num;
 +  }
 +}
 +
 +/** Make a note that we're running unit tests (rather than running Tor
 + * itself), so we avoid clobbering our state file. */
 +void
 +circuitbuild_running_unit_tests(void)
 +{
 +  unit_tests = 1;
 +}
 +
 +/**
 + * Return the initial default or configured timeout in milliseconds
 + */
 +static double
 +circuit_build_times_get_initial_timeout(void)
 +{
 +  double timeout;
 +  if (!unit_tests && get_options()->CircuitBuildTimeout) {
 +    timeout = get_options()->CircuitBuildTimeout*1000;
 +    if (timeout < circuit_build_times_min_timeout()) {
 +      log_warn(LD_CIRC, "Config CircuitBuildTimeout too low. Setting to %ds",
 +               circuit_build_times_min_timeout()/1000);
 +      timeout = circuit_build_times_min_timeout();
 +    }
 +  } else {
 +    timeout = circuit_build_times_initial_timeout();
 +  }
 +  return timeout;
 +}
 +
 +/**
 + * Reset the build time state.
 + *
 + * Leave estimated parameters, timeout and network liveness intact
 + * for future use.
 + */
 +void
 +circuit_build_times_reset(circuit_build_times_t *cbt)
 +{
 +  memset(cbt->circuit_build_times, 0, sizeof(cbt->circuit_build_times));
 +  cbt->total_build_times = 0;
 +  cbt->build_times_idx = 0;
 +  cbt->have_computed_timeout = 0;
 +}
 +
 +/**
 + * Initialize the buildtimes structure for first use.
 + *
 + * Sets the initial timeout values based on either the config setting,
 + * the consensus param, or the default (CBT_DEFAULT_TIMEOUT_INITIAL_VALUE).
 + */
 +void
 +circuit_build_times_init(circuit_build_times_t *cbt)
 +{
 +  memset(cbt, 0, sizeof(*cbt));
 +  cbt->liveness.num_recent_circs = circuit_build_times_recent_circuit_count();
 +  cbt->liveness.timeouts_after_firsthop = tor_malloc_zero(sizeof(int8_t)*
 +                                      cbt->liveness.num_recent_circs);
 +  cbt->close_ms = cbt->timeout_ms = circuit_build_times_get_initial_timeout();
 +  control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET);
 +}
 +
 +#if 0
 +/**
 + * Rewind our build time history by n positions.
 + */
 +static void
 +circuit_build_times_rewind_history(circuit_build_times_t *cbt, int n)
 +{
 +  int i = 0;
 +
 +  cbt->build_times_idx -= n;
 +  cbt->build_times_idx %= CBT_NCIRCUITS_TO_OBSERVE;
 +
 +  for (i = 0; i < n; i++) {
 +    cbt->circuit_build_times[(i+cbt->build_times_idx)
 +                             %CBT_NCIRCUITS_TO_OBSERVE]=0;
 +  }
 +
 +  if (cbt->total_build_times > n) {
 +    cbt->total_build_times -= n;
 +  } else {
 +    cbt->total_build_times = 0;
 +  }
 +
 +  log_info(LD_CIRC,
 +          "Rewound history by %d places. Current index: %d. "
 +          "Total: %d", n, cbt->build_times_idx, cbt->total_build_times);
 +}
 +#endif
 +
 +/**
 + * Add a new build time value <b>time</b> to the set of build times. Time
 + * units are milliseconds.
 + *
 + * circuit_build_times <b>cbt</a> is a circular array, so loop around when
 + * array is full.
 + */
 +int
 +circuit_build_times_add_time(circuit_build_times_t *cbt, build_time_t time)
 +{
 +  if (time <= 0 || time > CBT_BUILD_TIME_MAX) {
 +    log_warn(LD_BUG, "Circuit build time is too large (%u)."
 +                      "This is probably a bug.", time);
 +    tor_fragile_assert();
 +    return -1;
 +  }
 +
 +  log_debug(LD_CIRC, "Adding circuit build time %u", time);
 +
 +  cbt->circuit_build_times[cbt->build_times_idx] = time;
 +  cbt->build_times_idx = (cbt->build_times_idx + 1) % CBT_NCIRCUITS_TO_OBSERVE;
 +  if (cbt->total_build_times < CBT_NCIRCUITS_TO_OBSERVE)
 +    cbt->total_build_times++;
 +
 +  if ((cbt->total_build_times % CBT_SAVE_STATE_EVERY) == 0) {
 +    /* Save state every n circuit builds */
 +    if (!unit_tests && !get_options()->AvoidDiskWrites)
 +      or_state_mark_dirty(get_or_state(), 0);
 +  }
 +
 +  return 0;
 +}
 +
 +/**
 + * Return maximum circuit build time
 + */
 +static build_time_t
 +circuit_build_times_max(circuit_build_times_t *cbt)
 +{
 +  int i = 0;
 +  build_time_t max_build_time = 0;
 +  for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) {
 +    if (cbt->circuit_build_times[i] > max_build_time
 +            && cbt->circuit_build_times[i] != CBT_BUILD_ABANDONED)
 +      max_build_time = cbt->circuit_build_times[i];
 +  }
 +  return max_build_time;
 +}
 +
 +#if 0
 +/** Return minimum circuit build time */
 +build_time_t
 +circuit_build_times_min(circuit_build_times_t *cbt)
 +{
 +  int i = 0;
 +  build_time_t min_build_time = CBT_BUILD_TIME_MAX;
 +  for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) {
 +    if (cbt->circuit_build_times[i] && /* 0 <-> uninitialized */
 +        cbt->circuit_build_times[i] < min_build_time)
 +      min_build_time = cbt->circuit_build_times[i];
 +  }
 +  if (min_build_time == CBT_BUILD_TIME_MAX) {
 +    log_warn(LD_CIRC, "No build times less than CBT_BUILD_TIME_MAX!");
 +  }
 +  return min_build_time;
 +}
 +#endif
 +
 +/**
 + * Calculate and return a histogram for the set of build times.
 + *
 + * Returns an allocated array of histrogram bins representing
 + * the frequency of index*CBT_BIN_WIDTH millisecond
 + * build times. Also outputs the number of bins in nbins.
 + *
 + * The return value must be freed by the caller.
 + */
 +static uint32_t *
 +circuit_build_times_create_histogram(circuit_build_times_t *cbt,
 +                                     build_time_t *nbins)
 +{
 +  uint32_t *histogram;
 +  build_time_t max_build_time = circuit_build_times_max(cbt);
 +  int i, c;
 +
 +  *nbins = 1 + (max_build_time / CBT_BIN_WIDTH);
 +  histogram = tor_malloc_zero(*nbins * sizeof(build_time_t));
 +
 +  // calculate histogram
 +  for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) {
 +    if (cbt->circuit_build_times[i] == 0
 +            || cbt->circuit_build_times[i] == CBT_BUILD_ABANDONED)
 +      continue; /* 0 <-> uninitialized */
 +
 +    c = (cbt->circuit_build_times[i] / CBT_BIN_WIDTH);
 +    histogram[c]++;
 +  }
 +
 +  return histogram;
 +}
 +
 +/**
 + * Return the Pareto start-of-curve parameter Xm.
 + *
 + * Because we are not a true Pareto curve, we compute this as the
 + * weighted average of the N=3 most frequent build time bins.
 + */
 +static build_time_t
 +circuit_build_times_get_xm(circuit_build_times_t *cbt)
 +{
 +  build_time_t i, nbins;
 +  build_time_t *nth_max_bin;
 +  int32_t bin_counts=0;
 +  build_time_t ret = 0;
 +  uint32_t *histogram = circuit_build_times_create_histogram(cbt, &nbins);
 +  int n=0;
 +  int num_modes = circuit_build_times_default_num_xm_modes();
 +
 +  // Only use one mode if < 1000 buildtimes. Not enough data
 +  // for multiple.
 +  if (cbt->total_build_times < CBT_NCIRCUITS_TO_OBSERVE)
 +    num_modes = 1;
 +
 +  nth_max_bin = (build_time_t*)tor_malloc_zero(num_modes*sizeof(build_time_t));
 +
 +  for (i = 0; i < nbins; i++) {
 +    if (histogram[i] >= histogram[nth_max_bin[0]]) {
 +      nth_max_bin[0] = i;
 +    }
 +
 +    for (n = 1; n < num_modes; n++) {
 +      if (histogram[i] >= histogram[nth_max_bin[n]] &&
 +           (!histogram[nth_max_bin[n-1]]
 +               || histogram[i] < histogram[nth_max_bin[n-1]])) {
 +        nth_max_bin[n] = i;
 +      }
 +    }
 +  }
 +
 +  for (n = 0; n < num_modes; n++) {
 +    bin_counts += histogram[nth_max_bin[n]];
 +    ret += CBT_BIN_TO_MS(nth_max_bin[n])*histogram[nth_max_bin[n]];
 +    log_info(LD_CIRC, "Xm mode #%d: %u %u", n, CBT_BIN_TO_MS(nth_max_bin[n]),
 +             histogram[nth_max_bin[n]]);
 +  }
 +
 +  ret /= bin_counts;
 +  tor_free(histogram);
 +  tor_free(nth_max_bin);
 +
 +  return ret;
 +}
 +
 +/**
 + * Output a histogram of current circuit build times to
 + * the or_state_t state structure.
 + */
 +void
 +circuit_build_times_update_state(circuit_build_times_t *cbt,
 +                                 or_state_t *state)
 +{
 +  uint32_t *histogram;
 +  build_time_t i = 0;
 +  build_time_t nbins = 0;
 +  config_line_t **next, *line;
 +
 +  histogram = circuit_build_times_create_histogram(cbt, &nbins);
 +  // write to state
 +  config_free_lines(state->BuildtimeHistogram);
 +  next = &state->BuildtimeHistogram;
 +  *next = NULL;
 +
 +  state->TotalBuildTimes = cbt->total_build_times;
 +  state->CircuitBuildAbandonedCount = 0;
 +
 +  for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) {
 +    if (cbt->circuit_build_times[i] == CBT_BUILD_ABANDONED)
 +      state->CircuitBuildAbandonedCount++;
 +  }
 +
 +  for (i = 0; i < nbins; i++) {
 +    // compress the histogram by skipping the blanks
 +    if (histogram[i] == 0) continue;
 +    *next = line = tor_malloc_zero(sizeof(config_line_t));
 +    line->key = tor_strdup("CircuitBuildTimeBin");
 +    line->value = tor_malloc(25);
 +    tor_snprintf(line->value, 25, "%d %d",
 +            CBT_BIN_TO_MS(i), histogram[i]);
 +    next = &(line->next);
 +  }
 +
 +  if (!unit_tests) {
 +    if (!get_options()->AvoidDiskWrites)
 +      or_state_mark_dirty(get_or_state(), 0);
 +  }
 +
 +  tor_free(histogram);
 +}
 +
 +/**
 + * Shuffle the build times array.
 + *
 + * Stolen from http://en.wikipedia.org/wiki/Fisher\u2013Yates_shuffle
 + */
 +static void
 +circuit_build_times_shuffle_and_store_array(circuit_build_times_t *cbt,
 +                                            build_time_t *raw_times,
 +                                            int num_times)
 +{
 +  int n = num_times;
 +  if (num_times > CBT_NCIRCUITS_TO_OBSERVE) {
 +    log_notice(LD_CIRC, "Decreasing circuit_build_times size from %d to %d",
 +               num_times, CBT_NCIRCUITS_TO_OBSERVE);
 +  }
 +
 +  /* This code can only be run on a compact array */
 +  while (n-- > 1) {
 +    int k = crypto_rand_int(n + 1); /* 0 <= k <= n. */
 +    build_time_t tmp = raw_times[k];
 +    raw_times[k] = raw_times[n];
 +    raw_times[n] = tmp;
 +  }
 +
 +  /* Since the times are now shuffled, take a random CBT_NCIRCUITS_TO_OBSERVE
 +   * subset (ie the first CBT_NCIRCUITS_TO_OBSERVE values) */
 +  for (n = 0; n < MIN(num_times, CBT_NCIRCUITS_TO_OBSERVE); n++) {
 +    circuit_build_times_add_time(cbt, raw_times[n]);
 +  }
 +}
 +
 +/**
 + * Filter old synthetic timeouts that were created before the
 + * new right-censored Pareto calculation was deployed.
 + *
 + * Once all clients before 0.2.1.13-alpha are gone, this code
 + * will be unused.
 + */
 +static int
 +circuit_build_times_filter_timeouts(circuit_build_times_t *cbt)
 +{
 +  int num_filtered=0, i=0;
 +  double timeout_rate = 0;
 +  build_time_t max_timeout = 0;
 +
 +  timeout_rate = circuit_build_times_timeout_rate(cbt);
 +  max_timeout = (build_time_t)cbt->close_ms;
 +
 +  for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) {
 +    if (cbt->circuit_build_times[i] > max_timeout) {
 +      build_time_t replaced = cbt->circuit_build_times[i];
 +      num_filtered++;
 +      cbt->circuit_build_times[i] = CBT_BUILD_ABANDONED;
 +
 +      log_debug(LD_CIRC, "Replaced timeout %d with %d", replaced,
 +               cbt->circuit_build_times[i]);
 +    }
 +  }
 +
 +  log_info(LD_CIRC,
 +           "We had %d timeouts out of %d build times, "
 +           "and filtered %d above the max of %u",
 +          (int)(cbt->total_build_times*timeout_rate),
 +          cbt->total_build_times, num_filtered, max_timeout);
 +
 +  return num_filtered;
 +}
 +
 +/**
 + * Load histogram from <b>state</b>, shuffling the resulting array
 + * after we do so. Use this result to estimate parameters and
 + * calculate the timeout.
 + *
 + * Return -1 on error.
 + */
 +int
 +circuit_build_times_parse_state(circuit_build_times_t *cbt,
 +                                or_state_t *state)
 +{
 +  int tot_values = 0;
 +  uint32_t loaded_cnt = 0, N = 0;
 +  config_line_t *line;
 +  unsigned int i;
 +  build_time_t *loaded_times;
 +  int err = 0;
 +  circuit_build_times_init(cbt);
 +
 +  if (circuit_build_times_disabled()) {
 +    return 0;
 +  }
 +
 +  /* build_time_t 0 means uninitialized */
 +  loaded_times = tor_malloc_zero(sizeof(build_time_t)*state->TotalBuildTimes);
 +
 +  for (line = state->BuildtimeHistogram; line; line = line->next) {
 +    smartlist_t *args = smartlist_create();
 +    smartlist_split_string(args, line->value, " ",
 +                           SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
 +    if (smartlist_len(args) < 2) {
 +      log_warn(LD_GENERAL, "Unable to parse circuit build times: "
 +                           "Too few arguments to CircuitBuildTime");
 +      err = 1;
 +      SMARTLIST_FOREACH(args, char*, cp, tor_free(cp));
 +      smartlist_free(args);
 +      break;
 +    } else {
 +      const char *ms_str = smartlist_get(args,0);
 +      const char *count_str = smartlist_get(args,1);
 +      uint32_t count, k;
 +      build_time_t ms;
 +      int ok;
 +      ms = (build_time_t)tor_parse_ulong(ms_str, 0, 0,
 +                                         CBT_BUILD_TIME_MAX, &ok, NULL);
 +      if (!ok) {
 +        log_warn(LD_GENERAL, "Unable to parse circuit build times: "
 +                             "Unparsable bin number");
 +        err = 1;
 +        SMARTLIST_FOREACH(args, char*, cp, tor_free(cp));
 +        smartlist_free(args);
 +        break;
 +      }
 +      count = (uint32_t)tor_parse_ulong(count_str, 0, 0,
 +                                        UINT32_MAX, &ok, NULL);
 +      if (!ok) {
 +        log_warn(LD_GENERAL, "Unable to parse circuit build times: "
 +                             "Unparsable bin count");
 +        err = 1;
 +        SMARTLIST_FOREACH(args, char*, cp, tor_free(cp));
 +        smartlist_free(args);
 +        break;
 +      }
 +
 +      if (loaded_cnt+count+state->CircuitBuildAbandonedCount
 +            > state->TotalBuildTimes) {
 +        log_warn(LD_CIRC,
 +                 "Too many build times in state file. "
 +                 "Stopping short before %d",
 +                 loaded_cnt+count);
 +        SMARTLIST_FOREACH(args, char*, cp, tor_free(cp));
 +        smartlist_free(args);
 +        break;
 +      }
 +
 +      for (k = 0; k < count; k++) {
 +        loaded_times[loaded_cnt++] = ms;
 +      }
 +      N++;
 +      SMARTLIST_FOREACH(args, char*, cp, tor_free(cp));
 +      smartlist_free(args);
 +    }
 +  }
 +
 +  log_info(LD_CIRC,
 +           "Adding %d timeouts.", state->CircuitBuildAbandonedCount);
 +  for (i=0; i < state->CircuitBuildAbandonedCount; i++) {
 +    loaded_times[loaded_cnt++] = CBT_BUILD_ABANDONED;
 +  }
 +
 +  if (loaded_cnt != state->TotalBuildTimes) {
 +    log_warn(LD_CIRC,
 +            "Corrupt state file? Build times count mismatch. "
 +            "Read %d times, but file says %d", loaded_cnt,
 +            state->TotalBuildTimes);
 +    err = 1;
 +    circuit_build_times_reset(cbt);
 +    goto done;
 +  }
 +
 +  circuit_build_times_shuffle_and_store_array(cbt, loaded_times, loaded_cnt);
 +
 +  /* Verify that we didn't overwrite any indexes */
 +  for (i=0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) {
 +    if (!cbt->circuit_build_times[i])
 +      break;
 +    tot_values++;
 +  }
 +  log_info(LD_CIRC,
 +           "Loaded %d/%d values from %d lines in circuit time histogram",
 +           tot_values, cbt->total_build_times, N);
 +
 +  if (cbt->total_build_times != tot_values
 +        || cbt->total_build_times > CBT_NCIRCUITS_TO_OBSERVE) {
 +    log_warn(LD_CIRC,
 +            "Corrupt state file? Shuffled build times mismatch. "
 +            "Read %d times, but file says %d", tot_values,
 +            state->TotalBuildTimes);
 +    err = 1;
 +    circuit_build_times_reset(cbt);
 +    goto done;
 +  }
 +
 +  circuit_build_times_set_timeout(cbt);
 +
 +  if (!state->CircuitBuildAbandonedCount && cbt->total_build_times) {
 +    circuit_build_times_filter_timeouts(cbt);
 +  }
 +
 + done:
 +  tor_free(loaded_times);
 +  return err ? -1 : 0;
 +}
 +
 +/**
 + * Estimates the Xm and Alpha parameters using
 + * http://en.wikipedia.org/wiki/Pareto_distribution#Parameter_estimation
 + *
 + * The notable difference is that we use mode instead of min to estimate Xm.
 + * This is because our distribution is frechet-like. We claim this is
 + * an acceptable approximation because we are only concerned with the
 + * accuracy of the CDF of the tail.
 + */
 +int
 +circuit_build_times_update_alpha(circuit_build_times_t *cbt)
 +{
 +  build_time_t *x=cbt->circuit_build_times;
 +  double a = 0;
 +  int n=0,i=0,abandoned_count=0;
 +  build_time_t max_time=0;
 +
 +  /* http://en.wikipedia.org/wiki/Pareto_distribution#Parameter_estimation */
 +  /* We sort of cheat here and make our samples slightly more pareto-like
 +   * and less frechet-like. */
 +  cbt->Xm = circuit_build_times_get_xm(cbt);
 +
 +  tor_assert(cbt->Xm > 0);
 +
 +  for (i=0; i< CBT_NCIRCUITS_TO_OBSERVE; i++) {
 +    if (!x[i]) {
 +      continue;
 +    }
 +
 +    if (x[i] < cbt->Xm) {
 +      a += tor_mathlog(cbt->Xm);
 +    } else if (x[i] == CBT_BUILD_ABANDONED) {
 +      abandoned_count++;
 +    } else {
 +      a += tor_mathlog(x[i]);
 +      if (x[i] > max_time)
 +        max_time = x[i];
 +    }
 +    n++;
 +  }
 +
 +  /*
 +   * We are erring and asserting here because this can only happen
 +   * in codepaths other than startup. The startup state parsing code
 +   * performs this same check, and resets state if it hits it. If we
 +   * hit it at runtime, something serious has gone wrong.
 +   */
 +  if (n!=cbt->total_build_times) {
 +    log_err(LD_CIRC, "Discrepancy in build times count: %d vs %d", n,
 +            cbt->total_build_times);
 +  }
 +  tor_assert(n==cbt->total_build_times);
 +
 +  if (max_time <= 0) {
 +    /* This can happen if Xm is actually the *maximum* value in the set.
 +     * It can also happen if we've abandoned every single circuit somehow.
 +     * In either case, tell the caller not to compute a new build timeout. */
 +    log_warn(LD_BUG,
 +             "Could not determine largest build time (%d). "
 +             "Xm is %dms and we've abandoned %d out of %d circuits.", max_time,
 +             cbt->Xm, abandoned_count, n);
 +    return 0;
 +  }
 +
 +  a += abandoned_count*tor_mathlog(max_time);
 +
 +  a -= n*tor_mathlog(cbt->Xm);
 +  // Estimator comes from Eq #4 in:
 +  // "Bayesian estimation based on trimmed samples from Pareto populations"
 +  // by Arturo J. Fernández. We are right-censored only.
 +  a = (n-abandoned_count)/a;
 +
 +  cbt->alpha = a;
 +
 +  return 1;
 +}
 +
 +/**
 + * This is the Pareto Quantile Function. It calculates the point x
 + * in the distribution such that F(x) = quantile (ie quantile*100%
 + * of the mass of the density function is below x on the curve).
 + *
 + * We use it to calculate the timeout and also to generate synthetic
 + * values of time for circuits that timeout before completion.
 + *
 + * See http://en.wikipedia.org/wiki/Quantile_function,
 + * http://en.wikipedia.org/wiki/Inverse_transform_sampling and
 + * http://en.wikipedia.org/wiki/Pareto_distribution#Generating_a_
 + *     random_sample_from_Pareto_distribution
 + * That's right. I'll cite wikipedia all day long.
 + *
 + * Return value is in milliseconds.
 + */
 +double
 +circuit_build_times_calculate_timeout(circuit_build_times_t *cbt,
 +                                      double quantile)
 +{
 +  double ret;
 +  tor_assert(quantile >= 0);
 +  tor_assert(1.0-quantile > 0);
 +  tor_assert(cbt->Xm > 0);
 +
 +  ret = cbt->Xm/pow(1.0-quantile,1.0/cbt->alpha);
 +  if (ret > INT32_MAX) {
 +    ret = INT32_MAX;
 +  }
 +  tor_assert(ret > 0);
 +  return ret;
 +}
 +
 +/** Pareto CDF */
 +double
 +circuit_build_times_cdf(circuit_build_times_t *cbt, double x)
 +{
 +  double ret;
 +  tor_assert(cbt->Xm > 0);
 +  ret = 1.0-pow(cbt->Xm/x,cbt->alpha);
 +  tor_assert(0 <= ret && ret <= 1.0);
 +  return ret;
 +}
 +
 +/**
 + * Generate a synthetic time using our distribution parameters.
 + *
 + * The return value will be within the [q_lo, q_hi) quantile points
 + * on the CDF.
 + */
 +build_time_t
 +circuit_build_times_generate_sample(circuit_build_times_t *cbt,
 +                                    double q_lo, double q_hi)
 +{
 +  double randval = crypto_rand_double();
 +  build_time_t ret;
 +  double u;
 +
 +  /* Generate between [q_lo, q_hi) */
 +  /*XXXX This is what nextafter is supposed to be for; we should use it on the
 +   * platforms that support it. */
 +  q_hi -= 1.0/(INT32_MAX);
 +
 +  tor_assert(q_lo >= 0);
 +  tor_assert(q_hi < 1);
 +  tor_assert(q_lo < q_hi);
 +
 +  u = q_lo + (q_hi-q_lo)*randval;
 +
 +  tor_assert(0 <= u && u < 1.0);
 +  /* circuit_build_times_calculate_timeout returns <= INT32_MAX */
 +  ret = (build_time_t)
 +    tor_lround(circuit_build_times_calculate_timeout(cbt, u));
 +  tor_assert(ret > 0);
 +  return ret;
 +}
 +
 +/**
 + * Estimate an initial alpha parameter by solving the quantile
 + * function with a quantile point and a specific timeout value.
 + */
 +void
 +circuit_build_times_initial_alpha(circuit_build_times_t *cbt,
 +                                  double quantile, double timeout_ms)
 +{
 +  // Q(u) = Xm/((1-u)^(1/a))
 +  // Q(0.8) = Xm/((1-0.8))^(1/a)) = CircBuildTimeout
 +  // CircBuildTimeout = Xm/((1-0.8))^(1/a))
 +  // CircBuildTimeout = Xm*((1-0.8))^(-1/a))
 +  // ln(CircBuildTimeout) = ln(Xm)+ln(((1-0.8)))*(-1/a)
 +  // -ln(1-0.8)/(ln(CircBuildTimeout)-ln(Xm))=a
 +  tor_assert(quantile >= 0);
 +  tor_assert(cbt->Xm > 0);
 +  cbt->alpha = tor_mathlog(1.0-quantile)/
 +    (tor_mathlog(cbt->Xm)-tor_mathlog(timeout_ms));
 +  tor_assert(cbt->alpha > 0);
 +}
 +
 +/**
 + * Returns true if we need circuits to be built
 + */
 +int
 +circuit_build_times_needs_circuits(circuit_build_times_t *cbt)
 +{
 +  /* Return true if < MIN_CIRCUITS_TO_OBSERVE */
 +  return !circuit_build_times_enough_to_compute(cbt);
 +}
 +
 +/**
 + * Returns true if we should build a timeout test circuit
 + * right now.
 + */
 +int
 +circuit_build_times_needs_circuits_now(circuit_build_times_t *cbt)
 +{
 +  return circuit_build_times_needs_circuits(cbt) &&
 +    approx_time()-cbt->last_circ_at > circuit_build_times_test_frequency();
 +}
 +
 +/**
 + * Called to indicate that the network showed some signs of liveness,
 + * i.e. we received a cell.
 + *
 + * This is used by circuit_build_times_network_check_live() to decide
 + * if we should record the circuit build timeout or not.
 + *
 + * This function is called every time we receive a cell. Avoid
 + * syscalls, events, and other high-intensity work.
 + */
 +void
 +circuit_build_times_network_is_live(circuit_build_times_t *cbt)
 +{
 +  time_t now = approx_time();
 +  if (cbt->liveness.nonlive_timeouts > 0) {
 +    log_notice(LD_CIRC,
 +               "Tor now sees network activity. Restoring circuit build "
 +               "timeout recording. Network was down for %d seconds "
 +               "during %d circuit attempts.",
 +               (int)(now - cbt->liveness.network_last_live),
 +               cbt->liveness.nonlive_timeouts);
 +  }
 +  cbt->liveness.network_last_live = now;
 +  cbt->liveness.nonlive_timeouts = 0;
 +}
 +
 +/**
 + * Called to indicate that we completed a circuit. Because this circuit
 + * succeeded, it doesn't count as a timeout-after-the-first-hop.
 + *
 + * This is used by circuit_build_times_network_check_changed() to determine
 + * if we had too many recent timeouts and need to reset our learned timeout
 + * to something higher.
 + */
 +void
 +circuit_build_times_network_circ_success(circuit_build_times_t *cbt)
 +{
 +  cbt->liveness.timeouts_after_firsthop[cbt->liveness.after_firsthop_idx] = 0;
 +  cbt->liveness.after_firsthop_idx++;
 +  cbt->liveness.after_firsthop_idx %= cbt->liveness.num_recent_circs;
 +}
 +
 +/**
 + * A circuit just timed out. If it failed after the first hop, record it
 + * in our history for later deciding if the network speed has changed.
 + *
 + * This is used by circuit_build_times_network_check_changed() to determine
 + * if we had too many recent timeouts and need to reset our learned timeout
 + * to something higher.
 + */
 +static void
 +circuit_build_times_network_timeout(circuit_build_times_t *cbt,
 +                                    int did_onehop)
 +{
 +  if (did_onehop) {
 +    cbt->liveness.timeouts_after_firsthop[cbt->liveness.after_firsthop_idx]=1;
 +    cbt->liveness.after_firsthop_idx++;
 +    cbt->liveness.after_firsthop_idx %= cbt->liveness.num_recent_circs;
 +  }
 +}
 +
 +/**
 + * A circuit was just forcibly closed. If there has been no recent network
 + * activity at all, but this circuit was launched back when we thought the
 + * network was live, increment the number of "nonlive" circuit timeouts.
 + *
 + * This is used by circuit_build_times_network_check_live() to decide
 + * if we should record the circuit build timeout or not.
 + */
 +static void
 +circuit_build_times_network_close(circuit_build_times_t *cbt,
 +                                    int did_onehop, time_t start_time)
 +{
 +  time_t now = time(NULL);
 +  /*
 +   * Check if this is a timeout that was for a circuit that spent its
 +   * entire existence during a time where we have had no network activity.
 +   */
 +  if (cbt->liveness.network_last_live < start_time) {
 +    if (did_onehop) {
 +      char last_live_buf[ISO_TIME_LEN+1];
 +      char start_time_buf[ISO_TIME_LEN+1];
 +      char now_buf[ISO_TIME_LEN+1];
 +      format_local_iso_time(last_live_buf, cbt->liveness.network_last_live);
 +      format_local_iso_time(start_time_buf, start_time);
 +      format_local_iso_time(now_buf, now);
 +      log_warn(LD_BUG,
 +               "Circuit somehow completed a hop while the network was "
 +               "not live. Network was last live at %s, but circuit launched "
 +               "at %s. It's now %s.", last_live_buf, start_time_buf,
 +               now_buf);
 +    }
 +    cbt->liveness.nonlive_timeouts++;
 +    if (cbt->liveness.nonlive_timeouts == 1) {
 +      log_notice(LD_CIRC,
 +                 "Tor has not observed any network activity for the past %d "
 +                 "seconds. Disabling circuit build timeout code.",
 +                 (int)(now - cbt->liveness.network_last_live));
 +    } else {
 +      log_info(LD_CIRC,
 +             "Got non-live timeout. Current count is: %d",
 +             cbt->liveness.nonlive_timeouts);
 +    }
 +  }
 +}
 +
 +/**
 + * When the network is not live, we do not record circuit build times.
 + *
 + * The network is considered not live if there has been at least one
 + * circuit build that began and ended (had its close_ms measurement
 + * period expire) since we last received a cell.
 + *
 + * Also has the side effect of rewinding the circuit time history
 + * in the case of recent liveness changes.
 + */
 +int
 +circuit_build_times_network_check_live(circuit_build_times_t *cbt)
 +{
 +  if (cbt->liveness.nonlive_timeouts > 0) {
 +    return 0;
 +  }
 +
 +  return 1;
 +}
 +
 +/**
 + * Returns true if we have seen more than MAX_RECENT_TIMEOUT_COUNT of
 + * the past RECENT_CIRCUITS time out after the first hop. Used to detect
 + * if the network connection has changed significantly, and if so,
 + * resets our circuit build timeout to the default.
 + *
 + * Also resets the entire timeout history in this case and causes us
 + * to restart the process of building test circuits and estimating a
 + * new timeout.
 + */
 +int
 +circuit_build_times_network_check_changed(circuit_build_times_t *cbt)
 +{
 +  int total_build_times = cbt->total_build_times;
 +  int timeout_count=0;
 +  int i;
 +
 +  /* how many of our recent circuits made it to the first hop but then
 +   * timed out? */
 +  for (i = 0; i < cbt->liveness.num_recent_circs; i++) {
 +    timeout_count += cbt->liveness.timeouts_after_firsthop[i];
 +  }
 +
 +  /* If 80% of our recent circuits are timing out after the first hop,
 +   * we need to re-estimate a new initial alpha and timeout. */
 +  if (timeout_count < circuit_build_times_max_timeouts()) {
 +    return 0;
 +  }
 +
 +  circuit_build_times_reset(cbt);
 +  memset(cbt->liveness.timeouts_after_firsthop, 0,
 +          sizeof(*cbt->liveness.timeouts_after_firsthop)*
 +          cbt->liveness.num_recent_circs);
 +  cbt->liveness.after_firsthop_idx = 0;
 +
 +  /* Check to see if this has happened before. If so, double the timeout
 +   * to give people on abysmally bad network connections a shot at access */
 +  if (cbt->timeout_ms >= circuit_build_times_get_initial_timeout()) {
 +    if (cbt->timeout_ms > INT32_MAX/2 || cbt->close_ms > INT32_MAX/2) {
 +      log_warn(LD_CIRC, "Insanely large circuit build timeout value. "
 +              "(timeout = %lfmsec, close = %lfmsec)",
 +               cbt->timeout_ms, cbt->close_ms);
 +    } else {
 +      cbt->timeout_ms *= 2;
 +      cbt->close_ms *= 2;
 +    }
 +  } else {
 +    cbt->close_ms = cbt->timeout_ms
 +                  = circuit_build_times_get_initial_timeout();
 +  }
 +
 +  control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_RESET);
 +
 +  log_notice(LD_CIRC,
 +            "Network connection speed appears to have changed. Resetting "
 +            "timeout to %lds after %d timeouts and %d buildtimes.",
 +            tor_lround(cbt->timeout_ms/1000), timeout_count,
 +            total_build_times);
 +
 +  return 1;
 +}
 +
 +/**
 + * Count the number of timeouts in a set of cbt data.
 + */
 +double
 +circuit_build_times_timeout_rate(const circuit_build_times_t *cbt)
 +{
 +  int i=0,timeouts=0;
 +  for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) {
 +    if (cbt->circuit_build_times[i] >= cbt->timeout_ms) {
 +       timeouts++;
 +    }
 +  }
 +
 +  if (!cbt->total_build_times)
 +    return 0;
 +
 +  return ((double)timeouts)/cbt->total_build_times;
 +}
 +
 +/**
 + * Count the number of closed circuits in a set of cbt data.
 + */
 +double
 +circuit_build_times_close_rate(const circuit_build_times_t *cbt)
 +{
 +  int i=0,closed=0;
 +  for (i = 0; i < CBT_NCIRCUITS_TO_OBSERVE; i++) {
 +    if (cbt->circuit_build_times[i] == CBT_BUILD_ABANDONED) {
 +       closed++;
 +    }
 +  }
 +
 +  if (!cbt->total_build_times)
 +    return 0;
 +
 +  return ((double)closed)/cbt->total_build_times;
 +}
 +
 +/**
 + * Store a timeout as a synthetic value.
 + *
 + * Returns true if the store was successful and we should possibly
 + * update our timeout estimate.
 + */
 +int
 +circuit_build_times_count_close(circuit_build_times_t *cbt,
 +                                int did_onehop,
 +                                time_t start_time)
 +{
 +  if (circuit_build_times_disabled()) {
 +    cbt->close_ms = cbt->timeout_ms
 +                  = circuit_build_times_get_initial_timeout();
 +    return 0;
 +  }
 +
 +  /* Record this force-close to help determine if the network is dead */
 +  circuit_build_times_network_close(cbt, did_onehop, start_time);
 +
 +  /* Only count timeouts if network is live.. */
 +  if (!circuit_build_times_network_check_live(cbt)) {
 +    return 0;
 +  }
 +
 +  circuit_build_times_add_time(cbt, CBT_BUILD_ABANDONED);
 +  return 1;
 +}
 +
 +/**
 + * Update timeout counts to determine if we need to expire
 + * our build time history due to excessive timeouts.
 + *
 + * We do not record any actual time values at this stage;
 + * we are only interested in recording the fact that a timeout
 + * happened. We record the time values via
 + * circuit_build_times_count_close() and circuit_build_times_add_time().
 + */
 +void
 +circuit_build_times_count_timeout(circuit_build_times_t *cbt,
 +                                  int did_onehop)
 +{
 +  if (circuit_build_times_disabled()) {
 +    cbt->close_ms = cbt->timeout_ms
 +                  = circuit_build_times_get_initial_timeout();
 +    return;
 +  }
 +
 +  /* Register the fact that a timeout just occurred. */
 +  circuit_build_times_network_timeout(cbt, did_onehop);
 +
 +  /* If there are a ton of timeouts, we should reset
 +   * the circuit build timeout. */
 +  circuit_build_times_network_check_changed(cbt);
 +}
 +
 +/**
 + * Estimate a new timeout based on history and set our timeout
 + * variable accordingly.
 + */
 +static int
 +circuit_build_times_set_timeout_worker(circuit_build_times_t *cbt)
 +{
 +  build_time_t max_time;
 +  if (!circuit_build_times_enough_to_compute(cbt))
 +    return 0;
 +
 +  if (!circuit_build_times_update_alpha(cbt))
 +    return 0;
 +
 +  cbt->timeout_ms = circuit_build_times_calculate_timeout(cbt,
 +                                circuit_build_times_quantile_cutoff());
 +
 +  cbt->close_ms = circuit_build_times_calculate_timeout(cbt,
 +                                circuit_build_times_close_quantile());
 +
 +  max_time = circuit_build_times_max(cbt);
 +
 +  /* Sometimes really fast guard nodes give us such a steep curve
 +   * that this ends up being not that much greater than timeout_ms.
 +   * Make it be at least 1 min to handle this case. */
 +  cbt->close_ms = MAX(cbt->close_ms, circuit_build_times_initial_timeout());
 +
 +  if (cbt->timeout_ms > max_time) {
 +    log_notice(LD_CIRC,
 +               "Circuit build timeout of %dms is beyond the maximum build "
 +               "time we have ever observed. Capping it to %dms.",
 +               (int)cbt->timeout_ms, max_time);
 +    cbt->timeout_ms = max_time;
 +  }
 +
 +  if (max_time < INT32_MAX/2 && cbt->close_ms > 2*max_time) {
 +    log_notice(LD_CIRC,
 +               "Circuit build measurement period of %dms is more than twice "
 +               "the maximum build time we have ever observed. Capping it to "
 +               "%dms.", (int)cbt->close_ms, 2*max_time);
 +    cbt->close_ms = 2*max_time;
 +  }
 +
 +  cbt->have_computed_timeout = 1;
 +  return 1;
 +}
 +
 +/**
 + * Exposed function to compute a new timeout. Dispatches events and
 + * also filters out extremely high timeout values.
 + */
 +void
 +circuit_build_times_set_timeout(circuit_build_times_t *cbt)
 +{
 +  long prev_timeout = tor_lround(cbt->timeout_ms/1000);
 +  double timeout_rate;
 +
 +  if (!circuit_build_times_set_timeout_worker(cbt))
 +    return;
 +
 +  if (cbt->timeout_ms < circuit_build_times_min_timeout()) {
 +    log_warn(LD_CIRC, "Set buildtimeout to low value %lfms. Setting to %dms",
 +             cbt->timeout_ms, circuit_build_times_min_timeout());
 +    cbt->timeout_ms = circuit_build_times_min_timeout();
 +    if (cbt->close_ms < cbt->timeout_ms) {
 +      /* This shouldn't happen because of MAX() in timeout_worker above,
 +       * but doing it just in case */
 +      cbt->close_ms = circuit_build_times_initial_timeout();
 +    }
 +  }
 +
 +  control_event_buildtimeout_set(cbt, BUILDTIMEOUT_SET_EVENT_COMPUTED);
 +
 +  timeout_rate = circuit_build_times_timeout_rate(cbt);
 +
 +  if (prev_timeout > tor_lround(cbt->timeout_ms/1000)) {
 +    log_notice(LD_CIRC,
 +               "Based on %d circuit times, it looks like we don't need to "
 +               "wait so long for circuits to finish. We will now assume a "
 +               "circuit is too slow to use after waiting %ld seconds.",
 +               cbt->total_build_times,
 +               tor_lround(cbt->timeout_ms/1000));
 +    log_info(LD_CIRC,
 +             "Circuit timeout data: %lfms, %lfms, Xm: %d, a: %lf, r: %lf",
 +             cbt->timeout_ms, cbt->close_ms, cbt->Xm, cbt->alpha,
 +             timeout_rate);
 +  } else if (prev_timeout < tor_lround(cbt->timeout_ms/1000)) {
 +    log_notice(LD_CIRC,
 +               "Based on %d circuit times, it looks like we need to wait "
 +               "longer for circuits to finish. We will now assume a "
 +               "circuit is too slow to use after waiting %ld seconds.",
 +               cbt->total_build_times,
 +               tor_lround(cbt->timeout_ms/1000));
 +    log_info(LD_CIRC,
 +             "Circuit timeout data: %lfms, %lfms, Xm: %d, a: %lf, r: %lf",
 +             cbt->timeout_ms, cbt->close_ms, cbt->Xm, cbt->alpha,
 +             timeout_rate);
 +  } else {
 +    log_info(LD_CIRC,
 +             "Set circuit build timeout to %lds (%lfms, %lfms, Xm: %d, a: %lf,"
 +             " r: %lf) based on %d circuit times",
 +             tor_lround(cbt->timeout_ms/1000),
 +             cbt->timeout_ms, cbt->close_ms, cbt->Xm, cbt->alpha, timeout_rate,
 +             cbt->total_build_times);
 +  }
 +}
 +
  /** Iterate over values of circ_id, starting from conn-\>next_circ_id,
   * and with the high bit specified by conn-\>circ_id_type, until we get
   * a circ_id that is not in use by any other circuit on that conn.
@@@ -1382,21 -112,21 +1382,21 @@@ circuit_list_path_impl(origin_circuit_
    crypt_path_t *hop;
    smartlist_t *elements;
    const char *states[] = {"closed", "waiting for keys", "open"};
 -  char buf[128];
    char *s;
  
    elements = smartlist_create();
  
    if (verbose) {
      const char *nickname = build_state_get_exit_nickname(circ->build_state);
 -    tor_snprintf(buf, sizeof(buf), "%s%s circ (length %d%s%s):",
 +    char *cp;
 +    tor_asprintf(&cp, "%s%s circ (length %d%s%s):",
                   circ->build_state->is_internal ? "internal" : "exit",
                   circ->build_state->need_uptime ? " (high-uptime)" : "",
                   circ->build_state->desired_path_len,
                   circ->_base.state == CIRCUIT_STATE_OPEN ? "" : ", exit ",
                   circ->_base.state == CIRCUIT_STATE_OPEN ? "" :
                   (nickname?nickname:"*unnamed*"));
 -    smartlist_add(elements, tor_strdup(buf));
 +    smartlist_add(elements, cp);
    }
  
    hop = circ->cpath;
@@@ -1418,7 -148,8 +1418,7 @@@
          router_get_verbose_nickname(elt, ri);
        } else if ((rs = router_get_consensus_status_by_id(id))) {
          routerstatus_get_verbose_nickname(elt, rs);
 -      } else if (hop->extend_info->nickname &&
 -                 is_legal_nickname(hop->extend_info->nickname)) {
 +      } else if (is_legal_nickname(hop->extend_info->nickname)) {
          elt[0] = '$';
          base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN);
          elt[HEX_DIGEST_LEN+1]= '~';
@@@ -1486,7 -217,7 +1486,7 @@@ voi
  circuit_log_path(int severity, unsigned int domain, origin_circuit_t *circ)
  {
    char *s = circuit_list_path(circ,1);
 -  log(severity,domain,"%s",s);
 +  tor_log(severity,domain,"%s",s);
    tor_free(s);
  }
  
@@@ -1538,7 -269,7 +1538,7 @@@ static in
  onion_populate_cpath(origin_circuit_t *circ)
  {
    int r;
 -again:
 + again:
    r = onion_extend_cpath(circ);
    if (r < 0) {
      log_info(LD_CIRC,"Generating cpath hop failed.");
@@@ -1630,10 -361,9 +1630,10 @@@ circuit_handle_first_hop(origin_circuit
  
    if (!n_conn) {
      /* not currently connected in a useful way. */
 -    const char *name = firsthop->extend_info->nickname ?
 +    const char *name = strlen(firsthop->extend_info->nickname) ?
        firsthop->extend_info->nickname : fmt_addr(&firsthop->extend_info->addr);
 -    log_info(LD_CIRC, "Next router is %s: %s ", safe_str(name), msg?msg:"???");
 +    log_info(LD_CIRC, "Next router is %s: %s ",
 +             safe_str_client(name), msg?msg:"???");
      circ->_base.n_hop = extend_info_dup(firsthop->extend_info);
  
      if (should_launch) {
@@@ -1776,8 -506,7 +1776,8 @@@ circuit_deliver_create_cell(circuit_t *
    cell.circ_id = circ->n_circ_id;
  
    memcpy(cell.payload, payload, ONIONSKIN_CHALLENGE_LEN);
 -  append_cell_to_circuit_queue(circ, circ->n_conn, &cell, CELL_DIRECTION_OUT);
 +  append_cell_to_circuit_queue(circ, circ->n_conn, &cell,
 +                               CELL_DIRECTION_OUT, 0);
  
    if (CIRCUIT_IS_ORIGIN(circ)) {
      /* mark it so it gets better rate limiting treatment. */
@@@ -1807,7 -536,7 +1807,7 @@@ inform_testing_reachability(void
                                  "CHECKING_REACHABILITY DIRADDRESS=%s:%d",
                                  me->address, me->dir_port);
    }
 -  log(LOG_NOTICE, LD_OR, "Now checking whether ORPort %s:%d%s %s reachable... "
 +  log_notice(LD_OR, "Now checking whether ORPort %s:%d%s %s reachable... "
                           "(this may take up to %d minutes -- look for log "
                           "messages indicating success)",
        me->address, me->or_port,
@@@ -1840,18 -569,6 +1840,18 @@@ should_use_create_fast_for_circuit(orig
    return 1;
  }
  
 +/** Return true if <b>circ</b> is the type of circuit we want to count
 + * timeouts from. In particular, we want it to have not completed yet
 + * (already completing indicates we cannibalized it), and we want it to
 + * have exactly three hops.
 + */
 +int
 +circuit_timeout_want_to_count_circ(origin_circuit_t *circ)
 +{
 +  return !circ->has_opened
 +          && circ->build_state->desired_path_len == DEFAULT_ROUTE_LEN;
 +}
 +
  /** This is the backbone function for building circuits.
   *
   * If circ's first hop is closed, then we need to build a create
@@@ -1925,42 -642,15 +1925,42 @@@ circuit_send_next_onion_skin(origin_cir
      if (!hop) {
        /* done building the circuit. whew. */
        circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN);
 +      if (circuit_timeout_want_to_count_circ(circ)) {
 +        struct timeval end;
 +        long timediff;
 +        tor_gettimeofday(&end);
 +        timediff = tv_mdiff(&circ->_base.highres_created, &end);
 +
 +        /*
 +         * If the circuit build time is much greater than we would have cut
 +         * it off at, we probably had a suspend event along this codepath,
 +         * and we should discard the value.
 +         */
 +        if (timediff < 0 || timediff > 2*circ_times.close_ms+1000) {
 +          log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. "
 +                              "Assuming clock jump. Purpose %d", timediff,
 +                              circ->_base.purpose);
 +        } else if (!circuit_build_times_disabled()) {
 +          /* Only count circuit times if the network is live */
 +          if (circuit_build_times_network_check_live(&circ_times)) {
 +            circuit_build_times_add_time(&circ_times, (build_time_t)timediff);
 +            circuit_build_times_set_timeout(&circ_times);
 +          }
 +
 +          if (circ->_base.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
 +            circuit_build_times_network_circ_success(&circ_times);
 +          }
 +        }
 +      }
        log_info(LD_CIRC,"circuit built!");
        circuit_reset_failure_count(0);
        if (circ->build_state->onehop_tunnel)
          control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0);
 -      if (!has_completed_circuit && !circ->build_state->onehop_tunnel) {
 +      if (!can_complete_circuit && !circ->build_state->onehop_tunnel) {
          or_options_t *options = get_options();
 -        has_completed_circuit=1;
 +        can_complete_circuit=1;
          /* FFFF Log a count of known routers here */
 -        log(LOG_NOTICE, LD_GENERAL,
 +        log_notice(LD_GENERAL,
              "Tor has successfully opened a circuit. "
              "Looks like client functionality is working.");
          control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0);
@@@ -1972,10 -662,6 +1972,10 @@@
        }
        circuit_rep_hist_note_result(circ);
        circuit_has_opened(circ); /* do other actions as necessary */
 +
 +      /* We're done with measurement circuits here. Just close them */
 +      if (circ->_base.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT)
 +        circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
        return 0;
      }
  
@@@ -2019,13 -705,13 +2019,13 @@@ voi
  circuit_note_clock_jumped(int seconds_elapsed)
  {
    int severity = server_mode(get_options()) ? LOG_WARN : LOG_NOTICE;
 -  log(severity, LD_GENERAL, "Your system clock just jumped %d seconds %s; "
 +  tor_log(severity, LD_GENERAL, "Your system clock just jumped %d seconds %s; "
        "assuming established circuits no longer work.",
        seconds_elapsed >=0 ? seconds_elapsed : -seconds_elapsed,
        seconds_elapsed >=0 ? "forward" : "backward");
    control_event_general_status(LOG_WARN, "CLOCK_JUMPED TIME=%d",
                                 seconds_elapsed);
 -  has_completed_circuit=0; /* so it'll log when it works again */
 +  can_complete_circuit=0; /* so it'll log when it works again */
    control_event_client_status(severity, "CIRCUIT_NOT_ESTABLISHED REASON=%s",
                                "CLOCK_JUMPED");
    circuit_mark_all_unused_circs();
@@@ -2257,9 -943,10 +2257,9 @@@ circuit_finish_handshake(origin_circuit
      return -END_CIRC_REASON_TORPROTOCOL;
    }
  
 -  if (hop->dh_handshake_state) {
 -    crypto_dh_free(hop->dh_handshake_state); /* don't need it anymore */
 -    hop->dh_handshake_state = NULL;
 -  }
 +  crypto_dh_free(hop->dh_handshake_state); /* don't need it anymore */
 +  hop->dh_handshake_state = NULL;
 +
    memset(hop->fast_handshake_state, 0, sizeof(hop->fast_handshake_state));
  
    if (circuit_init_cpath_crypto(hop, keys, 0)<0) {
@@@ -2347,8 -1034,8 +2347,8 @@@ onionskin_answer(or_circuit_t *circ, ui
           cell_type == CELL_CREATED ? ONIONSKIN_REPLY_LEN : DIGEST_LEN*2);
  
    log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.",
 -            (unsigned int)*(uint32_t*)(keys),
 -            (unsigned int)*(uint32_t*)(keys+20));
 +            (unsigned int)get_uint32(keys),
 +            (unsigned int)get_uint32(keys+20));
    if (circuit_init_cpath_crypto(tmp_cpath, keys, 0)<0) {
      log_warn(LD_BUG,"Circuit initialization failed");
      tor_free(tmp_cpath);
@@@ -2369,7 -1056,7 +2369,7 @@@
    circ->is_first_hop = (cell_type == CELL_CREATED_FAST);
  
    append_cell_to_circuit_queue(TO_CIRCUIT(circ),
 -                               circ->p_conn, &cell, CELL_DIRECTION_IN);
 +                               circ->p_conn, &cell, CELL_DIRECTION_IN, 0);
    log_debug(LD_CIRC,"Finished sending 'created' cell.");
  
    if (!is_local_addr(&circ->p_conn->_base.addr) &&
@@@ -2398,7 -1085,7 +2398,7 @@@ new_route_len(uint8_t purpose, extend_i
  
    tor_assert(routers);
  
 -  routelen = 3;
 +  routelen = DEFAULT_ROUTE_LEN;
    if (exit &&
        purpose != CIRCUIT_PURPOSE_TESTING &&
        purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)
@@@ -2463,8 -1150,6 +2463,8 @@@ circuit_all_predicted_ports_handled(tim
    smartlist_t *LongLivedServices = get_options()->LongLivedPorts;
    tor_assert(need_uptime);
    tor_assert(need_capacity);
 +  // Always predict need_capacity
 +  *need_capacity = 1;
    enough = (smartlist_len(sl) == 0);
    for (i = 0; i < smartlist_len(sl); ++i) {
      port = smartlist_get(sl, i);
@@@ -2487,8 -1172,6 +2487,8 @@@ router_handles_some_port(routerinfo_t *
  
    for (i = 0; i < smartlist_len(needed_ports); ++i) {
      addr_policy_result_t r;
 +    /* alignment issues aren't a worry for this dereference, since
 +       needed_ports is explicitly a smartlist of uint16_t's */
      port = *(uint16_t *)smartlist_get(needed_ports, i);
      tor_assert(port);
      r = compare_addr_to_addr_policy(0, port, router->exit_policy);
@@@ -2571,16 -1254,9 +2571,16 @@@ choose_good_exit_server_general(routerl
        n_supported[i] = -1;
        continue; /* skip routers that are known to be down or bad exits */
      }
 -    if (router_is_unreliable(router, need_uptime, need_capacity, 0)) {
 +    if (router_is_unreliable(router, need_uptime, need_capacity, 0) &&
 +        (!options->ExitNodes ||
 +         !routerset_contains_router(options->ExitNodes, router))) {
 +      /* FFFF Someday, differentiate between a routerset that names
 +       * routers, and a routerset that names countries, and only do this
 +       * check if they've asked for specific exit relays. Or if the country
 +       * they ask for is rare. Or something. */
        n_supported[i] = -1;
 -      continue; /* skip routers that are not suitable */
 +      continue; /* skip routers that are not suitable, unless we have
 +                 * ExitNodes set, in which case we asked for it */
      }
      if (!(router->is_valid || options->_AllowInvalid & ALLOW_INVALID_EXIT)) {
        /* if it's invalid and we don't want it */
@@@ -2605,7 -1281,7 +2605,7 @@@
      {
        if (!ap_stream_wants_exit_attention(conn))
          continue; /* Skip everything but APs in CIRCUIT_WAIT */
 -      if (connection_ap_can_use_exit(TO_EDGE_CONN(conn), router)) {
 +      if (connection_ap_can_use_exit(TO_EDGE_CONN(conn), router, 1)) {
          ++n_supported[i];
  //        log_fn(LOG_DEBUG,"%s is supported. n_supported[%d] now %d.",
  //               router->nickname, i, n_supported[i]);
@@@ -2647,8 -1323,7 +2647,8 @@@
  
      routersets_get_disjunction(use, supporting, options->ExitNodes,
                                 options->_ExcludeExitNodesUnion, 1);
 -    if (smartlist_len(use) == 0 && !options->StrictExitNodes) {
 +    if (smartlist_len(use) == 0 && options->ExitNodes &&
 +        !options->StrictNodes) { /* give up on exitnodes and try again */
        routersets_get_disjunction(use, supporting, NULL,
                                   options->_ExcludeExitNodesUnion, 1);
      }
@@@ -2660,7 -1335,7 +2660,7 @@@
       * possibly support any of them.  Choose a router at random that satisfies
       * at least one predicted exit port. */
  
 -    int try;
 +    int attempt;
      smartlist_t *needed_ports, *supporting, *use;
  
      if (best_support == -1) {
@@@ -2673,20 -1348,19 +2673,20 @@@
          tor_free(n_supported);
          return choose_good_exit_server_general(dir, 0, 0);
        }
 -      log_notice(LD_CIRC, "All routers are down or won't exit -- choosing a "
 -                 "doomed exit at random.");
 +      log_notice(LD_CIRC, "All routers are down or won't exit%s -- "
 +                 "choosing a doomed exit at random.",
 +                 options->_ExcludeExitNodesUnion ? " or are Excluded" : "");
      }
      supporting = smartlist_create();
      use = smartlist_create();
      needed_ports = circuit_get_unhandled_ports(time(NULL));
 -    for (try = 0; try < 2; try++) {
 +    for (attempt = 0; attempt < 2; attempt++) {
        /* try once to pick only from routers that satisfy a needed port,
         * then if there are none, pick from any that support exiting. */
        for (i = 0; i < smartlist_len(dir->routers); i++) {
          router = smartlist_get(dir->routers, i);
          if (n_supported[i] != -1 &&
 -            (try || router_handles_some_port(router, needed_ports))) {
 +            (attempt || router_handles_some_port(router, needed_ports))) {
  //          log_fn(LOG_DEBUG,"Try %d: '%s' is a possibility.",
  //                 try, router->nickname);
            smartlist_add(supporting, router);
@@@ -2695,14 -1369,12 +2695,14 @@@
  
        routersets_get_disjunction(use, supporting, options->ExitNodes,
                                   options->_ExcludeExitNodesUnion, 1);
 -      if (smartlist_len(use) == 0 && !options->StrictExitNodes) {
 +      if (smartlist_len(use) == 0 && options->ExitNodes &&
 +          !options->StrictNodes) { /* give up on exitnodes and try again */
          routersets_get_disjunction(use, supporting, NULL,
                                     options->_ExcludeExitNodesUnion, 1);
        }
 -      /* XXX sometimes the above results in null, when the requested
 -       * exit node is down. we should pick it anyway. */
 +      /* FFF sometimes the above results in null, when the requested
 +       * exit node is considered down by the consensus. we should pick
 +       * it anyway, since the user asked for it. */
        router = routerlist_sl_choose_by_bandwidth(use, WEIGHT_FOR_EXIT);
        if (router)
          break;
@@@ -2720,10 -1392,10 +2720,10 @@@
      log_info(LD_CIRC, "Chose exit server '%s'", router->nickname);
      return router;
    }
 -  if (options->StrictExitNodes) {
 +  if (options->ExitNodes && options->StrictNodes) {
      log_warn(LD_CIRC,
               "No specified exit routers seem to be running, and "
 -             "StrictExitNodes is set: can't choose an exit.");
 +             "StrictNodes is set: can't choose an exit.");
    }
    return NULL;
  }
@@@ -2754,13 -1426,15 +2754,13 @@@ choose_good_exit_server(uint8_t purpose
        if (options->_AllowInvalid & ALLOW_INVALID_MIDDLE)
          flags |= CRN_ALLOW_INVALID;
        if (is_internal) /* pick it like a middle hop */
 -        return router_choose_random_node(NULL, NULL,
 -                                         options->ExcludeNodes, flags);
 +        return router_choose_random_node(NULL, options->ExcludeNodes, flags);
        else
          return choose_good_exit_server_general(dir,need_uptime,need_capacity);
      case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
        if (options->_AllowInvalid & ALLOW_INVALID_RENDEZVOUS)
          flags |= CRN_ALLOW_INVALID;
 -      return router_choose_random_node(NULL, NULL,
 -                                       options->ExcludeNodes, flags);
 +      return router_choose_random_node(NULL, options->ExcludeNodes, flags);
    }
    log_warn(LD_BUG,"Unhandled purpose %d", purpose);
    tor_fragile_assert();
@@@ -2880,7 -1554,8 +2880,7 @@@ circuit_append_new_exit(origin_circuit_
  
    state = circ->build_state;
    tor_assert(state);
 -  if (state->chosen_exit)
 -    extend_info_free(state->chosen_exit);
 +  extend_info_free(state->chosen_exit);
    state->chosen_exit = extend_info_dup(exit);
  
    ++circ->build_state->desired_path_len;
@@@ -3002,7 -1677,8 +3002,7 @@@ choose_good_middle_server(uint8_t purpo
      flags |= CRN_NEED_CAPACITY;
    if (options->_AllowInvalid & ALLOW_INVALID_MIDDLE)
      flags |= CRN_ALLOW_INVALID;
 -  choice = router_choose_random_node(NULL,
 -                                     excluded, options->ExcludeNodes, flags);
 +  choice = router_choose_random_node(excluded, options->ExcludeNodes, flags);
    smartlist_free(excluded);
    return choice;
  }
@@@ -3066,7 -1742,11 +3066,7 @@@ choose_good_entry_server(uint8_t purpos
    if (options->_AllowInvalid & ALLOW_INVALID_ENTRY)
      flags |= CRN_ALLOW_INVALID;
  
 -  choice = router_choose_random_node(
 -           NULL,
 -           excluded,
 -           options->ExcludeNodes,
 -           flags);
 +  choice = router_choose_random_node(excluded, options->ExcludeNodes, flags);
    smartlist_free(excluded);
    return choice;
  }
@@@ -3187,9 -1867,9 +3187,9 @@@ extend_info_from_router(routerinfo_t *r
  void
  extend_info_free(extend_info_t *info)
  {
 -  tor_assert(info);
 -  if (info->onion_key)
 -    crypto_free_pk_env(info->onion_key);
 +  if (!info)
 +    return;
 +  crypto_free_pk_env(info->onion_key);
    tor_free(info);
  }
  
@@@ -3248,6 -1928,8 +3248,6 @@@ entry_guard_set_status(entry_guard_t *e
    char buf[HEX_DIGEST_LEN+1];
    int changed = 0;
  
 -  tor_assert(options);
 -
    *reason = NULL;
  
    /* Do we want to mark this guard as bad? */
@@@ -3310,58 -1992,35 +3310,58 @@@ entry_is_time_to_retry(entry_guard_t *e
   * - Listed as either up or never yet contacted;
   * - Present in the routerlist;
   * - Listed as 'stable' or 'fast' by the current dirserver consensus,
 - *   if demanded by <b>need_uptime</b> or <b>need_capacity</b>;
 - *   (This check is currently redundant with the Guard flag, but in
 - *   the future that might change. Best to leave it in for now.)
 + *   if demanded by <b>need_uptime</b> or <b>need_capacity</b>
 + *   (unless it's a configured EntryNode);
   * - Allowed by our current ReachableORAddresses config option; and
 - * - Currently thought to be reachable by us (unless assume_reachable
 + * - Currently thought to be reachable by us (unless <b>assume_reachable</b>
   *   is true).
 + *
 + * If the answer is no, set *<b>msg</b> to an explanation of why.
   */
  static INLINE routerinfo_t *
  entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity,
 -              int assume_reachable)
 +              int assume_reachable, const char **msg)
  {
    routerinfo_t *r;
 -  if (e->bad_since)
 +  or_options_t *options = get_options();
 +  tor_assert(msg);
 +
 +  if (e->bad_since) {
 +    *msg = "bad";
      return NULL;
 +  }
    /* no good if it's unreachable, unless assume_unreachable or can_retry. */
    if (!assume_reachable && !e->can_retry &&
 -      e->unreachable_since && !entry_is_time_to_retry(e, time(NULL)))
 +      e->unreachable_since && !entry_is_time_to_retry(e, time(NULL))) {
 +    *msg = "unreachable";
      return NULL;
 +  }
    r = router_get_by_digest(e->identity);
 -  if (!r)
 +  if (!r) {
 +    *msg = "no descriptor";
      return NULL;
 -  if (get_options()->UseBridges && r->purpose != ROUTER_PURPOSE_BRIDGE)
 +  }
 +  if (get_options()->UseBridges && r->purpose != ROUTER_PURPOSE_BRIDGE) {
 +    *msg = "not a bridge";
      return NULL;
 -  if (!get_options()->UseBridges && r->purpose != ROUTER_PURPOSE_GENERAL)
 +  }
 +  if (!get_options()->UseBridges && r->purpose != ROUTER_PURPOSE_GENERAL) {
 +    *msg = "not general-purpose";
      return NULL;
 -  if (router_is_unreliable(r, need_uptime, need_capacity, 0))
 +  }
 +  if (options->EntryNodes &&
 +      routerset_contains_router(options->EntryNodes, r)) {
 +    /* they asked for it, they get it */
 +    need_uptime = need_capacity = 0;
 +  }
 +  if (router_is_unreliable(r, need_uptime, need_capacity, 0)) {
 +    *msg = "not fast/stable";
      return NULL;
 -  if (!fascist_firewall_allows_or(r))
 +  }
 +  if (!fascist_firewall_allows_or(r)) {
 +    *msg = "unreachable by config";
      return NULL;
 +  }
    return r;
  }
  
@@@ -3370,12 -2029,11 +3370,12 @@@ static in
  num_live_entry_guards(void)
  {
    int n = 0;
 +  const char *msg;
    if (! entry_guards)
      return 0;
    SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry,
      {
 -      if (entry_is_live(entry, 0, 1, 0))
 +      if (entry_is_live(entry, 0, 1, 0, &msg))
          ++n;
      });
    return n;
@@@ -3399,21 -2057,16 +3399,21 @@@ static voi
  log_entry_guards(int severity)
  {
    smartlist_t *elements = smartlist_create();
 -  char buf[1024];
    char *s;
  
    SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e,
      {
 -      tor_snprintf(buf, sizeof(buf), "%s (%s%s)",
 -                   e->nickname,
 -                   entry_is_live(e, 0, 1, 0) ? "up " : "down ",
 -                   e->made_contact ? "made-contact" : "never-contacted");
 -      smartlist_add(elements, tor_strdup(buf));
 +      const char *msg = NULL;
 +      char *cp;
 +      if (entry_is_live(e, 0, 1, 0, &msg))
 +        tor_asprintf(&cp, "%s (up %s)",
 +                     e->nickname,
 +                     e->made_contact ? "made-contact" : "never-contacted");
 +      else
 +        tor_asprintf(&cp, "%s (%s, %s)",
 +                     e->nickname, msg,
 +                     e->made_contact ? "made-contact" : "never-contacted");
 +      smartlist_add(elements, cp);
      });
  
    s = smartlist_join_strings(elements, ",", 0, NULL);
@@@ -3437,13 -2090,12 +3437,13 @@@ control_event_guard_deferred(void
     **/
  #if 0
    int n = 0;
 +  const char *msg;
    or_options_t *options = get_options();
    if (!entry_guards)
      return;
    SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry,
      {
 -      if (entry_is_live(entry, 0, 1, 0)) {
 +      if (entry_is_live(entry, 0, 1, 0, &msg)) {
          if (n++ == options->NumEntryGuards) {
            control_event_guard(entry->nickname, entry->identity, "DEFERRED");
            return;
@@@ -3505,8 -2157,9 +3505,8 @@@ add_an_entry_guard(routerinfo_t *chosen
  /** If the use of entry guards is configured, choose more entry guards
   * until we have enough in the list. */
  static void
 -pick_entry_guards(void)
 +pick_entry_guards(or_options_t *options)
  {
 -  or_options_t *options = get_options();
    int changed = 0;
  
    tor_assert(entry_guards);
@@@ -3528,8 -2181,7 +3528,8 @@@
  static void
  entry_guard_free(entry_guard_t *e)
  {
 -  tor_assert(e);
 +  if (!e)
 +    return;
    tor_free(e->chosen_by_version);
    tor_free(e);
  }
@@@ -3538,9 -2190,10 +3538,9 @@@
   * or which was selected by a version of Tor that's known to select
   * entry guards badly. */
  static int
 -remove_obsolete_entry_guards(void)
 +remove_obsolete_entry_guards(time_t now)
  {
    int changed = 0, i;
 -  time_t now = time(NULL);
  
    for (i = 0; i < smartlist_len(entry_guards); ++i) {
      entry_guard_t *entry = smartlist_get(entry_guards, i);
@@@ -3600,10 -2253,11 +3600,10 @@@
   * long that we don't think they'll come up again. Return 1 if we
   * removed any, or 0 if we did nothing. */
  static int
 -remove_dead_entry_guards(void)
 +remove_dead_entry_guards(time_t now)
  {
    char dbuf[HEX_DIGEST_LEN+1];
    char tbuf[ISO_TIME_LEN+1];
 -  time_t now = time(NULL);
    int i;
    int changed = 0;
  
@@@ -3638,17 -2292,19 +3638,17 @@@
   * think that things are unlisted.
   */
  void
 -entry_guards_compute_status(void)
 +entry_guards_compute_status(or_options_t *options, time_t now)
  {
 -  time_t now;
    int changed = 0;
    int severity = LOG_DEBUG;
 -  or_options_t *options;
    digestmap_t *reasons;
 +
    if (! entry_guards)
      return;
  
 -  options = get_options();
 -
 -  now = time(NULL);
 +  if (options->EntryNodes) /* reshuffle the entry guard list if needed */
 +    entry_nodes_should_be_added();
  
    reasons = digestmap_new();
    SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry)
@@@ -3665,7 -2321,7 +3665,7 @@@
      }
    SMARTLIST_FOREACH_END(entry);
  
 -  if (remove_dead_entry_guards())
 +  if (remove_dead_entry_guards(now))
      changed = 1;
  
    severity = changed ? LOG_DEBUG : LOG_INFO;
@@@ -3673,16 -2329,13 +3673,16 @@@
    if (changed) {
      SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) {
        const char *reason = digestmap_get(reasons, entry->identity);
 -      log_info(LD_CIRC, "Summary: Entry '%s' is %s, %s%s%s, and %s.",
 +      const char *live_msg = "";
 +      routerinfo_t *r = entry_is_live(entry, 0, 1, 0, &live_msg);
 +      log_info(LD_CIRC, "Summary: Entry '%s' is %s, %s%s%s, and %s%s.",
                 entry->nickname,
                 entry->unreachable_since ? "unreachable" : "reachable",
                 entry->bad_since ? "unusable" : "usable",
                 reason ? ", ": "",
                 reason ? reason : "",
 -               entry_is_live(entry, 0, 1, 0) ? "live" : "not live");
 +               r ? "live" : "not live / ",
 +               r ? "" : live_msg);
      } SMARTLIST_FOREACH_END(entry);
      log_info(LD_CIRC, "    (%d/%d entry guards are usable/new)",
               num_live_entry_guards(), smartlist_len(entry_guards));
@@@ -3753,7 -2406,6 +3753,7 @@@ entry_guard_register_connect_status(con
                 "Removing from the list. %d/%d entry guards usable/new.",
                 entry->nickname, buf,
                 num_live_entry_guards()-1, smartlist_len(entry_guards)-1);
 +      control_event_guard(entry->nickname, entry->identity, "DROPPED");
        entry_guard_free(entry);
        smartlist_del_keeporder(entry_guards, idx);
        log_entry_guards(LOG_INFO);
@@@ -3790,8 -2442,7 +3790,8 @@@
          if (e == entry)
            break;
          if (e->made_contact) {
 -          routerinfo_t *r = entry_is_live(e, 0, 1, 1);
 +          const char *msg;
 +          routerinfo_t *r = entry_is_live(e, 0, 1, 1, &msg);
            if (r && e->unreachable_since) {
              refuse_conn = 1;
              e->can_retry = 1;
@@@ -3822,16 -2473,16 +3822,16 @@@ static int should_add_entry_nodes = 0
  void
  entry_nodes_should_be_added(void)
  {
 -  log_info(LD_CIRC, "New EntryNodes config option detected. Will use.");
 +  log_info(LD_CIRC, "EntryNodes config option set. Putting configured "
 +           "relays at the front of the entry guard list.");
    should_add_entry_nodes = 1;
  }
  
  /** Add all nodes in EntryNodes that aren't currently guard nodes to the list
   * of guard nodes, at the front. */
  static void
 -entry_guards_prepend_from_config(void)
 +entry_guards_prepend_from_config(or_options_t *options)
  {
 -  or_options_t *options = get_options();
    smartlist_t *entry_routers, *entry_fps;
    smartlist_t *old_entry_guards_on_list, *old_entry_guards_not_on_list;
    tor_assert(entry_guards);
@@@ -3846,7 -2497,7 +3846,7 @@@
      return;
    }
  
 -  if (options->EntryNodes) {
 +  {
      char *string = routerset_to_string(options->EntryNodes);
      log_info(LD_CIRC,"Adding configured EntryNodes '%s'.", string);
      tor_free(string);
@@@ -3890,9 -2541,8 +3890,9 @@@
    SMARTLIST_FOREACH(entry_routers, routerinfo_t *, ri, {
      add_an_entry_guard(ri, 0);
    });
 -  /* Finally, the remaining EntryNodes, unless we're strict */
 -  if (options->StrictEntryNodes) {
 +  /* Finally, the remaining previously configured guards that are not in
 +   * EntryNodes, unless we're strict in which case we drop them */
 +  if (options->StrictNodes) {
      SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e,
                        entry_guard_free(e));
    } else {
@@@ -3906,30 -2556,16 +3906,30 @@@
    entry_guards_changed();
  }
  
 -/** Return 1 if we're fine adding arbitrary routers out of the
 - * directory to our entry guard list. Else return 0. */
 +/** Return 0 if we're fine adding arbitrary routers out of the
 + * directory to our entry guard list, or return 1 if we have a
 + * list already and we'd prefer to stick to it.
 + */
  int
 -entry_list_can_grow(or_options_t *options)
 +entry_list_is_constrained(or_options_t *options)
  {
 -  if (options->StrictEntryNodes)
 -    return 0;
 +  if (options->EntryNodes)
 +    return 1;
    if (options->UseBridges)
 -    return 0;
 -  return 1;
 +    return 1;
 +  return 0;
 +}
 +
 +/* Are we dead set against changing our entry guard list, or would we
 + * change it if it means keeping Tor usable? */
 +static int
 +entry_list_is_totally_static(or_options_t *options)
 +{
 +  if (options->EntryNodes && options->StrictNodes)
 +    return 1;
 +  if (options->UseBridges)
 +    return 1;
 +  return 0;
  }
  
  /** Pick a live (up and listed) entry guard from entry_guards. If
@@@ -3947,7 -2583,7 +3947,7 @@@ choose_random_entry(cpath_build_state_
    routerinfo_t *r = NULL;
    int need_uptime = state ? state->need_uptime : 0;
    int need_capacity = state ? state->need_capacity : 0;
 -  int consider_exit_family = 0;
 +  int preferred_min, consider_exit_family = 0;
  
    if (chosen_exit) {
      smartlist_add(exit_family, chosen_exit);
@@@ -3959,62 -2595,38 +3959,62 @@@
      entry_guards = smartlist_create();
  
    if (should_add_entry_nodes)
 -    entry_guards_prepend_from_config();
 +    entry_guards_prepend_from_config(options);
  
 -  if (entry_list_can_grow(options) &&
 -      (! entry_guards ||
 -       smartlist_len(entry_guards) < options->NumEntryGuards))
 -    pick_entry_guards();
 +  if (!entry_list_is_constrained(options) &&
 +      smartlist_len(entry_guards) < options->NumEntryGuards)
 +    pick_entry_guards(options);
  
   retry:
    smartlist_clear(live_entry_guards);
    SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry,
      {
 -      r = entry_is_live(entry, need_uptime, need_capacity, 0);
 -      if (r && (!consider_exit_family || !smartlist_isin(exit_family, r))) {
 -        smartlist_add(live_entry_guards, r);
 -        if (!entry->made_contact) {
 -          /* Always start with the first not-yet-contacted entry
 -           * guard. Otherwise we might add several new ones, pick
 -           * the second new one, and now we've expanded our entry
 -           * guard list without needing to. */
 -          goto choose_and_finish;
 +      const char *msg;
 +      r = entry_is_live(entry, need_uptime, need_capacity, 0, &msg);
 +      if (!r)
 +        continue; /* down, no point */
 +      if (consider_exit_family && smartlist_isin(exit_family, r))
 +        continue; /* avoid relays that are family members of our exit */
 +      if (options->EntryNodes &&
 +          !routerset_contains_router(options->EntryNodes, r)) {
 +        /* We've come to the end of our preferred entry nodes. */
 +        if (smartlist_len(live_entry_guards))
 +          goto choose_and_finish; /* only choose from the ones we like */
 +        if (options->StrictNodes) {
 +          /* in theory this case should never happen, since
 +           * entry_guards_prepend_from_config() drops unwanted relays */
 +          tor_fragile_assert();
 +        } else {
 +          log_info(LD_CIRC,
 +                   "No relays from EntryNodes available. Using others.");
          }
 -        if (smartlist_len(live_entry_guards) >= options->NumEntryGuards)
 -          break; /* we have enough */
        }
 +      smartlist_add(live_entry_guards, r);
 +      if (!entry->made_contact) {
 +        /* Always start with the first not-yet-contacted entry
 +         * guard. Otherwise we might add several new ones, pick
 +         * the second new one, and now we've expanded our entry
 +         * guard list without needing to. */
 +        goto choose_and_finish;
 +      }
 +      if (smartlist_len(live_entry_guards) >= options->NumEntryGuards)
 +        break; /* we have enough */
      });
  
 -  /* Try to have at least 2 choices available. This way we don't
 -   * get stuck with a single live-but-crummy entry and just keep
 -   * using him.
 -   * (We might get 2 live-but-crummy entry guards, but so be it.) */
 -  if (smartlist_len(live_entry_guards) < 2) {
 -    if (entry_list_can_grow(options)) {
 +  if (entry_list_is_constrained(options)) {
 +    /* If we prefer the entry nodes we've got, and we have at least
 +     * one choice, that's great. Use it. */
 +    preferred_min = 1;
 +  } else {
 +    /* Try to have at least 2 choices available. This way we don't
 +     * get stuck with a single live-but-crummy entry and just keep
 +     * using him.
 +     * (We might get 2 live-but-crummy entry guards, but so be it.) */
 +    preferred_min = 2;
 +  }
 +
 +  if (smartlist_len(live_entry_guards) < preferred_min) {
 +    if (!entry_list_is_totally_static(options)) {
        /* still no? try adding a new entry then */
        /* XXX if guard doesn't imply fast and stable, then we need
         * to tell add_an_entry_guard below what we want, or it might
@@@ -4039,7 -2651,7 +4039,7 @@@
        need_capacity = 0;
        goto retry;
      }
 -    if (!r && !entry_list_can_grow(options) && consider_exit_family) {
 +    if (!r && entry_list_is_constrained(options) && consider_exit_family) {
        /* still no? if we're using bridges or have strictentrynodes
         * set, and our chosen exit is in the same family as all our
         * bridges/entry guards, then be flexible about families. */
@@@ -4050,15 -2662,15 +4050,15 @@@
    }
  
   choose_and_finish:
 -  if (entry_list_can_grow(options)) {
 +  if (entry_list_is_constrained(options)) {
 +    /* We need to weight by bandwidth, because our bridges or entryguards
 +     * were not already selected proportional to their bandwidth. */
 +    r = routerlist_sl_choose_by_bandwidth(live_entry_guards, WEIGHT_FOR_GUARD);
 +  } else {
      /* We choose uniformly at random here, because choose_good_entry_server()
       * already weights its choices by bandwidth, so we don't want to
       * *double*-weight our guard selection. */
      r = smartlist_choose(live_entry_guards);
 -  } else {
 -    /* We need to weight by bandwidth, because our bridges or entryguards
 -     * were not already selected proportional to their bandwidth. */
 -    r = routerlist_sl_choose_by_bandwidth(live_entry_guards, WEIGHT_FOR_GUARD);
    }
    smartlist_free(live_entry_guards);
    smartlist_free(exit_family);
@@@ -4192,7 -2804,7 +4192,7 @@@ entry_guards_parse_state(or_state_t *st
      entry_guards_dirty = 0;
      /* XXX022 hand new_entry_guards to this func, and move it up a
       * few lines, so we don't have to re-dirty it */
 -    if (remove_obsolete_entry_guards())
 +    if (remove_obsolete_entry_guards(now))
        entry_guards_dirty = 1;
    }
    digestmap_free(added_by, _tor_free);
@@@ -4291,11 -2903,9 +4291,11 @@@ entry_guards_update_state(or_state_t *s
   * */
  int
  getinfo_helper_entry_guards(control_connection_t *conn,
 -                            const char *question, char **answer)
 +                            const char *question, char **answer,
 +                            const char **errmsg)
  {
 -  int use_long_names = conn->use_long_names;
 +  (void) conn;
 +  (void) errmsg;
  
    if (!strcmp(question,"entry-guards") ||
        !strcmp(question,"helper-nodes")) {
@@@ -4304,13 -2914,12 +4304,13 @@@
      char nbuf[MAX_VERBOSE_NICKNAME_LEN+1];
      if (!entry_guards)
        entry_guards = smartlist_create();
 -    SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e,
 -      {
 +    SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
          size_t len = MAX_VERBOSE_NICKNAME_LEN+ISO_TIME_LEN+32;
          char *c = tor_malloc(len);
          const char *status = NULL;
          time_t when = 0;
 +        routerinfo_t *ri;
 +
          if (!e->made_contact) {
            status = "never-connected";
          } else if (e->bad_since) {
@@@ -4319,17 -2928,19 +4319,17 @@@
          } else {
            status = "up";
          }
 -        if (use_long_names) {
 -          routerinfo_t *ri = router_get_by_digest(e->identity);
 -          if (ri) {
 -            router_get_verbose_nickname(nbuf, ri);
 -          } else {
 -            nbuf[0] = '$';
 -            base16_encode(nbuf+1, sizeof(nbuf)-1, e->identity, DIGEST_LEN);
 -            /* e->nickname field is not very reliable if we don't know about
 -             * this router any longer; don't include it. */
 -          }
 +
 +        ri = router_get_by_digest(e->identity);
 +        if (ri) {
 +          router_get_verbose_nickname(nbuf, ri);
          } else {
 -          base16_encode(nbuf, sizeof(nbuf), e->identity, DIGEST_LEN);
 +          nbuf[0] = '$';
 +          base16_encode(nbuf+1, sizeof(nbuf)-1, e->identity, DIGEST_LEN);
 +          /* e->nickname field is not very reliable if we don't know about
 +           * this router any longer; don't include it. */
          }
 +
          if (when) {
            format_iso_time(tbuf, when);
            tor_snprintf(c, len, "%s %s %s\n", nbuf, status, tbuf);
@@@ -4337,7 -2948,7 +4337,7 @@@
            tor_snprintf(c, len, "%s %s\n", nbuf, status);
          }
          smartlist_add(sl, c);
 -      });
 +    } SMARTLIST_FOREACH_END(e);
      *answer = smartlist_join_strings(sl, "", 0, NULL);
      SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
      smartlist_free(sl);
@@@ -4378,56 -2989,29 +4378,56 @@@ clear_bridge_list(void
   * (either by comparing keys if possible, else by comparing addr/port).
   * Else return NULL. */
  static bridge_info_t *
 -routerinfo_get_configured_bridge(routerinfo_t *ri)
 +get_configured_bridge_by_addr_port_digest(tor_addr_t *addr, uint16_t port,
 +                                          const char *digest)
  {
    if (!bridge_list)
      return NULL;
    SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
      {
        if (tor_digest_is_zero(bridge->identity) &&
 -          tor_addr_eq_ipv4h(&bridge->addr, ri->addr) &&
 -          bridge->port == ri->or_port)
 +          !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) &&
 +          bridge->port == port)
          return bridge;
 -      if (!memcmp(bridge->identity, ri->cache_info.identity_digest,
 -                  DIGEST_LEN))
 +      if (!memcmp(bridge->identity, digest, DIGEST_LEN))
          return bridge;
      }
    SMARTLIST_FOREACH_END(bridge);
    return NULL;
  }
  
 +/** Wrapper around get_configured_bridge_by_addr_port_digest() to look
 + * it up via router descriptor <b>ri</b>. */
 +static bridge_info_t *
 +get_configured_bridge_by_routerinfo(routerinfo_t *ri)
 +{
 +  tor_addr_t addr;
 +  tor_addr_from_ipv4h(&addr, ri->addr);
 +  return get_configured_bridge_by_addr_port_digest(&addr,
 +                              ri->or_port, ri->cache_info.identity_digest);
 +}
 +
  /** Return 1 if <b>ri</b> is one of our known bridges, else 0. */
  int
  routerinfo_is_a_configured_bridge(routerinfo_t *ri)
  {
 -  return routerinfo_get_configured_bridge(ri) ? 1 : 0;
 +  return get_configured_bridge_by_routerinfo(ri) ? 1 : 0;
 +}
 +
 +/** We made a connection to a router at <b>addr</b>:<b>port</b>
 + * without knowing its digest. Its digest turned out to be <b>digest</b>.
 + * If it was a bridge, and we still don't know its digest, record it.
 + */
 +void
 +learned_router_identity(tor_addr_t *addr, uint16_t port, const char *digest)
 +{
 +  bridge_info_t *bridge =
 +    get_configured_bridge_by_addr_port_digest(addr, port, digest);
 +  if (bridge && tor_digest_is_zero(bridge->identity)) {
 +    memcpy(bridge->identity, digest, DIGEST_LEN);
 +    log_notice(LD_DIR, "Learned fingerprint %s for bridge %s:%d",
 +               hex_str(digest, DIGEST_LEN), fmt_addr(addr), port);
 +  }
  }
  
  /** Remember a new bridge at <b>addr</b>:<b>port</b>. If <b>digest</b>
@@@ -4497,8 -3081,9 +4497,8 @@@ retry_bridge_descriptor_fetch_directly(
   * descriptor, fetch a new copy of its descriptor -- either directly
   * from the bridge or via a bridge authority. */
  void
 -fetch_bridge_descriptors(time_t now)
 +fetch_bridge_descriptors(or_options_t *options, time_t now)
  {
 -  or_options_t *options = get_options();
    int num_bridge_auths = get_n_authorities(BRIDGE_AUTHORITY);
    int ask_bridge_directly;
    int can_use_bridge_authority;
@@@ -4566,7 -3151,7 +4566,7 @@@ learned_bridge_descriptor(routerinfo_t 
    tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE);
    if (get_options()->UseBridges) {
      int first = !any_bridge_descriptors_known();
 -    bridge_info_t *bridge = routerinfo_get_configured_bridge(ri);
 +    bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri);
      time_t now = time(NULL);
      ri->is_running = 1;
  
@@@ -4622,38 -3207,26 +4622,38 @@@ any_pending_bridge_descriptor_fetches(v
    return 0;
  }
  
 -/** Return 1 if we have at least one descriptor for a bridge and
 - * all descriptors we know are down. Else return 0. If <b>act</b> is
 - * 1, then mark the down bridges up; else just observe and report. */
 +/** Return 1 if we have at least one descriptor for an entry guard
 + * (bridge or member of EntryNodes) and all descriptors we know are
 + * down. Else return 0. If <b>act</b> is 1, then mark the down guards
 + * up; else just observe and report. */
  static int
 -bridges_retry_helper(int act)
 +entries_retry_helper(or_options_t *options, int act)
  {
    routerinfo_t *ri;
    int any_known = 0;
    int any_running = 0;
 +  int purpose = options->UseBridges ?
 +                  ROUTER_PURPOSE_BRIDGE : ROUTER_PURPOSE_GENERAL;
    if (!entry_guards)
      entry_guards = smartlist_create();
    SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e,
      {
        ri = router_get_by_digest(e->identity);
 -      if (ri && ri->purpose == ROUTER_PURPOSE_BRIDGE) {
 +      if (ri && ri->purpose == purpose) {
          any_known = 1;
          if (ri->is_running)
 -          any_running = 1; /* some bridge is both known and running */
 -        else if (act) { /* mark it for retry */
 -          ri->is_running = 1;
 +          any_running = 1; /* some entry is both known and running */
 +        else if (act) {
 +          /* Mark all current connections to this OR as unhealthy, since
 +           * otherwise there could be one that started 30 seconds
 +           * ago, and in 30 seconds it will time out, causing us to mark
 +           * the node down and undermine the retry attempt. We mark even
 +           * the established conns, since if the network just came back
 +           * we'll want to attach circuits to fresh conns. */
 +          connection_or_set_bad_connections(ri->cache_info.identity_digest, 1);
 +
 +          /* mark this entry node for retry */
 +          router_set_status(ri->cache_info.identity_digest, 1);
            e->can_retry = 1;
            e->bad_since = 0;
          }
@@@ -4664,21 -3237,19 +4664,21 @@@
    return any_known && !any_running;
  }
  
 -/** Do we know any descriptors for our bridges, and are they all
 - * down? */
 +/** Do we know any descriptors for our bridges / entrynodes, and are
 + * all the ones we have descriptors for down? */
  int
 -bridges_known_but_down(void)
 +entries_known_but_down(or_options_t *options)
  {
 -  return bridges_retry_helper(0);
 +  tor_assert(entry_list_is_constrained(options));
 +  return entries_retry_helper(options, 0);
  }
  
 -/** Mark all down known bridges up. */
 +/** Mark all down known bridges / entrynodes up. */
  void
 -bridges_retry_all(void)
 +entries_retry_all(or_options_t *options)
  {
 -  bridges_retry_helper(1);
 +  tor_assert(entry_list_is_constrained(options));
 +  entries_retry_helper(options, 1);
  }
  
  /** Release all storage held by the list of entry guards and related
diff --combined src/or/circuitlist.c
index fb4b69b,5248568..58ff27e
--- a/src/or/circuitlist.c
+++ b/src/or/circuitlist.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -10,21 -10,6 +10,21 @@@
   **/
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "connection.h"
 +#include "config.h"
 +#include "connection_edge.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "networkstatus.h"
 +#include "onion.h"
 +#include "relay.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rephist.h"
 +#include "routerlist.h"
  #include "ht.h"
  
  /********* START VARIABLES **********/
@@@ -367,8 -352,6 +367,8 @@@ circuit_purpose_to_controller_string(ui
  
      case CIRCUIT_PURPOSE_TESTING:
        return "TESTING";
 +    case CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT:
 +      return "MEASURE_TIMEOUT";
      case CIRCUIT_PURPOSE_CONTROLLER:
        return "CONTROLLER";
  
@@@ -397,17 -380,10 +397,17 @@@ static voi
  init_circuit_base(circuit_t *circ)
  {
    circ->timestamp_created = time(NULL);
 +  tor_gettimeofday(&circ->highres_created);
  
    circ->package_window = circuit_initial_package_window();
    circ->deliver_window = CIRCWINDOW_START;
  
 +  /* Initialize the cell_ewma_t structure */
 +  circ->n_cell_ewma.last_adjusted_tick = cell_ewma_get_tick();
 +  circ->n_cell_ewma.cell_count = 0.0;
 +  circ->n_cell_ewma.heap_index = -1;
 +  circ->n_cell_ewma.is_for_p_conn = 0;
 +
    circuit_add(circ);
  }
  
@@@ -432,8 -408,6 +432,8 @@@ origin_circuit_new(void
  
    init_circuit_base(TO_CIRCUIT(circ));
  
 +  circ_times.last_circ_at = approx_time();
 +
    return circ;
  }
  
@@@ -455,16 -429,6 +455,16 @@@ or_circuit_new(circid_t p_circ_id, or_c
  
    init_circuit_base(TO_CIRCUIT(circ));
  
 +  /* Initialize the cell_ewma_t structure */
 +
 +  /* Initialize the cell counts to 0 */
 +  circ->p_cell_ewma.cell_count = 0.0;
 +  circ->p_cell_ewma.last_adjusted_tick = cell_ewma_get_tick();
 +  circ->p_cell_ewma.is_for_p_conn = 1;
 +
 +  /* It's not in any heap yet. */
 +  circ->p_cell_ewma.heap_index = -1;
 +
    return circ;
  }
  
@@@ -475,37 -439,39 +475,37 @@@ circuit_free(circuit_t *circ
  {
    void *mem;
    size_t memlen;
 -  tor_assert(circ);
 +  if (!circ)
 +    return;
 +
    if (CIRCUIT_IS_ORIGIN(circ)) {
      origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
      mem = ocirc;
      memlen = sizeof(origin_circuit_t);
      tor_assert(circ->magic == ORIGIN_CIRCUIT_MAGIC);
      if (ocirc->build_state) {
 -      if (ocirc->build_state->chosen_exit)
          extend_info_free(ocirc->build_state->chosen_exit);
 -      if (ocirc->build_state->pending_final_cpath)
          circuit_free_cpath_node(ocirc->build_state->pending_final_cpath);
      }
      tor_free(ocirc->build_state);
  
      circuit_free_cpath(ocirc->cpath);
 -    if (ocirc->intro_key)
 -      crypto_free_pk_env(ocirc->intro_key);
 -    if (ocirc->rend_data)
 -      rend_data_free(ocirc->rend_data);
 +
 +    crypto_free_pk_env(ocirc->intro_key);
 +    rend_data_free(ocirc->rend_data);
    } else {
      or_circuit_t *ocirc = TO_OR_CIRCUIT(circ);
 +    /* Remember cell statistics for this circuit before deallocating. */
 +    if (get_options()->CellStatistics)
 +      rep_hist_buffer_stats_add_circ(circ, time(NULL));
      mem = ocirc;
      memlen = sizeof(or_circuit_t);
      tor_assert(circ->magic == OR_CIRCUIT_MAGIC);
  
 -    if (ocirc->p_crypto)
 -      crypto_free_cipher_env(ocirc->p_crypto);
 -    if (ocirc->p_digest)
 -      crypto_free_digest_env(ocirc->p_digest);
 -    if (ocirc->n_crypto)
 -      crypto_free_cipher_env(ocirc->n_crypto);
 -    if (ocirc->n_digest)
 -      crypto_free_digest_env(ocirc->n_digest);
 +    crypto_free_cipher_env(ocirc->p_crypto);
 +    crypto_free_digest_env(ocirc->p_digest);
 +    crypto_free_cipher_env(ocirc->n_crypto);
 +    crypto_free_digest_env(ocirc->n_digest);
  
      if (ocirc->rend_splice) {
        or_circuit_t *other = ocirc->rend_splice;
@@@ -521,7 -487,8 +521,7 @@@
      cell_queue_clear(&ocirc->p_conn_cells);
    }
  
 -  if (circ->n_hop)
 -    extend_info_free(circ->n_hop);
 +  extend_info_free(circ->n_hop);
    tor_free(circ->n_conn_onionskin);
  
    /* Remove from map. */
@@@ -531,7 -498,7 +531,7 @@@
     * "active" checks will be violated. */
    cell_queue_clear(&circ->n_conn_cells);
  
 -  memset(circ, 0xAA, memlen); /* poison memory */
 +  memset(mem, 0xAA, memlen); /* poison memory */
    tor_free(mem);
  }
  
@@@ -574,10 -541,10 +574,10 @@@ circuit_free_all(void
      circuit_free(global_circuitlist);
      global_circuitlist = next;
    }
 -  if (circuits_pending_or_conns) {
 -    smartlist_free(circuits_pending_or_conns);
 -    circuits_pending_or_conns = NULL;
 -  }
 +
 +  smartlist_free(circuits_pending_or_conns);
 +  circuits_pending_or_conns = NULL;
 +
    HT_CLEAR(orconn_circid_map, &orconn_circid_circuit_map);
  }
  
@@@ -585,15 -552,18 +585,15 @@@
  static void
  circuit_free_cpath_node(crypt_path_t *victim)
  {
 -  if (victim->f_crypto)
 -    crypto_free_cipher_env(victim->f_crypto);
 -  if (victim->b_crypto)
 -    crypto_free_cipher_env(victim->b_crypto);
 -  if (victim->f_digest)
 -    crypto_free_digest_env(victim->f_digest);
 -  if (victim->b_digest)
 -    crypto_free_digest_env(victim->b_digest);
 -  if (victim->dh_handshake_state)
 -    crypto_dh_free(victim->dh_handshake_state);
 -  if (victim->extend_info)
 -    extend_info_free(victim->extend_info);
 +  if (!victim)
 +    return;
 +
 +  crypto_free_cipher_env(victim->f_crypto);
 +  crypto_free_cipher_env(victim->b_crypto);
 +  crypto_free_digest_env(victim->f_digest);
 +  crypto_free_digest_env(victim->b_digest);
 +  crypto_dh_free(victim->dh_handshake_state);
 +  extend_info_free(victim->extend_info);
  
    memset(victim, 0xBB, sizeof(crypt_path_t)); /* poison memory */
    tor_free(victim);
@@@ -922,10 -892,6 +922,10 @@@ circuit_find_to_cannibalize(uint8_t pur
    int need_capacity = (flags & CIRCLAUNCH_NEED_CAPACITY) != 0;
    int internal = (flags & CIRCLAUNCH_IS_INTERNAL) != 0;
  
 +  /* Make sure we're not trying to create a onehop circ by
 +   * cannibalization. */
 +  tor_assert(!(flags & CIRCLAUNCH_ONEHOP_TUNNEL));
 +
    log_debug(LD_CIRC,
              "Hunting for a circ to cannibalize: purpose %d, uptime %d, "
              "capacity %d, internal %d",
@@@ -941,8 -907,7 +941,8 @@@
        if ((!need_uptime || circ->build_state->need_uptime) &&
            (!need_capacity || circ->build_state->need_capacity) &&
            (internal == circ->build_state->is_internal) &&
 -          circ->remaining_relay_early_cells) {
 +          circ->remaining_relay_early_cells &&
 +          !circ->build_state->onehop_tunnel) {
          if (info) {
            /* need to make sure we don't duplicate hops */
            crypt_path_t *hop = circ->cpath;
@@@ -1118,16 -1083,14 +1118,16 @@@ _circuit_mark_for_close(circuit_t *circ
      tor_assert(ocirc->rend_data);
      /* treat this like getting a nack from it */
      log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). "
 -             "Removing from descriptor.",
 -             safe_str(ocirc->rend_data->onion_address),
 -             safe_str(build_state_get_exit_nickname(ocirc->build_state)));
 +           "Removing from descriptor.",
 +           safe_str_client(ocirc->rend_data->onion_address),
 +           safe_str_client(build_state_get_exit_nickname(ocirc->build_state)));
      rend_client_remove_intro_point(ocirc->build_state->chosen_exit,
                                     ocirc->rend_data);
    }
 -  if (circ->n_conn)
 +  if (circ->n_conn) {
 +    circuit_clear_cell_queue(circ, circ->n_conn);
      connection_or_send_destroy(circ->n_circ_id, circ->n_conn, reason);
 +  }
  
    if (! CIRCUIT_IS_ORIGIN(circ)) {
      or_circuit_t *or_circ = TO_OR_CIRCUIT(circ);
@@@ -1151,10 -1114,8 +1151,10 @@@
        conn->on_circuit = NULL;
      }
  
 -    if (or_circ->p_conn)
 +    if (or_circ->p_conn) {
 +      circuit_clear_cell_queue(circ, or_circ->p_conn);
        connection_or_send_destroy(or_circ->p_circ_id, or_circ->p_conn, reason);
 +    }
    } else {
      origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
      edge_connection_t *conn;
@@@ -1275,6 -1236,11 +1275,6 @@@ assert_circuit_ok(const circuit_t *c
        tor_assert(c == c2);
      }
    }
 -#if 0 /* false now that rendezvous exits are attached to p_streams */
 -  if (origin_circ)
 -    for (conn = origin_circ->p_streams; conn; conn = conn->next_stream)
 -      tor_assert(conn->_base.type == CONN_TYPE_AP);
 -#endif
    if (or_circ)
      for (conn = or_circ->n_streams; conn; conn = conn->next_stream)
        tor_assert(conn->_base.type == CONN_TYPE_EXIT);
diff --combined src/or/circuituse.c
index 3af0fb6,996c99c..8d9d115
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -10,20 -10,6 +10,20 @@@
   **/
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "control.h"
 +#include "policies.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rendservice.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
  
  /********* START VARIABLES **********/
  
@@@ -127,7 -113,7 +127,7 @@@ circuit_is_acceptable(circuit_t *circ, 
          return 0;
        }
      }
 -    if (exitrouter && !connection_ap_can_use_exit(conn, exitrouter)) {
 +    if (exitrouter && !connection_ap_can_use_exit(conn, exitrouter, 0)) {
        /* can't exit from this router */
        return 0;
      }
@@@ -277,22 -263,16 +277,22 @@@ circuit_conforms_to_options(const origi
  void
  circuit_expire_building(time_t now)
  {
 -  circuit_t *victim, *circ = global_circuitlist;
 -  time_t general_cutoff = now - get_options()->CircuitBuildTimeout;
 -  time_t begindir_cutoff = now - get_options()->CircuitBuildTimeout/2;
 +  circuit_t *victim, *next_circ = global_circuitlist;
 +  /* circ_times.timeout_ms and circ_times.close_ms are from
 +   * circuit_build_times_get_initial_timeout() if we haven't computed
 +   * custom timeouts yet */
 +  time_t general_cutoff = now - tor_lround(circ_times.timeout_ms/1000);
 +  time_t begindir_cutoff = now - tor_lround(circ_times.timeout_ms/2000);
 +  time_t fourhop_cutoff = now - tor_lround(4*circ_times.timeout_ms/3000);
 +  time_t cannibalize_cutoff = now - tor_lround(circ_times.timeout_ms/2000);
 +  time_t close_cutoff = now - tor_lround(circ_times.close_ms/1000);
    time_t introcirc_cutoff = begindir_cutoff;
    cpath_build_state_t *build_state;
  
 -  while (circ) {
 +  while (next_circ) {
      time_t cutoff;
 -    victim = circ;
 -    circ = circ->next;
 +    victim = next_circ;
 +    next_circ = next_circ->next;
      if (!CIRCUIT_IS_ORIGIN(victim) || /* didn't originate here */
          victim->marked_for_close) /* don't mess with marked circs */
        continue;
@@@ -300,18 -280,10 +300,18 @@@
      build_state = TO_ORIGIN_CIRCUIT(victim)->build_state;
      if (build_state && build_state->onehop_tunnel)
        cutoff = begindir_cutoff;
 +    else if (build_state && build_state->desired_path_len == 4
 +             && !TO_ORIGIN_CIRCUIT(victim)->has_opened)
 +      cutoff = fourhop_cutoff;
 +    else if (TO_ORIGIN_CIRCUIT(victim)->has_opened)
 +      cutoff = cannibalize_cutoff;
      else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING)
        cutoff = introcirc_cutoff;
 +    else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT)
 +      cutoff = close_cutoff;
      else
        cutoff = general_cutoff;
 +
      if (victim->timestamp_created > cutoff)
        continue; /* it's still young, leave it alone */
  
@@@ -371,54 -343,6 +371,54 @@@
              continue;
            break;
        }
 +    } else { /* circuit not open, consider recording failure as timeout */
 +      int first_hop_succeeded = TO_ORIGIN_CIRCUIT(victim)->cpath &&
 +            TO_ORIGIN_CIRCUIT(victim)->cpath->state == CPATH_STATE_OPEN;
 +
 +      if (TO_ORIGIN_CIRCUIT(victim)->p_streams != NULL) {
 +        log_warn(LD_BUG, "Circuit %d (purpose %d) has timed out, "
 +                 "yet has attached streams!",
 +                 TO_ORIGIN_CIRCUIT(victim)->global_identifier,
 +                 victim->purpose);
 +        tor_fragile_assert();
 +        continue;
 +      }
 +
 +      if (circuit_timeout_want_to_count_circ(TO_ORIGIN_CIRCUIT(victim)) &&
 +          circuit_build_times_enough_to_compute(&circ_times)) {
 +        /* Circuits are allowed to last longer for measurement.
 +         * Switch their purpose and wait. */
 +        if (victim->purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
 +          control_event_circuit_status(TO_ORIGIN_CIRCUIT(victim),
 +                                       CIRC_EVENT_FAILED,
 +                                       END_CIRC_REASON_TIMEOUT);
 +          victim->purpose = CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT;
 +          /* Record this failure to check for too many timeouts
 +           * in a row. This function does not record a time value yet
 +           * (we do that later); it only counts the fact that we did
 +           * have a timeout. */
 +          circuit_build_times_count_timeout(&circ_times,
 +                                            first_hop_succeeded);
 +          continue;
 +        }
 +
 +        /*
 +         * If the circuit build time is much greater than we would have cut
 +         * it off at, we probably had a suspend event along this codepath,
 +         * and we should discard the value.
 +         */
 +        if (now - victim->timestamp_created > 2*circ_times.close_ms/1000+1) {
 +          log_notice(LD_CIRC,
 +                     "Extremely large value for circuit build timeout: %lds. "
 +                     "Assuming clock jump. Purpose %d",
 +                     (long)(now - victim->timestamp_created),
 +                      victim->purpose);
 +        } else if (circuit_build_times_count_close(&circ_times,
 +                                            first_hop_succeeded,
 +                                            victim->timestamp_created)) {
 +          circuit_build_times_set_timeout(&circ_times);
 +        }
 +      }
      }
  
      if (victim->n_conn)
@@@ -433,10 -357,7 +433,10 @@@
                 circuit_state_to_string(victim->state), victim->purpose);
  
      circuit_log_path(LOG_INFO,LD_CIRC,TO_ORIGIN_CIRCUIT(victim));
 -    circuit_mark_for_close(victim, END_CIRC_REASON_TIMEOUT);
 +    if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT)
 +      circuit_mark_for_close(victim, END_CIRC_REASON_MEASUREMENT_EXPIRED);
 +    else
 +      circuit_mark_for_close(victim, END_CIRC_REASON_TIMEOUT);
    }
  }
  
@@@ -493,7 -414,7 +493,7 @@@ circuit_stream_is_being_handled(edge_co
        if (exitrouter && (!need_uptime || build_state->need_uptime)) {
          int ok;
          if (conn) {
 -          ok = connection_ap_can_use_exit(conn, exitrouter);
 +          ok = connection_ap_can_use_exit(conn, exitrouter, 0);
          } else {
            addr_policy_result_t r = compare_addr_to_addr_policy(
                0, port, exitrouter->exit_policy);
@@@ -510,11 -431,11 +510,11 @@@
  }
  
  /** Don't keep more than this many unused open circuits around. */
 -#define MAX_UNUSED_OPEN_CIRCUITS 12
 +#define MAX_UNUSED_OPEN_CIRCUITS 14
  
  /** Figure out how many circuits we have open that are clean. Make
   * sure it's enough for all the upcoming behaviors we predict we'll have.
 - * But if we have too many, close the not-so-useful ones.
 + * But put an upper bound on the total number of circuits.
   */
  static void
  circuit_predict_and_launch_new(void)
@@@ -596,19 -517,6 +596,19 @@@
      circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, flags);
      return;
    }
 +
 +  /* Finally, check to see if we still need more circuits to learn
 +   * a good build timeout. But if we're close to our max number we
 +   * want, don't do another -- we want to leave a few slots open so
 +   * we can still build circuits preemptively as needed. */
 +  if (num < MAX_UNUSED_OPEN_CIRCUITS-2 &&
 +      circuit_build_times_needs_circuits_now(&circ_times)) {
 +    flags = CIRCLAUNCH_NEED_CAPACITY;
 +    log_info(LD_CIRC,
 +             "Have %d clean circs need another buildtime test circ.", num);
 +    circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, flags);
 +    return;
 +  }
  }
  
  /** Build a new test circuit every 5 minutes */
@@@ -716,11 -624,6 +716,11 @@@ circuit_detach_stream(circuit_t *circ, 
    tor_fragile_assert();
  }
  
 +/** If we haven't yet decided on a good timeout value for circuit
 + * building, we close idles circuits aggressively so we can get more
 + * data points. */
 +#define IDLE_TIMEOUT_WHILE_LEARNING (10*60)
 +
  /** Find each circuit that has been unused for too long, or dirty
   * for too long and has no streams on it: mark it for close.
   */
@@@ -728,18 -631,10 +728,18 @@@ static voi
  circuit_expire_old_circuits_clientside(time_t now)
  {
    circuit_t *circ;
 -  time_t cutoff = now - get_options()->CircuitIdleTimeout;
 +  time_t cutoff;
 +
 +  if (circuit_build_times_needs_circuits(&circ_times)) {
 +    /* Circuits should be shorter lived if we need more of them
 +     * for learning a good build timeout */
 +    cutoff = now - IDLE_TIMEOUT_WHILE_LEARNING;
 +  } else {
 +    cutoff = now - get_options()->CircuitIdleTimeout;
 +  }
  
    for (circ = global_circuitlist; circ; circ = circ->next) {
 -    if (circ->marked_for_close || ! CIRCUIT_IS_ORIGIN(circ))
 +    if (circ->marked_for_close || !CIRCUIT_IS_ORIGIN(circ))
        continue;
      /* If the circuit has been dirty for too long, and there are no streams
       * on it, mark it for close.
@@@ -752,38 -647,14 +752,38 @@@
                  circ->n_circ_id, (int)(now - circ->timestamp_dirty),
                  circ->purpose);
        circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
 -    } else if (!circ->timestamp_dirty &&
 -               circ->state == CIRCUIT_STATE_OPEN &&
 -               circ->purpose == CIRCUIT_PURPOSE_C_GENERAL) {
 +    } else if (!circ->timestamp_dirty && circ->state == CIRCUIT_STATE_OPEN) {
        if (circ->timestamp_created < cutoff) {
 -        log_debug(LD_CIRC,
 -                  "Closing circuit that has been unused for %d seconds.",
 -                  (int)(now - circ->timestamp_created));
 -        circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
 +        if (circ->purpose == CIRCUIT_PURPOSE_C_GENERAL ||
 +                circ->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT ||
 +                circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
 +                circ->purpose == CIRCUIT_PURPOSE_TESTING ||
 +                (circ->purpose >= CIRCUIT_PURPOSE_C_INTRODUCING &&
 +                circ->purpose <= CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) ||
 +                circ->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND) {
 +          log_debug(LD_CIRC,
 +                    "Closing circuit that has been unused for %ld seconds.",
 +                    (long)(now - circ->timestamp_created));
 +          circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
 +        } else if (!TO_ORIGIN_CIRCUIT(circ)->is_ancient) {
 +          /* Server-side rend joined circuits can end up really old, because
 +           * they are reused by clients for longer than normal. The client
 +           * controls their lifespan. (They never become dirty, because
 +           * connection_exit_begin_conn() never marks anything as dirty.)
 +           * Similarly, server-side intro circuits last a long time. */
 +          if (circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED &&
 +              circ->purpose != CIRCUIT_PURPOSE_S_INTRO) {
 +            log_notice(LD_CIRC,
 +                       "Ancient non-dirty circuit %d is still around after "
 +                       "%ld seconds. Purpose: %d",
 +                       TO_ORIGIN_CIRCUIT(circ)->global_identifier,
 +                       (long)(now - circ->timestamp_created),
 +                       circ->purpose);
 +            /* FFFF implement a new circuit_purpose_to_string() so we don't
 +             * just print out a number for circ->purpose */
 +            TO_ORIGIN_CIRCUIT(circ)->is_ancient = 1;
 +          }
 +        }
        }
      }
    }
@@@ -923,11 -794,6 +923,11 @@@ circuit_has_opened(origin_circuit_t *ci
  {
    control_event_circuit_status(circ, CIRC_EVENT_BUILT, 0);
  
 +  /* Remember that this circuit has finished building. Now if we start
 +   * it building again later (e.g. by extending it), we will know not
 +   * to consider its build time. */
 +  circ->has_opened = 1;
 +
    switch (TO_CIRCUIT(circ)->purpose) {
      case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
        rend_client_rendcirc_has_opened(circ);
@@@ -980,29 -846,15 +980,29 @@@ circuit_build_failed(origin_circuit_t *
       * to blame, blame it. Also, avoid this relay for a while, and
       * fail any one-hop directory fetches destined for it. */
      const char *n_conn_id = circ->cpath->extend_info->identity_digest;
 +    int already_marked = 0;
      if (circ->_base.n_conn) {
        or_connection_t *n_conn = circ->_base.n_conn;
 +      if (n_conn->is_bad_for_new_circs) {
 +        /* We only want to blame this router when a fresh healthy
 +         * connection fails. So don't mark this router as newly failed,
 +         * since maybe this was just an old circuit attempt that's
 +         * finally timing out now. Also, there's no need to blow away
 +         * circuits/streams/etc, since the failure of an unhealthy conn
 +         * doesn't tell us much about whether a healthy conn would
 +         * succeed. */
 +        already_marked = 1;
 +      }
        log_info(LD_OR,
                 "Our circuit failed to get a response from the first hop "
                 "(%s:%d). I'm going to try to rotate to a better connection.",
                 n_conn->_base.address, n_conn->_base.port);
        n_conn->is_bad_for_new_circs = 1;
 +    } else {
 +      log_info(LD_OR,
 +               "Our circuit died before the first hop with no connection");
      }
 -    if (n_conn_id) {
 +    if (n_conn_id && !already_marked) {
        entry_guard_register_connect_status(n_conn_id, 0, 1, time(NULL));
        /* if there are any one-hop streams waiting on this circuit, fail
         * them now so they can retry elsewhere. */
@@@ -1084,8 -936,8 +1084,8 @@@ circuit_launch_by_router(uint8_t purpos
    if (exit)
      info = extend_info_from_router(exit);
    circ = circuit_launch_by_extend_info(purpose, info, flags);
 -  if (info)
 -    extend_info_free(info);
 +
 +  extend_info_free(info);
    return circ;
  }
  
@@@ -1228,17 -1080,15 +1228,17 @@@ circuit_get_open_circ_or_launch(edge_co
        int severity = LOG_NOTICE;
        /* FFFF if this is a tunneled directory fetch, don't yell
         * as loudly. the user doesn't even know it's happening. */
 -      if (options->UseBridges && bridges_known_but_down()) {
 +      if (entry_list_is_constrained(options) &&
 +          entries_known_but_down(options)) {
          log_fn(severity, LD_APP|LD_DIR,
 -               "Application request when we're believed to be "
 -               "offline. Optimistically trying known bridges again.");
 -        bridges_retry_all();
 +               "Application request when we haven't used client functionality "
 +               "lately. Optimistically trying known %s again.",
 +               options->UseBridges ? "bridges" : "entrynodes");
 +        entries_retry_all(options);
        } else if (!options->UseBridges || any_bridge_descriptors_known()) {
          log_fn(severity, LD_APP|LD_DIR,
 -               "Application request when we're believed to be "
 -               "offline. Optimistically trying directory fetches again.");
 +               "Application request when we haven't used client functionality "
 +               "lately. Optimistically trying directory fetches again.");
          routerlist_retry_directory_downloads(time(NULL));
        }
      }
@@@ -1261,7 -1111,7 +1261,7 @@@
                                                  need_uptime)) {
          log_notice(LD_APP,
                     "No Tor server allows exit to %s:%d. Rejecting.",
 -                   safe_str(conn->socks_request->address),
 +                   safe_str_client(conn->socks_request->address),
                     conn->socks_request->port);
          return -1;
        }
@@@ -1269,7 -1119,7 +1269,7 @@@
        /* XXXX022 Duplicates checks in connection_ap_handshake_attach_circuit */
        routerinfo_t *router = router_get_by_nickname(conn->chosen_exit_name, 1);
        int opt = conn->chosen_exit_optional;
 -      if (router && !connection_ap_can_use_exit(conn, router)) {
 +      if (router && !connection_ap_can_use_exit(conn, router, 0)) {
          log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP,
                 "Requested exit point '%s' would refuse request. %s.",
                 conn->chosen_exit_name, opt ? "Trying others" : "Closing");
@@@ -1302,14 -1152,19 +1302,14 @@@
        if (!extend_info) {
          log_info(LD_REND,
                   "No intro points for '%s': re-fetching service descriptor.",
 -                 safe_str(conn->rend_data->onion_address));
 -        /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
 -         * arrives first. Exception: When using client authorization, only
 -         * fetch v2 descriptors.*/
 +                 safe_str_client(conn->rend_data->onion_address));
          rend_client_refetch_v2_renddesc(conn->rend_data);
 -        if (conn->rend_data->auth_type == REND_NO_AUTH)
 -          rend_client_refetch_renddesc(conn->rend_data->onion_address);
          conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
          return 0;
        }
        log_info(LD_REND,"Chose '%s' as intro point for '%s'.",
                 extend_info->nickname,
 -               safe_str(conn->rend_data->onion_address));
 +               safe_str_client(conn->rend_data->onion_address));
      }
  
      /* If we have specified a particular exit node for our
@@@ -1338,7 -1193,7 +1338,7 @@@
              }
              if (tor_addr_from_str(&addr, conn->socks_request->address) < 0) {
                log_info(LD_DIR, "Broken address %s on tunnel conn. Closing.",
 -                       escaped_safe_str(conn->socks_request->address));
 +                       escaped_safe_str_client(conn->socks_request->address));
                return -1;
              }
              extend_info = extend_info_alloc(conn->chosen_exit_name+1,
@@@ -1380,7 -1235,8 +1380,7 @@@
                                             flags);
      }
  
 -    if (extend_info)
 -      extend_info_free(extend_info);
 +    extend_info_free(extend_info);
  
      if (desired_circuit_purpose != CIRCUIT_PURPOSE_C_GENERAL) {
        /* help predict this next time */
@@@ -1562,7 -1418,7 +1562,7 @@@ connection_ap_handshake_attach_circuit(
        LOG_INFO : LOG_NOTICE;
      log_fn(severity, LD_APP,
             "Tried for %d seconds to get a connection to %s:%d. Giving up.",
 -           conn_age, safe_str(conn->socks_request->address),
 +           conn_age, safe_str_client(conn->socks_request->address),
             conn->socks_request->port);
      return -1;
    }
@@@ -1589,7 -1445,7 +1589,7 @@@
          }
          return -1;
        }
 -      if (router && !connection_ap_can_use_exit(conn, router)) {
 +      if (router && !connection_ap_can_use_exit(conn, router, 0)) {
          log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP,
                 "Requested exit point '%s' would refuse request. %s.",
                 conn->chosen_exit_name, opt ? "Trying others" : "Closing");
diff --combined src/or/command.c
index beb5d09,69205c4..6a3ca13
--- a/src/or/command.c
+++ b/src/or/command.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -16,19 -16,6 +16,19 @@@
   */
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "command.h"
 +#include "connection.h"
 +#include "connection_or.h"
 +#include "config.h"
 +#include "control.h"
 +#include "cpuworker.h"
 +#include "hibernate.h"
 +#include "onion.h"
 +#include "relay.h"
 +#include "router.h"
 +#include "routerlist.h"
  
  /** How many CELL_PADDING cells have we received, ever? */
  uint64_t stats_n_padding_cells_processed = 0;
@@@ -288,14 -275,7 +288,14 @@@ command_process_create_cell(cell_t *cel
  
      /* hand it off to the cpuworkers, and then return. */
      if (assign_onionskin_to_cpuworker(NULL, circ, onionskin) < 0) {
 -      log_warn(LD_GENERAL,"Failed to hand off onionskin. Closing.");
 +#define WARN_HANDOFF_FAILURE_INTERVAL (6*60*60)
 +      static ratelim_t handoff_warning =
 +        RATELIM_INIT(WARN_HANDOFF_FAILURE_INTERVAL);
 +      char *m;
 +      if ((m = rate_limit_log(&handoff_warning, approx_time()))) {
 +        log_warn(LD_GENERAL,"Failed to hand off onionskin. Closing.%s",m);
 +        tor_free(m);
 +      }
        circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
        return;
      }
@@@ -416,18 -396,15 +416,18 @@@ command_process_relay_cell(cell_t *cell
     * gotten no more than MAX_RELAY_EARLY_CELLS_PER_CIRCUIT of them. */
    if (cell->command == CELL_RELAY_EARLY) {
      if (direction == CELL_DIRECTION_IN) {
 -      /* XXX Allow an unlimited number of inbound relay_early cells for
 -       * now, for hidden service compatibility. See bug 1038. -RD */
 +      /* Allow an unlimited number of inbound relay_early cells,
 +       * for hidden service compatibility. There isn't any way to make
 +       * a long circuit through inbound relay_early cells anyway. See
 +       * bug 1038. -RD */
      } else {
        or_circuit_t *or_circ = TO_OR_CIRCUIT(circ);
        if (or_circ->remaining_relay_early_cells == 0) {
          log_fn(LOG_PROTOCOL_WARN, LD_OR,
                 "Received too many RELAY_EARLY cells on circ %d from %s:%d."
                 "  Closing circuit.",
 -               cell->circ_id, safe_str(conn->_base.address), conn->_base.port);
 +               cell->circ_id, safe_str(conn->_base.address),
 +               conn->_base.port);
          circuit_mark_for_close(circ, END_CIRC_REASON_TORPROTOCOL);
          return;
        }
@@@ -535,8 -512,7 +535,8 @@@ command_process_versions_cell(var_cell_
    conn->handshake_state->received_versions = 1;
  
    log_info(LD_OR, "Negotiated version %d with %s:%d; sending NETINFO.",
 -           highest_supported_version, safe_str(conn->_base.address),
 +           highest_supported_version,
 +           safe_str_client(conn->_base.address),
             conn->_base.port);
    tor_assert(conn->link_proto >= 2);
  
@@@ -650,8 -626,8 +650,8 @@@ command_process_netinfo_cell(cell_t *ce
    else
      log_info(LD_OR, "Got good NETINFO cell from %s:%d; OR connection is now "
               "open, using protocol version %d",
 -             safe_str(conn->_base.address), conn->_base.port,
 -             (int)conn->link_proto);
 +             safe_str_client(conn->_base.address),
 +             conn->_base.port, (int)conn->link_proto);
    assert_connection_ok(TO_CONN(conn),time(NULL));
  }
  
diff --combined src/or/config.c
index 7fea850,45f6114..17d776e
--- a/src/or/config.c
+++ b/src/or/config.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -12,28 -12,6 +12,28 @@@
  #define CONFIG_PRIVATE
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "cpuworker.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "dns.h"
 +#include "geoip.h"
 +#include "hibernate.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "policies.h"
 +#include "relay.h"
 +#include "rendclient.h"
 +#include "rendservice.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
  #ifdef MS_WINDOWS
  #include <shlobj.h>
  #endif
@@@ -83,12 -61,11 +83,12 @@@ static config_abbrev_t _option_abbrevs[
    PLURAL(LongLivedPort),
    PLURAL(HiddenServiceNode),
    PLURAL(HiddenServiceExcludeNode),
 -  PLURAL(NumCpu),
 +  PLURAL(NumCPU),
    PLURAL(RendNode),
    PLURAL(RendExcludeNode),
    PLURAL(StrictEntryNode),
    PLURAL(StrictExitNode),
 +  PLURAL(StrictNode),
    { "l", "Log", 1, 0},
    { "AllowUnverifiedNodes", "AllowInvalidNodes", 0, 0},
    { "AutomapHostSuffixes", "AutomapHostsSuffixes", 0, 0},
@@@ -106,12 -83,10 +106,12 @@@
    { "NumEntryNodes", "NumEntryGuards", 0, 0},
    { "ResolvConf", "ServerDNSResolvConfFile", 0, 1},
    { "SearchDomains", "ServerDNSSearchDomains", 0, 1},
 -  { "ServerDNSAllowBrokenResolvConf", "ServerDNSAllowBrokenConfig", 0, 0 },
 +  { "ServerDNSAllowBrokenResolvConf", "ServerDNSAllowBrokenConfig", 0, 0},
    { "PreferTunnelledDirConns", "PreferTunneledDirConns", 0, 0},
    { "BridgeAuthoritativeDirectory", "BridgeAuthoritativeDir", 0, 0},
    { "HashedControlPassword", "__HashedControlSessionPassword", 1, 0},
 +  { "StrictEntryNodes", "StrictNodes", 0, 1},
 +  { "StrictExitNodes", "StrictNodes", 0, 1},
    { NULL, NULL, 0, 0},
  };
  
@@@ -159,7 -134,6 +159,7 @@@ static config_var_t _option_vars[] = 
    V(AccountingMax,               MEMUNIT,  "0 bytes"),
    V(AccountingStart,             STRING,   NULL),
    V(Address,                     STRING,   NULL),
 +  V(AllowDotExit,                BOOL,     "0"),
    V(AllowInvalidNodes,           CSV,      "middle,rendezvous"),
    V(AllowNonRFC953Hostnames,     BOOL,     "0"),
    V(AllowSingleHopCircuits,      BOOL,     "0"),
@@@ -188,15 -162,10 +188,15 @@@
    V(BridgePassword,              STRING,   NULL),
    V(BridgeRecordUsageByCountry,  BOOL,     "1"),
    V(BridgeRelay,                 BOOL,     "0"),
 -  V(CircuitBuildTimeout,         INTERVAL, "1 minute"),
 +  V(CellStatistics,              BOOL,     "0"),
 +  V(LearnCircuitBuildTimeout,    BOOL,     "1"),
 +  V(CircuitBuildTimeout,         INTERVAL, "0"),
    V(CircuitIdleTimeout,          INTERVAL, "1 hour"),
 +  V(CircuitStreamTimeout,        INTERVAL, "0"),
 +  V(CircuitPriorityHalflife,     DOUBLE,  "-100.0"), /*negative:'Use default'*/
    V(ClientDNSRejectInternalAddresses, BOOL,"1"),
    V(ClientOnly,                  BOOL,     "0"),
 +  V(ConsensusParams,             STRING,   NULL),
    V(ConnLimit,                   UINT,     "1000"),
    V(ConstrainedSockets,          BOOL,     "0"),
    V(ConstrainedSockSize,         MEMUNIT,  "8192"),
@@@ -217,19 -186,18 +217,19 @@@
    V(DirPort,                     UINT,     "0"),
    V(DirPortFrontPage,            FILENAME, NULL),
    OBSOLETE("DirPostPeriod"),
 -#ifdef ENABLE_GEOIP_STATS
 -  V(DirRecordUsageByCountry,     BOOL,     "0"),
 -  V(DirRecordUsageGranularity,   UINT,     "4"),
 -  V(DirRecordUsageRetainIPs,     INTERVAL, "14 days"),
 -  V(DirRecordUsageSaveInterval,  INTERVAL, "6 hours"),
 -#endif
 +  OBSOLETE("DirRecordUsageByCountry"),
 +  OBSOLETE("DirRecordUsageGranularity"),
 +  OBSOLETE("DirRecordUsageRetainIPs"),
 +  OBSOLETE("DirRecordUsageSaveInterval"),
 +  V(DirReqStatistics,            BOOL,     "0"),
    VAR("DirServer",               LINELIST, DirServers, NULL),
 +  V(DisableAllSwap,              BOOL,     "0"),
    V(DNSPort,                     UINT,     "0"),
    V(DNSListenAddress,            LINELIST, NULL),
    V(DownloadExtraInfo,           BOOL,     "0"),
    V(EnforceDistinctSubnets,      BOOL,     "1"),
    V(EntryNodes,                  ROUTERSET,   NULL),
 +  V(EntryStatistics,             BOOL,     "0"),
    V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "10 minutes"),
    V(ExcludeNodes,                ROUTERSET, NULL),
    V(ExcludeExitNodes,            ROUTERSET, NULL),
@@@ -237,20 -205,12 +237,20 @@@
    V(ExitNodes,                   ROUTERSET, NULL),
    V(ExitPolicy,                  LINELIST, NULL),
    V(ExitPolicyRejectPrivate,     BOOL,     "1"),
 +  V(ExitPortStatistics,          BOOL,     "0"),
 +  V(ExtraInfoStatistics,         BOOL,     "0"),
 +
 +#if defined (WINCE)
 +  V(FallbackNetworkstatusFile,   FILENAME, "fallback-consensus"),
 +#else
    V(FallbackNetworkstatusFile,   FILENAME,
      SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "fallback-consensus"),
 +#endif
    V(FascistFirewall,             BOOL,     "0"),
    V(FirewallPorts,               CSV,      ""),
    V(FastFirstHopPK,              BOOL,     "1"),
    V(FetchDirInfoEarly,           BOOL,     "0"),
 +  V(FetchDirInfoExtraEarly,      BOOL,     "0"),
    V(FetchServerDescriptors,      BOOL,     "1"),
    V(FetchHidServDescriptors,     BOOL,     "1"),
    V(FetchUselessDescriptors,     BOOL,     "0"),
@@@ -262,8 -222,6 +262,8 @@@
  #endif
    OBSOLETE("Group"),
    V(HardwareAccel,               BOOL,     "0"),
 +  V(AccelName,                   STRING,   NULL),
 +  V(AccelDir,                    FILENAME, NULL),
    V(HashedControlPassword,       LINELIST, NULL),
    V(HidServDirectoryV2,          BOOL,     "1"),
    VAR("HiddenServiceDir",    LINELIST_S, RendConfigLines,    NULL),
@@@ -275,15 -233,11 +275,15 @@@
    VAR("HiddenServiceAuthorizeClient",LINELIST_S,RendConfigLines, NULL),
    V(HidServAuth,                 LINELIST, NULL),
    V(HSAuthoritativeDir,          BOOL,     "0"),
 -  V(HSAuthorityRecordStats,      BOOL,     "0"),
 -  V(HttpProxy,                   STRING,   NULL),
 -  V(HttpProxyAuthenticator,      STRING,   NULL),
 -  V(HttpsProxy,                  STRING,   NULL),
 -  V(HttpsProxyAuthenticator,     STRING,   NULL),
 +  OBSOLETE("HSAuthorityRecordStats"),
 +  V(HTTPProxy,                   STRING,   NULL),
 +  V(HTTPProxyAuthenticator,      STRING,   NULL),
 +  V(HTTPSProxy,                  STRING,   NULL),
 +  V(HTTPSProxyAuthenticator,     STRING,   NULL),
 +  V(Socks4Proxy,                 STRING,   NULL),
 +  V(Socks5Proxy,                 STRING,   NULL),
 +  V(Socks5ProxyUsername,         STRING,   NULL),
 +  V(Socks5ProxyPassword,         STRING,   NULL),
    OBSOLETE("IgnoreVersion"),
    V(KeepalivePeriod,             INTERVAL, "5 minutes"),
    VAR("Log",                     LINELIST, Logs,             NULL),
@@@ -300,20 -254,17 +300,20 @@@
    V(MyFamily,                    STRING,   NULL),
    V(NewCircuitPeriod,            INTERVAL, "30 seconds"),
    VAR("NamingAuthoritativeDirectory",BOOL, NamingAuthoritativeDir, "0"),
 -  V(NatdListenAddress,           LINELIST, NULL),
 -  V(NatdPort,                    UINT,     "0"),
 +  V(NATDListenAddress,           LINELIST, NULL),
 +  V(NATDPort,                    UINT,     "0"),
    V(Nickname,                    STRING,   NULL),
 -  V(NoPublish,                   BOOL,     "0"),
 +  V(WarnUnsafeSocks,              BOOL,     "1"),
 +  OBSOLETE("NoPublish"),
    VAR("NodeFamily",              LINELIST, NodeFamilies,         NULL),
 -  V(NumCpus,                     UINT,     "1"),
 +  V(NumCPUs,                     UINT,     "1"),
    V(NumEntryGuards,              UINT,     "3"),
    V(ORListenAddress,             LINELIST, NULL),
    V(ORPort,                      UINT,     "0"),
    V(OutboundBindAddress,         STRING,   NULL),
    OBSOLETE("PathlenCoinWeight"),
 +  V(PerConnBWBurst,              MEMUNIT,  "0"),
 +  V(PerConnBWRate,               MEMUNIT,  "0"),
    V(PidFile,                     STRING,   NULL),
    V(TestingTorNetwork,           BOOL,     "0"),
    V(PreferTunneledDirConns,      BOOL,     "1"),
@@@ -327,7 -278,6 +327,7 @@@
    V(RecommendedClientVersions,   LINELIST, NULL),
    V(RecommendedServerVersions,   LINELIST, NULL),
    OBSOLETE("RedirectExit"),
 +  V(RefuseUnknownExits,          STRING,   "auto"),
    V(RejectPlaintextPorts,        CSV,      ""),
    V(RelayBandwidthBurst,         MEMUNIT,  "0"),
    V(RelayBandwidthRate,          MEMUNIT,  "0"),
@@@ -337,9 -287,8 +337,9 @@@
    V(RephistTrackTime,            INTERVAL, "24 hours"),
    OBSOLETE("RouterFile"),
    V(RunAsDaemon,                 BOOL,     "0"),
 -  V(RunTesting,                  BOOL,     "0"),
 -  V(SafeLogging,                 BOOL,     "1"),
 +//  V(RunTesting,                  BOOL,     "0"),
 +  OBSOLETE("RunTesting"), // currently unused
 +  V(SafeLogging,                 STRING,   "1"),
    V(SafeSocks,                   BOOL,     "0"),
    V(ServerDNSAllowBrokenConfig,  BOOL,     "1"),
    V(ServerDNSAllowNonRFC953Hostnames, BOOL,"0"),
@@@ -355,7 -304,8 +355,7 @@@
    V(SocksPort,                   UINT,     "9050"),
    V(SocksTimeout,                INTERVAL, "2 minutes"),
    OBSOLETE("StatusFetchPeriod"),
 -  V(StrictEntryNodes,            BOOL,     "0"),
 -  V(StrictExitNodes,             BOOL,     "0"),
 +  V(StrictNodes,                 BOOL,     "0"),
    OBSOLETE("SysLog"),
    V(TestSocks,                   BOOL,     "0"),
    OBSOLETE("TestVia"),
@@@ -380,7 -330,6 +380,7 @@@
    V(V3AuthDistDelay,             INTERVAL, "5 minutes"),
    V(V3AuthNIntervalsValid,       UINT,     "3"),
    V(V3AuthUseLegacyKey,          BOOL,     "0"),
 +  V(V3BandwidthsFile,            FILENAME, NULL),
    VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"),
    V(VirtualAddrNetwork,          STRING,   "127.192.0.0/10"),
    V(WarnPlaintextPorts,          CSV,      "23,109,110,143"),
@@@ -391,7 -340,6 +391,7 @@@
    VAR("__HashedControlSessionPassword", LINELIST, HashedControlSessionPassword,
        NULL),
    V(MinUptimeHidServDirectoryV2, INTERVAL, "24 hours"),
 +
    { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL }
  };
  
@@@ -414,7 -362,6 +414,7 @@@ static config_var_t testing_tor_network
    V(TestingV3AuthInitialDistDelay, INTERVAL, "20 seconds"),
    V(TestingAuthDirTimeToLearnReachability, INTERVAL, "0 minutes"),
    V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "0 minutes"),
 +  V(MinUptimeHidServDirectoryV2, INTERVAL, "0 minutes"),
    { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL }
  };
  #undef VAR
@@@ -430,9 -377,6 +430,9 @@@ static config_var_t _state_vars[] = 
    V(AccountingExpectedUsage,          MEMUNIT,  NULL),
    V(AccountingIntervalStart,          ISOTIME,  NULL),
    V(AccountingSecondsActive,          INTERVAL, NULL),
 +  V(AccountingSecondsToReachSoftLimit,INTERVAL, NULL),
 +  V(AccountingSoftLimitHitAt,         ISOTIME,  NULL),
 +  V(AccountingBytesAtSoftLimit,       MEMUNIT,  NULL),
  
    VAR("EntryGuard",              LINELIST_S,  EntryGuards,             NULL),
    VAR("EntryGuardDownSince",     LINELIST_S,  EntryGuards,             NULL),
@@@ -446,23 -390,12 +446,23 @@@
    V(BWHistoryWriteEnds,               ISOTIME,  NULL),
    V(BWHistoryWriteInterval,           UINT,     "900"),
    V(BWHistoryWriteValues,             CSV,      ""),
 +  V(BWHistoryDirReadEnds,             ISOTIME,  NULL),
 +  V(BWHistoryDirReadInterval,         UINT,     "900"),
 +  V(BWHistoryDirReadValues,           CSV,      ""),
 +  V(BWHistoryDirWriteEnds,            ISOTIME,  NULL),
 +  V(BWHistoryDirWriteInterval,        UINT,     "900"),
 +  V(BWHistoryDirWriteValues,          CSV,      ""),
  
    V(TorVersion,                       STRING,   NULL),
  
    V(LastRotatedOnionKey,              ISOTIME,  NULL),
    V(LastWritten,                      ISOTIME,  NULL),
  
 +  V(TotalBuildTimes,                  UINT,     NULL),
 +  V(CircuitBuildAbandonedCount,       UINT,     "0"),
 +  VAR("CircuitBuildTimeBin",          LINELIST_S, BuildtimeHistogram, NULL),
 +  VAR("BuildtimeHistogram",           LINELIST_V, BuildtimeHistogram, NULL),
 +
    { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL }
  };
  
@@@ -477,6 -410,213 +477,6 @@@ typedef struct config_var_description_
    const char *description;
  } config_var_description_t;
  
 -/** Descriptions of the configuration options, to be displayed by online
 - * option browsers */
 -/* XXXX022 did anybody want this? at all? If not, kill it.*/
 -static config_var_description_t options_description[] = {
 -  /* ==== general options */
 -  { "AvoidDiskWrites", "If non-zero, try to write to disk less frequently than"
 -    " we would otherwise." },
 -  { "BandwidthRate", "A token bucket limits the average incoming bandwidth on "
 -    "this node to the specified number of bytes per second." },
 -  { "BandwidthBurst", "Limit the maximum token buffer size (also known as "
 -    "burst) to the given number of bytes." },
 -  { "ConnLimit", "Minimum number of simultaneous sockets we must have." },
 -  { "ConstrainedSockets", "Shrink tx and rx buffers for sockets to avoid "
 -    "system limits on vservers and related environments.  See man page for "
 -    "more information regarding this option." },
 -  { "ConstrainedSockSize", "Limit socket buffers to this size when "
 -    "ConstrainedSockets is enabled." },
 -  /*  ControlListenAddress */
 -  { "ControlPort", "If set, Tor will accept connections from the same machine "
 -    "(localhost only) on this port, and allow those connections to control "
 -    "the Tor process using the Tor Control Protocol (described in "
 -    "control-spec.txt).", },
 -  { "CookieAuthentication", "If this option is set to 1, don't allow any "
 -    "connections to the control port except when the connecting process "
 -    "can read a file that Tor creates in its data directory." },
 -  { "DataDirectory", "Store working data, state, keys, and caches here." },
 -  { "DirServer", "Tor only trusts directories signed with one of these "
 -    "servers' keys.  Used to override the standard list of directory "
 -    "authorities." },
 -  /* { "FastFirstHopPK", "" }, */
 -  /* FetchServerDescriptors, FetchHidServDescriptors,
 -   * FetchUselessDescriptors */
 -  { "HardwareAccel", "If set, Tor tries to use hardware crypto accelerators "
 -    "when it can." },
 -  /* HashedControlPassword */
 -  { "HTTPProxy", "Force Tor to make all HTTP directory requests through this "
 -    "host:port (or host:80 if port is not set)." },
 -  { "HTTPProxyAuthenticator", "A username:password pair to be used with "
 -    "HTTPProxy." },
 -  { "HTTPSProxy", "Force Tor to make all TLS (SSL) connections through this "
 -    "host:port (or host:80 if port is not set)." },
 -  { "HTTPSProxyAuthenticator", "A username:password pair to be used with "
 -    "HTTPSProxy." },
 -  { "KeepalivePeriod", "Send a padding cell every N seconds to keep firewalls "
 -    "from closing our connections while Tor is not in use." },
 -  { "Log", "Where to send logging messages.  Format is "
 -    "minSeverity[-maxSeverity] (stderr|stdout|syslog|file FILENAME)." },
 -  { "OutboundBindAddress", "Make all outbound connections originate from the "
 -    "provided IP address (only useful for multiple network interfaces)." },
 -  { "PIDFile", "On startup, write our PID to this file. On clean shutdown, "
 -    "remove the file." },
 -  { "PreferTunneledDirConns", "If non-zero, avoid directory servers that "
 -    "don't support tunneled connections." },
 -  /* PreferTunneledDirConns */
 -  /* ProtocolWarnings */
 -  /* RephistTrackTime */
 -  { "RunAsDaemon", "If set, Tor forks and daemonizes to the background when "
 -    "started.  Unix only." },
 -  { "SafeLogging", "If set to 0, Tor logs potentially sensitive strings "
 -    "rather than replacing them with the string [scrubbed]." },
 -  { "TunnelDirConns", "If non-zero, when a directory server we contact "
 -    "supports it, we will build a one-hop circuit and make an encrypted "
 -    "connection via its ORPort." },
 -  { "User", "On startup, setuid to this user." },
 -
 -  /* ==== client options */
 -  { "AllowInvalidNodes", "Where on our circuits should Tor allow servers "
 -    "that the directory authorities haven't called \"valid\"?" },
 -  { "AllowNonRFC953Hostnames", "If set to 1, we don't automatically reject "
 -    "hostnames for having invalid characters." },
 -  /*  CircuitBuildTimeout, CircuitIdleTimeout */
 -  { "ClientOnly", "If set to 1, Tor will under no circumstances run as a "
 -    "server, even if ORPort is enabled." },
 -  { "EntryNodes", "A list of preferred entry nodes to use for the first hop "
 -    "in circuits, when possible." },
 -  /* { "EnforceDistinctSubnets" , "" }, */
 -  { "ExitNodes", "A list of preferred nodes to use for the last hop in "
 -    "circuits, when possible." },
 -  { "ExcludeNodes", "A list of nodes never to use when building a circuit." },
 -  { "FascistFirewall", "If set, Tor will only create outgoing connections to "
 -    "servers running on the ports listed in FirewallPorts." },
 -  { "FirewallPorts", "A list of ports that we can connect to.  Only used "
 -    "when FascistFirewall is set." },
 -  { "LongLivedPorts", "A list of ports for services that tend to require "
 -    "high-uptime connections." },
 -  { "MapAddress", "Force Tor to treat all requests for one address as if "
 -    "they were for another." },
 -  { "NewCircuitPeriod", "Force Tor to consider whether to build a new circuit "
 -    "every NUM seconds." },
 -  { "MaxCircuitDirtiness", "Do not attach new streams to a circuit that has "
 -    "been used more than this many seconds ago." },
 -  /* NatdPort, NatdListenAddress */
 -  { "NodeFamily", "A list of servers that constitute a 'family' and should "
 -    "never be used in the same circuit." },
 -  { "NumEntryGuards", "How many entry guards should we keep at a time?" },
 -  /* PathlenCoinWeight */
 -  { "ReachableAddresses", "Addresses we can connect to, as IP/bits:port-port. "
 -    "By default, we assume all addresses are reachable." },
 -  /* reachablediraddresses, reachableoraddresses. */
 -  /* SafeSOCKS */
 -  { "SOCKSPort", "The port where we listen for SOCKS connections from "
 -    "applications." },
 -  { "SOCKSListenAddress", "Bind to this address to listen to connections from "
 -    "SOCKS-speaking applications." },
 -  { "SOCKSPolicy", "Set an entry policy to limit which addresses can connect "
 -    "to the SOCKSPort." },
 -  /* SocksTimeout */
 -  { "StrictExitNodes", "If set, Tor will fail to operate when none of the "
 -    "configured ExitNodes can be used." },
 -  { "StrictEntryNodes", "If set, Tor will fail to operate when none of the "
 -    "configured EntryNodes can be used." },
 -  /* TestSocks */
 -  { "TrackHostsExit", "Hosts and domains which should, if possible, be "
 -    "accessed from the same exit node each time we connect to them." },
 -  { "TrackHostsExitExpire", "Time after which we forget which exit we were "
 -    "using to connect to hosts in TrackHostsExit." },
 -  /* "TransPort", "TransListenAddress */
 -  { "UseEntryGuards", "Set to 0 if we want to pick from the whole set of "
 -    "servers for the first position in each circuit, rather than picking a "
 -    "set of 'Guards' to prevent profiling attacks." },
 -
 -  /* === server options */
 -  { "Address", "The advertised (external) address we should use." },
 -  /* Accounting* options. */
 -  /* AssumeReachable */
 -  { "ContactInfo", "Administrative contact information to advertise for this "
 -    "server." },
 -  { "ExitPolicy", "Address/port ranges for which to accept or reject outgoing "
 -    "connections on behalf of Tor users." },
 -  /*  { "ExitPolicyRejectPrivate, "" }, */
 -  { "MaxAdvertisedBandwidth", "If set, we will not advertise more than this "
 -    "amount of bandwidth for our bandwidth rate, regardless of how much "
 -    "bandwidth we actually detect." },
 -  { "MaxOnionsPending", "Reject new attempts to extend circuits when we "
 -    "already have this many pending." },
 -  { "MyFamily", "Declare a list of other servers as belonging to the same "
 -    "family as this one, so that clients will not use two from the same "
 -    "family in the same circuit." },
 -  { "Nickname", "Set the server nickname." },
 -  { "NoPublish", "{DEPRECATED}" },
 -  { "NumCPUs", "How many processes to use at once for public-key crypto." },
 -  { "ORPort", "Advertise this port to listen for connections from Tor clients "
 -    "and servers." },
 -  { "ORListenAddress", "Bind to this address to listen for connections from "
 -    "clients and servers, instead of the default 0.0.0.0:ORPort." },
 -  { "PublishServerDescriptor", "Set to 0 to keep the server from "
 -    "uploading info to the directory authorities." },
 -  /* ServerDNS: DetectHijacking, ResolvConfFile, SearchDomains */
 -  { "ShutdownWaitLength", "Wait this long for clients to finish when "
 -    "shutting down because of a SIGINT." },
 -
 -  /* === directory cache options */
 -  { "DirPort", "Serve directory information from this port, and act as a "
 -    "directory cache." },
 -  { "DirPortFrontPage", "Serve a static html disclaimer on DirPort." },
 -  { "DirListenAddress", "Bind to this address to listen for connections from "
 -    "clients and servers, instead of the default 0.0.0.0:DirPort." },
 -  { "DirPolicy", "Set a policy to limit who can connect to the directory "
 -    "port." },
 -
 -  /*  Authority options: AuthDirBadExit, AuthDirInvalid, AuthDirReject,
 -   * AuthDirRejectUnlisted, AuthDirListBadExits, AuthoritativeDirectory,
 -   * DirAllowPrivateAddresses, HSAuthoritativeDir,
 -   * NamingAuthoritativeDirectory, RecommendedVersions,
 -   * RecommendedClientVersions, RecommendedServerVersions, RendPostPeriod,
 -   * RunTesting, V1AuthoritativeDirectory, VersioningAuthoritativeDirectory, */
 -
 -  /* Hidden service options: HiddenService: dir,excludenodes, nodes,
 -   * options, port.  PublishHidServDescriptor */
 -
 -  /* Nonpersistent options: __LeaveStreamsUnattached, __AllDirActionsPrivate */
 -  { NULL, NULL },
 -};
 -
 -/** Online description of state variables. */
 -static config_var_description_t state_description[] = {
 -  { "AccountingBytesReadInInterval",
 -    "How many bytes have we read in this accounting period?" },
 -  { "AccountingBytesWrittenInInterval",
 -    "How many bytes have we written in this accounting period?" },
 -  { "AccountingExpectedUsage",
 -    "How many bytes did we expect to use per minute? (0 for no estimate.)" },
 -  { "AccountingIntervalStart", "When did this accounting period begin?" },
 -  { "AccountingSecondsActive", "How long have we been awake in this period?" },
 -
 -  { "BWHistoryReadEnds", "When does the last-recorded read-interval end?" },
 -  { "BWHistoryReadInterval", "How long is each read-interval (in seconds)?" },
 -  { "BWHistoryReadValues", "Number of bytes read in each interval." },
 -  { "BWHistoryWriteEnds", "When does the last-recorded write-interval end?" },
 -  { "BWHistoryWriteInterval", "How long is each write-interval (in seconds)?"},
 -  { "BWHistoryWriteValues", "Number of bytes written in each interval." },
 -
 -  { "EntryGuard", "One of the nodes we have chosen as a fixed entry" },
 -  { "EntryGuardDownSince",
 -    "The last entry guard has been unreachable since this time." },
 -  { "EntryGuardUnlistedSince",
 -    "The last entry guard has been unusable since this time." },
 -
 -  { "LastRotatedOnionKey",
 -    "The last time at which we changed the medium-term private key used for "
 -    "building circuits." },
 -  { "LastWritten", "When was this state file last regenerated?" },
 -
 -  { "TorVersion", "Which version of Tor generated this state file?" },
 -  { NULL, NULL },
 -};
 -
  /** Type of a callback to validate whether a given configuration is
   * well-formed and consistent. See options_trial_assign() for documentation
   * of arguments. */
@@@ -495,6 -635,8 +495,6 @@@ typedef struct 
    config_var_t *vars; /**< List of variables we recognize, their default
                         * values, and where we stick them in the structure. */
    validate_fn_t validate_fn; /**< Function to validate config. */
 -  /** Documentation for configuration variables. */
 -  config_var_description_t *descriptions;
    /** If present, extra is a LINELIST variable for unrecognized
     * lines.  Otherwise, unrecognized lines are an error. */
    config_var_t *extra;
@@@ -558,6 -700,20 +558,6 @@@ static uint64_t config_parse_memunit(co
  static int config_parse_interval(const char *s, int *ok);
  static void init_libevent(void);
  static int opt_streq(const char *s1, const char *s2);
 -/** Versions of libevent. */
 -typedef enum {
 -  /* Note: we compare these, so it's important that "old" precede everything,
 -   * and that "other" come last. */
 -  LE_OLD=0, LE_10C, LE_10D, LE_10E, LE_11, LE_11A, LE_11B, LE_12, LE_12A,
 -  LE_13, LE_13A, LE_13B, LE_13C, LE_13D, LE_13E,
 -  LE_140, LE_141, LE_142, LE_143, LE_144, LE_145, LE_146, LE_147, LE_148,
 -  LE_1499,
 -  LE_OTHER
 -} le_version_t;
 -static le_version_t decode_libevent_version(const char *v, int *bincompat_out);
 -#if defined(HAVE_EVENT_GET_VERSION) && defined(HAVE_EVENT_GET_METHOD)
 -static void check_libevent_version(const char *m, int server);
 -#endif
  
  /** Magic value for or_options_t. */
  #define OR_OPTIONS_MAGIC 9090909
@@@ -570,6 -726,7 +570,6 @@@ static config_format_t options_format 
    _option_abbrevs,
    _option_vars,
    (validate_fn_t)options_validate,
 -  options_description,
    NULL
  };
  
@@@ -590,6 -747,7 +590,6 @@@ static config_format_t state_format = 
    _state_abbrevs,
    _state_vars,
    (validate_fn_t)or_state_validate,
 -  state_description,
    &state_extra_var,
  };
  
@@@ -654,13 -812,13 +654,13 @@@ set_options(or_options_t *new_val, cha
              "Acting on config options left us in a broken state. Dying.");
      exit(1);
    }
 -  if (old_options)
 -    config_free(&options_format, old_options);
 +
 +  config_free(&options_format, old_options);
  
    return 0;
  }
  
 -extern const char tor_svn_revision[]; /* from tor_main.c */
 +extern const char tor_git_revision[]; /* from tor_main.c */
  
  /** The version of this Tor process, as parsed. */
  static char *_version = NULL;
@@@ -670,10 -828,10 +670,10 @@@ const char 
  get_version(void)
  {
    if (_version == NULL) {
 -    if (strlen(tor_svn_revision)) {
 -      size_t len = strlen(VERSION)+strlen(tor_svn_revision)+8;
 +    if (strlen(tor_git_revision)) {
 +      size_t len = strlen(VERSION)+strlen(tor_git_revision)+16;
        _version = tor_malloc(len);
 -      tor_snprintf(_version, len, "%s (r%s)", VERSION, tor_svn_revision);
 +      tor_snprintf(_version, len, "%s (git-%s)", VERSION, tor_git_revision);
      } else {
        _version = tor_strdup(VERSION);
      }
@@@ -686,10 -844,8 +686,10 @@@
  static void
  or_options_free(or_options_t *options)
  {
 -  if (options->_ExcludeExitNodesUnion)
 -    routerset_free(options->_ExcludeExitNodesUnion);
 +  if (!options)
 +    return;
 +
 +  routerset_free(options->_ExcludeExitNodesUnion);
    config_free(&options_format, options);
  }
  
@@@ -698,72 -854,43 +698,72 @@@
  void
  config_free_all(void)
  {
 -  if (global_options) {
 -    or_options_free(global_options);
 -    global_options = NULL;
 -  }
 -  if (global_state) {
 -    config_free(&state_format, global_state);
 -    global_state = NULL;
 -  }
 -  if (global_cmdline_options) {
 -    config_free_lines(global_cmdline_options);
 -    global_cmdline_options = NULL;
 -  }
 +  or_options_free(global_options);
 +  global_options = NULL;
 +
 +  config_free(&state_format, global_state);
 +  global_state = NULL;
 +
 +  config_free_lines(global_cmdline_options);
 +  global_cmdline_options = NULL;
 +
    tor_free(torrc_fname);
    tor_free(_version);
    tor_free(global_dirfrontpagecontents);
  }
  
 -/** If options->SafeLogging is on, return a not very useful string,
 - * else return address.
 +/** Make <b>address</b> -- a piece of information related to our operation as
 + * a client -- safe to log according to the settings in options->SafeLogging,
 + * and return it.
 + *
 + * (We return "[scrubbed]" if SafeLogging is "1", and address otherwise.)
 + */
 +const char *
 +safe_str_client(const char *address)
 +{
 +  tor_assert(address);
 +  if (get_options()->_SafeLogging == SAFELOG_SCRUB_ALL)
 +    return "[scrubbed]";
 +  else
 +    return address;
 +}
 +
 +/** Make <b>address</b> -- a piece of information of unspecified sensitivity
 + * -- safe to log according to the settings in options->SafeLogging, and
 + * return it.
 + *
 + * (We return "[scrubbed]" if SafeLogging is anything besides "0", and address
 + * otherwise.)
   */
  const char *
  safe_str(const char *address)
  {
    tor_assert(address);
 -  if (get_options()->SafeLogging)
 +  if (get_options()->_SafeLogging != SAFELOG_SCRUB_NONE)
      return "[scrubbed]";
    else
      return address;
  }
  
 +/** Equivalent to escaped(safe_str_client(address)).  See reentrancy note on
 + * escaped(): don't use this outside the main thread, or twice in the same
 + * log statement. */
 +const char *
 +escaped_safe_str_client(const char *address)
 +{
 +  if (get_options()->_SafeLogging == SAFELOG_SCRUB_ALL)
 +    return "[scrubbed]";
 +  else
 +    return escaped(address);
 +}
 +
  /** Equivalent to escaped(safe_str(address)).  See reentrancy note on
   * escaped(): don't use this outside the main thread, or twice in the same
   * log statement. */
  const char *
  escaped_safe_str(const char *address)
  {
 -  if (get_options()->SafeLogging)
 +  if (get_options()->_SafeLogging != SAFELOG_SCRUB_NONE)
      return "[scrubbed]";
    else
      return escaped(address);
@@@ -962,12 -1089,10 +962,12 @@@ options_act_reversible(or_options_t *ol
      }
  
      /* Launch the listeners.  (We do this before we setuid, so we can bind to
 -     * ports under 1024.) */
 -    if (retry_all_listeners(replaced_listeners, new_listeners) < 0) {
 -      *msg = tor_strdup("Failed to bind one of the listener ports.");
 -      goto rollback;
 +     * ports under 1024.)  We don't want to rebind if we're hibernating. */
 +    if (!we_are_hibernating()) {
 +      if (retry_all_listeners(replaced_listeners, new_listeners) < 0) {
 +        *msg = tor_strdup("Failed to bind one of the listener ports.");
 +        goto rollback;
 +      }
      }
    }
  
@@@ -981,15 -1106,6 +981,15 @@@
    }
  #endif
  
 +  /* Attempt to lock all current and future memory with mlockall() only once */
 +  if (options->DisableAllSwap) {
 +    if (tor_mlockall() == -1) {
 +      *msg = tor_strdup("DisableAllSwap failure. Do you have proper "
 +                        "permissions?");
 +      goto done;
 +    }
 +  }
 +
    /* Setuid/setgid as appropriate */
    if (options->User) {
      if (switch_id(options->User) != 0) {
@@@ -1002,9 -1118,11 +1002,9 @@@
    /* Ensure data directory is private; create if possible. */
    if (check_private_dir(options->DataDirectory,
                          running_tor ? CPD_CREATE : CPD_CHECK)<0) {
 -    char buf[1024];
 -    int tmp = tor_snprintf(buf, sizeof(buf),
 +    tor_asprintf(msg,
                "Couldn't access/create private data directory \"%s\"",
                options->DataDirectory);
 -    *msg = tor_strdup(tmp >= 0 ? buf : "internal error");
      goto done;
      /* No need to roll back, since you can't change the value. */
    }
@@@ -1015,8 -1133,10 +1015,8 @@@
      tor_snprintf(fn, len, "%s"PATH_SEPARATOR"cached-status",
                   options->DataDirectory);
      if (check_private_dir(fn, running_tor ? CPD_CREATE : CPD_CHECK) < 0) {
 -      char buf[1024];
 -      int tmp = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
                  "Couldn't access/create private data directory \"%s\"", fn);
 -      *msg = tor_strdup(tmp >= 0 ? buf : "internal error");
        tor_free(fn);
        goto done;
      }
@@@ -1116,6 -1236,7 +1116,6 @@@ get_effective_bwrate(or_options_t *opti
      bw = options->MaxAdvertisedBandwidth;
    if (options->RelayBandwidthRate > 0 && bw > options->RelayBandwidthRate)
      bw = options->RelayBandwidthRate;
 -
    /* ensure_bandwidth_cap() makes sure that this cast can't overflow. */
    return (uint32_t)bw;
  }
@@@ -1193,14 -1314,14 +1193,14 @@@ options_act(or_options_t *old_options
      return 0;
  
    /* Finish backgrounding the process */
 -  if (running_tor && options->RunAsDaemon) {
 +  if (options->RunAsDaemon) {
      /* We may be calling this for the n'th time (on SIGHUP), but it's safe. */
      finish_daemon(options->DataDirectory);
    }
  
    /* Write our PID to the PID file. If we do not have write permissions we
     * will log a warning */
 -  if (running_tor && options->PidFile)
 +  if (options->PidFile)
      write_pidfile(options->PidFile);
  
    /* Register addressmap directives */
@@@ -1233,66 -1354,18 +1233,66 @@@
    if (accounting_is_enabled(options))
      configure_accounting(time(NULL));
  
 +  /* parse RefuseUnknownExits tristate */
 +  if (!strcmp(options->RefuseUnknownExits, "0"))
 +    options->RefuseUnknownExits_ = 0;
 +  else if (!strcmp(options->RefuseUnknownExits, "1"))
 +    options->RefuseUnknownExits_ = 1;
 +  else if (!strcmp(options->RefuseUnknownExits, "auto"))
 +    options->RefuseUnknownExits_ = -1;
 +  else {
 +    /* Should have caught this in options_validate */
 +    return -1;
 +  }
 +
 +  /* Change the cell EWMA settings */
 +  cell_ewma_set_scale_factor(options, networkstatus_get_latest_consensus());
 +
    /* Check for transitions that need action. */
    if (old_options) {
 -    if (options->UseEntryGuards && !old_options->UseEntryGuards) {
 +
 +    if ((options->UseEntryGuards && !old_options->UseEntryGuards) ||
 +        (options->ExcludeNodes &&
 +         !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes)) ||
 +        (options->ExcludeExitNodes &&
 +         !routerset_equal(old_options->ExcludeExitNodes,
 +                          options->ExcludeExitNodes)) ||
 +        (options->EntryNodes &&
 +         !routerset_equal(old_options->EntryNodes, options->EntryNodes)) ||
 +        (options->ExitNodes &&
 +         !routerset_equal(old_options->ExitNodes, options->ExitNodes)) ||
 +        options->StrictNodes != old_options->StrictNodes) {
        log_info(LD_CIRC,
 -               "Switching to entry guards; abandoning previous circuits");
 +               "Changed to using entry guards, or changed preferred or "
 +               "excluded node lists. Abandoning previous circuits.");
        circuit_mark_all_unused_circs();
        circuit_expire_all_dirty_circs();
      }
  
 +/* How long should we delay counting bridge stats after becoming a bridge?
 + * We use this so we don't count people who used our bridge thinking it is
 + * a relay. If you change this, don't forget to change the log message
 + * below. It's 4 hours (the time it takes to stop being used by clients)
 + * plus some extra time for clock skew. */
 +#define RELAY_BRIDGE_STATS_DELAY (6 * 60 * 60)
 +
      if (! bool_eq(options->BridgeRelay, old_options->BridgeRelay)) {
 -      log_info(LD_GENERAL, "Bridge status changed.  Forgetting GeoIP stats.");
 -      geoip_remove_old_clients(time(NULL)+(2*60*60));
 +      int was_relay = 0;
 +      if (options->BridgeRelay) {
 +        time_t int_start = time(NULL);
 +        if (old_options->ORPort == options->ORPort) {
 +          int_start += RELAY_BRIDGE_STATS_DELAY;
 +          was_relay = 1;
 +        }
 +        geoip_bridge_stats_init(int_start);
 +        log_info(LD_CONFIG, "We are acting as a bridge now.  Starting new "
 +                 "GeoIP stats interval%s.", was_relay ? " in 6 "
 +                 "hours from now" : "");
 +      } else {
 +        geoip_bridge_stats_term();
 +        log_info(LD_GENERAL, "We are no longer acting as a bridge.  "
 +                 "Forgetting GeoIP stats.");
 +      }
      }
  
      if (options_transition_affects_workers(old_options, options)) {
@@@ -1304,7 -1377,7 +1304,7 @@@
            return -1;
          }
          ip_address_changed(0);
 -        if (has_completed_circuit || !any_predicted_circuits(time(NULL)))
 +        if (can_complete_circuit || !any_predicted_circuits(time(NULL)))
            inform_testing_reachability();
        }
        cpuworkers_rotate();
@@@ -1317,10 -1390,6 +1317,10 @@@
  
      if (options->V3AuthoritativeDir && !old_options->V3AuthoritativeDir)
        init_keys();
 +
 +    if (options->PerConnBWRate != old_options->PerConnBWRate ||
 +        options->PerConnBWBurst != old_options->PerConnBWBurst)
 +      connection_or_update_token_buckets(get_connection_array(), options);
    }
  
    /* Maybe load geoip file */
@@@ -1343,63 -1412,13 +1343,63 @@@
      geoip_load_file(actual_fname, options);
      tor_free(actual_fname);
    }
 -#ifdef ENABLE_GEOIP_STATS
 -  log_warn(LD_CONFIG, "We are configured to measure GeoIP statistics, but "
 -           "the way these statistics are measured has changed "
 -           "significantly in later versions of Tor. The results may not be "
 -           "as expected if you are used to later versions.  Be sure you "
 -           "know what you are doing.");
 -#endif
 +
 +  if (options->DirReqStatistics && !geoip_is_loaded()) {
 +    /* Check if GeoIP database could be loaded. */
 +    log_warn(LD_CONFIG, "Configured to measure directory request "
 +             "statistics, but no GeoIP database found!");
 +    return -1;
 +  }
 +
 +  if (options->EntryStatistics) {
 +    if (should_record_bridge_info(options)) {
 +      /* Don't allow measuring statistics on entry guards when configured
 +       * as bridge. */
 +      log_warn(LD_CONFIG, "Bridges cannot be configured to measure "
 +               "additional GeoIP statistics as entry guards.");
 +      return -1;
 +    } else if (!geoip_is_loaded()) {
 +      /* Check if GeoIP database could be loaded. */
 +      log_warn(LD_CONFIG, "Configured to measure entry node statistics, "
 +               "but no GeoIP database found!");
 +      return -1;
 +    }
 +  }
 +
 +  if (options->CellStatistics || options->DirReqStatistics ||
 +      options->EntryStatistics || options->ExitPortStatistics) {
 +    time_t now = time(NULL);
 +    if ((!old_options || !old_options->CellStatistics) &&
 +        options->CellStatistics)
 +      rep_hist_buffer_stats_init(now);
 +    if ((!old_options || !old_options->DirReqStatistics) &&
 +        options->DirReqStatistics)
 +      geoip_dirreq_stats_init(now);
 +    if ((!old_options || !old_options->EntryStatistics) &&
 +        options->EntryStatistics)
 +      geoip_entry_stats_init(now);
 +    if ((!old_options || !old_options->ExitPortStatistics) &&
 +        options->ExitPortStatistics)
 +      rep_hist_exit_stats_init(now);
 +    if (!old_options)
 +      log_notice(LD_CONFIG, "Configured to measure statistics. Look for "
 +                 "the *-stats files that will first be written to the "
 +                 "data directory in 24 hours from now.");
 +  }
 +
 +  if (old_options && old_options->CellStatistics &&
 +      !options->CellStatistics)
 +    rep_hist_buffer_stats_term();
 +  if (old_options && old_options->DirReqStatistics &&
 +      !options->DirReqStatistics)
 +    geoip_dirreq_stats_term();
 +  if (old_options && old_options->EntryStatistics &&
 +      !options->EntryStatistics)
 +    geoip_entry_stats_term();
 +  if (old_options && old_options->ExitPortStatistics &&
 +      !options->ExitPortStatistics)
 +    rep_hist_exit_stats_term();
 +
    /* Check if we need to parse and add the EntryNodes config option. */
    if (options->EntryNodes &&
        (!old_options ||
@@@ -1472,10 -1491,7 +1472,10 @@@ expand_abbrev(config_format_t *fmt, con
                   fmt->abbrevs[i].abbreviated,
                   fmt->abbrevs[i].full);
        }
 -      return fmt->abbrevs[i].full;
 +      /* Keep going through the list in case we want to rewrite it more.
 +       * (We could imagine recursing here, but I don't want to get the
 +       * user into an infinite loop if we craft our list wrong.) */
 +      option = fmt->abbrevs[i].full;
      }
    }
    return option;
@@@ -1520,10 -1536,7 +1520,10 @@@ config_get_commandlines(int argc, char 
      *new = tor_malloc_zero(sizeof(config_line_t));
      s = argv[i];
  
 -    while (*s == '-')
 +    /* Each keyword may be prefixed with one or two dashes. */
 +    if (*s == '-')
 +      s++;
 +    if (*s == '-')
        s++;
  
      (*new)->key = tor_strdup(expand_abbrev(&options_format, s, 1, 1));
@@@ -1615,6 -1628,19 +1615,6 @@@ config_free_lines(config_line_t *front
    }
  }
  
 -/** Return the description for a given configuration variable, or NULL if no
 - * description exists. */
 -static const char *
 -config_find_description(config_format_t *fmt, const char *name)
 -{
 -  int i;
 -  for (i=0; fmt->descriptions[i].name; ++i) {
 -    if (!strcasecmp(name, fmt->descriptions[i].name))
 -      return fmt->descriptions[i].description;
 -  }
 -  return NULL;
 -}
 -
  /** If <b>key</b> is a configuration option, return the corresponding
   * config_var_t.  Otherwise, if <b>key</b> is a non-standard abbreviation,
   * warn, and return the corresponding config_var_t.  Otherwise return NULL.
@@@ -1645,16 -1671,6 +1645,16 @@@ config_find_option(config_format_t *fmt
    return NULL;
  }
  
 +/** Return the number of option entries in <b>fmt</b>. */
 +static int
 +config_count_options(config_format_t *fmt)
 +{
 +  int i;
 +  for (i=0; fmt->vars[i].name; ++i)
 +    ;
 +  return i;
 +}
 +
  /*
   * Functions to assign config options.
   */
@@@ -1668,7 -1684,8 +1668,7 @@@ static in
  config_assign_value(config_format_t *fmt, or_options_t *options,
                      config_line_t *c, char **msg)
  {
 -  int i, r, ok;
 -  char buf[1024];
 +  int i, ok;
    config_var_t *var;
    void *lvalue;
  
@@@ -1684,9 -1701,10 +1684,9 @@@
    case CONFIG_TYPE_UINT:
      i = (int)tor_parse_long(c->value, 10, 0, INT_MAX, &ok, NULL);
      if (!ok) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Int keyword '%s %s' is malformed or out of bounds.",
            c->key, c->value);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      *(int *)lvalue = i;
@@@ -1695,9 -1713,10 +1695,9 @@@
    case CONFIG_TYPE_INTERVAL: {
      i = config_parse_interval(c->value, &ok);
      if (!ok) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Interval '%s %s' is malformed or out of bounds.",
            c->key, c->value);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      *(int *)lvalue = i;
@@@ -1707,9 -1726,10 +1707,9 @@@
    case CONFIG_TYPE_MEMUNIT: {
      uint64_t u64 = config_parse_memunit(c->value, &ok);
      if (!ok) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Value '%s %s' is malformed or out of bounds.",
            c->key, c->value);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      *(uint64_t *)lvalue = u64;
@@@ -1719,9 -1739,10 +1719,9 @@@
    case CONFIG_TYPE_BOOL:
      i = (int)tor_parse_long(c->value, 10, 0, 1, &ok, NULL);
      if (!ok) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Boolean '%s %s' expects 0 or 1.",
            c->key, c->value);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      *(int *)lvalue = i;
@@@ -1739,8 -1760,9 +1739,8 @@@
  
    case CONFIG_TYPE_ISOTIME:
      if (parse_iso_time(c->value, (time_t *)lvalue)) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Invalid time '%s' for keyword '%s'", c->value, c->key);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      break;
@@@ -1751,8 -1773,9 +1751,8 @@@
      }
      *(routerset_t**)lvalue = routerset_new();
      if (routerset_parse(*(routerset_t**)lvalue, c->value, c->key)<0) {
 -      tor_snprintf(buf, sizeof(buf), "Invalid exit list '%s' for option '%s'",
 +      tor_asprintf(msg, "Invalid exit list '%s' for option '%s'",
                     c->value, c->key);
 -      *msg = tor_strdup(buf);
        return -1;
      }
      break;
@@@ -1777,8 -1800,9 +1777,8 @@@
      log_warn(LD_CONFIG, "Skipping obsolete configuration option '%s'", c->key);
      break;
    case CONFIG_TYPE_LINELIST_V:
 -    r = tor_snprintf(buf, sizeof(buf),
 +    tor_asprintf(msg,
          "You may not provide a value for virtual option '%s'", c->key);
 -    *msg = tor_strdup(r >= 0 ? buf : "internal error");
      return -1;
    default:
      tor_assert(0);
@@@ -1799,7 -1823,7 +1799,7 @@@
  static int
  config_assign_line(config_format_t *fmt, or_options_t *options,
                     config_line_t *c, int use_defaults,
 -                   int clear_first, char **msg)
 +                   int clear_first, bitarray_t *options_seen, char **msg)
  {
    config_var_t *var;
  
@@@ -1814,12 -1838,13 +1814,12 @@@
        config_line_append((config_line_t**)lvalue, c->key, c->value);
        return 0;
      } else {
 -      char buf[1024];
 -      int tmp = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
                  "Unknown option '%s'.  Failing.", c->key);
        return -1;
      }
    }
 +
    /* Put keyword into canonical case. */
    if (strcmp(var->name, c->key)) {
      tor_free(c->key);
@@@ -1842,18 -1867,6 +1842,18 @@@
      return 0;
    }
  
 +  if (options_seen && (var->type != CONFIG_TYPE_LINELIST &&
 +                       var->type != CONFIG_TYPE_LINELIST_S)) {
 +    /* We're tracking which options we've seen, and this option is not
 +     * supposed to occur more than once. */
 +    int var_index = (int)(var - fmt->vars);
 +    if (bitarray_is_set(options_seen, var_index)) {
 +      log_warn(LD_CONFIG, "Option '%s' used more than once; all but the last "
 +               "value will be ignored.", var->name);
 +    }
 +    bitarray_set(options_seen, var_index);
 +  }
 +
    if (config_assign_value(fmt, options, c, msg) < 0)
      return -2;
    return 0;
@@@ -1954,6 -1967,7 +1954,6 @@@ get_assigned_option(config_format_t *fm
  {
    config_var_t *var;
    const void *value;
 -  char buf[32];
    config_line_t *result;
    tor_assert(options && key);
  
@@@ -1994,16 -2008,19 +1994,16 @@@
      case CONFIG_TYPE_UINT:
        /* This means every or_options_t uint or bool element
         * needs to be an int. Not, say, a uint16_t or char. */
 -      tor_snprintf(buf, sizeof(buf), "%d", *(int*)value);
 -      result->value = tor_strdup(buf);
 +      tor_asprintf(&result->value, "%d", *(int*)value);
        escape_val = 0; /* Can't need escape. */
        break;
      case CONFIG_TYPE_MEMUNIT:
 -      tor_snprintf(buf, sizeof(buf), U64_FORMAT,
 +      tor_asprintf(&result->value, U64_FORMAT,
                     U64_PRINTF_ARG(*(uint64_t*)value));
        escape_val = 0; /* Can't need escape. */
        break;
      case CONFIG_TYPE_DOUBLE:
 -      tor_snprintf(buf, sizeof(buf), "%f", *(double*)value);
 -      result->value = tor_strdup(buf);
 +      tor_asprintf(&result->value, "%f", *(double*)value);
        escape_val = 0; /* Can't need escape. */
        break;
      case CONFIG_TYPE_BOOL:
@@@ -2122,8 -2139,6 +2122,8 @@@ config_assign(config_format_t *fmt, voi
                int use_defaults, int clear_first, char **msg)
  {
    config_line_t *p;
 +  bitarray_t *options_seen;
 +  const int n_options = config_count_options(fmt);
  
    CHECK(fmt, options);
  
@@@ -2143,18 -2158,14 +2143,18 @@@
        config_reset_line(fmt, options, p->key, use_defaults);
    }
  
 +  options_seen = bitarray_init_zero(n_options);
    /* pass 3: assign. */
    while (list) {
      int r;
      if ((r=config_assign_line(fmt, options, list, use_defaults,
 -                              clear_first, msg)))
 +                              clear_first, options_seen, msg))) {
 +      bitarray_free(options_seen);
        return r;
 +    }
      list = list->next;
    }
 +  bitarray_free(options_seen);
    return 0;
  }
  
@@@ -2283,7 -2294,7 +2283,7 @@@ print_usage(void
    printf(
  "Copyright (c) 2001-2004, Roger Dingledine\n"
  "Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson\n"
- "Copyright (c) 2007-2010, The Tor Project, Inc.\n\n"
+ "Copyright (c) 2007-2011, The Tor Project, Inc.\n\n"
  "tor -f <torrc> [args]\n"
  "See man page for options, or https://www.torproject.org/ for "
  "documentation.\n");
@@@ -2297,10 -2308,20 +2297,10 @@@ list_torrc_options(void
    smartlist_t *lines = smartlist_create();
    for (i = 0; _option_vars[i].name; ++i) {
      config_var_t *var = &_option_vars[i];
      if (var->type == CONFIG_TYPE_OBSOLETE ||
          var->type == CONFIG_TYPE_LINELIST_V)
        continue;
 -    desc = config_find_description(&options_format, var->name);
      printf("%s\n", var->name);
 -    if (desc) {
 -      wrap_string(lines, desc, 76, "    ", "    ");
 -      SMARTLIST_FOREACH(lines, char *, cp, {
 -          printf("%s", cp);
 -          tor_free(cp);
 -        });
 -      smartlist_clear(lines);
 -    }
    }
    smartlist_free(lines);
  }
@@@ -2319,7 -2340,7 +2319,7 @@@ resolve_my_address(int warn_severity, o
                     uint32_t *addr_out, char **hostname_out)
  {
    struct in_addr in;
 -  uint32_t addr;
 +  uint32_t addr; /* host order */
    char hostname[256];
    int explicit_ip=1;
    int explicit_hostname=1;
@@@ -2349,8 -2370,8 +2349,8 @@@
    if (tor_inet_aton(hostname, &in) == 0) {
      /* then we have to resolve it */
      explicit_ip = 0;
 -    if (tor_lookup_hostname(hostname, &addr)) {
 -      uint32_t interface_ip;
 +    if (tor_lookup_hostname(hostname, &addr)) { /* failed to resolve */
 +      uint32_t interface_ip; /* host order */
  
        if (explicit_hostname) {
          log_fn(warn_severity, LD_CONFIG,
@@@ -2371,7 -2392,7 +2371,7 @@@
        log_fn(notice_severity, LD_CONFIG, "Learned IP address '%s' for "
               "local interface. Using that.", tmpbuf);
        strlcpy(hostname, "<guessed from interfaces>", sizeof(hostname));
 -    } else {
 +    } else { /* resolved hostname into addr */
        in.s_addr = htonl(addr);
  
        if (!explicit_hostname &&
@@@ -2545,10 -2566,7 +2545,10 @@@ config_free(config_format_t *fmt, void 
  {
    int i;
  
 -  tor_assert(options);
 +  if (!options)
 +    return;
 +
 +  tor_assert(fmt);
  
    for (i=0; fmt->vars[i].name; ++i)
      option_clear(fmt, options, &(fmt->vars[i]));
@@@ -2650,8 -2668,6 +2650,8 @@@ is_listening_on_low_port(uint16_t port_
                           const config_line_t *listen_options)
  {
  #ifdef MS_WINDOWS
 +  (void) port_option;
 +  (void) listen_options;
    return 0; /* No port is too low for windows. */
  #else
    const config_line_t *l;
@@@ -2701,6 -2717,7 +2701,6 @@@ config_dump(config_format_t *fmt, void 
    config_line_t *line, *assigned;
    char *result;
    int i;
 -  const char *desc;
    char *msg = NULL;
  
    defaults = config_alloc(fmt);
@@@ -2728,13 -2745,24 +2728,13 @@@
               option_is_same(fmt, options, defaults, fmt->vars[i].name))
        comment_option = 1;
  
 -    desc = config_find_description(fmt, fmt->vars[i].name);
      line = assigned = get_assigned_option(fmt, options, fmt->vars[i].name, 1);
  
 -    if (line && desc) {
 -      /* Only dump the description if there's something to describe. */
 -      wrap_string(elements, desc, 78, "# ", "# ");
 -    }
 -
      for (; line; line = line->next) {
 -      size_t len = strlen(line->key) + strlen(line->value) + 5;
        char *tmp;
 -      tmp = tor_malloc(len);
 -      if (tor_snprintf(tmp, len, "%s%s %s\n",
 -                       comment_option ? "# " : "",
 -                       line->key, line->value)<0) {
 -        log_err(LD_BUG,"Internal error writing option value");
 -        tor_assert(0);
 -      }
 +      tor_asprintf(&tmp, "%s%s %s\n",
 +                   comment_option ? "# " : "",
 +                   line->key, line->value);
        smartlist_add(elements, tmp);
      }
      config_free_lines(assigned);
@@@ -2743,8 -2771,13 +2743,8 @@@
    if (fmt->extra) {
      line = *(config_line_t**)STRUCT_VAR_P(options, fmt->extra->var_offset);
      for (; line; line = line->next) {
 -      size_t len = strlen(line->key) + strlen(line->value) + 3;
        char *tmp;
 -      tmp = tor_malloc(len);
 -      if (tor_snprintf(tmp, len, "%s %s\n", line->key, line->value)<0) {
 -        log_err(LD_BUG,"Internal error writing option value");
 -        tor_assert(0);
 -      }
 +      tor_asprintf(&tmp, "%s %s\n", line->key, line->value);
        smartlist_add(elements, tmp);
      }
    }
@@@ -2760,7 -2793,7 +2760,7 @@@
   * the configuration in <b>options</b>.  If <b>minimal</b> is true, do not
   * include options that are the same as Tor's defaults.
   */
 -static char *
 +char *
  options_dump(or_options_t *options, int minimal)
  {
    return config_dump(&options_format, options, minimal, 0);
@@@ -2773,6 -2806,7 +2773,6 @@@ static in
  validate_ports_csv(smartlist_t *sl, const char *name, char **msg)
  {
    int i;
 -  char buf[1024];
    tor_assert(name);
  
    if (!sl)
@@@ -2782,7 -2816,9 +2782,7 @@@
    {
      i = atoi(cp);
      if (i < 1 || i > 65535) {
 -      int r = tor_snprintf(buf, sizeof(buf),
 -                           "Port '%s' out of range in %s", cp, name);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
 +      tor_asprintf(msg, "Port '%s' out of range in %s", cp, name);
        return -1;
      }
    });
@@@ -2796,15 -2832,18 +2796,15 @@@
  static int
  ensure_bandwidth_cap(uint64_t *value, const char *desc, char **msg)
  {
 -  int r;
 -  char buf[1024];
    if (*value > ROUTER_MAX_DECLARED_BANDWIDTH) {
      /* This handles an understandable special case where somebody says "2gb"
       * whereas our actual maximum is 2gb-1 (INT_MAX) */
      --*value;
    }
    if (*value > ROUTER_MAX_DECLARED_BANDWIDTH) {
 -    r = tor_snprintf(buf, sizeof(buf), "%s ("U64_FORMAT") must be at most %d",
 -                     desc, U64_PRINTF_ARG(*value),
 -                     ROUTER_MAX_DECLARED_BANDWIDTH);
 -    *msg = tor_strdup(r >= 0 ? buf : "internal error");
 +    tor_asprintf(msg, "%s ("U64_FORMAT") must be at most %d",
 +                 desc, U64_PRINTF_ARG(*value),
 +                 ROUTER_MAX_DECLARED_BANDWIDTH);
      return -1;
    }
    return 0;
@@@ -2855,14 -2894,15 +2855,14 @@@ compute_publishserverdescriptor(or_opti
  /** Highest allowable value for RendPostPeriod. */
  #define MAX_DIR_PERIOD (MIN_ONION_KEY_LIFETIME/2)
  
 -/** Lowest allowable value for CircuitBuildTimeout; values too low will
 - * increase network load because of failing connections being retried, and
 - * might prevent users from connecting to the network at all. */
 -#define MIN_CIRCUIT_BUILD_TIMEOUT 30
 -
  /** Lowest allowable value for MaxCircuitDirtiness; if this is too low, Tor
   * will generate too many circuits and potentially overload the network. */
  #define MIN_MAX_CIRCUIT_DIRTINESS 10
  
 +/** Lowest allowable value for CircuitStreamTimeout; if this is too low, Tor
 + * will generate too many circuits and potentially overload the network. */
 +#define MIN_CIRCUIT_STREAM_TIMEOUT 10
 +
  /** Return 0 if every setting in <b>options</b> is reasonable, and a
   * permissible transition from <b>old_options</b>. Else return -1.
   * Should have no side effects, except for normalizing the contents of
@@@ -2879,9 -2919,10 +2879,9 @@@ static in
  options_validate(or_options_t *old_options, or_options_t *options,
                   int from_setconf, char **msg)
  {
 -  int i, r;
 +  int i;
    config_line_t *cl;
    const char *uname = get_uname();
 -  char buf[1024];
  #define REJECT(arg) \
    STMT_BEGIN *msg = tor_strdup(arg); return -1; STMT_END
  #define COMPLAIN(arg) STMT_BEGIN log(LOG_WARN, LD_CONFIG, arg); STMT_END
@@@ -2898,7 -2939,7 +2898,7 @@@
         !strcmpstart(uname, "Windows Me"))) {
      log(LOG_WARN, LD_CONFIG, "Tor is running as a server, but you are "
          "running %s; this probably won't work. See "
 -        "http://wiki.noreply.org/noreply/TheOnionRouter/TorFAQ#ServerOS "
 +        "https://wiki.torproject.org/TheOnionRouter/TorFAQ#ServerOS "
          "for details.", uname);
    }
  
@@@ -2917,8 -2958,8 +2917,8 @@@
    if (options->TransPort == 0 && options->TransListenAddress != NULL)
      REJECT("TransPort must be defined if TransListenAddress is defined.");
  
 -  if (options->NatdPort == 0 && options->NatdListenAddress != NULL)
 -    REJECT("NatdPort must be defined if NatdListenAddress is defined.");
 +  if (options->NATDPort == 0 && options->NATDListenAddress != NULL)
 +    REJECT("NATDPort must be defined if NATDListenAddress is defined.");
  
    /* Don't gripe about SocksPort 0 with SocksListenAddress set; a standard
     * configuration does this. */
@@@ -2937,8 -2978,8 +2937,8 @@@
        old = old_options ? old_options->TransListenAddress : NULL;
        tp = "transparent proxy";
      } else {
 -      opt = options->NatdListenAddress;
 -      old = old_options ? old_options->NatdListenAddress : NULL;
 +      opt = options->NATDListenAddress;
 +      old = old_options ? old_options->NATDListenAddress : NULL;
        tp = "natd proxy";
      }
  
@@@ -2976,9 -3017,10 +2976,9 @@@
      }
    } else {
      if (!is_legal_nickname(options->Nickname)) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "Nickname '%s' is wrong length or contains illegal characters.",
            options->Nickname);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
    }
@@@ -2995,6 -3037,14 +2995,6 @@@
    if (options_init_logs(options, 1)<0) /* Validate the log(s) */
      REJECT("Failed to validate Log options. See logs for details.");
  
 -  if (options->NoPublish) {
 -    log(LOG_WARN, LD_CONFIG,
 -        "NoPublish is obsolete. Use PublishServerDescriptor instead.");
 -    SMARTLIST_FOREACH(options->PublishServerDescriptor, char *, s,
 -                      tor_free(s));
 -    smartlist_clear(options->PublishServerDescriptor);
 -  }
 -
    if (authdir_mode(options)) {
      /* confirm that our address isn't broken, so we can complain now */
      uint32_t tmp;
@@@ -3002,12 -3052,6 +3002,12 @@@
        REJECT("Failed to resolve/guess local address. See logs for details.");
    }
  
 +  if (strcmp(options->RefuseUnknownExits, "0") &&
 +      strcmp(options->RefuseUnknownExits, "1") &&
 +      strcmp(options->RefuseUnknownExits, "auto")) {
 +    REJECT("RefuseUnknownExits must be 0, 1, or auto");
 +  }
 +
  #ifndef MS_WINDOWS
    if (options->RunAsDaemon && torrc_fname && path_is_relative(torrc_fname))
      REJECT("Can't use a relative path to torrc when RunAsDaemon is set.");
@@@ -3022,14 -3066,14 +3022,14 @@@
    if (options->TransPort < 0 || options->TransPort > 65535)
      REJECT("TransPort option out of bounds.");
  
 -  if (options->NatdPort < 0 || options->NatdPort > 65535)
 -    REJECT("NatdPort option out of bounds.");
 +  if (options->NATDPort < 0 || options->NATDPort > 65535)
 +    REJECT("NATDPort option out of bounds.");
  
    if (options->SocksPort == 0 && options->TransPort == 0 &&
 -      options->NatdPort == 0 && options->ORPort == 0 &&
 +      options->NATDPort == 0 && options->ORPort == 0 &&
        options->DNSPort == 0 && !options->RendConfigLines)
      log(LOG_WARN, LD_CONFIG,
 -        "SocksPort, TransPort, NatdPort, DNSPort, and ORPort are all "
 +        "SocksPort, TransPort, NATDPort, DNSPort, and ORPort are all "
          "undefined, and there aren't any hidden services configured.  "
          "Tor will still run, but probably won't do anything.");
  
@@@ -3063,11 -3107,19 +3063,11 @@@
      routerset_union(options->_ExcludeExitNodesUnion,options->ExcludeNodes);
    }
  
 -  if (options->StrictExitNodes &&
 -      (!options->ExitNodes) &&
 -      (!old_options ||
 -       (old_options->StrictExitNodes != options->StrictExitNodes) ||
 -       (!routerset_equal(old_options->ExitNodes,options->ExitNodes))))
 -    COMPLAIN("StrictExitNodes set, but no ExitNodes listed.");
 -
 -  if (options->StrictEntryNodes &&
 -      (!options->EntryNodes) &&
 -      (!old_options ||
 -       (old_options->StrictEntryNodes != options->StrictEntryNodes) ||
 -       (!routerset_equal(old_options->EntryNodes,options->EntryNodes))))
 -    COMPLAIN("StrictEntryNodes set, but no EntryNodes listed.");
 +  if (options->ExcludeNodes && options->StrictNodes) {
 +    COMPLAIN("You have asked to exclude certain relays from all positions "
 +             "in your circuits. Expect hidden services and other Tor "
 +             "features to be broken in unpredictable ways.");
 +  }
  
    if (options->EntryNodes && !routerset_is_list(options->EntryNodes)) {
      /* XXXX fix this; see entry_guards_prepend_from_config(). */
@@@ -3105,10 -3157,6 +3105,10 @@@
            options->V3AuthoritativeDir))
        REJECT("AuthoritativeDir is set, but none of "
               "(Bridge/HS/V1/V2/V3)AuthoritativeDir is set.");
 +    /* If we have a v3bandwidthsfile and it's broken, complain on startup */
 +    if (options->V3BandwidthsFile && !old_options) {
 +      dirserv_read_measured_bandwidths(options->V3BandwidthsFile, NULL);
 +    }
    }
  
    if (options->AuthoritativeDir && !options->DirPort)
@@@ -3120,14 -3168,15 +3120,14 @@@
    if (options->AuthoritativeDir && options->ClientOnly)
      REJECT("Running as authoritative directory, but ClientOnly also set.");
  
 -  if (options->HSAuthorityRecordStats && !options->HSAuthoritativeDir)
 -    REJECT("HSAuthorityRecordStats is set but we're not running as "
 -           "a hidden service authority.");
 +  if (options->FetchDirInfoExtraEarly && !options->FetchDirInfoEarly)
 +    REJECT("FetchDirInfoExtraEarly requires that you also set "
 +           "FetchDirInfoEarly");
  
    if (options->ConnLimit <= 0) {
 -    r = tor_snprintf(buf, sizeof(buf),
 +    tor_asprintf(msg,
          "ConnLimit must be greater than 0, but was set to %d",
          options->ConnLimit);
 -    *msg = tor_strdup(r >= 0 ? buf : "internal error");
      return -1;
    }
  
@@@ -3246,29 -3295,18 +3246,29 @@@
          else if (!strcasecmp(cp, "rendezvous"))
            options->_AllowInvalid |= ALLOW_INVALID_RENDEZVOUS;
          else {
 -          r = tor_snprintf(buf, sizeof(buf),
 +          tor_asprintf(msg,
                "Unrecognized value '%s' in AllowInvalidNodes", cp);
 -          *msg = tor_strdup(r >= 0 ? buf : "internal error");
            return -1;
          }
        });
    }
  
 +  if (!options->SafeLogging ||
 +      !strcasecmp(options->SafeLogging, "0")) {
 +    options->_SafeLogging = SAFELOG_SCRUB_NONE;
 +  } else if (!strcasecmp(options->SafeLogging, "relay")) {
 +    options->_SafeLogging = SAFELOG_SCRUB_RELAY;
 +  } else if (!strcasecmp(options->SafeLogging, "1")) {
 +    options->_SafeLogging = SAFELOG_SCRUB_ALL;
 +  } else {
 +    tor_asprintf(msg,
 +                     "Unrecognized value '%s' in SafeLogging",
 +                     escaped(options->SafeLogging));
 +    return -1;
 +  }
 +
    if (compute_publishserverdescriptor(options) < 0) {
 -    r = tor_snprintf(buf, sizeof(buf),
 -                     "Unrecognized value in PublishServerDescriptor");
 -    *msg = tor_strdup(r >= 0 ? buf : "internal error");
 +    tor_asprintf(msg, "Unrecognized value in PublishServerDescriptor");
      return -1;
    }
  
@@@ -3281,12 -3319,6 +3281,12 @@@
             "PublishServerDescriptor line.");
    }
  
 +  if (options->BridgeRelay && options->DirPort) {
 +    log_warn(LD_CONFIG, "Can't set a DirPort on a bridge relay; disabling "
 +             "DirPort");
 +    options->DirPort = 0;
 +  }
 +
    if (options->MinUptimeHidServDirectoryV2 < 0) {
      log_warn(LD_CONFIG, "MinUptimeHidServDirectoryV2 option must be at "
                          "least 0 seconds. Changing to 0.");
@@@ -3294,30 -3326,29 +3294,30 @@@
    }
  
    if (options->RendPostPeriod < MIN_REND_POST_PERIOD) {
 -    log(LOG_WARN,LD_CONFIG,"RendPostPeriod option is too short; "
 -        "raising to %d seconds.", MIN_REND_POST_PERIOD);
 +    log_warn(LD_CONFIG, "RendPostPeriod option is too short; "
 +             "raising to %d seconds.", MIN_REND_POST_PERIOD);
      options->RendPostPeriod = MIN_REND_POST_PERIOD;
    }
  
    if (options->RendPostPeriod > MAX_DIR_PERIOD) {
 -    log(LOG_WARN, LD_CONFIG, "RendPostPeriod is too large; clipping to %ds.",
 -        MAX_DIR_PERIOD);
 +    log_warn(LD_CONFIG, "RendPostPeriod is too large; clipping to %ds.",
 +             MAX_DIR_PERIOD);
      options->RendPostPeriod = MAX_DIR_PERIOD;
    }
  
 -  if (options->CircuitBuildTimeout < MIN_CIRCUIT_BUILD_TIMEOUT) {
 -    log(LOG_WARN, LD_CONFIG, "CircuitBuildTimeout option is too short; "
 -      "raising to %d seconds.", MIN_CIRCUIT_BUILD_TIMEOUT);
 -    options->CircuitBuildTimeout = MIN_CIRCUIT_BUILD_TIMEOUT;
 -  }
 -
    if (options->MaxCircuitDirtiness < MIN_MAX_CIRCUIT_DIRTINESS) {
 -    log(LOG_WARN, LD_CONFIG, "MaxCircuitDirtiness option is too short; "
 -      "raising to %d seconds.", MIN_MAX_CIRCUIT_DIRTINESS);
 +    log_warn(LD_CONFIG, "MaxCircuitDirtiness option is too short; "
 +             "raising to %d seconds.", MIN_MAX_CIRCUIT_DIRTINESS);
      options->MaxCircuitDirtiness = MIN_MAX_CIRCUIT_DIRTINESS;
    }
  
 +  if (options->CircuitStreamTimeout &&
 +      options->CircuitStreamTimeout < MIN_CIRCUIT_STREAM_TIMEOUT) {
 +    log_warn(LD_CONFIG, "CircuitStreamTimeout option is too short; "
 +             "raising to %d seconds.", MIN_CIRCUIT_STREAM_TIMEOUT);
 +    options->CircuitStreamTimeout = MIN_CIRCUIT_STREAM_TIMEOUT;
 +  }
 +
    if (options->KeepalivePeriod < 1)
      REJECT("KeepalivePeriod option must be positive.");
  
@@@ -3336,37 -3367,34 +3336,37 @@@
    if (ensure_bandwidth_cap(&options->RelayBandwidthBurst,
                             "RelayBandwidthBurst", msg) < 0)
      return -1;
 +  if (ensure_bandwidth_cap(&options->PerConnBWRate,
 +                           "PerConnBWRate", msg) < 0)
 +    return -1;
 +  if (ensure_bandwidth_cap(&options->PerConnBWBurst,
 +                           "PerConnBWBurst", msg) < 0)
 +    return -1;
  
    if (server_mode(options)) {
      if (options->BandwidthRate < ROUTER_REQUIRED_MIN_BANDWIDTH) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
                         "BandwidthRate is set to %d bytes/second. "
                         "For servers, it must be at least %d.",
                         (int)options->BandwidthRate,
                         ROUTER_REQUIRED_MIN_BANDWIDTH);
        return -1;
      } else if (options->MaxAdvertisedBandwidth <
                 ROUTER_REQUIRED_MIN_BANDWIDTH/2) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
                         "MaxAdvertisedBandwidth is set to %d bytes/second. "
                         "For servers, it must be at least %d.",
                         (int)options->MaxAdvertisedBandwidth,
                         ROUTER_REQUIRED_MIN_BANDWIDTH/2);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      if (options->RelayBandwidthRate &&
        options->RelayBandwidthRate < ROUTER_REQUIRED_MIN_BANDWIDTH) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
                         "RelayBandwidthRate is set to %d bytes/second. "
                         "For servers, it must be at least %d.",
                         (int)options->RelayBandwidthRate,
                         ROUTER_REQUIRED_MIN_BANDWIDTH);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
    }
@@@ -3391,73 -3419,34 +3391,73 @@@
    if (accounting_parse_options(options, 1)<0)
      REJECT("Failed to parse accounting options. See logs for details.");
  
 -  if (options->HttpProxy) { /* parse it now */
 -    if (parse_addr_port(LOG_WARN, options->HttpProxy, NULL,
 -                        &options->HttpProxyAddr, &options->HttpProxyPort) < 0)
 -      REJECT("HttpProxy failed to parse or resolve. Please fix.");
 -    if (options->HttpProxyPort == 0) { /* give it a default */
 -      options->HttpProxyPort = 80;
 +  if (options->HTTPProxy) { /* parse it now */
 +    if (tor_addr_port_parse(options->HTTPProxy,
 +                        &options->HTTPProxyAddr, &options->HTTPProxyPort) < 0)
 +      REJECT("HTTPProxy failed to parse or resolve. Please fix.");
 +    if (options->HTTPProxyPort == 0) { /* give it a default */
 +      options->HTTPProxyPort = 80;
 +    }
 +  }
 +
 +  if (options->HTTPProxyAuthenticator) {
 +    if (strlen(options->HTTPProxyAuthenticator) >= 48)
 +      REJECT("HTTPProxyAuthenticator is too long (>= 48 chars).");
 +  }
 +
 +  if (options->HTTPSProxy) { /* parse it now */
 +    if (tor_addr_port_parse(options->HTTPSProxy,
 +                        &options->HTTPSProxyAddr, &options->HTTPSProxyPort) <0)
 +      REJECT("HTTPSProxy failed to parse or resolve. Please fix.");
 +    if (options->HTTPSProxyPort == 0) { /* give it a default */
 +      options->HTTPSProxyPort = 443;
      }
    }
  
 -  if (options->HttpProxyAuthenticator) {
 -    if (strlen(options->HttpProxyAuthenticator) >= 48)
 -      REJECT("HttpProxyAuthenticator is too long (>= 48 chars).");
 +  if (options->HTTPSProxyAuthenticator) {
 +    if (strlen(options->HTTPSProxyAuthenticator) >= 48)
 +      REJECT("HTTPSProxyAuthenticator is too long (>= 48 chars).");
    }
  
 -  if (options->HttpsProxy) { /* parse it now */
 -    if (parse_addr_port(LOG_WARN, options->HttpsProxy, NULL,
 -                        &options->HttpsProxyAddr, &options->HttpsProxyPort) <0)
 -      REJECT("HttpsProxy failed to parse or resolve. Please fix.");
 -    if (options->HttpsProxyPort == 0) { /* give it a default */
 -      options->HttpsProxyPort = 443;
 +  if (options->Socks4Proxy) { /* parse it now */
 +    if (tor_addr_port_parse(options->Socks4Proxy,
 +                        &options->Socks4ProxyAddr,
 +                        &options->Socks4ProxyPort) <0)
 +      REJECT("Socks4Proxy failed to parse or resolve. Please fix.");
 +    if (options->Socks4ProxyPort == 0) { /* give it a default */
 +      options->Socks4ProxyPort = 1080;
      }
    }
  
 -  if (options->HttpsProxyAuthenticator) {
 -    if (strlen(options->HttpsProxyAuthenticator) >= 48)
 -      REJECT("HttpsProxyAuthenticator is too long (>= 48 chars).");
 +  if (options->Socks5Proxy) { /* parse it now */
 +    if (tor_addr_port_parse(options->Socks5Proxy,
 +                            &options->Socks5ProxyAddr,
 +                            &options->Socks5ProxyPort) <0)
 +      REJECT("Socks5Proxy failed to parse or resolve. Please fix.");
 +    if (options->Socks5ProxyPort == 0) { /* give it a default */
 +      options->Socks5ProxyPort = 1080;
 +    }
    }
  
 +  if (options->Socks4Proxy && options->Socks5Proxy)
 +    REJECT("You cannot specify both Socks4Proxy and SOCKS5Proxy");
 +
 +  if (options->Socks5ProxyUsername) {
 +    size_t len;
 +
 +    len = strlen(options->Socks5ProxyUsername);
 +    if (len < 1 || len > 255)
 +      REJECT("Socks5ProxyUsername must be between 1 and 255 characters.");
 +
 +    if (!options->Socks5ProxyPassword)
 +      REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
 +
 +    len = strlen(options->Socks5ProxyPassword);
 +    if (len < 1 || len > 255)
 +      REJECT("Socks5ProxyPassword must be between 1 and 255 characters.");
 +  } else if (options->Socks5ProxyPassword)
 +    REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
 +
    if (options->HashedControlPassword) {
      smartlist_t *sl = decode_hashed_passwords(options->HashedControlPassword);
      if (!sl) {
@@@ -3521,13 -3510,6 +3521,13 @@@
               "upgrade your Tor controller as soon as possible.");
    }
  
 +  if (options->CookieAuthFileGroupReadable && !options->CookieAuthFile) {
 +    log_warn(LD_CONFIG, "You set the CookieAuthFileGroupReadable but did "
 +             "not configure a the path for the cookie file via "
 +             "CookieAuthFile. This means your cookie will not be group "
 +             "readable.");
 +  }
 +
    if (options->UseEntryGuards && ! options->NumEntryGuards)
      REJECT("Cannot enable UseEntryGuards with NumEntryGuards set to 0");
  
@@@ -3561,10 -3543,11 +3561,10 @@@
      if (options->ConstrainedSockSize < MIN_CONSTRAINED_TCP_BUFFER ||
          options->ConstrainedSockSize > MAX_CONSTRAINED_TCP_BUFFER ||
          options->ConstrainedSockSize % 1024) {
 -      r = tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(msg,
            "ConstrainedSockSize is invalid.  Must be a value between %d and %d "
            "in 1024 byte increments.",
            MIN_CONSTRAINED_TCP_BUFFER, MAX_CONSTRAINED_TCP_BUFFER);
 -      *msg = tor_strdup(r >= 0 ? buf : "internal error");
        return -1;
      }
      if (options->DirPort) {
@@@ -3612,12 -3595,6 +3612,12 @@@
    if (options->PreferTunneledDirConns && !options->TunnelDirConns)
      REJECT("Must set TunnelDirConns if PreferTunneledDirConns is set.");
  
 +  if ((options->Socks4Proxy || options->Socks5Proxy) &&
 +      !options->HTTPProxy && !options->PreferTunneledDirConns)
 +    REJECT("When Socks4Proxy or Socks5Proxy is configured, "
 +           "PreferTunneledDirConns and TunnelDirConns must both be "
 +           "set to 1, or HTTPProxy must be configured.");
 +
    if (options->AutomapHostsSuffixes) {
      SMARTLIST_FOREACH(options->AutomapHostsSuffixes, char *, suf,
      {
@@@ -3632,13 -3609,6 +3632,13 @@@
             "a non-default set of DirServers.");
    }
  
 +  if (options->AllowSingleHopExits && !options->DirServers) {
 +    COMPLAIN("You have set AllowSingleHopExits; now your relay will allow "
 +             "others to make one-hop exits. However, since by default most "
 +             "clients avoid relays that set this option, most clients will "
 +             "ignore you.");
 +  }
 +
    /*XXXX022 checking for defaults manually like this is a bit fragile.*/
  
    /* Keep changes to hard-coded values synchronous to man page and default
@@@ -3704,26 -3674,6 +3704,26 @@@
                          "testing Tor network!");
    }
  
 +  if (options->AccelName && !options->HardwareAccel)
 +    options->HardwareAccel = 1;
 +  if (options->AccelDir && !options->AccelName)
 +    REJECT("Can't use hardware crypto accelerator dir without engine name.");
 +
 +  if (options->PublishServerDescriptor)
 +    SMARTLIST_FOREACH(options->PublishServerDescriptor, const char *, pubdes, {
 +      if (!strcmp(pubdes, "1") || !strcmp(pubdes, "0"))
 +        if (smartlist_len(options->PublishServerDescriptor) > 1) {
 +          COMPLAIN("You have passed a list of multiple arguments to the "
 +                   "PublishServerDescriptor option that includes 0 or 1. "
 +                   "0 or 1 should only be used as the sole argument. "
 +                   "This configuration will be rejected in a future release.");
 +          break;
 +        }
 +    });
 +
 +  if (options->BridgeRelay == 1 && options->ORPort == 0)
 +      REJECT("BridgeRelay is 1, ORPort is 0. This is an invalid combination.");
 +
    return 0;
  #undef REJECT
  #undef COMPLAIN
@@@ -3762,10 -3712,12 +3762,10 @@@ options_transition_allowed(or_options_
    }
  
    if (strcmp(old->DataDirectory,new_val->DataDirectory)!=0) {
 -    char buf[1024];
 -    int r = tor_snprintf(buf, sizeof(buf),
 +    tor_asprintf(msg,
                 "While Tor is running, changing DataDirectory "
                 "(\"%s\"->\"%s\") is not allowed.",
                 old->DataDirectory, new_val->DataDirectory);
 -    *msg = tor_strdup(r >= 0 ? buf : "internal error");
      return -1;
    }
  
@@@ -3774,22 -3726,19 +3774,22 @@@
      return -1;
    }
  
 -  if (!opt_streq(old->Group, new_val->Group)) {
 -    *msg = tor_strdup("While Tor is running, changing Group is not allowed.");
 +  if ((old->HardwareAccel != new_val->HardwareAccel)
 +      || !opt_streq(old->AccelName, new_val->AccelName)
 +      || !opt_streq(old->AccelDir, new_val->AccelDir)) {
 +    *msg = tor_strdup("While Tor is running, changing OpenSSL hardware "
 +                      "acceleration engine is not allowed.");
      return -1;
    }
  
 -  if (old->HardwareAccel != new_val->HardwareAccel) {
 -    *msg = tor_strdup("While Tor is running, changing HardwareAccel is "
 -                      "not allowed.");
 +  if (old->TestingTorNetwork != new_val->TestingTorNetwork) {
 +    *msg = tor_strdup("While Tor is running, changing TestingTorNetwork "
 +                      "is not allowed.");
      return -1;
    }
  
 -  if (old->TestingTorNetwork != new_val->TestingTorNetwork) {
 -    *msg = tor_strdup("While Tor is running, changing TestingTorNetwork "
 +  if (old->DisableAllSwap != new_val->DisableAllSwap) {
 +    *msg = tor_strdup("While Tor is running, changing DisableAllSwap "
                        "is not allowed.");
      return -1;
    }
@@@ -3804,7 -3753,7 +3804,7 @@@ options_transition_affects_workers(or_o
                                     or_options_t *new_options)
  {
    if (!opt_streq(old_options->DataDirectory, new_options->DataDirectory) ||
 -      old_options->NumCpus != new_options->NumCpus ||
 +      old_options->NumCPUs != new_options->NumCPUs ||
        old_options->ORPort != new_options->ORPort ||
        old_options->ServerDNSSearchDomains !=
                                         new_options->ServerDNSSearchDomains ||
@@@ -3836,6 -3785,7 +3836,6 @@@ options_transition_affects_descriptor(o
        old_options->ORPort != new_options->ORPort ||
        old_options->DirPort != new_options->DirPort ||
        old_options->ClientOnly != new_options->ClientOnly ||
 -      old_options->NoPublish != new_options->NoPublish ||
        old_options->_PublishServerDescriptor !=
          new_options->_PublishServerDescriptor ||
        get_effective_bwrate(old_options) != get_effective_bwrate(new_options) ||
@@@ -3858,7 -3808,6 +3858,7 @@@ get_windows_conf_root(void
  {
    static int is_set = 0;
    static char path[MAX_PATH+1];
 +  TCHAR tpath[MAX_PATH] = {0};
  
    LPITEMIDLIST idl;
    IMalloc *m;
@@@ -3876,7 -3825,7 +3876,7 @@@
  #define APPDATA_PATH CSIDL_APPDATA
  #endif
    if (!SUCCEEDED(SHGetSpecialFolderLocation(NULL, APPDATA_PATH, &idl))) {
 -    GetCurrentDirectory(MAX_PATH, path);
 +    getcwd(path,MAX_PATH);
      is_set = 1;
      log_warn(LD_CONFIG,
               "I couldn't find your application data folder: are you "
@@@ -3885,15 -3834,8 +3885,15 @@@
      return path;
    }
    /* Convert the path from an "ID List" (whatever that is!) to a path. */
 -  result = SHGetPathFromIDList(idl, path);
 -  /* Now we need to free the */
 +  result = SHGetPathFromIDList(idl, tpath);
 +#ifdef UNICODE
 +  wcstombs(path,tpath,MAX_PATH);
 +#else
 +  strlcpy(path,tpath,sizeof(path));
 +#endif
 +
 +  /* Now we need to free the memory that the path-idl was stored in.  In
 +   * typical Windows fashion, we can't just call 'free()' on it. */
    SHGetMalloc(&m);
    if (m) {
      m->lpVtbl->Free(m, idl);
@@@ -3941,7 -3883,10 +3941,7 @@@ check_nickname_list(const char *lst, co
    SMARTLIST_FOREACH(sl, const char *, s,
      {
        if (!is_legal_nickname_or_hexdigest(s)) {
 -        char buf[1024];
 -        int tmp = tor_snprintf(buf, sizeof(buf),
 -                  "Invalid nickname '%s' in %s line", s, name);
 -        *msg = tor_strdup(tmp >= 0 ? buf : "internal error");
 +        tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name);
          r = -1;
          break;
        }
@@@ -3965,7 -3910,13 +3965,7 @@@ find_torrc_filename(int argc, char **ar
          log(LOG_WARN, LD_CONFIG, "Duplicate -f options on command line.");
          tor_free(fname);
        }
 -#ifdef MS_WINDOWS
 -      /* XXX one day we might want to extend expand_filename to work
 -       * under Windows as well. */
 -      fname = tor_strdup(argv[i+1]);
 -#else
        fname = expand_filename(argv[i+1]);
 -#endif
        *using_default_torrc = 0;
        ++i;
      } else if (!strcmp(argv[i],"--ignore-missing-torrc")) {
@@@ -4071,12 -4022,6 +4071,12 @@@ options_init_from_torrc(int argc, char 
      printf("Tor version %s.\n",get_version());
      exit(0);
    }
 +  if (argc > 1 && (!strcmp(argv[1],"--digests"))) {
 +    printf("Tor version %s.\n",get_version());
 +    printf("%s", libor_get_digests());
 +    printf("%s", tor_get_digests());
 +    exit(0);
 +  }
  
    /* Go through command-line variables */
    if (!global_cmdline_options) {
@@@ -4244,9 -4189,12 +4244,9 @@@ options_init_from_string(const char *cf
   err:
    config_free(&options_format, newoptions);
    if (*msg) {
 -    int len = (int)strlen(*msg)+256;
 -    char *newmsg = tor_malloc(len);
 -
 -    tor_snprintf(newmsg, len, "Failed to parse/validate config: %s", *msg);
 -    tor_free(*msg);
 -    *msg = newmsg;
 +    char *old_msg = *msg;
 +    tor_asprintf(msg, "Failed to parse/validate config: %s", old_msg);
 +    tor_free(old_msg);
    }
    return err;
  }
@@@ -4634,7 -4582,7 +4634,7 @@@ normalize_data_directory(or_options_t *
  }
  
  /** Check and normalize the value of options->DataDirectory; return 0 if it
 - * sane, -1 otherwise. */
 + * is sane, -1 otherwise. */
  static int
  validate_data_directory(or_options_t *options)
  {
@@@ -4666,6 -4614,7 +4666,6 @@@ write_configuration_file(const char *fn
  {
    char *old_val=NULL, *new_val=NULL, *new_conf=NULL;
    int rename_old = 0, r;
 -  size_t len;
  
    tor_assert(fname);
  
@@@ -4692,7 -4641,9 +4692,7 @@@
      goto err;
    }
  
 -  len = strlen(new_conf)+256;
 -  new_val = tor_malloc(len);
 -  tor_snprintf(new_val, len, "%s\n%s\n\n%s",
 +  tor_asprintf(&new_val, "%s\n%s\n\n%s",
                 GENERATED_FILE_PREFIX, GENERATED_FILE_COMMENT, new_conf);
  
    if (rename_old) {
@@@ -4742,12 -4693,15 +4742,12 @@@
  int
  options_save_current(void)
  {
 -  if (torrc_fname) {
 -    /* This fails if we can't write to our configuration file.
 -     *
 -     * If we try falling back to datadirectory or something, we have a better
 -     * chance of saving the configuration, but a better chance of doing
 -     * something the user never expected. Let's just warn instead. */
 -    return write_configuration_file(torrc_fname, get_options());
 -  }
 -  return write_configuration_file(get_default_conf_file(), get_options());
 +  /* This fails if we can't write to our configuration file.
 +   *
 +   * If we try falling back to datadirectory or something, we have a better
 +   * chance of saving the configuration, but a better chance of doing
 +   * something the user never expected. */
 +  return write_configuration_file(get_torrc_fname(), get_options());
  }
  
  /** Mapping from a unit name to a multiplier for converting that unit into a
@@@ -4812,47 -4766,30 +4812,47 @@@ static struct unit_table_t time_units[
  static uint64_t
  config_parse_units(const char *val, struct unit_table_t *u, int *ok)
  {
 -  uint64_t v;
 +  uint64_t v = 0;
 +  double d = 0;
 +  int use_float = 0;
    char *cp;
  
    tor_assert(ok);
  
    v = tor_parse_uint64(val, 10, 0, UINT64_MAX, ok, &cp);
 -  if (!*ok)
 -    return 0;
 +  if (!*ok || (cp && *cp == '.')) {
 +    d = tor_parse_double(val, 0, UINT64_MAX, ok, &cp);
 +    if (!*ok)
 +      goto done;
 +    use_float = 1;
 +  }
 +
    if (!cp) {
      *ok = 1;
 -    return v;
 +    v = use_float ? DBL_TO_U64(d) :  v;
 +    goto done;
    }
 -  while (TOR_ISSPACE(*cp))
 -    ++cp;
 +
 +  cp = (char*) eat_whitespace(cp);
 +
    for ( ;u->unit;++u) {
      if (!strcasecmp(u->unit, cp)) {
 -      v *= u->multiplier;
 +      if (use_float)
 +        v = u->multiplier * d;
 +      else
 +        v *= u->multiplier;
        *ok = 1;
 -      return v;
 +      goto done;
      }
    }
    log_warn(LD_CONFIG, "Unknown unit '%s'.", cp);
    *ok = 0;
 -  return 0;
 + done:
 +
 +  if (*ok)
 +    return v;
 +  else
 +    return 0;
  }
  
  /** Parse a string in the format "number unit", where unit is a unit of
@@@ -4862,8 -4799,7 +4862,8 @@@
  static uint64_t
  config_parse_memunit(const char *s, int *ok)
  {
 -  return config_parse_units(s, memory_units, ok);
 +  uint64_t u = config_parse_units(s, memory_units, ok);
 +  return u;
  }
  
  /** Parse a string in the format "number unit", where unit is a unit of time.
@@@ -4885,37 -4821,256 +4885,37 @@@ config_parse_interval(const char *s, in
    return (int)r;
  }
  
 -/* This is what passes for version detection on OSX.  We set
 - * MACOSX_KQUEUE_IS_BROKEN to true iff we're on a version of OSX before
 - * 10.4.0 (aka 1040). */
 -#ifdef __APPLE__
 -#ifdef __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
 -#define MACOSX_KQUEUE_IS_BROKEN \
 -  (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1040)
 -#else
 -#define MACOSX_KQUEUE_IS_BROKEN 0
 -#endif
 -#endif
 -
  /**
   * Initialize the libevent library.
   */
  static void
  init_libevent(void)
  {
 +  const char *badness=NULL;
 +
    configure_libevent_logging();
    /* If the kernel complains that some method (say, epoll) doesn't
     * exist, we don't care about it, since libevent will cope.
     */
    suppress_libevent_log_msg("Function not implemented");
 -#ifdef __APPLE__
 -  if (MACOSX_KQUEUE_IS_BROKEN ||
 -      decode_libevent_version(event_get_version(), NULL) < LE_11B) {
 -    setenv("EVENT_NOKQUEUE","1",1);
 -  }
 -#endif
  
 -  /* In libevent versions before 2.0, it's hard to keep binary compatibility
 -   * between upgrades, and unpleasant to detect when the version we compiled
 -   * against is unlike the version we have linked against. Here's how. */
 -#if defined(_EVENT_VERSION) && defined(HAVE_EVENT_GET_VERSION)
 -  /* We have a header-file version and a function-call version. Easy. */
 -  if (strcmp(_EVENT_VERSION, event_get_version())) {
 -    int compat1 = -1, compat2 = -1;
 -    int verybad, prettybad ;
 -    decode_libevent_version(_EVENT_VERSION, &compat1);
 -    decode_libevent_version(event_get_version(), &compat2);
 -    verybad = compat1 != compat2;
 -    prettybad = (compat1 == -1 || compat2 == -1) && compat1 != compat2;
 -
 -    log(verybad ? LOG_WARN : (prettybad ? LOG_NOTICE : LOG_INFO),
 -        LD_GENERAL, "We were compiled with headers from version %s "
 -        "of Libevent, but we're using a Libevent library that says it's "
 -        "version %s.", _EVENT_VERSION, event_get_version());
 -    if (verybad)
 -      log_warn(LD_GENERAL, "This will almost certainly make Tor crash.");
 -    else if (prettybad)
 -      log_notice(LD_GENERAL, "If Tor crashes, this might be why.");
 -    else
 -      log_info(LD_GENERAL, "I think these versions are binary-compatible.");
 -  }
 -#elif defined(HAVE_EVENT_GET_VERSION)
 -  /* event_get_version but no _EVENT_VERSION.  We might be in 1.4.0-beta or
 -     earlier, where that's normal.  To see whether we were compiled with an
 -     earlier version, let's see whether the struct event defines MIN_HEAP_IDX.
 -  */
 -#ifdef HAVE_STRUCT_EVENT_MIN_HEAP_IDX
 -  /* The header files are 1.4.0-beta or later. If the version is not
 -   * 1.4.0-beta, we are incompatible. */
 -  {
 -    if (strcmp(event_get_version(), "1.4.0-beta")) {
 -      log_warn(LD_GENERAL, "It's a little hard to tell, but you seem to have "
 -               "Libevent 1.4.0-beta header files, whereas you have linked "
 -               "against Libevent %s.  This will probably make Tor crash.",
 -               event_get_version());
 -    }
 -  }
 -#else
 -  /* Our headers are 1.3e or earlier. If the library version is not 1.4.x or
 -     later, we're probably fine. */
 -  {
 -    const char *v = event_get_version();
 -    if ((v[0] == '1' && v[2] == '.' && v[3] > '3') || v[0] > '1') {
 -      log_warn(LD_GENERAL, "It's a little hard to tell, but you seem to have "
 -               "Libevent header file from 1.3e or earlier, whereas you have "
 -               "linked against Libevent %s.  This will probably make Tor "
 -               "crash.", event_get_version());
 -    }
 -  }
 -#endif
 +  tor_check_libevent_header_compatibility();
  
 -#elif defined(_EVENT_VERSION)
 -#warn "_EVENT_VERSION is defined but not get_event_version(): Libevent is odd."
 -#else
 -  /* Your libevent is ancient. */
 -#endif
 +  tor_libevent_initialize();
  
 -  event_init();
    suppress_libevent_log_msg(NULL);
 -#if defined(HAVE_EVENT_GET_VERSION) && defined(HAVE_EVENT_GET_METHOD)
 -  /* Making this a NOTICE for now so we can link bugs to a libevent versions
 -   * or methods better. */
 -  log(LOG_NOTICE, LD_GENERAL,
 -      "Initialized libevent version %s using method %s. Good.",
 -      event_get_version(), event_get_method());
 -  check_libevent_version(event_get_method(), get_options()->ORPort != 0);
 -#else
 -  log(LOG_NOTICE, LD_GENERAL,
 -      "Initialized old libevent (version 1.0b or earlier).");
 -  log(LOG_WARN, LD_GENERAL,
 -      "You have a *VERY* old version of libevent.  It is likely to be buggy; "
 -      "please build Tor with a more recent version.");
 -#endif
 -}
 -
 -/** Table mapping return value of event_get_version() to le_version_t. */
 -static const struct {
 -  const char *name; le_version_t version; int bincompat;
 -} le_version_table[] = {
 -  /* earlier versions don't have get_version. */
 -  { "1.0c", LE_10C, 1},
 -  { "1.0d", LE_10D, 1},
 -  { "1.0e", LE_10E, 1},
 -  { "1.1",  LE_11,  1 },
 -  { "1.1a", LE_11A, 1 },
 -  { "1.1b", LE_11B, 1 },
 -  { "1.2",  LE_12,  1 },
 -  { "1.2a", LE_12A, 1 },
 -  { "1.3",  LE_13,  1 },
 -  { "1.3a", LE_13A, 1 },
 -  { "1.3b", LE_13B, 1 },
 -  { "1.3c", LE_13C, 1 },
 -  { "1.3d", LE_13D, 1 },
 -  { "1.3e", LE_13E, 1 },
 -  { "1.4.0-beta", LE_140, 2 },
 -  { "1.4.1-beta", LE_141, 2 },
 -  { "1.4.2-rc",   LE_142, 2 },
 -  { "1.4.3-stable", LE_143, 2 },
 -  { "1.4.4-stable", LE_144, 2 },
 -  { "1.4.5-stable", LE_145, 2 },
 -  { "1.4.6-stable", LE_146, 2 },
 -  { "1.4.7-stable", LE_147, 2 },
 -  { "1.4.8-stable", LE_148, 2 },
 -  { "1.4.99-trunk", LE_1499, 3 },
 -  { NULL, LE_OTHER, 0 }
 -};
  
 -/** Return the le_version_t for the current version of libevent.  If the
 - * version is very new, return LE_OTHER.  If the version is so old that it
 - * doesn't support event_get_version(), return LE_OLD. */
 -static le_version_t
 -decode_libevent_version(const char *v, int *bincompat_out)
 -{
 -  int i;
 -  for (i=0; le_version_table[i].name; ++i) {
 -    if (!strcmp(le_version_table[i].name, v)) {
 -      if (bincompat_out)
 -        *bincompat_out = le_version_table[i].bincompat;
 -      return le_version_table[i].version;
 -    }
 -  }
 -  if (v[0] != '1' && bincompat_out)
 -    *bincompat_out = 100;
 -  else if (!strcmpstart(v, "1.4") && bincompat_out)
 -    *bincompat_out = 2;
 -  return LE_OTHER;
 -}
 -
 -#if defined(HAVE_EVENT_GET_VERSION) && defined(HAVE_EVENT_GET_METHOD)
 -/**
 - * Compare the given libevent method and version to a list of versions
 - * which are known not to work.  Warn the user as appropriate.
 - */
 -static void
 -check_libevent_version(const char *m, int server)
 -{
 -  int buggy = 0, iffy = 0, slow = 0, thread_unsafe = 0;
 -  le_version_t version;
 -  const char *v = event_get_version();
 -  const char *badness = NULL;
 -  const char *sad_os = "";
 -
 -  version = decode_libevent_version(v, NULL);
 -
 -  /* XXX Would it be worthwhile disabling the methods that we know
 -   * are buggy, rather than just warning about them and then proceeding
 -   * to use them? If so, we should probably not wrap this whole thing
 -   * in HAVE_EVENT_GET_VERSION and HAVE_EVENT_GET_METHOD. -RD */
 -  /* XXXX The problem is that it's not trivial to get libevent to change it's
 -   * method once it's initialized, and it's not trivial to tell what method it
 -   * will use without initializing it.  I guess we could preemptively disable
 -   * buggy libevent modes based on the version _before_ initializing it,
 -   * though, but then there's no good way (afaict) to warn "I would have used
 -   * kqueue, but instead I'm using select." -NM */
 -  if (!strcmp(m, "kqueue")) {
 -    if (version < LE_11B)
 -      buggy = 1;
 -  } else if (!strcmp(m, "epoll")) {
 -    if (version < LE_11)
 -      iffy = 1;
 -  } else if (!strcmp(m, "poll")) {
 -    if (version < LE_10E)
 -      buggy = 1;
 -    else if (version < LE_11)
 -      slow = 1;
 -  } else if (!strcmp(m, "select")) {
 -    if (version < LE_11)
 -      slow = 1;
 -  } else if (!strcmp(m, "win32")) {
 -    if (version < LE_11B)
 -      buggy = 1;
 -  }
 -
 -  /* Libevent versions before 1.3b do very badly on operating systems with
 -   * user-space threading implementations. */
 -#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
 -  if (server && version < LE_13B) {
 -    thread_unsafe = 1;
 -    sad_os = "BSD variants";
 -  }
 -#elif defined(__APPLE__) || defined(__darwin__)
 -  if (server && version < LE_13B) {
 -    thread_unsafe = 1;
 -    sad_os = "Mac OS X";
 -  }
 -#endif
 -
 -  if (thread_unsafe) {
 -    log(LOG_WARN, LD_GENERAL,
 -        "Libevent version %s often crashes when running a Tor server with %s. "
 -        "Please use the latest version of libevent (1.3b or later)",v,sad_os);
 -    badness = "BROKEN";
 -  } else if (buggy) {
 -    log(LOG_WARN, LD_GENERAL,
 -        "There are serious bugs in using %s with libevent %s. "
 -        "Please use the latest version of libevent.", m, v);
 -    badness = "BROKEN";
 -  } else if (iffy) {
 -    log(LOG_WARN, LD_GENERAL,
 -        "There are minor bugs in using %s with libevent %s. "
 -        "You may want to use the latest version of libevent.", m, v);
 -    badness = "BUGGY";
 -  } else if (slow && server) {
 -    log(LOG_WARN, LD_GENERAL,
 -        "libevent %s can be very slow with %s. "
 -        "When running a server, please use the latest version of libevent.",
 -        v,m);
 -    badness = "SLOW";
 -  }
 +  tor_check_libevent_version(tor_libevent_get_method(),
 +                             get_options()->ORPort != 0,
 +                             &badness);
    if (badness) {
 +    const char *v = tor_libevent_get_version_str();
 +    const char *m = tor_libevent_get_method();
      control_event_general_status(LOG_WARN,
          "BAD_LIBEVENT VERSION=%s METHOD=%s BADNESS=%s RECOVERED=NO",
                                   v, m, badness);
    }
 -
  }
 -#endif
  
  /** Return the persistent state struct for this Tor. */
  or_state_t *
@@@ -4997,61 -5152,22 +4997,61 @@@ or_state_validate(or_state_t *old_state
  }
  
  /** Replace the current persistent state with <b>new_state</b> */
 -static void
 +static int
  or_state_set(or_state_t *new_state)
  {
    char *err = NULL;
 +  int ret = 0;
    tor_assert(new_state);
 -  if (global_state)
 -    config_free(&state_format, global_state);
 +  config_free(&state_format, global_state);
    global_state = new_state;
    if (entry_guards_parse_state(global_state, 1, &err)<0) {
      log_warn(LD_GENERAL,"%s",err);
      tor_free(err);
 +    ret = -1;
    }
    if (rep_hist_load_state(global_state, &err)<0) {
      log_warn(LD_GENERAL,"Unparseable bandwidth history state: %s",err);
      tor_free(err);
 +    ret = -1;
 +  }
 +  if (circuit_build_times_parse_state(&circ_times, global_state) < 0) {
 +    ret = -1;
 +  }
 +  return ret;
 +}
 +
 +/**
 + * Save a broken state file to a backup location.
 + */
 +static void
 +or_state_save_broken(char *fname)
 +{
 +  int i;
 +  file_status_t status;
 +  size_t len = strlen(fname)+16;
 +  char *fname2 = tor_malloc(len);
 +  for (i = 0; i < 100; ++i) {
 +    tor_snprintf(fname2, len, "%s.%d", fname, i);
 +    status = file_status(fname2);
 +    if (status == FN_NOENT)
 +      break;
 +  }
 +  if (i == 100) {
 +    log_warn(LD_BUG, "Unable to parse state in \"%s\"; too many saved bad "
 +             "state files to move aside. Discarding the old state file.",
 +             fname);
 +    unlink(fname);
 +  } else {
 +    log_warn(LD_BUG, "Unable to parse state in \"%s\". Moving it aside "
 +             "to \"%s\".  This could be a bug in Tor; please tell "
 +             "the developers.", fname, fname2);
 +    if (rename(fname, fname2) < 0) {
 +      log_warn(LD_BUG, "Weirdly, I couldn't even move the state aside. The "
 +               "OS gave an error of %s", strerror(errno));
 +    }
    }
 +  tor_free(fname2);
  }
  
  /** Reload the persistent state from disk, generating a new state as needed.
@@@ -5113,8 -5229,31 +5113,8 @@@ or_state_load(void
               " This is a bug in Tor.");
      goto done;
    } else if (badstate && contents) {
 -    int i;
 -    file_status_t status;
 -    size_t len = strlen(fname)+16;
 -    char *fname2 = tor_malloc(len);
 -    for (i = 0; i < 100; ++i) {
 -      tor_snprintf(fname2, len, "%s.%d", fname, i);
 -      status = file_status(fname2);
 -      if (status == FN_NOENT)
 -        break;
 -    }
 -    if (i == 100) {
 -      log_warn(LD_BUG, "Unable to parse state in \"%s\"; too many saved bad "
 -               "state files to move aside. Discarding the old state file.",
 -               fname);
 -      unlink(fname);
 -    } else {
 -      log_warn(LD_BUG, "Unable to parse state in \"%s\". Moving it aside "
 -               "to \"%s\".  This could be a bug in Tor; please tell "
 -               "the developers.", fname, fname2);
 -      if (rename(fname, fname2) < 0) {
 -        log_warn(LD_BUG, "Weirdly, I couldn't even move the state aside. The "
 -                 "OS gave an error of %s", strerror(errno));
 -      }
 -    }
 -    tor_free(fname2);
 +    or_state_save_broken(fname);
 +
      tor_free(contents);
      config_free(&state_format, new_state);
  
@@@ -5126,9 -5265,7 +5126,9 @@@
    } else {
      log_info(LD_GENERAL, "Initialized state");
    }
 -  or_state_set(new_state);
 +  if (or_state_set(new_state) == -1) {
 +    or_state_save_broken(fname);
 +  }
    new_state = NULL;
    if (!contents) {
      global_state->next_write = 0;
@@@ -5151,6 -5288,7 +5151,6 @@@ or_state_save(time_t now
  {
    char *state, *contents;
    char tbuf[ISO_TIME_LEN+1];
 -  size_t len;
    char *fname;
  
    tor_assert(global_state);
@@@ -5162,16 -5300,20 +5162,16 @@@
     * to avoid redundant writes. */
    entry_guards_update_state(global_state);
    rep_hist_update_state(global_state);
 +  circuit_build_times_update_state(&circ_times, global_state);
    if (accounting_is_enabled(get_options()))
      accounting_run_housekeeping(now);
  
 -  global_state->LastWritten = time(NULL);
    tor_free(global_state->TorVersion);
 -  len = strlen(get_version())+8;
 -  global_state->TorVersion = tor_malloc(len);
 -  tor_snprintf(global_state->TorVersion, len, "Tor %s", get_version());
 +  tor_asprintf(&global_state->TorVersion, "Tor %s", get_version());
  
    state = config_dump(&state_format, global_state, 1, 0);
 -  len = strlen(state)+256;
 -  contents = tor_malloc(len);
    format_local_iso_time(tbuf, time(NULL));
 -  tor_snprintf(contents, len,
 +  tor_asprintf(&contents,
                 "# Tor state file last generated on %s local time\n"
                 "# Other times below are in GMT\n"
                 "# You *do not* need to edit this file.\n\n%s",
@@@ -5180,13 -5322,10 +5180,13 @@@
    fname = get_datadir_fname("state");
    if (write_str_to_file(fname, contents, 0)<0) {
      log_warn(LD_FS, "Unable to write state to file \"%s\"", fname);
 +    global_state->LastWritten = -1;
      tor_free(fname);
      tor_free(contents);
      return -1;
    }
 +
 +  global_state->LastWritten = time(NULL);
    log_info(LD_GENERAL, "Saved state to \"%s\"", fname);
    tor_free(fname);
    tor_free(contents);
@@@ -5218,18 -5357,18 +5218,18 @@@ remove_file_if_very_old(const char *fna
   * types. */
  int
  getinfo_helper_config(control_connection_t *conn,
 -                      const char *question, char **answer)
 +                      const char *question, char **answer,
 +                      const char **errmsg)
  {
    (void) conn;
 +  (void) errmsg;
    if (!strcmp(question, "config/names")) {
      smartlist_t *sl = smartlist_create();
      int i;
      for (i = 0; _option_vars[i].name; ++i) {
        config_var_t *var = &_option_vars[i];
 -      const char *type, *desc;
 +      const char *type;
        char *line;
 -      size_t len;
 -      desc = config_find_description(&options_format, var->name);
        switch (var->type) {
          case CONFIG_TYPE_STRING: type = "String"; break;
          case CONFIG_TYPE_FILENAME: type = "Filename"; break;
@@@ -5250,7 -5389,14 +5250,7 @@@
        }
        if (!type)
          continue;
 -      len = strlen(var->name)+strlen(type)+16;
 -      if (desc)
 -        len += strlen(desc);
 -      line = tor_malloc(len);
 -      if (desc)
 -        tor_snprintf(line, len, "%s %s %s\n",var->name,type,desc);
 -      else
 -        tor_snprintf(line, len, "%s %s\n",var->name,type);
 +      tor_asprintf(&line, "%s %s\n",var->name,type);
        smartlist_add(sl, line);
      }
      *answer = smartlist_join_strings(sl, "", 0, NULL);
diff --combined src/or/connection.c
index 85cede8,4869a24..8a21d81
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -11,30 -11,6 +11,30 @@@
   **/
  
  #include "or.h"
 +#include "buffers.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "cpuworker.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dns.h"
 +#include "dnsserv.h"
 +#include "geoip.h"
 +#include "main.h"
 +#include "policies.h"
 +#include "reasons.h"
 +#include "relay.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerparse.h"
  
  static connection_t *connection_create_listener(
                                 struct sockaddr *listensockaddr,
@@@ -45,8 -21,7 +45,8 @@@ static void connection_init(time_t now
  static int connection_init_accepted_conn(connection_t *conn,
                                           uint8_t listener_type);
  static int connection_handle_listener_read(connection_t *conn, int new_type);
 -static int connection_read_bucket_should_increase(or_connection_t *conn);
 +static int connection_bucket_should_increase(int bucket,
 +                                             or_connection_t *conn);
  static int connection_finished_flushing(connection_t *conn);
  static int connection_flushed_some(connection_t *conn);
  static int connection_finished_connecting(connection_t *conn);
@@@ -57,10 -32,6 +57,10 @@@ static int connection_process_inbuf(con
  static void client_check_address_changed(int sock);
  static void set_constrained_socket_buffers(int sock, int size);
  
 +static const char *connection_proxy_state_to_string(int state);
 +static int connection_read_https_proxy_response(connection_t *conn);
 +static void connection_send_socks5_connect(connection_t *conn);
 +
  /** The last IPv4 address that our network interface seemed to have been
   * binding to, in host order.  We use this to detect when our IP changes. */
  static uint32_t last_interface_ip = 0;
@@@ -121,7 -92,8 +121,7 @@@ conn_state_to_string(int type, int stat
      case CONN_TYPE_OR:
        switch (state) {
          case OR_CONN_STATE_CONNECTING: return "connect()ing";
 -        case OR_CONN_STATE_PROXY_FLUSHING: return "proxy flushing";
 -        case OR_CONN_STATE_PROXY_READING: return "proxy reading";
 +        case OR_CONN_STATE_PROXY_HANDSHAKING: return "handshaking (proxy)";
          case OR_CONN_STATE_TLS_HANDSHAKING: return "handshaking (TLS)";
          case OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING:
            return "renegotiating (TLS)";
@@@ -205,9 -177,6 +205,9 @@@ or_connection_new(int socket_family
    or_conn->timestamp_last_added_nonpadding = time(NULL);
    or_conn->next_circ_id = crypto_rand_int(1<<15);
  
 +  or_conn->active_circuit_pqueue = smartlist_create();
 +  or_conn->active_circuit_pqueue_last_recalibrated = cell_ewma_get_tick();
 +
    return or_conn;
  }
  
@@@ -233,7 -202,6 +233,7 @@@ control_connection_new(int socket_famil
      tor_malloc_zero(sizeof(control_connection_t));
    connection_init(time(NULL),
                    TO_CONN(control_conn), CONN_TYPE_CONTROL, socket_family);
 +  log_notice(LD_CONTROL, "New control connection opened.");
    return control_conn;
  }
  
@@@ -331,6 -299,25 +331,6 @@@ connection_link_connections(connection_
    conn_b->linked_conn = conn_a;
  }
  
 -/** Tell libevent that we don't care about <b>conn</b> any more. */
 -void
 -connection_unregister_events(connection_t *conn)
 -{
 -  if (conn->read_event) {
 -    if (event_del(conn->read_event))
 -      log_warn(LD_BUG, "Error removing read event for %d", conn->s);
 -    tor_free(conn->read_event);
 -  }
 -  if (conn->write_event) {
 -    if (event_del(conn->write_event))
 -      log_warn(LD_BUG, "Error removing write event for %d", conn->s);
 -    tor_free(conn->write_event);
 -  }
 -  if (conn->dns_server_port) {
 -    dnsserv_close_listener(conn);
 -  }
 -}
 -
  /** Deallocate memory used by <b>conn</b>. Deallocate its buffers if
   * necessary, close its socket if necessary, and mark the directory as dirty
   * if <b>conn</b> is an OR or OP connection.
@@@ -340,9 -327,6 +340,9 @@@ _connection_free(connection_t *conn
  {
    void *mem;
    size_t memlen;
 +  if (!conn)
 +    return;
 +
    switch (conn->type) {
      case CONN_TYPE_OR:
        tor_assert(conn->magic == OR_CONNECTION_MAGIC);
@@@ -400,11 -384,14 +400,11 @@@
  
    if (connection_speaks_cells(conn)) {
      or_connection_t *or_conn = TO_OR_CONN(conn);
 -    if (or_conn->tls) {
 -      tor_tls_free(or_conn->tls);
 -      or_conn->tls = NULL;
 -    }
 -    if (or_conn->handshake_state) {
 -      or_handshake_state_free(or_conn->handshake_state);
 -      or_conn->handshake_state = NULL;
 -    }
 +    tor_tls_free(or_conn->tls);
 +    or_conn->tls = NULL;
 +    or_handshake_state_free(or_conn->handshake_state);
 +    or_conn->handshake_state = NULL;
 +    smartlist_free(or_conn->active_circuit_pqueue);
      tor_free(or_conn->nickname);
    }
    if (CONN_IS_EDGE(conn)) {
@@@ -414,8 -401,8 +414,8 @@@
        memset(edge_conn->socks_request, 0xcc, sizeof(socks_request_t));
        tor_free(edge_conn->socks_request);
      }
 -    if (edge_conn->rend_data)
 -      rend_data_free(edge_conn->rend_data);
 +
 +    rend_data_free(edge_conn->rend_data);
    }
    if (conn->type == CONN_TYPE_CONTROL) {
      control_connection_t *control_conn = TO_CONTROL_CONN(conn);
@@@ -428,15 -415,16 +428,15 @@@
    if (conn->type == CONN_TYPE_DIR) {
      dir_connection_t *dir_conn = TO_DIR_CONN(conn);
      tor_free(dir_conn->requested_resource);
 -    if (dir_conn->zlib_state)
 -      tor_zlib_free(dir_conn->zlib_state);
 +
 +    tor_zlib_free(dir_conn->zlib_state);
      if (dir_conn->fingerprint_stack) {
        SMARTLIST_FOREACH(dir_conn->fingerprint_stack, char *, cp, tor_free(cp));
        smartlist_free(dir_conn->fingerprint_stack);
      }
 -    if (dir_conn->cached_dir)
 -      cached_dir_decref(dir_conn->cached_dir);
 -    if (dir_conn->rend_data)
 -      rend_data_free(dir_conn->rend_data);
 +
 +    cached_dir_decref(dir_conn->cached_dir);
 +    rend_data_free(dir_conn->rend_data);
    }
  
    if (conn->s >= 0) {
@@@ -451,7 -439,7 +451,7 @@@
      connection_or_remove_from_identity_map(TO_OR_CONN(conn));
    }
  
 -  memset(conn, 0xAA, memlen); /* poison memory */
 +  memset(mem, 0xCC, memlen); /* poison memory */
    tor_free(mem);
  }
  
@@@ -460,8 -448,7 +460,8 @@@
  void
  connection_free(connection_t *conn)
  {
 -  tor_assert(conn);
 +  if (!conn)
 +    return;
    tor_assert(!connection_is_on_closeable_list(conn));
    tor_assert(!connection_in_array(conn));
    if (conn->linked_conn) {
@@@ -557,6 -544,13 +557,6 @@@ connection_about_to_close_connection(co
           * failed: forget about this router, and maybe try again. */
          connection_dir_request_failed(dir_conn);
        }
 -      if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC && dir_conn->rend_data) {
 -        /* Give it a try. However, there is no re-fetching for v0 rend
 -         * descriptors; if the response is empty or the descriptor is
 -         * unusable, close pending connections (unless a v2 request is
 -         * still in progress). */
 -        rend_client_desc_trynow(dir_conn->rend_data->onion_address, 0);
 -      }
        /* If we were trying to fetch a v2 rend desc and did not succeed,
         * retry as needed. (If a fetch is successful, the connection state
         * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC to mark that
@@@ -579,7 -573,7 +579,7 @@@
            or_options_t *options = get_options();
            rep_hist_note_connect_failed(or_conn->identity_digest, now);
            entry_guard_register_connect_status(or_conn->identity_digest,0,
 -                                              !options->HttpsProxy, now);
 +                                              !options->HTTPSProxy, now);
            if (conn->state >= OR_CONN_STATE_TLS_HANDSHAKING) {
              int reason = tls_error_to_orconn_end_reason(or_conn->tls_error);
              control_event_or_conn_status(or_conn, OR_CONN_EVENT_FAILED,
@@@ -595,7 -589,7 +595,7 @@@
          rep_hist_note_disconnect(or_conn->identity_digest, now);
          control_event_or_conn_status(or_conn, OR_CONN_EVENT_CLOSED,
                  tls_error_to_orconn_end_reason(or_conn->tls_error));
 -      } else if (or_conn->identity_digest) {
 +      } else if (!tor_digest_is_zero(or_conn->identity_digest)) {
          rep_hist_note_connection_died(or_conn->identity_digest, now);
          control_event_or_conn_status(or_conn, OR_CONN_EVENT_CLOSED,
                  tls_error_to_orconn_end_reason(or_conn->tls_error));
@@@ -839,16 -833,16 +839,16 @@@ static voi
  warn_too_many_conns(void)
  {
  #define WARN_TOO_MANY_CONNS_INTERVAL (6*60*60)
 -  static time_t last_warned = 0;
 -  time_t now = time(NULL);
 -  int n_conns = get_n_open_sockets();
 -  if (last_warned + WARN_TOO_MANY_CONNS_INTERVAL < now) {
 +  static ratelim_t last_warned = RATELIM_INIT(WARN_TOO_MANY_CONNS_INTERVAL);
 +  char *m;
 +  if ((m = rate_limit_log(&last_warned, approx_time()))) {
 +    int n_conns = get_n_open_sockets();
      log_warn(LD_NET,"Failing because we have %d connections already. Please "
 -             "raise your ulimit -n.", n_conns);
 -    last_warned = now;
 +             "raise your ulimit -n.%s", n_conns, m);
 +    tor_free(m);
 +    control_event_general_status(LOG_WARN, "TOO_MANY_CONNECTIONS CURRENT=%d",
 +                                 n_conns);
    }
 -  control_event_general_status(LOG_WARN, "TOO_MANY_CONNECTIONS CURRENT=%d",
 -                               n_conns);
  }
  
  /** Bind a new non-blocking socket listening to the socket described
@@@ -1260,7 -1254,7 +1260,7 @@@ connection_connect(connection_t *conn, 
      return -1;
    }
  
 -  if (options->OutboundBindAddress) {
 +  if (options->OutboundBindAddress && !tor_addr_is_loopback(addr)) {
      struct sockaddr_in ext_addr;
  
      memset(&ext_addr, 0, sizeof(ext_addr));
@@@ -1291,8 -1285,7 +1291,8 @@@
    dest_addr_len = tor_addr_to_sockaddr(addr, port, dest_addr, sizeof(addrbuf));
    tor_assert(dest_addr_len > 0);
  
 -  log_debug(LD_NET,"Connecting to %s:%u.",escaped_safe_str(address),port);
 +  log_debug(LD_NET, "Connecting to %s:%u.",
 +            escaped_safe_str_client(address), port);
  
    if (connect(s, dest_addr, dest_addr_len) < 0) {
      int e = tor_socket_errno(s);
@@@ -1300,8 -1293,7 +1300,8 @@@
        /* yuck. kill it. */
        *socket_error = e;
        log_info(LD_NET,
 -               "connect() to %s:%u failed: %s",escaped_safe_str(address),
 +               "connect() to %s:%u failed: %s",
 +               escaped_safe_str_client(address),
                 port, tor_socket_strerror(e));
        tor_close_socket(s);
        return -1;
@@@ -1315,8 -1307,7 +1315,8 @@@
  
    /* it succeeded. we're connected. */
    log_fn(inprogress?LOG_DEBUG:LOG_INFO, LD_NET,
 -         "Connection to %s:%u %s (sock %d).",escaped_safe_str(address),
 +         "Connection to %s:%u %s (sock %d).",
 +         escaped_safe_str_client(address),
           port, inprogress?"in progress":"established", s);
    conn->s = s;
    if (connection_add(conn) < 0) /* no space, forget it */
@@@ -1324,353 -1315,6 +1324,353 @@@
    return inprogress ? 0 : 1;
  }
  
 +/** Convert state number to string representation for logging purposes.
 + */
 +static const char *
 +connection_proxy_state_to_string(int state)
 +{
 +  static const char *unknown = "???";
 +  static const char *states[] = {
 +    "PROXY_NONE",
 +    "PROXY_HTTPS_WANT_CONNECT_OK",
 +    "PROXY_SOCKS4_WANT_CONNECT_OK",
 +    "PROXY_SOCKS5_WANT_AUTH_METHOD_NONE",
 +    "PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929",
 +    "PROXY_SOCKS5_WANT_AUTH_RFC1929_OK",
 +    "PROXY_SOCKS5_WANT_CONNECT_OK",
 +    "PROXY_CONNECTED",
 +  };
 +
 +  if (state < PROXY_NONE || state > PROXY_CONNECTED)
 +    return unknown;
 +
 +  return states[state];
 +}
 +
 +/** Write a proxy request of <b>type</b> (socks4, socks5, https) to conn
 + * for conn->addr:conn->port, authenticating with the auth details given
 + * in the configuration (if available). SOCKS 5 and HTTP CONNECT proxies
 + * support authentication.
 + *
 + * Returns -1 if conn->addr is incompatible with the proxy protocol, and
 + * 0 otherwise.
 + *
 + * Use connection_read_proxy_handshake() to complete the handshake.
 + */
 +int
 +connection_proxy_connect(connection_t *conn, int type)
 +{
 +  or_options_t *options;
 +
 +  tor_assert(conn);
 +
 +  options = get_options();
 +
 +  switch (type) {
 +    case PROXY_CONNECT: {
 +      char buf[1024];
 +      char *base64_authenticator=NULL;
 +      const char *authenticator = options->HTTPSProxyAuthenticator;
 +
 +      /* Send HTTP CONNECT and authentication (if available) in
 +       * one request */
 +
 +      if (authenticator) {
 +        base64_authenticator = alloc_http_authenticator(authenticator);
 +        if (!base64_authenticator)
 +          log_warn(LD_OR, "Encoding https authenticator failed");
 +      }
 +
 +      if (base64_authenticator) {
 +        tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.1\r\n"
 +                     "Proxy-Authorization: Basic %s\r\n\r\n",
 +                     fmt_addr(&conn->addr),
 +                     conn->port, base64_authenticator);
 +        tor_free(base64_authenticator);
 +      } else {
 +        tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n\r\n",
 +                     fmt_addr(&conn->addr), conn->port);
 +      }
 +
 +      connection_write_to_buf(buf, strlen(buf), conn);
 +      conn->proxy_state = PROXY_HTTPS_WANT_CONNECT_OK;
 +      break;
 +    }
 +
 +    case PROXY_SOCKS4: {
 +      unsigned char buf[9];
 +      uint16_t portn;
 +      uint32_t ip4addr;
 +
 +      /* Send a SOCKS4 connect request with empty user id */
 +
 +      if (tor_addr_family(&conn->addr) != AF_INET) {
 +        log_warn(LD_NET, "SOCKS4 client is incompatible with IPv6");
 +        return -1;
 +      }
 +
 +      ip4addr = tor_addr_to_ipv4n(&conn->addr);
 +      portn = htons(conn->port);
 +
 +      buf[0] = 4; /* version */
 +      buf[1] = SOCKS_COMMAND_CONNECT; /* command */
 +      memcpy(buf + 2, &portn, 2); /* port */
 +      memcpy(buf + 4, &ip4addr, 4); /* addr */
 +      buf[8] = 0; /* userid (empty) */
 +
 +      connection_write_to_buf((char *)buf, sizeof(buf), conn);
 +      conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK;
 +      break;
 +    }
 +
 +    case PROXY_SOCKS5: {
 +      unsigned char buf[4]; /* fields: vers, num methods, method list */
 +
 +      /* Send a SOCKS5 greeting (connect request must wait) */
 +
 +      buf[0] = 5; /* version */
 +
 +      /* number of auth methods */
 +      if (options->Socks5ProxyUsername) {
 +        buf[1] = 2;
 +        buf[2] = 0x00; /* no authentication */
 +        buf[3] = 0x02; /* rfc1929 Username/Passwd auth */
 +        conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929;
 +      } else {
 +        buf[1] = 1;
 +        buf[2] = 0x00; /* no authentication */
 +        conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_NONE;
 +      }
 +
 +      connection_write_to_buf((char *)buf, 2 + buf[1], conn);
 +      break;
 +    }
 +
 +    default:
 +      log_err(LD_BUG, "Invalid proxy protocol, %d", type);
 +      tor_fragile_assert();
 +      return -1;
 +  }
 +
 +  log_debug(LD_NET, "set state %s",
 +            connection_proxy_state_to_string(conn->proxy_state));
 +
 +  return 0;
 +}
 +
 +/** Read conn's inbuf. If the http response from the proxy is all
 + * here, make sure it's good news, then return 1. If it's bad news,
 + * return -1. Else return 0 and hope for better luck next time.
 + */
 +static int
 +connection_read_https_proxy_response(connection_t *conn)
 +{
 +  char *headers;
 +  char *reason=NULL;
 +  int status_code;
 +  time_t date_header;
 +
 +  switch (fetch_from_buf_http(conn->inbuf,
 +                              &headers, MAX_HEADERS_SIZE,
 +                              NULL, NULL, 10000, 0)) {
 +    case -1: /* overflow */
 +      log_warn(LD_PROTOCOL,
 +               "Your https proxy sent back an oversized response. Closing.");
 +      return -1;
 +    case 0:
 +      log_info(LD_NET,"https proxy response not all here yet. Waiting.");
 +      return 0;
 +    /* case 1, fall through */
 +  }
 +
 +  if (parse_http_response(headers, &status_code, &date_header,
 +                          NULL, &reason) < 0) {
 +    log_warn(LD_NET,
 +             "Unparseable headers from proxy (connecting to '%s'). Closing.",
 +             conn->address);
 +    tor_free(headers);
 +    return -1;
 +  }
 +  if (!reason) reason = tor_strdup("[no reason given]");
 +
 +  if (status_code == 200) {
 +    log_info(LD_NET,
 +             "HTTPS connect to '%s' successful! (200 %s) Starting TLS.",
 +             conn->address, escaped(reason));
 +    tor_free(reason);
 +    return 1;
 +  }
 +  /* else, bad news on the status code */
 +  log_warn(LD_NET,
 +           "The https proxy sent back an unexpected status code %d (%s). "
 +           "Closing.",
 +           status_code, escaped(reason));
 +  tor_free(reason);
 +  return -1;
 +}
 +
 +/** Send SOCKS5 CONNECT command to <b>conn</b>, copying <b>conn->addr</b>
 + * and <b>conn->port</b> into the request.
 + */
 +static void
 +connection_send_socks5_connect(connection_t *conn)
 +{
 +  unsigned char buf[1024];
 +  size_t reqsize = 6;
 +  uint16_t port = htons(conn->port);
 +
 +  buf[0] = 5; /* version */
 +  buf[1] = SOCKS_COMMAND_CONNECT; /* command */
 +  buf[2] = 0; /* reserved */
 +
 +  if (tor_addr_family(&conn->addr) == AF_INET) {
 +    uint32_t addr = tor_addr_to_ipv4n(&conn->addr);
 +
 +    buf[3] = 1;
 +    reqsize += 4;
 +    memcpy(buf + 4, &addr, 4);
 +    memcpy(buf + 8, &port, 2);
 +  } else { /* AF_INET6 */
 +    buf[3] = 4;
 +    reqsize += 16;
 +    memcpy(buf + 4, tor_addr_to_in6(&conn->addr), 16);
 +    memcpy(buf + 20, &port, 2);
 +  }
 +
 +  connection_write_to_buf((char *)buf, reqsize, conn);
 +
 +  conn->proxy_state = PROXY_SOCKS5_WANT_CONNECT_OK;
 +}
 +
 +/** Call this from connection_*_process_inbuf() to advance the proxy
 + * handshake.
 + *
 + * No matter what proxy protocol is used, if this function returns 1, the
 + * handshake is complete, and the data remaining on inbuf may contain the
 + * start of the communication with the requested server.
 + *
 + * Returns 0 if the current buffer contains an incomplete response, and -1
 + * on error.
 + */
 +int
 +connection_read_proxy_handshake(connection_t *conn)
 +{
 +  int ret = 0;
 +  char *reason = NULL;
 +
 +  log_debug(LD_NET, "enter state %s",
 +            connection_proxy_state_to_string(conn->proxy_state));
 +
 +  switch (conn->proxy_state) {
 +    case PROXY_HTTPS_WANT_CONNECT_OK:
 +      ret = connection_read_https_proxy_response(conn);
 +      if (ret == 1)
 +        conn->proxy_state = PROXY_CONNECTED;
 +      break;
 +
 +    case PROXY_SOCKS4_WANT_CONNECT_OK:
 +      ret = fetch_from_buf_socks_client(conn->inbuf,
 +                                        conn->proxy_state,
 +                                        &reason);
 +      if (ret == 1)
 +        conn->proxy_state = PROXY_CONNECTED;
 +      break;
 +
 +    case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE:
 +      ret = fetch_from_buf_socks_client(conn->inbuf,
 +                                        conn->proxy_state,
 +                                        &reason);
 +      /* no auth needed, do connect */
 +      if (ret == 1) {
 +        connection_send_socks5_connect(conn);
 +        ret = 0;
 +      }
 +      break;
 +
 +    case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929:
 +      ret = fetch_from_buf_socks_client(conn->inbuf,
 +                                        conn->proxy_state,
 +                                        &reason);
 +
 +      /* send auth if needed, otherwise do connect */
 +      if (ret == 1) {
 +        connection_send_socks5_connect(conn);
 +        ret = 0;
 +      } else if (ret == 2) {
 +        unsigned char buf[1024];
 +        size_t reqsize, usize, psize;
 +        const char *user, *pass;
 +
 +        user = get_options()->Socks5ProxyUsername;
 +        pass = get_options()->Socks5ProxyPassword;
 +        tor_assert(user && pass);
 +
 +        /* XXX len of user and pass must be <= 255 !!! */
 +        usize = strlen(user);
 +        psize = strlen(pass);
 +        tor_assert(usize <= 255 && psize <= 255);
 +        reqsize = 3 + usize + psize;
 +
 +        buf[0] = 1; /* negotiation version */
 +        buf[1] = usize;
 +        memcpy(buf + 2, user, usize);
 +        buf[2 + usize] = psize;
 +        memcpy(buf + 3 + usize, pass, psize);
 +
 +        connection_write_to_buf((char *)buf, reqsize, conn);
 +
 +        conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_RFC1929_OK;
 +        ret = 0;
 +      }
 +      break;
 +
 +    case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK:
 +      ret = fetch_from_buf_socks_client(conn->inbuf,
 +                                        conn->proxy_state,
 +                                        &reason);
 +      /* send the connect request */
 +      if (ret == 1) {
 +        connection_send_socks5_connect(conn);
 +        ret = 0;
 +      }
 +      break;
 +
 +    case PROXY_SOCKS5_WANT_CONNECT_OK:
 +      ret = fetch_from_buf_socks_client(conn->inbuf,
 +                                        conn->proxy_state,
 +                                        &reason);
 +      if (ret == 1)
 +        conn->proxy_state = PROXY_CONNECTED;
 +      break;
 +
 +    default:
 +      log_err(LD_BUG, "Invalid proxy_state for reading, %d",
 +              conn->proxy_state);
 +      tor_fragile_assert();
 +      ret = -1;
 +      break;
 +  }
 +
 +  log_debug(LD_NET, "leaving state %s",
 +            connection_proxy_state_to_string(conn->proxy_state));
 +
 +  if (ret < 0) {
 +    if (reason) {
 +      log_warn(LD_NET, "Proxy Client: unable to connect to %s:%d (%s)",
 +                conn->address, conn->port, escaped(reason));
 +      tor_free(reason);
 +    } else {
 +      log_warn(LD_NET, "Proxy Client: unable to connect to %s:%d",
 +                conn->address, conn->port);
 +    }
 +  } else if (ret == 1) {
 +    log_info(LD_NET, "Proxy Client: connection to %s:%d successful",
 +              conn->address, conn->port);
 +  }
 +
 +  return ret;
 +}
 +
  /**
   * Launch any configured listener connections of type <b>type</b>.  (A
   * listener is configured if <b>port_option</b> is non-zero.  If any
@@@ -1862,8 -1506,8 +1862,8 @@@ retry_all_listeners(smartlist_t *replac
                        replaced_conns, new_conns, 0,
                        AF_INET)<0)
      return -1;
 -  if (retry_listeners(CONN_TYPE_AP_NATD_LISTENER, options->NatdListenAddress,
 -                      options->NatdPort, "127.0.0.1",
 +  if (retry_listeners(CONN_TYPE_AP_NATD_LISTENER, options->NATDListenAddress,
 +                      options->NATDPort, "127.0.0.1",
                        replaced_conns, new_conns, 0,
                        AF_INET)<0)
      return -1;
@@@ -1999,7 -1643,6 +1999,7 @@@ connection_bucket_write_limit(connectio
    int base = connection_speaks_cells(conn) ?
                 CELL_NETWORK_SIZE : RELAY_PAYLOAD_SIZE;
    int priority = conn->type != CONN_TYPE_DIR;
 +  int conn_bucket = (int)conn->outbuf_flushlen;
    int global_bucket = global_write_bucket;
  
    if (!connection_is_rate_limited(conn)) {
@@@ -2007,22 -1650,12 +2007,22 @@@
      return conn->outbuf_flushlen;
    }
  
 +  if (connection_speaks_cells(conn)) {
 +    /* use the per-conn write limit if it's lower, but if it's less
 +     * than zero just use zero */
 +    or_connection_t *or_conn = TO_OR_CONN(conn);
 +    if (conn->state == OR_CONN_STATE_OPEN)
 +      if (or_conn->write_bucket < conn_bucket)
 +        conn_bucket = or_conn->write_bucket >= 0 ?
 +                        or_conn->write_bucket : 0;
 +  }
 +
    if (connection_counts_as_relayed_traffic(conn, now) &&
        global_relayed_write_bucket <= global_write_bucket)
      global_bucket = global_relayed_write_bucket;
  
 -  return connection_bucket_round_robin(base, priority, global_bucket,
 -                                       conn->outbuf_flushlen);
 +  return connection_bucket_round_robin(base, priority,
 +                                       global_bucket, conn_bucket);
  }
  
  /** Return 1 if the global write buckets are low enough that we
@@@ -2076,12 -1709,14 +2076,12 @@@ global_write_bucket_low(connection_t *c
    return 0;
  }
  
 -/** We just read num_read and wrote num_written onto conn.
 - * Decrement buckets appropriately. */
 +/** We just read <b>num_read</b> and wrote <b>num_written</b> bytes
 + * onto <b>conn</b>. Decrement buckets appropriately. */
  static void
  connection_buckets_decrement(connection_t *conn, time_t now,
                               size_t num_read, size_t num_written)
  {
 -  if (!connection_is_rate_limited(conn))
 -    return; /* local IPs are free */
    if (num_written >= INT_MAX || num_read >= INT_MAX) {
      log_err(LD_BUG, "Value out of range. num_read=%lu, num_written=%lu, "
               "connection type=%s, state=%s",
@@@ -2093,24 -1728,10 +2093,24 @@@
      tor_fragile_assert();
    }
  
 -  if (num_read > 0)
 +  /* Count bytes of answering direct and tunneled directory requests */
 +  if (conn->type == CONN_TYPE_DIR && conn->purpose == DIR_PURPOSE_SERVER) {
 +    if (num_read > 0)
 +      rep_hist_note_dir_bytes_read(num_read, now);
 +    if (num_written > 0)
 +      rep_hist_note_dir_bytes_written(num_written, now);
 +  }
 +
 +  if (!connection_is_rate_limited(conn))
 +    return; /* local IPs are free */
 +  if (num_read > 0) {
      rep_hist_note_bytes_read(num_read, now);
 -  if (num_written > 0)
 +  }
 +  if (num_written > 0) {
      rep_hist_note_bytes_written(num_written, now);
 +  }
 +  if (conn->type == CONN_TYPE_EXIT)
 +    rep_hist_note_exit_bytes(conn->port, num_written, num_read);
  
    if (connection_counts_as_relayed_traffic(conn, now)) {
      global_relayed_read_bucket -= (int)num_read;
@@@ -2118,10 -1739,8 +2118,10 @@@
    }
    global_read_bucket -= (int)num_read;
    global_write_bucket -= (int)num_written;
 -  if (connection_speaks_cells(conn) && conn->state == OR_CONN_STATE_OPEN)
 +  if (connection_speaks_cells(conn) && conn->state == OR_CONN_STATE_OPEN) {
      TO_OR_CONN(conn)->read_bucket -= (int)num_read;
 +    TO_OR_CONN(conn)->write_bucket -= (int)num_written;
 +  }
  }
  
  /** If we have exhausted our global buckets, or the buckets for conn,
@@@ -2160,10 -1779,12 +2160,10 @@@ connection_consider_empty_write_buckets
    } else if (connection_counts_as_relayed_traffic(conn, approx_time()) &&
               global_relayed_write_bucket <= 0) {
      reason = "global relayed write bucket exhausted. Pausing.";
 -#if 0
    } else if (connection_speaks_cells(conn) &&
               conn->state == OR_CONN_STATE_OPEN &&
               TO_OR_CONN(conn)->write_bucket <= 0) {
      reason = "connection write bucket exhausted. Pausing.";
 -#endif
    } else
      return; /* all good, no need to stop it */
  
@@@ -2259,19 -1880,14 +2259,19 @@@ connection_bucket_refill(int seconds_el
    {
      if (connection_speaks_cells(conn)) {
        or_connection_t *or_conn = TO_OR_CONN(conn);
 -      if (connection_read_bucket_should_increase(or_conn)) {
 +      if (connection_bucket_should_increase(or_conn->read_bucket, or_conn)) {
          connection_bucket_refill_helper(&or_conn->read_bucket,
                                          or_conn->bandwidthrate,
                                          or_conn->bandwidthburst,
                                          seconds_elapsed,
                                          "or_conn->read_bucket");
 -        //log_fn(LOG_DEBUG,"Receiver bucket %d now %d.", i,
 -        //       conn->read_bucket);
 +      }
 +      if (connection_bucket_should_increase(or_conn->write_bucket, or_conn)) {
 +        connection_bucket_refill_helper(&or_conn->write_bucket,
 +                                        or_conn->bandwidthrate,
 +                                        or_conn->bandwidthburst,
 +                                        seconds_elapsed,
 +                                        "or_conn->write_bucket");
        }
      }
  
@@@ -2292,10 -1908,8 +2292,10 @@@
      if (conn->write_blocked_on_bw == 1
          && global_write_bucket > 0 /* and we're allowed to write */
          && (!connection_counts_as_relayed_traffic(conn, now) ||
 -            global_relayed_write_bucket > 0)) {
 -            /* even if we're relayed traffic */
 +            global_relayed_write_bucket > 0) /* even if it's relayed traffic */
 +        && (!connection_speaks_cells(conn) ||
 +            conn->state != OR_CONN_STATE_OPEN ||
 +            TO_OR_CONN(conn)->write_bucket > 0)) {
        LOG_FN_CONN(conn, (LOG_DEBUG,LD_NET,
                           "waking up conn (fd %d) for write", conn->s));
        conn->write_blocked_on_bw = 0;
@@@ -2304,17 -1918,17 +2304,17 @@@
    });
  }
  
 -/** Is the receiver bucket for connection <b>conn</b> low enough that we
 +/** Is the <b>bucket</b> for connection <b>conn</b> low enough that we
   * should add another pile of tokens to it?
   */
  static int
 -connection_read_bucket_should_increase(or_connection_t *conn)
 +connection_bucket_should_increase(int bucket, or_connection_t *conn)
  {
    tor_assert(conn);
  
    if (conn->_base.state != OR_CONN_STATE_OPEN)
      return 0; /* only open connections play the rate limiting game */
 -  if (conn->read_bucket >= conn->bandwidthburst)
 +  if (bucket >= conn->bandwidthburst)
      return 0;
  
    return 1;
@@@ -2332,8 -1946,8 +2332,8 @@@
   * Mark the connection and return -1 if you want to close it, else
   * return 0.
   */
 -int
 -connection_handle_read(connection_t *conn)
 +static int
 +connection_handle_read_impl(connection_t *conn)
  {
    int max_to_read=-1, try_to_read;
    size_t before, n_read = 0;
@@@ -2361,7 -1975,7 +2361,7 @@@
        return 0;
    }
  
 -loop_again:
 + loop_again:
    try_to_read = max_to_read;
    tor_assert(!conn->marked_for_close);
  
@@@ -2402,16 -2016,12 +2402,16 @@@
      return -1;
    }
    if (conn->linked_conn) {
 -    /* The other side's handle_write will never actually get called, so
 +    /* The other side's handle_write() will never actually get called, so
       * we need to invoke the appropriate callbacks ourself. */
      connection_t *linked = conn->linked_conn;
  
      if (n_read) {
 -      /* Probably a no-op, but hey. */
 +      /* Probably a no-op, since linked conns typically don't count for
 +       * bandwidth rate limiting. But do it anyway so we can keep stats
 +       * accurately. Note that since we read the bytes from conn, and
 +       * we're writing the bytes onto the linked connection, we count
 +       * these as <i>written</i> bytes. */
        connection_buckets_decrement(linked, approx_time(), 0, n_read);
  
        if (connection_flushed_some(linked) < 0)
@@@ -2423,7 -2033,7 +2423,7 @@@
      if (!buf_datalen(linked->outbuf) && conn->active_on_link)
        connection_stop_reading_from_linked_conn(conn);
    }
 -  /* If we hit the EOF, call connection_reached_eof. */
 +  /* If we hit the EOF, call connection_reached_eof(). */
    if (!conn->marked_for_close &&
        conn->inbuf_reached_eof &&
        connection_reached_eof(conn) < 0) {
@@@ -2432,16 -2042,6 +2432,16 @@@
    return 0;
  }
  
 +int
 +connection_handle_read(connection_t *conn)
 +{
 +  int res;
 +
 +  tor_gettimeofday_cache_clear();
 +  res = connection_handle_read_impl(conn);
 +  return res;
 +}
 +
  /** Pull in new bytes from conn-\>s or conn-\>linked_conn onto conn-\>inbuf,
   * either directly or via TLS. Reduce the token buckets by the number of bytes
   * read.
@@@ -2475,7 -2075,7 +2475,7 @@@ connection_read_to_buf(connection_t *co
    }
  
    if (connection_speaks_cells(conn) &&
 -      conn->state > OR_CONN_STATE_PROXY_READING) {
 +      conn->state > OR_CONN_STATE_PROXY_HANDSHAKING) {
      int pending;
      or_connection_t *or_conn = TO_OR_CONN(conn);
      size_t initial_size;
@@@ -2643,8 -2243,8 +2643,8 @@@ connection_outbuf_too_full(connection_
   * Mark the connection and return -1 if you want to close it, else
   * return 0.
   */
 -int
 -connection_handle_write(connection_t *conn, int force)
 +static int
 +connection_handle_write_impl(connection_t *conn, int force)
  {
    int e;
    socklen_t len=(socklen_t)sizeof(e);
@@@ -2659,7 -2259,7 +2659,7 @@@
      return 0; /* do nothing */
  
    if (conn->in_flushed_some) {
 -    log_warn(LD_BUG, "called recursively from inside conn->in_flushed_some()");
 +    log_warn(LD_BUG, "called recursively from inside conn->in_flushed_some");
      return 0;
    }
  
@@@ -2703,7 -2303,7 +2703,7 @@@
      : connection_bucket_write_limit(conn, now);
  
    if (connection_speaks_cells(conn) &&
 -      conn->state > OR_CONN_STATE_PROXY_READING) {
 +      conn->state > OR_CONN_STATE_PROXY_HANDSHAKING) {
      or_connection_t *or_conn = TO_OR_CONN(conn);
      if (conn->state == OR_CONN_STATE_TLS_HANDSHAKING ||
          conn->state == OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING) {
@@@ -2722,13 -2322,6 +2722,13 @@@
      /* else open, or closing */
      result = flush_buf_tls(or_conn->tls, conn->outbuf,
                             max_to_write, &conn->outbuf_flushlen);
 +
 +    /* If we just flushed the last bytes, check if this tunneled dir
 +     * request is done. */
 +    if (buf_datalen(conn->outbuf) == 0 && conn->dirreq_id)
 +      geoip_change_dirreq_state(conn->dirreq_id, DIRREQ_TUNNELED,
 +                                DIRREQ_OR_CONN_BUFFER_FLUSHED);
 +
      switch (result) {
        CASE_TOR_TLS_ERROR_ANY:
        case TOR_TLS_CLOSE:
@@@ -2748,8 -2341,8 +2748,8 @@@
          if (!connection_is_reading(conn)) {
            connection_stop_writing(conn);
            conn->write_blocked_on_bw = 1;
 -          /* we'll start reading again when the next second arrives,
 -           * and then also start writing again.
 +          /* we'll start reading again when we get more tokens in our
 +           * read bucket; then we'll start writing again too.
             */
          }
          /* else no problem, we're already reading */
@@@ -2811,15 -2404,6 +2811,15 @@@
    return 0;
  }
  
 +int
 +connection_handle_write(connection_t *conn, int force)
 +{
 +    int res;
 +    tor_gettimeofday_cache_clear();
 +    res = connection_handle_write_impl(conn, force);
 +    return res;
 +}
 +
  /** OpenSSL TLS record size is 16383; this is close. The goal here is to
   * push data out as soon as we know there's enough for a TLS record, so
   * during periods of high load we won't read entire megabytes from
@@@ -2993,11 -2577,13 +2993,11 @@@ connection_get_by_type_state(int type, 
  
  /** Return a connection of type <b>type</b> that has rendquery equal
   * to <b>rendquery</b>, and that is not marked for close. If state
 - * is non-zero, conn must be of that state too. If rendversion is
 - * nonnegative, conn must be fetching that rendversion, too.
 + * is non-zero, conn must be of that state too.
   */
  connection_t *
  connection_get_by_type_state_rendquery(int type, int state,
 -                                       const char *rendquery,
 -                                       int rendversion)
 +                                       const char *rendquery)
  {
    smartlist_t *conns = get_connection_array();
  
@@@ -3012,6 -2598,8 +3012,6 @@@
          (!state || state == conn->state)) {
        if (type == CONN_TYPE_DIR &&
            TO_DIR_CONN(conn)->rend_data &&
 -          (rendversion < 0 ||
 -           rendversion == TO_DIR_CONN(conn)->rend_data->rend_desc_version) &&
            !rend_cmp_service_ids(rendquery,
                                  TO_DIR_CONN(conn)->rend_data->onion_address))
          return conn;
@@@ -3129,10 -2717,10 +3129,10 @@@ alloc_http_authenticator(const char *au
  static void
  client_check_address_changed(int sock)
  {
 -  uint32_t iface_ip, ip_out;
 +  uint32_t iface_ip, ip_out; /* host order */
    struct sockaddr_in out_addr;
    socklen_t out_addr_len = (socklen_t) sizeof(out_addr);
 -  uint32_t *ip;
 +  uint32_t *ip; /* host order */
  
    if (!last_interface_ip)
      get_interface_address(LOG_INFO, &last_interface_ip);
@@@ -3146,7 -2734,7 +3146,7 @@@
      return;
    }
  
 -  /* Okay.  If we've used this address previously, we're okay. */
 +  /* If we've used this address previously, we're okay. */
    ip_out = ntohl(out_addr.sin_addr.s_addr);
    SMARTLIST_FOREACH(outgoing_addrs, uint32_t*, ip_ptr,
                      if (*ip_ptr == ip_out) return;
@@@ -3445,7 -3033,7 +3445,7 @@@ assert_connection_ok(connection_t *conn
      }
  //    tor_assert(conn->addr && conn->port);
      tor_assert(conn->address);
 -    if (conn->state > OR_CONN_STATE_PROXY_READING)
 +    if (conn->state > OR_CONN_STATE_PROXY_HANDSHAKING)
        tor_assert(or_conn->tls);
    }
  
diff --combined src/or/connection_edge.c
index 2bfa88e,5e74ad6..87efb15
--- a/src/or/connection_edge.c
+++ b/src/or/connection_edge.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -10,28 -10,6 +10,28 @@@
   **/
  
  #include "or.h"
 +#include "buffers.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "dns.h"
 +#include "dnsserv.h"
 +#include "dirserv.h"
 +#include "hibernate.h"
 +#include "main.h"
 +#include "policies.h"
 +#include "reasons.h"
 +#include "relay.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rendservice.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
  
  #ifdef HAVE_LINUX_TYPES_H
  #include <linux/types.h>
@@@ -147,7 -125,7 +147,7 @@@ connection_edge_process_inbuf(edge_conn
        return 0;
      case AP_CONN_STATE_OPEN:
      case EXIT_CONN_STATE_OPEN:
 -      if (connection_edge_package_raw_inbuf(conn, package_partial) < 0) {
 +      if (connection_edge_package_raw_inbuf(conn, package_partial, NULL) < 0) {
          /* (We already sent an end cell if possible) */
          connection_mark_for_close(TO_CONN(conn));
          return -1;
@@@ -352,13 -330,11 +352,13 @@@ connection_edge_finished_connecting(edg
    tor_assert(conn->state == EXIT_CONN_STATE_CONNECTING);
  
    log_info(LD_EXIT,"Exit connection to %s:%u (%s) established.",
 -           escaped_safe_str(conn->address),conn->port,
 +           escaped_safe_str(conn->address), conn->port,
             safe_str(fmt_addr(&conn->addr)));
  
 +  rep_hist_note_exit_stream_opened(conn->port);
 +
    conn->state = EXIT_CONN_STATE_OPEN;
 -  connection_watch_events(conn, EV_READ); /* stop writing, continue reading */
 +  connection_watch_events(conn, READ_EVENT); /* stop writing, keep reading */
    if (connection_wants_to_flush(conn)) /* in case there are any queued relay
                                          * cells */
      connection_start_writing(conn);
@@@ -399,16 -375,13 +399,16 @@@
  static int
  compute_retry_timeout(edge_connection_t *conn)
  {
 +  int timeout = get_options()->CircuitStreamTimeout;
 +  if (timeout) /* if our config options override the default, use them */
 +    return timeout;
    if (conn->num_socks_retries < 2) /* try 0 and try 1 */
      return 10;
    return 15;
  }
  
  /** Find all general-purpose AP streams waiting for a response that sent their
 - * begin/resolve cell >=15 seconds ago. Detach from their current circuit, and
 + * begin/resolve cell too long ago. Detach from their current circuit, and
   * mark their current circuit as unsuitable for new streams. Then call
   * connection_ap_handshake_attach_circuit() to attach to a new circuit (if
   * available) or launch a new one.
@@@ -450,8 -423,7 +450,8 @@@ connection_ap_expire_beginning(void
          log_fn(severity, LD_APP,
              "Tried for %d seconds to get a connection to %s:%d. "
              "Giving up. (%s)",
 -            seconds_since_born, safe_str(conn->socks_request->address),
 +            seconds_since_born,
 +            safe_str_client(conn->socks_request->address),
              conn->socks_request->port,
              conn_state_to_string(CONN_TYPE_AP, conn->_base.state));
          connection_mark_unattached_ap(conn, END_STREAM_REASON_TIMEOUT);
@@@ -468,7 -440,7 +468,7 @@@
      circ = circuit_get_by_edge_conn(conn);
      if (!circ) { /* it's vanished? */
        log_info(LD_APP,"Conn is waiting (address %s), but lost its circ.",
 -               safe_str(conn->socks_request->address));
 +               safe_str_client(conn->socks_request->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_TIMEOUT);
        continue;
      }
@@@ -478,7 -450,7 +478,7 @@@
                 "Rend stream is %d seconds late. Giving up on address"
                 " '%s.onion'.",
                 seconds_idle,
 -               safe_str(conn->socks_request->address));
 +               safe_str_client(conn->socks_request->address));
          connection_edge_end(conn, END_STREAM_REASON_TIMEOUT);
          connection_mark_unattached_ap(conn, END_STREAM_REASON_TIMEOUT);
        }
@@@ -488,8 -460,7 +488,8 @@@
      log_fn(cutoff < 15 ? LOG_INFO : severity, LD_APP,
             "We tried for %d seconds to connect to '%s' using exit '%s'."
             " Retrying on a new circuit.",
 -           seconds_idle, safe_str(conn->socks_request->address),
 +           seconds_idle,
 +           safe_str_client(conn->socks_request->address),
             conn->cpath_layer ?
               conn->cpath_layer->extend_info->nickname : "*unnamed*");
      /* send an end down the circuit */
@@@ -600,14 -571,14 +600,14 @@@ circuit_discard_optional_exit_enclaves(
          !edge_conn->chosen_exit_retries)
        continue;
      r1 = router_get_by_nickname(edge_conn->chosen_exit_name, 0);
 -    r2 = router_get_by_nickname(info->nickname, 0);
 +    r2 = router_get_by_digest(info->identity_digest);
      if (!r1 || !r2 || r1 != r2)
        continue;
      tor_assert(edge_conn->socks_request);
      if (edge_conn->chosen_exit_optional) {
        log_info(LD_APP, "Giving up on enclave exit '%s' for destination %s.",
 -               safe_str(edge_conn->chosen_exit_name),
 -               escaped_safe_str(edge_conn->socks_request->address));
 +               safe_str_client(edge_conn->chosen_exit_name),
 +               escaped_safe_str_client(edge_conn->socks_request->address));
        edge_conn->chosen_exit_optional = 0;
        tor_free(edge_conn->chosen_exit_name); /* clears it */
        /* if this port is dangerous, warn or reject it now that we don't
@@@ -712,11 -683,7 +712,11 @@@ addressmap_init(void
  static void
  addressmap_ent_free(void *_ent)
  {
 -  addressmap_entry_t *ent = _ent;
 +  addressmap_entry_t *ent;
 +  if (!_ent)
 +    return;
 +
 +  ent = _ent;
    tor_free(ent->new_address);
    tor_free(ent);
  }
@@@ -725,11 -692,7 +725,11 @@@
  static void
  addressmap_virtaddress_ent_free(void *_ent)
  {
 -  virtaddress_entry_t *ent = _ent;
 +  virtaddress_entry_t *ent;
 +  if (!_ent)
 +    return;
 +
 +  ent = _ent;
    tor_free(ent->ipv4_address);
    tor_free(ent->hostname_address);
    tor_free(ent);
@@@ -819,11 -782,14 +819,11 @@@ addressmap_clean(time_t now
  void
  addressmap_free_all(void)
  {
 -  if (addressmap) {
 -    strmap_free(addressmap, addressmap_ent_free);
 -    addressmap = NULL;
 -  }
 -  if (virtaddress_reversemap) {
 -    strmap_free(virtaddress_reversemap, addressmap_virtaddress_ent_free);
 -    virtaddress_reversemap = NULL;
 -  }
 +  strmap_free(addressmap, addressmap_ent_free);
 +  addressmap = NULL;
 +
 +  strmap_free(virtaddress_reversemap, addressmap_virtaddress_ent_free);
 +  virtaddress_reversemap = NULL;
  }
  
  /** Look at address, and rewrite it until it doesn't want any
@@@ -850,9 -816,9 +850,9 @@@ addressmap_rewrite(char *address, size_
        return (rewrites > 0); /* done, no rewrite needed */
      }
  
 -    cp = tor_strdup(escaped_safe_str(ent->new_address));
 +    cp = tor_strdup(escaped_safe_str_client(ent->new_address));
      log_info(LD_APP, "Addressmap: rewriting %s to %s",
 -             escaped_safe_str(address), cp);
 +             escaped_safe_str_client(address), cp);
      if (ent->expires > 1 && ent->expires < expires)
        expires = ent->expires;
      tor_free(cp);
@@@ -860,7 -826,7 +860,7 @@@
    }
    log_warn(LD_CONFIG,
             "Loop detected: we've rewritten %s 16 times! Using it as-is.",
 -           escaped_safe_str(address));
 +           escaped_safe_str_client(address));
    /* it's fine to rewrite a rewrite, but don't loop forever */
    if (expires_out)
      *expires_out = TIME_MAX;
@@@ -882,9 -848,9 +882,9 @@@ addressmap_rewrite_reverse(char *addres
    tor_snprintf(s, len, "REVERSE[%s]", address);
    ent = strmap_get(addressmap, s);
    if (ent) {
 -    cp = tor_strdup(escaped_safe_str(ent->new_address));
 +    cp = tor_strdup(escaped_safe_str_client(ent->new_address));
      log_info(LD_APP, "Rewrote reverse lookup %s -> %s",
 -             escaped_safe_str(s), cp);
 +             escaped_safe_str_client(s), cp);
      tor_free(cp);
      strlcpy(address, ent->new_address, maxlen);
      r = 1;
@@@ -946,9 -912,7 +946,9 @@@ addressmap_register(const char *address
      if (expires > 1) {
        log_info(LD_APP,"Temporary addressmap ('%s' to '%s') not performed, "
                 "since it's already mapped to '%s'",
 -      safe_str(address), safe_str(new_address), safe_str(ent->new_address));
 +      safe_str_client(address),
 +      safe_str_client(new_address),
 +      safe_str_client(ent->new_address));
        tor_free(new_address);
        return;
      }
@@@ -967,8 -931,7 +967,8 @@@
    ent->source = source;
  
    log_info(LD_CONFIG, "Addressmap: (re)mapped '%s' to '%s'",
 -           safe_str(address), safe_str(ent->new_address));
 +           safe_str_client(address),
 +           safe_str_client(ent->new_address));
    control_event_address_mapped(address, ent->new_address, expires, NULL);
  }
  
@@@ -988,8 -951,7 +988,8 @@@ client_dns_incr_failures(const char *ad
    if (ent->num_resolve_failures < SHORT_MAX)
      ++ent->num_resolve_failures; /* don't overflow */
    log_info(LD_APP, "Address %s now has %d resolve failures.",
 -           safe_str(address), ent->num_resolve_failures);
 +           safe_str_client(address),
 +           ent->num_resolve_failures);
    return ent->num_resolve_failures;
  }
  
@@@ -1268,10 -1230,8 +1268,10 @@@ addressmap_register_virtual_address(in
        log_warn(LD_BUG,
                 "Internal confusion: I thought that '%s' was mapped to by "
                 "'%s', but '%s' really maps to '%s'. This is a harmless bug.",
 -               safe_str(new_address), safe_str(*addrp), safe_str(*addrp),
 -               ent?safe_str(ent->new_address):"(nothing)");
 +               safe_str_client(new_address),
 +               safe_str_client(*addrp),
 +               safe_str_client(*addrp),
 +               ent?safe_str_client(ent->new_address):"(nothing)");
    }
  
    tor_free(*addrp);
@@@ -1292,8 -1252,7 +1292,8 @@@
                             (type == RESOLVED_TYPE_IPV4) ?
                             vent->ipv4_address : vent->hostname_address));
      log_info(LD_APP, "Map from %s to %s okay.",
 -           safe_str(*addrp),safe_str(new_address));
 +             safe_str_client(*addrp),
 +             safe_str_client(new_address));
    }
  #endif
  
@@@ -1408,26 -1367,6 +1408,26 @@@ consider_plaintext_ports(edge_connectio
   * different one? */
  #define TRACKHOSTEXITS_RETRIES 5
  
 +/** Call connection_ap_handshake_rewrite_and_attach() unless a controller
 + *  asked us to leave streams unattached. Return 0 in that case.
 + *
 + *  See connection_ap_handshake_rewrite_and_attach()'s
 + *  documentation for arguments and return value.
 + */
 +int
 +connection_ap_rewrite_and_attach_if_allowed(edge_connection_t *conn,
 +                                            origin_circuit_t *circ,
 +                                            crypt_path_t *cpath)
 +{
 +  or_options_t *options = get_options();
 +
 +  if (options->LeaveStreamsUnattached) {
 +    conn->_base.state = AP_CONN_STATE_CONTROLLER_WAIT;
 +    return 0;
 +  }
 +  return connection_ap_handshake_rewrite_and_attach(conn, circ, cpath);
 +}
 +
  /** Connection <b>conn</b> just finished its socks handshake, or the
   * controller asked us to take care of it. If <b>circ</b> is defined,
   * then that's where we'll want to attach it. Otherwise we have to
@@@ -1461,7 -1400,7 +1461,7 @@@ connection_ap_handshake_rewrite_and_att
    tor_strlower(socks->address); /* normalize it */
    strlcpy(orig_address, socks->address, sizeof(orig_address));
    log_debug(LD_APP,"Client asked for %s:%d",
 -            safe_str(socks->address),
 +            safe_str_client(socks->address),
              socks->port);
  
    if (socks->command == SOCKS_COMMAND_RESOLVE &&
@@@ -1478,8 -1417,7 +1478,8 @@@
                                RESOLVED_TYPE_IPV4, tor_strdup(socks->address));
        tor_assert(new_addr);
        log_info(LD_APP, "Automapping %s to %s",
 -               escaped_safe_str(socks->address), safe_str(new_addr));
 +               escaped_safe_str_client(socks->address),
 +               safe_str_client(new_addr));
        strlcpy(socks->address, new_addr, sizeof(socks->address));
      }
    }
@@@ -1536,7 -1474,7 +1536,7 @@@
       * information.
       */
      log_warn(LD_APP,"Missing mapping for virtual address '%s'. Refusing.",
 -             socks->address); /* don't safe_str() this yet. */
 +             safe_str_client(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_INTERNAL);
      return -1;
    }
@@@ -1544,12 -1482,11 +1544,12 @@@
    /* Parse the address provided by SOCKS.  Modify it in-place if it
     * specifies a hidden-service (.onion) or particular exit node (.exit).
     */
 -  addresstype = parse_extended_hostname(socks->address);
 +  addresstype = parse_extended_hostname(socks->address,
 +                         remapped_to_exit || options->AllowDotExit);
  
    if (addresstype == BAD_HOSTNAME) {
      log_warn(LD_APP, "Invalid onion hostname %s; rejecting",
 -             safe_str(socks->address));
 +             safe_str_client(socks->address));
      control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                  escaped(socks->address));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
@@@ -1558,7 -1495,7 +1558,7 @@@
  
    if (addresstype == EXIT_HOSTNAME) {
      /* foo.exit -- modify conn->chosen_exit_node to specify the exit
 -     * node, and conn->address to hold only the address portion.*/
 +     * node, and conn->address to hold only the address portion. */
      char *s = strrchr(socks->address,'.');
      tor_assert(!automap);
      if (s) {
@@@ -1569,7 -1506,7 +1569,7 @@@
          *s = 0;
        } else {
          log_warn(LD_APP,"Malformed exit address '%s.exit'. Refusing.",
 -                 safe_str(socks->address));
 +                 safe_str_client(socks->address));
          control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                      escaped(socks->address));
          connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
@@@ -1585,7 -1522,7 +1585,7 @@@
        } else {
          log_warn(LD_APP,
                   "Unrecognized server in exit address '%s.exit'. Refusing.",
 -                 safe_str(socks->address));
 +                 safe_str_client(socks->address));
          connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
          return -1;
        }
@@@ -1599,7 -1536,7 +1599,7 @@@
                                    escaped(socks->address));
        log_warn(LD_APP,
                 "Destination '%s' seems to be an invalid hostname. Failing.",
 -               safe_str(socks->address));
 +               safe_str_client(socks->address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
        return -1;
      }
@@@ -1608,6 -1545,18 +1608,6 @@@
        uint32_t answer;
        struct in_addr in;
        /* Reply to resolves immediately if we can. */
 -      if (strlen(socks->address) > RELAY_PAYLOAD_SIZE) {
 -        log_warn(LD_APP,"Address to be resolved is too large. Failing.");
 -        control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
 -                                    escaped(socks->address));
 -        connection_ap_handshake_socks_resolved(conn,
 -                                               RESOLVED_TYPE_ERROR_TRANSIENT,
 -                                               0,NULL,-1,TIME_MAX);
 -        connection_mark_unattached_ap(conn,
 -                                END_STREAM_REASON_SOCKSPROTOCOL |
 -                                END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
 -        return -1;
 -      }
        if (tor_inet_aton(socks->address, &in)) { /* see if it's an IP already */
          /* leave it in network order */
          answer = in.s_addr;
@@@ -1638,7 -1587,7 +1638,7 @@@
          if (r) {
            log_info(LD_APP,
                     "Redirecting address %s to exit at enclave router %s",
 -                   safe_str(socks->address), r->nickname);
 +                   safe_str_client(socks->address), r->nickname);
            /* use the hex digest, not nickname, in case there are two
               routers with this nickname */
            conn->chosen_exit_name =
@@@ -1702,12 -1651,12 +1702,12 @@@
      strlcpy(conn->rend_data->onion_address, socks->address,
              sizeof(conn->rend_data->onion_address));
      log_info(LD_REND,"Got a hidden service request for ID '%s'",
 -             safe_str(conn->rend_data->onion_address));
 +             safe_str_client(conn->rend_data->onion_address));
      /* see if we already have it cached */
      r = rend_cache_lookup_entry(conn->rend_data->onion_address, -1, &entry);
      if (r<0) {
        log_warn(LD_BUG,"Invalid service name '%s'",
 -               safe_str(conn->rend_data->onion_address));
 +               safe_str_client(conn->rend_data->onion_address));
        connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
        return -1;
      }
@@@ -1729,15 -1678,32 +1729,15 @@@
      if (r==0) {
        conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
        log_info(LD_REND, "Unknown descriptor %s. Fetching.",
 -               safe_str(conn->rend_data->onion_address));
 -      /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
 -       * arrives first. Exception: When using client authorization, only
 -       * fetch v2 descriptors.*/
 +               safe_str_client(conn->rend_data->onion_address));
        rend_client_refetch_v2_renddesc(conn->rend_data);
 -      if (conn->rend_data->auth_type == REND_NO_AUTH)
 -        rend_client_refetch_renddesc(conn->rend_data->onion_address);
      } else { /* r > 0 */
 -      if (now - entry->received < NUM_SECONDS_BEFORE_HS_REFETCH) {
 -        conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT;
 -        log_info(LD_REND, "Descriptor is here and fresh enough. Great.");
 -        if (connection_ap_handshake_attach_circuit(conn) < 0) {
 -          if (!conn->_base.marked_for_close)
 -            connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH);
 -          return -1;
 -        }
 -      } else {
 -        conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
 -        log_info(LD_REND, "Stale descriptor %s. Re-fetching.",
 -                 safe_str(conn->rend_data->onion_address));
 -        /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
 -         * arrives first. Exception: When using client authorization, only
 -         * fetch v2 descriptors.*/
 -        rend_client_refetch_v2_renddesc(conn->rend_data);
 -        if (conn->rend_data->auth_type == REND_NO_AUTH)
 -          rend_client_refetch_renddesc(conn->rend_data->onion_address);
 +      conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT;
 +      log_info(LD_REND, "Descriptor is here. Great.");
 +      if (connection_ap_handshake_attach_circuit(conn) < 0) {
 +        if (!conn->_base.marked_for_close)
 +          connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH);
 +        return -1;
        }
      }
      return 0;
@@@ -1925,12 -1891,24 +1925,12 @@@ connection_ap_handshake_process_socks(e
      return -1;
    } /* else socks handshake is done, continue processing */
  
 -  if (hostname_is_noconnect_address(socks->address))
 -  {
 -    control_event_stream_status(conn, STREAM_EVENT_NEW, 0);
 -    control_event_stream_status(conn, STREAM_EVENT_CLOSED, 0);
 -    connection_mark_unattached_ap(conn, END_STREAM_REASON_DONE);
 -    return -1;
 -  }
 -
    if (SOCKS_COMMAND_IS_CONNECT(socks->command))
      control_event_stream_status(conn, STREAM_EVENT_NEW, 0);
    else
      control_event_stream_status(conn, STREAM_EVENT_NEW_RESOLVE, 0);
  
 -  if (options->LeaveStreamsUnattached) {
 -    conn->_base.state = AP_CONN_STATE_CONTROLLER_WAIT;
 -    return 0;
 -  }
 -  return connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
 +  return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL);
  }
  
  /** connection_init_accepted_conn() found a new trans AP conn.
@@@ -1944,6 -1922,7 +1944,6 @@@ in
  connection_ap_process_transparent(edge_connection_t *conn)
  {
    socks_request_t *socks;
 -  or_options_t *options = get_options();
  
    tor_assert(conn);
    tor_assert(conn->_base.type == CONN_TYPE_AP);
@@@ -1967,7 -1946,11 +1967,7 @@@
  
    control_event_stream_status(conn, STREAM_EVENT_NEW, 0);
  
 -  if (options->LeaveStreamsUnattached) {
 -    conn->_base.state = AP_CONN_STATE_CONTROLLER_WAIT;
 -    return 0;
 -  }
 -  return connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
 +  return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL);
  }
  
  /** connection_edge_process_inbuf() found a conn in state natd_wait. See if
@@@ -1988,6 -1971,7 +1988,6 @@@ connection_ap_process_natd(edge_connect
    size_t tlen = 30;
    int err, port_ok;
    socks_request_t *socks;
 -  or_options_t *options = get_options();
  
    tor_assert(conn);
    tor_assert(conn->_base.type == CONN_TYPE_AP);
@@@ -2003,13 -1987,13 +2003,13 @@@
    if (err == 0)
      return 0;
    if (err < 0) {
 -    log_warn(LD_APP,"Natd handshake failed (DEST too long). Closing");
 +    log_warn(LD_APP,"NATD handshake failed (DEST too long). Closing");
      connection_mark_unattached_ap(conn, END_STREAM_REASON_INVALID_NATD_DEST);
      return -1;
    }
  
    if (strcmpstart(tmp_buf, "[DEST ")) {
 -    log_warn(LD_APP,"Natd handshake was ill-formed; closing. The client "
 +    log_warn(LD_APP,"NATD handshake was ill-formed; closing. The client "
               "said: %s",
               escaped(tmp_buf));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_INVALID_NATD_DEST);
@@@ -2018,7 -2002,7 +2018,7 @@@
  
    daddr = tbuf = &tmp_buf[0] + 6; /* after end of "[DEST " */
    if (!(tbuf = strchr(tbuf, ' '))) {
 -    log_warn(LD_APP,"Natd handshake was ill-formed; closing. The client "
 +    log_warn(LD_APP,"NATD handshake was ill-formed; closing. The client "
               "said: %s",
               escaped(tmp_buf));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_INVALID_NATD_DEST);
@@@ -2032,7 -2016,7 +2032,7 @@@
    socks->port = (uint16_t)
      tor_parse_long(tbuf, 10, 1, 65535, &port_ok, &daddr);
    if (!port_ok) {
 -    log_warn(LD_APP,"Natd handshake failed; port %s is ill-formed or out "
 +    log_warn(LD_APP,"NATD handshake failed; port %s is ill-formed or out "
               "of range.", escaped(tbuf));
      connection_mark_unattached_ap(conn, END_STREAM_REASON_INVALID_NATD_DEST);
      return -1;
@@@ -2043,9 -2027,13 +2043,9 @@@
  
    control_event_stream_status(conn, STREAM_EVENT_NEW, 0);
  
 -  if (options->LeaveStreamsUnattached) {
 -    conn->_base.state = AP_CONN_STATE_CONTROLLER_WAIT;
 -    return 0;
 -  }
    conn->_base.state = AP_CONN_STATE_CIRCUIT_WAIT;
  
 -  return connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
 +  return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL);
  }
  
  /** Iterate over the two bytes of stream_id until we get one that is not
@@@ -2058,7 -2046,7 +2058,7 @@@ get_unique_stream_id_by_circ(origin_cir
    streamid_t test_stream_id;
    uint32_t attempts=0;
  
 -again:
 + again:
    test_stream_id = circ->next_stream_id++;
    if (++attempts > 1<<16) {
      /* Make sure we don't loop forever if all stream_id's are used. */
@@@ -2174,7 -2162,7 +2174,7 @@@ connection_ap_handshake_send_resolve(ed
      r = tor_addr_parse_reverse_lookup_name(&addr, a, AF_INET, 1);
      if (r <= 0) {
        log_warn(LD_APP, "Rejecting ill-formed reverse lookup of %s",
 -               safe_str(a));
 +               safe_str_client(a));
        connection_mark_unattached_ap(ap_conn, END_STREAM_REASON_INTERNAL);
        return -1;
      }
@@@ -2182,7 -2170,7 +2182,7 @@@
      r = tor_addr_to_reverse_lookup_name(inaddr_buf, sizeof(inaddr_buf), &addr);
      if (r < 0) {
        log_warn(LD_BUG, "Couldn't generate reverse lookup hostname of %s",
 -               safe_str(a));
 +               safe_str_client(a));
        connection_mark_unattached_ap(ap_conn, END_STREAM_REASON_INTERNAL);
        return -1;
      }
@@@ -2192,6 -2180,12 +2192,6 @@@
      tor_assert(payload_len <= (int)sizeof(inaddr_buf));
    }
  
 -  if (payload_len > RELAY_PAYLOAD_SIZE) {
 -    /* This should be impossible: we don't accept addresses this big. */
 -    connection_mark_unattached_ap(ap_conn, END_STREAM_REASON_INTERNAL);
 -    return -1;
 -  }
 -
    log_debug(LD_APP,
              "Sending relay cell to begin stream %d.", ap_conn->stream_id);
  
@@@ -2223,8 -2217,7 +2223,8 @@@ connection_ap_make_link(char *address, 
    edge_connection_t *conn;
  
    log_info(LD_APP,"Making internal %s tunnel to %s:%d ...",
 -           want_onehop ? "direct" : "anonymized" , safe_str(address),port);
 +           want_onehop ? "direct" : "anonymized",
 +           safe_str_client(address), port);
  
    conn = edge_connection_new(CONN_TYPE_AP, AF_INET);
    conn->_base.linked = 1; /* so that we can add it safely below. */
@@@ -2490,7 -2483,6 +2490,7 @@@ connection_exit_begin_conn(cell_t *cell
    char *address=NULL;
    uint16_t port;
    or_circuit_t *or_circ = NULL;
 +  or_options_t *options = get_options();
  
    assert_circuit_ok(circ);
    if (!CIRCUIT_IS_ORIGIN(circ))
@@@ -2505,7 -2497,7 +2505,7 @@@
     * that we have a stream connected to a circuit, and we don't connect to a
     * circuit until we have a pending/successful resolve. */
  
 -  if (!server_mode(get_options()) &&
 +  if (!server_mode(options) &&
        circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED) {
      log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
             "Relay begin cell at non-server. Closing.");
@@@ -2539,30 -2531,21 +2539,30 @@@
        tor_free(address);
        return 0;
      }
 -    if (or_circ && or_circ->is_first_hop &&
 -        !get_options()->AllowSingleHopExits) {
 +    if (or_circ && or_circ->p_conn && !options->AllowSingleHopExits &&
 +        (or_circ->is_first_hop ||
 +         (!connection_or_digest_is_known_relay(
 +                                       or_circ->p_conn->identity_digest) &&
 +          should_refuse_unknown_exits(options)))) {
        /* Don't let clients use us as a single-hop proxy, unless the user
 -       * has explicitly allowed that in the config.  It attracts attackers
 +       * has explicitly allowed that in the config. It attracts attackers
         * and users who'd be better off with, well, single-hop proxies.
         */
        log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
 -             "Attempt to open a stream on first hop of circuit. Closing.");
 +             "Attempt by %s to open a stream %s. Closing.",
 +             safe_str(or_circ->p_conn->_base.address),
 +             or_circ->is_first_hop ? "on first hop of circuit" :
 +                                     "from unknown relay");
        relay_send_end_cell_from_edge(rh.stream_id, circ,
 -                                    END_STREAM_REASON_TORPROTOCOL, NULL);
 +                                    or_circ->is_first_hop ?
 +                                      END_STREAM_REASON_TORPROTOCOL :
 +                                      END_STREAM_REASON_MISC,
 +                                    NULL);
        tor_free(address);
        return 0;
      }
    } else if (rh.command == RELAY_COMMAND_BEGIN_DIR) {
 -    if (!directory_permits_begindir_requests(get_options()) ||
 +    if (!directory_permits_begindir_requests(options) ||
          circ->purpose != CIRCUIT_PURPOSE_OR) {
        relay_send_end_cell_from_edge(rh.stream_id, circ,
                                      END_STREAM_REASON_NOTDIRECTORY, NULL);
@@@ -2588,11 -2571,6 +2588,11 @@@
  
    log_debug(LD_EXIT,"Creating new exit connection.");
    n_stream = edge_connection_new(CONN_TYPE_EXIT, AF_INET);
 +
 +  /* Remember the tunneled request ID in the new edge connection, so that
 +   * we can measure download times. */
 +  TO_CONN(n_stream)->dirreq_id = circ->dirreq_id;
 +
    n_stream->_base.purpose = EXIT_PURPOSE_CONNECT;
  
    n_stream->stream_id = rh.stream_id;
@@@ -2623,7 -2601,7 +2623,7 @@@
      log_debug(LD_REND,"Finished assigning addr/port");
      n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */
  
 -    /* add it into the linked list of n_streams on this circuit */
 +    /* add it into the linked list of p_streams on this circuit */
      n_stream->next_stream = origin_circ->p_streams;
      n_stream->on_circuit = circ;
      origin_circ->p_streams = n_stream;
@@@ -2650,7 -2628,7 +2650,7 @@@
    if (rh.command == RELAY_COMMAND_BEGIN_DIR) {
      tor_assert(or_circ);
      if (or_circ->p_conn && !tor_addr_is_null(&or_circ->p_conn->real_addr))
 -      tor_addr_assign(&n_stream->_base.addr, &or_circ->p_conn->real_addr);
 +      tor_addr_copy(&n_stream->_base.addr, &or_circ->p_conn->real_addr);
      return connection_exit_connect_dir(n_stream);
    }
  
@@@ -2743,7 -2721,7 +2743,7 @@@ connection_exit_connect(edge_connection
    if (!connection_edge_is_rendezvous_stream(edge_conn) &&
        router_compare_to_my_exit_policy(edge_conn)) {
      log_info(LD_EXIT,"%s:%d failed exit policy. Closing.",
 -             escaped_safe_str(conn->address), conn->port);
 +             escaped_safe_str_client(conn->address), conn->port);
      connection_edge_end(edge_conn, END_STREAM_REASON_EXITPOLICY);
      circuit_detach_stream(circuit_get_by_edge_conn(edge_conn), edge_conn);
      connection_free(conn);
@@@ -2765,7 -2743,7 +2765,7 @@@
      case 0:
        conn->state = EXIT_CONN_STATE_CONNECTING;
  
 -      connection_watch_events(conn, EV_WRITE | EV_READ);
 +      connection_watch_events(conn, READ_EVENT | WRITE_EVENT);
        /* writable indicates finish;
         * readable/error indicates broken link in windows-land. */
        return;
@@@ -2778,7 -2756,7 +2778,7 @@@
      log_warn(LD_BUG,"newly connected conn had data waiting!");
  //    connection_start_writing(conn);
    }
 -  connection_watch_events(conn, EV_READ);
 +  connection_watch_events(conn, READ_EVENT);
  
    /* also, deliver a 'connected' cell back through the circuit. */
    if (connection_edge_is_rendezvous_stream(edge_conn)) {
@@@ -2825,17 -2803,13 +2825,17 @@@ connection_exit_connect_dir(edge_connec
  
    dirconn = dir_connection_new(AF_INET);
  
 -  tor_addr_assign(&dirconn->_base.addr, &exitconn->_base.addr);
 +  tor_addr_copy(&dirconn->_base.addr, &exitconn->_base.addr);
    dirconn->_base.port = 0;
    dirconn->_base.address = tor_strdup(exitconn->_base.address);
    dirconn->_base.type = CONN_TYPE_DIR;
    dirconn->_base.purpose = DIR_PURPOSE_SERVER;
    dirconn->_base.state = DIR_CONN_STATE_SERVER_COMMAND_WAIT;
  
 +  /* Note that the new dir conn belongs to the same tunneled request as
 +   * the edge conn, so that we can measure download times. */
 +  TO_CONN(dirconn)->dirreq_id = TO_CONN(exitconn)->dirreq_id;
 +
    connection_link_connections(TO_CONN(dirconn), TO_CONN(exitconn));
  
    if (connection_add(TO_CONN(exitconn))<0) {
@@@ -2886,16 -2860,10 +2886,16 @@@ connection_edge_is_rendezvous_stream(ed
   * to exit from it, or 0 if it probably will not allow it.
   * (We might be uncertain if conn's destination address has not yet been
   * resolved.)
 + *
 + * If <b>excluded_means_no</b> is 1 and Exclude*Nodes is set and excludes
 + * this relay, return 0.
   */
  int
 -connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit)
 +connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit,
 +                           int excluded_means_no)
  {
 +  or_options_t *options = get_options();
 +
    tor_assert(conn);
    tor_assert(conn->_base.type == CONN_TYPE_AP);
    tor_assert(conn->socks_request);
@@@ -2941,35 -2909,20 +2941,35 @@@
      if (!conn->chosen_exit_name && policy_is_reject_star(exit->exit_policy))
        return 0;
    }
 +  if (options->_ExcludeExitNodesUnion &&
 +      (options->StrictNodes || excluded_means_no) &&
 +      routerset_contains_router(options->_ExcludeExitNodesUnion, exit)) {
 +    /* If we are trying to avoid this node as exit, and we have StrictNodes
 +     * set, then this is not a suitable exit. Refuse it.
 +     *
 +     * If we don't have StrictNodes set, then this function gets called in
 +     * two contexts. First, we've got a circuit open and we want to know
 +     * whether we can use it. In that case, we somehow built this circuit
 +     * despite having the last hop in ExcludeExitNodes, so we should be
 +     * willing to use it. Second, we are evaluating whether this is an
 +     * acceptable exit for a new circuit. In that case, skip it. */
 +    return 0;
 +  }
 +
    return 1;
  }
  
  /** If address is of the form "y.onion" with a well-formed handle y:
   *     Put a NUL after y, lower-case it, and return ONION_HOSTNAME.
   *
 - * If address is of the form "y.exit":
 + * If address is of the form "y.exit" and <b>allowdotexit</b> is true:
   *     Put a NUL after y and return EXIT_HOSTNAME.
   *
   * Otherwise:
   *     Return NORMAL_HOSTNAME and change nothing.
   */
  hostname_type_t
 -parse_extended_hostname(char *address)
 +parse_extended_hostname(char *address, int allowdotexit)
  {
      char *s;
      char query[REND_SERVICE_ID_LEN_BASE32+1];
@@@ -2978,16 -2931,8 +2978,16 @@@
      if (!s)
        return NORMAL_HOSTNAME; /* no dot, thus normal */
      if (!strcmp(s+1,"exit")) {
 -      *s = 0; /* NUL-terminate it */
 -      return EXIT_HOSTNAME; /* .exit */
 +      if (allowdotexit) {
 +        *s = 0; /* NUL-terminate it */
 +        return EXIT_HOSTNAME; /* .exit */
 +      } else {
 +        log_warn(LD_APP, "The \".exit\" notation is disabled in Tor due to "
 +                 "security risks. Set AllowDotExit in your torrc to enable "
 +                 "it.");
 +        /* FFFF send a controller event too to notify Vidalia users */
 +        return BAD_HOSTNAME;
 +      }
      }
      if (strcmp(s+1,"onion"))
        return NORMAL_HOSTNAME; /* neither .exit nor .onion, thus normal */
@@@ -3000,9 -2945,17 +3000,9 @@@
      if (rend_valid_service_id(query)) {
        return ONION_HOSTNAME; /* success */
      }
 -failed:
 + failed:
      /* otherwise, return to previous state and return 0 */
      *s = '.';
      return BAD_HOSTNAME;
  }
  
 -/** Check if the address is of the form "y.noconnect"
 - */
 -int
 -hostname_is_noconnect_address(const char *address)
 -{
 -  return ! strcasecmpend(address, ".noconnect");
 -}
 -
diff --combined src/or/connection_or.c
index 45266a9,e70edc7..cf7c09a
--- a/src/or/connection_or.c
+++ b/src/or/connection_or.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -11,22 -11,6 +11,22 @@@
   **/
  
  #include "or.h"
 +#include "buffers.h"
 +#include "circuitbuild.h"
 +#include "command.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "dirserv.h"
 +#include "geoip.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "reasons.h"
 +#include "relay.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
  
  static int connection_tls_finish_handshake(or_connection_t *conn);
  static int connection_or_process_cells_from_inbuf(or_connection_t *conn);
@@@ -96,8 -80,10 +96,8 @@@ connection_or_clear_identity_map(void
      }
    });
  
 -  if (orconn_identity_map) {
 -    digestmap_free(orconn_identity_map, NULL);
 -    orconn_identity_map = NULL;
 -  }
 +  digestmap_free(orconn_identity_map, NULL);
 +  orconn_identity_map = NULL;
  }
  
  /** Change conn->identity_digest to digest, and add conn into
@@@ -147,7 -133,7 +147,7 @@@ voi
  cell_pack(packed_cell_t *dst, const cell_t *src)
  {
    char *dest = dst->body;
 -  *(uint16_t*)dest    = htons(src->circ_id);
 +  set_uint16(dest, htons(src->circ_id));
    *(uint8_t*)(dest+2) = src->command;
    memcpy(dest+3, src->payload, CELL_PAYLOAD_SIZE);
  }
@@@ -158,7 -144,7 +158,7 @@@
  static void
  cell_unpack(cell_t *dest, const char *src)
  {
 -  dest->circ_id = ntohs(*(uint16_t*)(src));
 +  dest->circ_id = ntohs(get_uint16(src));
    dest->command = *(uint8_t*)(src+2);
    memcpy(dest->payload, src+3, CELL_PAYLOAD_SIZE);
  }
@@@ -201,6 -187,66 +201,6 @@@ connection_or_reached_eof(or_connection
    return 0;
  }
  
 -/** Read conn's inbuf. If the http response from the proxy is all
 - * here, make sure it's good news, and begin the tls handshake. If
 - * it's bad news, close the connection and return -1. Else return 0
 - * and hope for better luck next time.
 - */
 -static int
 -connection_or_read_proxy_response(or_connection_t *or_conn)
 -{
 -  char *headers;
 -  char *reason=NULL;
 -  int status_code;
 -  time_t date_header;
 -  connection_t *conn = TO_CONN(or_conn);
 -
 -  switch (fetch_from_buf_http(conn->inbuf,
 -                              &headers, MAX_HEADERS_SIZE,
 -                              NULL, NULL, 10000, 0)) {
 -    case -1: /* overflow */
 -      log_warn(LD_PROTOCOL,
 -               "Your https proxy sent back an oversized response. Closing.");
 -      return -1;
 -    case 0:
 -      log_info(LD_OR,"https proxy response not all here yet. Waiting.");
 -      return 0;
 -    /* case 1, fall through */
 -  }
 -
 -  if (parse_http_response(headers, &status_code, &date_header,
 -                          NULL, &reason) < 0) {
 -    log_warn(LD_OR,
 -             "Unparseable headers from proxy (connecting to '%s'). Closing.",
 -             conn->address);
 -    tor_free(headers);
 -    return -1;
 -  }
 -  if (!reason) reason = tor_strdup("[no reason given]");
 -
 -  if (status_code == 200) {
 -    log_info(LD_OR,
 -             "HTTPS connect to '%s' successful! (200 %s) Starting TLS.",
 -             conn->address, escaped(reason));
 -    tor_free(reason);
 -    if (connection_tls_start_handshake(or_conn, 0) < 0) {
 -      /* TLS handshaking error of some kind. */
 -      connection_mark_for_close(conn);
 -
 -      return -1;
 -    }
 -    return 0;
 -  }
 -  /* else, bad news on the status code */
 -  log_warn(LD_OR,
 -           "The https proxy sent back an unexpected status code %d (%s). "
 -           "Closing.",
 -           status_code, escaped(reason));
 -  tor_free(reason);
 -  connection_mark_for_close(conn);
 -  return -1;
 -}
 -
  /** Handle any new bytes that have come in on connection <b>conn</b>.
   * If conn is in 'open' state, hand it to
   * connection_or_process_cells_from_inbuf()
@@@ -209,24 -255,11 +209,24 @@@
  int
  connection_or_process_inbuf(or_connection_t *conn)
  {
 +  int ret;
    tor_assert(conn);
  
    switch (conn->_base.state) {
 -    case OR_CONN_STATE_PROXY_READING:
 -      return connection_or_read_proxy_response(conn);
 +    case OR_CONN_STATE_PROXY_HANDSHAKING:
 +      ret = connection_read_proxy_handshake(TO_CONN(conn));
 +
 +      /* start TLS after handshake completion, or deal with error */
 +      if (ret == 1) {
 +        tor_assert(TO_CONN(conn)->proxy_state == PROXY_CONNECTED);
 +        if (connection_tls_start_handshake(conn, 0) < 0)
 +          ret = -1;
 +      }
 +      if (ret < 0) {
 +        connection_mark_for_close(TO_CONN(conn));
 +      }
 +
 +      return ret;
      case OR_CONN_STATE_OPEN:
      case OR_CONN_STATE_OR_HANDSHAKING:
        return connection_or_process_cells_from_inbuf(conn);
@@@ -252,7 -285,8 +252,7 @@@ connection_or_flushed_some(or_connectio
    /* If we're under the low water mark, add cells until we're just over the
     * high water mark. */
    if (datalen < OR_CONN_LOWWATER) {
 -    ssize_t n = (OR_CONN_HIGHWATER - datalen + CELL_NETWORK_SIZE-1)
 -      / CELL_NETWORK_SIZE;
 +    ssize_t n = CEIL_DIV(OR_CONN_HIGHWATER - datalen, CELL_NETWORK_SIZE);
      time_t now = approx_time();
      while (conn->active_circuits && n > 0) {
        int flushed;
@@@ -278,7 -312,11 +278,7 @@@ connection_or_finished_flushing(or_conn
    assert_connection_ok(TO_CONN(conn),0);
  
    switch (conn->_base.state) {
 -    case OR_CONN_STATE_PROXY_FLUSHING:
 -      log_debug(LD_OR,"finished sending CONNECT to proxy.");
 -      conn->_base.state = OR_CONN_STATE_PROXY_READING;
 -      connection_stop_writing(TO_CONN(conn));
 -      break;
 +    case OR_CONN_STATE_PROXY_HANDSHAKING:
      case OR_CONN_STATE_OPEN:
      case OR_CONN_STATE_OR_HANDSHAKING:
        connection_stop_writing(TO_CONN(conn));
@@@ -296,34 -334,37 +296,34 @@@
  int
  connection_or_finished_connecting(or_connection_t *or_conn)
  {
 +  int proxy_type;
    connection_t *conn;
    tor_assert(or_conn);
    conn = TO_CONN(or_conn);
    tor_assert(conn->state == OR_CONN_STATE_CONNECTING);
  
 -  log_debug(LD_OR,"OR connect() to router at %s:%u finished.",
 +  log_debug(LD_HANDSHAKE,"OR connect() to router at %s:%u finished.",
              conn->address,conn->port);
    control_event_bootstrap(BOOTSTRAP_STATUS_HANDSHAKE, 0);
  
 -  if (get_options()->HttpsProxy) {
 -    char buf[1024];
 -    char *base64_authenticator=NULL;
 -    const char *authenticator = get_options()->HttpsProxyAuthenticator;
 +  proxy_type = PROXY_NONE;
  
 -    if (authenticator) {
 -      base64_authenticator = alloc_http_authenticator(authenticator);
 -      if (!base64_authenticator)
 -        log_warn(LD_OR, "Encoding https authenticator failed");
 -    }
 -    if (base64_authenticator) {
 -      tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.1\r\n"
 -                   "Proxy-Authorization: Basic %s\r\n\r\n",
 -                   fmt_addr(&conn->addr),
 -                   conn->port, base64_authenticator);
 -      tor_free(base64_authenticator);
 -    } else {
 -      tor_snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n\r\n",
 -                   fmt_addr(&conn->addr), conn->port);
 +  if (get_options()->HTTPSProxy)
 +    proxy_type = PROXY_CONNECT;
 +  else if (get_options()->Socks4Proxy)
 +    proxy_type = PROXY_SOCKS4;
 +  else if (get_options()->Socks5Proxy)
 +    proxy_type = PROXY_SOCKS5;
 +
 +  if (proxy_type != PROXY_NONE) {
 +    /* start proxy handshake */
 +    if (connection_proxy_connect(conn, proxy_type) < 0) {
 +      connection_mark_for_close(conn);
 +      return -1;
      }
 -    connection_write_to_buf(buf, strlen(buf), conn);
 -    conn->state = OR_CONN_STATE_PROXY_FLUSHING;
 +
 +    connection_start_reading(conn);
 +    conn->state = OR_CONN_STATE_PROXY_HANDSHAKING;
      return 0;
    }
  
@@@ -335,74 -376,6 +335,74 @@@
    return 0;
  }
  
 +/** Return 1 if identity digest <b>id_digest</b> is known to be a
 + * currently or recently running relay. Otherwise return 0. */
 +int
 +connection_or_digest_is_known_relay(const char *id_digest)
 +{
 +  if (router_get_consensus_status_by_id(id_digest))
 +    return 1; /* It's in the consensus: "yes" */
 +  if (router_get_by_digest(id_digest))
 +    return 1; /* Not in the consensus, but we have a descriptor for
 +               * it. Probably it was in a recent consensus. "Yes". */
 +  return 0;
 +}
 +
 +/** Set the per-conn read and write limits for <b>conn</b>. If it's a known
 + * relay, we will rely on the global read and write buckets, so give it
 + * per-conn limits that are big enough they'll never matter. But if it's
 + * not a known relay, first check if we set PerConnBwRate/Burst, then
 + * check if the consensus sets them, else default to 'big enough'.
 + */
 +static void
 +connection_or_update_token_buckets_helper(or_connection_t *conn, int reset,
 +                                          or_options_t *options)
 +{
 +  int rate, burst; /* per-connection rate limiting params */
 +  if (connection_or_digest_is_known_relay(conn->identity_digest)) {
 +    /* It's in the consensus, or we have a descriptor for it meaning it
 +     * was probably in a recent consensus. It's a recognized relay:
 +     * give it full bandwidth. */
 +    rate = (int)options->BandwidthRate;
 +    burst = (int)options->BandwidthBurst;
 +  } else {
 +    /* Not a recognized relay. Squeeze it down based on the suggested
 +     * bandwidth parameters in the consensus, but allow local config
 +     * options to override. */
 +    rate = options->PerConnBWRate ? (int)options->PerConnBWRate :
 +        (int)networkstatus_get_param(NULL, "perconnbwrate",
 +                                     (int)options->BandwidthRate);
 +    burst = options->PerConnBWBurst ? (int)options->PerConnBWBurst :
 +        (int)networkstatus_get_param(NULL, "perconnbwburst",
 +                                     (int)options->BandwidthBurst);
 +  }
 +
 +  conn->bandwidthrate = rate;
 +  conn->bandwidthburst = burst;
 +  if (reset) { /* set up the token buckets to be full */
 +    conn->read_bucket = conn->write_bucket = burst;
 +    return;
 +  }
 +  /* If the new token bucket is smaller, take out the extra tokens.
 +   * (If it's larger, don't -- the buckets can grow to reach the cap.) */
 +  if (conn->read_bucket > burst)
 +    conn->read_bucket = burst;
 +  if (conn->write_bucket > burst)
 +    conn->write_bucket = burst;
 +}
 +
 +/** Either our set of relays or our per-conn rate limits have changed.
 + * Go through all the OR connections and update their token buckets. */
 +void
 +connection_or_update_token_buckets(smartlist_t *conns, or_options_t *options)
 +{
 +  SMARTLIST_FOREACH(conns, connection_t *, conn,
 +  {
 +    if (connection_speaks_cells(conn))
 +      connection_or_update_token_buckets_helper(TO_OR_CONN(conn), 0, options);
 +  });
 +}
 +
  /** If we don't necessarily know the router we're connecting to, but we
   * have an addr/port/id_digest, then fill in as much as we can. Start
   * by checking to see if this describes a router we know. */
@@@ -412,9 -385,11 +412,9 @@@ connection_or_init_conn_from_address(or
                                       const char *id_digest,
                                       int started_here)
  {
 -  or_options_t *options = get_options();
    routerinfo_t *r = router_get_by_digest(id_digest);
 -  conn->bandwidthrate = (int)options->BandwidthRate;
 -  conn->read_bucket = conn->bandwidthburst = (int)options->BandwidthBurst;
    connection_or_set_identity_digest(conn, id_digest);
 +  connection_or_update_token_buckets_helper(conn, 1, get_options());
  
    conn->_base.port = port;
    tor_addr_copy(&conn->_base.addr, addr);
@@@ -606,24 -581,11 +606,24 @@@ connection_or_get_for_extend(const cha
  #define TIME_BEFORE_OR_CONN_IS_TOO_OLD (60*60*24*7)
  
  /** Given the head of the linked list for all the or_connections with a given
 - * identity, set elements of that list as is_bad_for_new_circs() as
 - * appropriate.  Helper for connection_or_set_bad_connections().
 + * identity, set elements of that list as is_bad_for_new_circs as
 + * appropriate. Helper for connection_or_set_bad_connections().
 + *
 + * Specifically, we set the is_bad_for_new_circs flag on:
 + *    - all connections if <b>force</b> is true.
 + *    - all connections that are too old.
 + *    - all open non-canonical connections for which a canonical connection
 + *      exists to the same router.
 + *    - all open canonical connections for which a 'better' canonical
 + *      connection exists to the same router.
 + *    - all open non-canonical connections for which a 'better' non-canonical
 + *      connection exists to the same router at the same address.
 + *
 + * See connection_or_is_better() for our idea of what makes one OR connection
 + * better than another.
   */
  static void
 -connection_or_group_set_badness(or_connection_t *head)
 +connection_or_group_set_badness(or_connection_t *head, int force)
  {
    or_connection_t *or_conn = NULL, *best = NULL;
    int n_old = 0, n_inprogress = 0, n_canonical = 0, n_other = 0;
@@@ -635,9 -597,8 +635,9 @@@
      if (or_conn->_base.marked_for_close ||
          or_conn->is_bad_for_new_circs)
        continue;
 -    if (or_conn->_base.timestamp_created + TIME_BEFORE_OR_CONN_IS_TOO_OLD
 -        < now) {
 +    if (force ||
 +        or_conn->_base.timestamp_created + TIME_BEFORE_OR_CONN_IS_TOO_OLD
 +          < now) {
        log_info(LD_OR,
                 "Marking OR conn to %s:%d as too old for new circuits "
                 "(fd %d, %d secs old).",
@@@ -670,7 -631,7 +670,7 @@@
        /* We have at least one open canonical connection to this router,
         * and this one is open but not canonical.  Mark it bad. */
        log_info(LD_OR,
 -               "Marking OR conn to %s:%d as too old for new circuits: "
 +               "Marking OR conn to %s:%d as unsuitable for new circuits: "
                 "(fd %d, %d secs old).  It is not canonical, and we have "
                 "another connection to that OR that is.",
                 or_conn->_base.address, or_conn->_base.port, or_conn->_base.s,
@@@ -710,7 -671,7 +710,7 @@@
           even when we're being forgiving. */
        if (best->is_canonical) {
          log_info(LD_OR,
 -                 "Marking OR conn to %s:%d as too old for new circuits: "
 +                 "Marking OR conn to %s:%d as unsuitable for new circuits: "
                   "(fd %d, %d secs old).  We have a better canonical one "
                   "(fd %d; %d secs old).",
                   or_conn->_base.address, or_conn->_base.port, or_conn->_base.s,
@@@ -720,9 -681,9 +720,9 @@@
        } else if (!tor_addr_compare(&or_conn->real_addr,
                                     &best->real_addr, CMP_EXACT)) {
          log_info(LD_OR,
 -                 "Marking OR conn to %s:%d as too old for new circuits: "
 -                 "(fd %d, %d secs old).  We have a better one "
 -                 "(fd %d; %d secs old).",
 +                 "Marking OR conn to %s:%d as unsuitable for new circuits: "
 +                 "(fd %d, %d secs old).  We have a better one with the "
 +                 "same address (fd %d; %d secs old).",
                   or_conn->_base.address, or_conn->_base.port, or_conn->_base.s,
                   (int)(now - or_conn->_base.timestamp_created),
                   best->_base.s, (int)(now - best->_base.timestamp_created));
@@@ -732,20 -693,27 +732,20 @@@
    }
  }
  
 -/** Go through all the OR connections, and set the is_bad_for_new_circs
 - * flag on:
 - *    - all connections that are too old.
 - *    - all open non-canonical connections for which a canonical connection
 - *      exists to the same router.
 - *    - all open canonical connections for which a 'better' canonical
 - *      connection exists to the same router.
 - *    - all open non-canonical connections for which a 'better' non-canonical
 - *      connection exists to the same router at the same address.
 - *
 - * See connection_or_is_better() for our idea of what makes one OR connection
 - * better than another.
 +/** Go through all the OR connections (or if <b>digest</b> is non-NULL, just
 + * the OR connections with that digest), and set the is_bad_for_new_circs
 + * flag based on the rules in connection_or_group_set_badness() (or just
 + * always set it if <b>force</b> is true).
   */
  void
 -connection_or_set_bad_connections(void)
 +connection_or_set_bad_connections(const char *digest, int force)
  {
    if (!orconn_identity_map)
      return;
  
    DIGESTMAP_FOREACH(orconn_identity_map, identity, or_connection_t *, conn) {
 -    connection_or_group_set_badness(conn);
 +    if (!digest || !memcmp(digest, conn->identity_digest, DIGEST_LEN))
 +      connection_or_group_set_badness(conn, force);
    } DIGESTMAP_FOREACH_END;
  }
  
@@@ -785,7 -753,6 +785,7 @@@ connection_or_connect(const tor_addr_t 
    or_connection_t *conn;
    or_options_t *options = get_options();
    int socket_error = 0;
 +  int using_proxy = 0;
    tor_addr_t addr;
  
    tor_assert(_addr);
@@@ -804,27 -771,19 +804,27 @@@
    conn->_base.state = OR_CONN_STATE_CONNECTING;
    control_event_or_conn_status(conn, OR_CONN_EVENT_LAUNCHED, 0);
  
 -  if (options->HttpsProxy) {
 -    /* we shouldn't connect directly. use the https proxy instead. */
 -    tor_addr_from_ipv4h(&addr, options->HttpsProxyAddr);
 -    port = options->HttpsProxyPort;
 +  /* use a proxy server if available */
 +  if (options->HTTPSProxy) {
 +    using_proxy = 1;
 +    tor_addr_copy(&addr, &options->HTTPSProxyAddr);
 +    port = options->HTTPSProxyPort;
 +  } else if (options->Socks4Proxy) {
 +    using_proxy = 1;
 +    tor_addr_copy(&addr, &options->Socks4ProxyAddr);
 +    port = options->Socks4ProxyPort;
 +  } else if (options->Socks5Proxy) {
 +    using_proxy = 1;
 +    tor_addr_copy(&addr, &options->Socks5ProxyAddr);
 +    port = options->Socks5ProxyPort;
    }
  
    switch (connection_connect(TO_CONN(conn), conn->_base.address,
                               &addr, port, &socket_error)) {
      case -1:
        /* If the connection failed immediately, and we're using
 -       * an https proxy, our https proxy is down. Don't blame the
 -       * Tor server. */
 -      if (!options->HttpsProxy)
 +       * a proxy, our proxy is down. Don't blame the Tor server. */
 +      if (!using_proxy)
          entry_guard_register_connect_status(conn->identity_digest,
                                              0, 1, time(NULL));
        connection_or_connect_failed(conn,
@@@ -833,7 -792,7 +833,7 @@@
        connection_free(TO_CONN(conn));
        return NULL;
      case 0:
 -      connection_watch_events(TO_CONN(conn), EV_READ | EV_WRITE);
 +      connection_watch_events(TO_CONN(conn), READ_EVENT | WRITE_EVENT);
        /* writable indicates finish, readable indicates broken link,
           error indicates broken link on windows */
        return conn;
@@@ -860,14 -819,13 +860,14 @@@ connection_tls_start_handshake(or_conne
  {
    conn->_base.state = OR_CONN_STATE_TLS_HANDSHAKING;
    conn->tls = tor_tls_new(conn->_base.s, receiving);
 -  tor_tls_set_logged_address(conn->tls, escaped_safe_str(conn->_base.address));
 +  tor_tls_set_logged_address(conn->tls, // XXX client and relay?
 +      escaped_safe_str(conn->_base.address));
    if (!conn->tls) {
      log_warn(LD_BUG,"tor_tls_new failed. Closing.");
      return -1;
    }
    connection_start_reading(TO_CONN(conn));
 -  log_debug(LD_OR,"starting TLS handshake on fd %d", conn->_base.s);
 +  log_debug(LD_HANDSHAKE,"starting TLS handshake on fd %d", conn->_base.s);
    note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C);
  
    if (connection_tls_continue_handshake(conn) < 0) {
@@@ -974,26 -932,23 +974,26 @@@ connection_or_nonopen_was_started_here(
   * return -1 if he is lying, broken, or otherwise something is wrong.
   *
   * If we initiated this connection (<b>started_here</b> is true), make sure
 - * the other side sent sent a correctly formed certificate. If I initiated the
 + * the other side sent a correctly formed certificate. If I initiated the
   * connection, make sure it's the right guy.
   *
   * Otherwise (if we _didn't_ initiate this connection), it's okay for
   * the certificate to be weird or absent.
   *
   * If we return 0, and the certificate is as expected, write a hash of the
 - * identity key into digest_rcvd, which must have DIGEST_LEN space in it. (If
 - * we return -1 this buffer is undefined.)  If the certificate is invalid
 - * or missing on an incoming connection, we return 0 and set digest_rcvd to
 - * DIGEST_LEN 0 bytes.
 + * identity key into <b>digest_rcvd_out</b>, which must have DIGEST_LEN
 + * space in it.
 + * If the certificate is invalid or missing on an incoming connection,
 + * we return 0 and set <b>digest_rcvd_out</b> to DIGEST_LEN NUL bytes.
 + * (If we return -1, the contents of this buffer are undefined.)
   *
   * As side effects,
   * 1) Set conn->circ_id_type according to tor-spec.txt.
   * 2) If we're an authdirserver and we initiated the connection: drop all
   *    descriptors that claim to be on that IP/port but that aren't
   *    this guy; and note that this guy is reachable.
 + * 3) If this is a bridge and we didn't configure its identity
 + *    fingerprint, remember the keyid we just learned.
   */
  static int
  connection_or_check_valid_tls_handshake(or_connection_t *conn,
@@@ -1004,23 -959,19 +1004,23 @@@
    or_options_t *options = get_options();
    int severity = server_mode(options) ? LOG_PROTOCOL_WARN : LOG_WARN;
    const char *safe_address =
 -    started_here ? conn->_base.address : safe_str(conn->_base.address);
 +    started_here ? conn->_base.address :
 +                   safe_str_client(conn->_base.address);
    const char *conn_type = started_here ? "outgoing" : "incoming";
 +  crypto_pk_env_t *our_identity =
 +    started_here ? get_tlsclient_identity_key() :
 +                   get_server_identity_key();
    int has_cert = 0, has_identity=0;
  
    check_no_tls_errors();
    has_cert = tor_tls_peer_has_cert(conn->tls);
    if (started_here && !has_cert) {
 -    log_info(LD_PROTOCOL,"Tried connecting to router at %s:%d, but it didn't "
 +    log_info(LD_HANDSHAKE,"Tried connecting to router at %s:%d, but it didn't "
               "send a cert! Closing.",
               safe_address, conn->_base.port);
      return -1;
    } else if (!has_cert) {
 -    log_debug(LD_PROTOCOL,"Got incoming connection with no certificate. "
 +    log_debug(LD_HANDSHAKE,"Got incoming connection with no certificate. "
                "That's ok.");
    }
    check_no_tls_errors();
@@@ -1029,16 -980,15 +1029,16 @@@
      int v = tor_tls_verify(started_here?severity:LOG_INFO,
                             conn->tls, &identity_rcvd);
      if (started_here && v<0) {
 -      log_fn(severity,LD_OR,"Tried connecting to router at %s:%d: It"
 +      log_fn(severity,LD_HANDSHAKE,"Tried connecting to router at %s:%d: It"
               " has a cert but it's invalid. Closing.",
               safe_address, conn->_base.port);
          return -1;
      } else if (v<0) {
 -      log_info(LD_PROTOCOL,"Incoming connection gave us an invalid cert "
 +      log_info(LD_HANDSHAKE,"Incoming connection gave us an invalid cert "
                 "chain; ignoring.");
      } else {
 -      log_debug(LD_OR,"The certificate seems to be valid on %s connection "
 +      log_debug(LD_HANDSHAKE,
 +                "The certificate seems to be valid on %s connection "
                  "with %s:%d", conn_type, safe_address, conn->_base.port);
      }
      check_no_tls_errors();
@@@ -1047,7 -997,7 +1047,7 @@@
    if (identity_rcvd) {
      has_identity = 1;
      crypto_pk_get_digest(identity_rcvd, digest_rcvd_out);
 -    if (crypto_pk_cmp_keys(get_identity_key(), identity_rcvd)<0) {
 +    if (crypto_pk_cmp_keys(our_identity, identity_rcvd)<0) {
        conn->circ_id_type = CIRC_ID_TYPE_LOWER;
      } else {
        conn->circ_id_type = CIRC_ID_TYPE_HIGHER;
@@@ -1065,13 -1015,9 +1065,13 @@@
      conn->nickname[0] = '$';
      base16_encode(conn->nickname+1, HEX_DIGEST_LEN+1,
                    conn->identity_digest, DIGEST_LEN);
 -    log_info(LD_OR, "Connected to router %s at %s:%d without knowing "
 +    log_info(LD_HANDSHAKE, "Connected to router %s at %s:%d without knowing "
                      "its key. Hoping for the best.",
                      conn->nickname, conn->_base.address, conn->_base.port);
 +    /* if it's a bridge and we didn't know its identity fingerprint, now
 +     * we do -- remember it for future attempts. */
 +    learned_router_identity(&conn->_base.addr, conn->_base.port,
 +                            digest_rcvd_out);
    }
  
    if (started_here) {
@@@ -1085,7 -1031,7 +1085,7 @@@
        base16_encode(seen, sizeof(seen), digest_rcvd_out, DIGEST_LEN);
        base16_encode(expected, sizeof(expected), conn->identity_digest,
                      DIGEST_LEN);
 -      log_fn(severity, LD_OR,
 +      log_fn(severity, LD_HANDSHAKE,
               "Tried connecting to router at %s:%d, but identity key was not "
               "as expected: wanted %s but got %s.",
               conn->_base.address, conn->_base.port, expected, seen);
@@@ -1127,8 -1073,8 +1127,8 @@@ connection_tls_finish_handshake(or_conn
    char digest_rcvd[DIGEST_LEN];
    int started_here = connection_or_nonopen_was_started_here(conn);
  
 -  log_debug(LD_OR,"tls handshake with %s done. verifying.",
 -            safe_str(conn->_base.address));
 +  log_debug(LD_HANDSHAKE,"tls handshake with %s done. verifying.",
 +            safe_str_client(conn->_base.address));
  
    directory_set_dirty();
  
@@@ -1136,8 -1082,6 +1136,8 @@@
                                                digest_rcvd) < 0)
      return -1;
  
 +  circuit_build_times_network_is_live(&circ_times);
 +
    if (tor_tls_used_v1_handshake(conn->tls)) {
      conn->link_proto = 1;
      if (!started_here) {
@@@ -1173,8 -1117,7 +1173,8 @@@ connection_init_or_handshake_state(or_c
  void
  or_handshake_state_free(or_handshake_state_t *state)
  {
 -  tor_assert(state);
 +  if (!state)
 +    return;
    memset(state, 0xBE, sizeof(or_handshake_state_t));
    tor_free(state);
  }
@@@ -1191,7 -1134,6 +1191,7 @@@ connection_or_set_state_open(or_connect
    control_event_or_conn_status(conn, OR_CONN_EVENT_CONNECTED, 0);
  
    if (started_here) {
 +    circuit_build_times_network_is_live(&circ_times);
      rep_hist_note_connect_succeeded(conn->identity_digest, now);
      if (entry_guard_register_connect_status(conn->identity_digest,
                                              1, 0, now) < 0) {
@@@ -1216,10 -1158,10 +1216,10 @@@
        }
      }
    }
 -  if (conn->handshake_state) {
 -    or_handshake_state_free(conn->handshake_state);
 -    conn->handshake_state = NULL;
 -  }
 +
 +  or_handshake_state_free(conn->handshake_state);
 +  conn->handshake_state = NULL;
 +
    connection_start_reading(TO_CONN(conn));
    circuit_n_conn_done(conn, 1); /* send the pending creates, if any. */
  
@@@ -1293,7 -1235,6 +1293,7 @@@ connection_or_process_cells_from_inbuf(
      if (connection_fetch_var_cell_from_buf(conn, &var_cell)) {
        if (!var_cell)
          return 0; /* not yet. */
 +      circuit_build_times_network_is_live(&circ_times);
        command_process_var_cell(var_cell, conn);
        var_cell_free(var_cell);
      } else {
@@@ -1303,7 -1244,6 +1303,7 @@@
                                                                   available? */
          return 0; /* not yet */
  
 +      circuit_build_times_network_is_live(&circ_times);
        connection_fetch_from_buf(buf, CELL_NETWORK_SIZE, TO_CONN(conn));
  
        /* retrieve cell info from buf (create the host-order struct from the
@@@ -1334,6 -1274,10 +1334,6 @@@ connection_or_send_destroy(circid_t cir
    cell.payload[0] = (uint8_t) reason;
    log_debug(LD_OR,"Sending destroy (circID %d).", circ_id);
  
 -  /* XXXX It's possible that under some circumstances, we want the destroy
 -   * to take precedence over other data waiting on the circuit's cell queue.
 -   */
 -
    connection_or_write_cell_to_buf(&cell, conn);
    return 0;
  }
diff --combined src/or/control.c
index ad316c4,eae6170..fb9c1ae
--- a/src/or/control.c
+++ b/src/or/control.c
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -11,26 -11,6 +11,26 @@@
  #define CONTROL_PRIVATE
  
  #include "or.h"
 +#include "buffers.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "control.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dnsserv.h"
 +#include "geoip.h"
 +#include "hibernate.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "policies.h"
 +#include "reasons.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  /** Yield true iff <b>s</b> is the state of a control_connection_t that has
   * finished authentication and is accepting commands. */
@@@ -63,8 -43,7 +63,8 @@@
  #define EVENT_STREAM_BANDWIDTH_USED   0x0014
  #define EVENT_CLIENTS_SEEN     0x0015
  #define EVENT_NEWCONSENSUS     0x0016
 -#define _EVENT_MAX             0x0016
 +#define EVENT_BUILDTIMEOUT_SET     0x0017
 +#define _EVENT_MAX             0x0017
  /* If _EVENT_MAX ever hits 0x0020, we need to make the mask wider. */
  
  /** Bitfield: The bit 1&lt;&lt;e is set if <b>any</b> open control
@@@ -75,9 -54,13 +75,9 @@@
   **/
  typedef uint32_t event_mask_t;
  
 -/** An event mask of all the events that controller with the LONG_NAMES option
 - * set is interested in receiving. */
 -static event_mask_t global_event_mask1long = 0;
 -
 -/** An event mask of all the events that controller with the SHORT_NAMES option
 - * set is interested in receiving. */
 -static event_mask_t global_event_mask1short = 0;
 +/** An event mask of all the events that any controller is interested in
 + * receiving. */
 +static event_mask_t global_event_mask = 0;
  
  /** True iff we have disabled log messages from being sent to the controller */
  static int disable_log_messages = 0;
@@@ -85,7 -68,13 +85,7 @@@
  /** Macro: true if any control connection is interested in events of type
   * <b>e</b>. */
  #define EVENT_IS_INTERESTING(e) \
 -  ((global_event_mask1long|global_event_mask1short) & (1<<(e)))
 -/** Macro: true if any control connection with the LONG_NAMES option is
 - * interested in events of type <b>e</b>. */
 -#define EVENT_IS_INTERESTING1L(e) (global_event_mask1long & (1<<(e)))
 -/** Macro: true if any control connection with the SHORT_NAMES option is
 - * interested in events of type <b>e</b>. */
 -#define EVENT_IS_INTERESTING1S(e) (global_event_mask1short & (1<<(e)))
 +  (global_event_mask & (1<<(e)))
  
  /** If we're using cookie-type authentication, how long should our cookies be?
   */
@@@ -106,13 -95,25 +106,13 @@@ static char authentication_cookie[AUTHE
   * of this so we can respond to getinfo status/bootstrap-phase queries. */
  static char last_sent_bootstrap_message[BOOTSTRAP_MSG_LEN];
  
 -/** Flag for event_format_t.  Indicates that we should use the old
 - * name format of nickname|hexdigest
 - */
 -#define SHORT_NAMES 1
 -/** Flag for event_format_t.  Indicates that we should use the new
 - * name format of $hexdigest[=~]nickname
 +/** Flag for event_format_t.  Indicates that we should use the one standard
 +    format.
   */
 -#define LONG_NAMES 2
 -#define ALL_NAMES (SHORT_NAMES|LONG_NAMES)
 -/** Flag for event_format_t.  Indicates that we should use the new event
 - * format where extra event fields are allowed using a NAME=VAL format. */
 -#define EXTENDED_FORMAT 4
 -/** Flag for event_format_t.  Indicates that we are using the old event format
 - * where extra fields aren't allowed. */
 -#define NONEXTENDED_FORMAT 8
 -#define ALL_FORMATS (EXTENDED_FORMAT|NONEXTENDED_FORMAT)
 +#define ALL_FORMATS 1
  
  /** Bit field of flags to select how to format a controller event.  Recognized
 - * flags are SHORT_NAMES, LONG_NAMES, EXTENDED_FORMAT, NONEXTENDED_FORMAT. */
 + * flag is ALL_FORMATS. */
  typedef int event_format_t;
  
  static void connection_printf_to_buf(control_connection_t *conn,
@@@ -122,6 -123,9 +122,6 @@@ static void send_control_done(control_c
  static void send_control_event(uint16_t event, event_format_t which,
                                 const char *format, ...)
    CHECK_PRINTF(3,4);
 -static void send_control_event_extended(uint16_t event, event_format_t which,
 -                                        const char *format, ...)
 -  CHECK_PRINTF(3,4);
  static int handle_control_setconf(control_connection_t *conn, uint32_t len,
                                    char *body);
  static int handle_control_resetconf(control_connection_t *conn, uint32_t len,
@@@ -135,6 -139,8 +135,6 @@@ static int handle_control_setevents(con
  static int handle_control_authenticate(control_connection_t *conn,
                                         uint32_t len,
                                         const char *body);
 -static int handle_control_saveconf(control_connection_t *conn, uint32_t len,
 -                                   const char *body);
  static int handle_control_signal(control_connection_t *conn, uint32_t len,
                                   const char *body);
  static int handle_control_mapaddress(control_connection_t *conn, uint32_t len,
@@@ -168,7 -174,7 +168,7 @@@ static int handle_control_usefeature(co
                                       const char *body);
  static int write_stream_target_to_buf(edge_connection_t *conn, char *buf,
                                        size_t len);
 -static void orconn_target_get_name(int long_names, char *buf, size_t len,
 +static void orconn_target_get_name(char *buf, size_t len,
                                     or_connection_t *conn);
  static char *get_cookie_file(void);
  
@@@ -208,19 -214,25 +208,19 @@@ control_update_global_event_mask(void
  {
    smartlist_t *conns = get_connection_array();
    event_mask_t old_mask, new_mask;
 -  old_mask = global_event_mask1short;
 -  old_mask |= global_event_mask1long;
 +  old_mask = global_event_mask;
  
 -  global_event_mask1short = 0;
 -  global_event_mask1long = 0;
 +  global_event_mask = 0;
    SMARTLIST_FOREACH(conns, connection_t *, _conn,
    {
      if (_conn->type == CONN_TYPE_CONTROL &&
          STATE_IS_OPEN(_conn->state)) {
        control_connection_t *conn = TO_CONTROL_CONN(_conn);
 -      if (conn->use_long_names)
 -        global_event_mask1long |= conn->event_mask;
 -      else
 -        global_event_mask1short |= conn->event_mask;
 +      global_event_mask |= conn->event_mask;
      }
    });
  
 -  new_mask = global_event_mask1short;
 -  new_mask |= global_event_mask1long;
 +  new_mask = global_event_mask;
  
    /* Handle the aftermath.  Set up the log callback to tell us only what
     * we want to hear...*/
@@@ -300,7 -312,7 +300,7 @@@ connection_write_str_to_buf(const char 
  /** Given a <b>len</b>-character string in <b>data</b>, made of lines
   * terminated by CRLF, allocate a new string in *<b>out</b>, and copy the
   * contents of <b>data</b> into *<b>out</b>, adding a period before any period
 - * that that appears at the start of a line, and adding a period-CRLF line at
 + * that appears at the start of a line, and adding a period-CRLF line at
   * the end. Replace all LF characters sequences with CRLF.  Return the number
   * of bytes in *<b>out</b>.
   */
@@@ -530,15 -542,28 +530,15 @@@ send_control_event_string(uint16_t even
                            const char *msg)
  {
    smartlist_t *conns = get_connection_array();
 +  (void)which;
    tor_assert(event >= _EVENT_MIN && event <= _EVENT_MAX);
  
 -  SMARTLIST_FOREACH(conns, connection_t *, conn,
 -  {
 +  SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) {
      if (conn->type == CONN_TYPE_CONTROL &&
          !conn->marked_for_close &&
          conn->state == CONTROL_CONN_STATE_OPEN) {
        control_connection_t *control_conn = TO_CONTROL_CONN(conn);
 -      if (control_conn->use_long_names) {
 -        if (!(which & LONG_NAMES))
 -          continue;
 -      } else {
 -        if (!(which & SHORT_NAMES))
 -          continue;
 -      }
 -      if (control_conn->use_extended_events) {
 -        if (!(which & EXTENDED_FORMAT))
 -          continue;
 -      } else {
 -        if (!(which & NONEXTENDED_FORMAT))
 -          continue;
 -      }
 +
        if (control_conn->event_mask & (1<<event)) {
          int is_err = 0;
          connection_write_to_buf(msg, strlen(msg), TO_CONN(control_conn));
@@@ -554,7 -579,7 +554,7 @@@
            connection_handle_write(TO_CONN(control_conn), 1);
        }
      }
 -  });
 +  } SMARTLIST_FOREACH_END(conn);
  }
  
  /** Helper for send_control1_event and send_control1_event_extended:
@@@ -562,17 -587,22 +562,17 @@@
   * <b>event</b>.  The event's body is created by the printf-style format in
   * <b>format</b>, and other arguments as provided.
   *
   * Currently the length of the message is limited to 1024 (including the
   * ending \\r\\n\\0). */
  static void
 -send_control_event_impl(uint16_t event, event_format_t which, int extended,
 -                        const char *format, va_list ap)
 +send_control_event_impl(uint16_t event, event_format_t which,
 +                         const char *format, va_list ap)
  {
    /* This is just a little longer than the longest allowed log message */
  #define SEND_CONTROL1_EVENT_BUFFERSIZE 10064
    int r;
    char buf[SEND_CONTROL1_EVENT_BUFFERSIZE];
    size_t len;
 -  char *cp;
  
    r = tor_vsnprintf(buf, sizeof(buf), format, ap);
    if (r<0) {
@@@ -588,7 -618,15 +588,7 @@@
      buf[SEND_CONTROL1_EVENT_BUFFERSIZE-3] = '\r';
    }
  
 -  if (extended && (cp = strchr(buf, '@'))) {
 -    which &= ~ALL_FORMATS;
 -    *cp = ' ';
 -    send_control_event_string(event, which|EXTENDED_FORMAT, buf);
 -    memcpy(cp, "\r\n\0", 3);
 -    send_control_event_string(event, which|NONEXTENDED_FORMAT, buf);
 -  } else {
 -    send_control_event_string(event, which|ALL_FORMATS, buf);
 -  }
 +  send_control_event_string(event, which|ALL_FORMATS, buf);
  }
  
  /** Send an event to all v1 controllers that are listening for code
@@@ -603,7 -641,27 +603,7 @@@ send_control_event(uint16_t event, even
  {
    va_list ap;
    va_start(ap, format);
 -  send_control_event_impl(event, which, 0, format, ap);
 -  va_end(ap);
 -}
 -
 -/** Send an event to all v1 controllers that are listening for code
 - * <b>event</b>.  The event's body is created by the printf-style format in
 - * <b>format</b>, and other arguments as provided.
 - *
 - * If the format contains a single '@' character, it will be replaced with a
 - * space and all text after that character will be sent only to controllers
 - * that have enabled extended events.
 - *
 - * Currently the length of the message is limited to 1024 (including the
 - * ending \\n\\r\\0. */
 -static void
 -send_control_event_extended(uint16_t event, event_format_t which,
 -                            const char *format, ...)
 -{
 -  va_list ap;
 -  va_start(ap, format);
 -  send_control_event_impl(event, which, 1, format, ap);
 +  send_control_event_impl(event, which, format, ap);
    va_end(ap);
  }
  
@@@ -849,37 -907,36 +849,37 @@@ handle_control_loadconf(control_connect
  
    retval = options_init_from_string(body, CMD_RUN_TOR, NULL, &errstring);
  
 -  if (retval != SETOPT_OK) {
 +  if (retval != SETOPT_OK)
      log_warn(LD_CONTROL,
               "Controller gave us config file that didn't validate: %s",
               errstring);
 -    switch (retval) {
 -      case SETOPT_ERR_PARSE:
 -        msg = "552 Invalid config file";
 -        break;
 -      case SETOPT_ERR_TRANSITION:
 -        msg = "553 Transition not allowed";
 -        break;
 -      case SETOPT_ERR_SETTING:
 -        msg = "553 Unable to set option";
 -        break;
 -      case SETOPT_ERR_MISC:
 -      default:
 -        msg = "550 Unable to load config";
 -        break;
 -      case SETOPT_OK:
 -        tor_fragile_assert();
 -        break;
 -    }
 +
 +  switch (retval) {
 +  case SETOPT_ERR_PARSE:
 +    msg = "552 Invalid config file";
 +    break;
 +  case SETOPT_ERR_TRANSITION:
 +    msg = "553 Transition not allowed";
 +    break;
 +  case SETOPT_ERR_SETTING:
 +    msg = "553 Unable to set option";
 +    break;
 +  case SETOPT_ERR_MISC:
 +  default:
 +    msg = "550 Unable to load config";
 +    break;
 +  case SETOPT_OK:
 +    break;
 +  }
 +  if (msg) {
      if (errstring)
        connection_printf_to_buf(conn, "%s: %s\r\n", msg, errstring);
      else
        connection_printf_to_buf(conn, "%s\r\n", msg);
 -    tor_free(errstring);
 -    return 0;
 +  } else {
 +    send_control_done(conn);
    }
 -  send_control_done(conn);
 +  tor_free(errstring);
    return 0;
  }
  
@@@ -891,6 -948,7 +891,6 @@@ handle_control_setevents(control_connec
  {
    uint16_t event_code;
    uint32_t event_mask = 0;
 -  unsigned int extended = 0;
    smartlist_t *events = smartlist_create();
  
    (void) len;
@@@ -900,6 -958,7 +900,6 @@@
    SMARTLIST_FOREACH_BEGIN(events, const char *, ev)
      {
        if (!strcasecmp(ev, "EXTENDED")) {
 -        extended = 1;
          continue;
        } else if (!strcasecmp(ev, "CIRC"))
          event_code = EVENT_CIRCUIT_STATUS;
@@@ -943,8 -1002,6 +943,8 @@@
          event_code = EVENT_CLIENTS_SEEN;
        else if (!strcasecmp(ev, "NEWCONSENSUS"))
          event_code = EVENT_NEWCONSENSUS;
 +      else if (!strcasecmp(ev, "BUILDTIMEOUT_SET"))
 +        event_code = EVENT_BUILDTIMEOUT_SET;
        else {
          connection_printf_to_buf(conn, "552 Unrecognized event \"%s\"\r\n",
                                   ev);
@@@ -959,6 -1016,8 +959,6 @@@
    smartlist_free(events);
  
    conn->event_mask = event_mask;
 -  if (extended)
 -    conn->use_extended_events = 1;
  
    control_update_global_event_mask();
    send_control_done(conn);
@@@ -1269,7 -1328,7 +1269,7 @@@ handle_control_mapaddress(control_conne
            smartlist_add(reply, ans);
            log_warn(LD_CONTROL,
                     "Unable to allocate address for '%s' in MapAddress msg",
 -                   safe_str(line));
 +                   safe_str_client(line));
          } else {
            tor_snprintf(ans, anslen, "250-%s=%s", address, to);
            smartlist_add(reply, ans);
@@@ -1286,8 -1345,7 +1286,8 @@@
                     "not of expected form 'foo=bar'.", line);
        smartlist_add(reply, ans);
        log_info(LD_CONTROL, "Skipping MapAddress '%s': wrong "
 -                           "number of items.", safe_str(line));
 +                           "number of items.",
 +                           safe_str_client(line));
      }
      SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
      smartlist_clear(elts);
@@@ -1316,15 -1374,13 +1316,15 @@@
   * trivial-to-implement questions. */
  static int
  getinfo_helper_misc(control_connection_t *conn, const char *question,
 -                    char **answer)
 +                    char **answer, const char **errmsg)
  {
    (void) conn;
    if (!strcmp(question, "version")) {
      *answer = tor_strdup(get_version());
    } else if (!strcmp(question, "config-file")) {
      *answer = tor_strdup(get_torrc_fname());
 +  } else if (!strcmp(question, "config-text")) {
 +    *answer = options_dump(get_options(), 1);
    } else if (!strcmp(question, "info/names")) {
      *answer = list_getinfo_options();
    } else if (!strcmp(question, "events/names")) {
@@@ -1336,19 -1392,15 +1336,19 @@@
      *answer = tor_strdup("VERBOSE_NAMES EXTENDED_EVENTS");
    } else if (!strcmp(question, "address")) {
      uint32_t addr;
 -    if (router_pick_published_address(get_options(), &addr) < 0)
 +    if (router_pick_published_address(get_options(), &addr) < 0) {
 +      *errmsg = "Address unknown";
        return -1;
 +    }
      *answer = tor_dup_ip(addr);
    } else if (!strcmp(question, "dir-usage")) {
      *answer = directory_dump_request_log();
    } else if (!strcmp(question, "fingerprint")) {
      routerinfo_t *me = router_get_my_routerinfo();
 -    if (!me)
 +    if (!me) {
 +      *errmsg = "No routerdesc known; am I really a server?";
        return -1;
 +    }
      *answer = tor_malloc(HEX_DIGEST_LEN+1);
      base16_encode(*answer, HEX_DIGEST_LEN+1, me->cache_info.identity_digest,
                    DIGEST_LEN);
@@@ -1409,10 -1461,8 +1409,10 @@@ munge_extrainfo_into_routerinfo(const c
   * directory information. */
  static int
  getinfo_helper_dir(control_connection_t *control_conn,
 -                   const char *question, char **answer)
 +                   const char *question, char **answer,
 +                   const char **errmsg)
  {
 +  (void) control_conn;
    if (!strcmpstart(question, "desc/id/")) {
      routerinfo_t *ri = router_get_by_hexdigest(question+strlen("desc/id/"));
      if (ri) {
@@@ -1487,7 -1537,6 +1487,7 @@@
        log_warn(LD_CONTROL, "getinfo '%s': %s", question, msg);
        smartlist_free(descs);
        tor_free(url);
 +      *errmsg = msg;
        return -1;
      }
      SMARTLIST_FOREACH(descs, signed_descriptor_t *, sd,
@@@ -1538,7 -1587,7 +1538,7 @@@
      }
    } else if (!strcmp(question, "dir/status-vote/current/consensus")) { /* v3 */
      if (directory_caches_dir_info(get_options())) {
 -      const cached_dir_t *consensus = dirserv_get_consensus();
 +      const cached_dir_t *consensus = dirserv_get_consensus("ns");
        if (consensus)
          *answer = tor_strdup(consensus->dir);
      }
@@@ -1549,8 -1598,10 +1549,8 @@@
      }
    } else if (!strcmp(question, "network-status")) { /* v1 */
      routerlist_t *routerlist = router_get_routerlist();
 -    int verbose = control_conn->use_long_names;
      if (!routerlist || !routerlist->routers ||
 -        list_server_status_v1(routerlist->routers, answer,
 -                              verbose ? 2 : 1) < 0) {
 +        list_server_status_v1(routerlist->routers, answer, 1) < 0) {
        return -1;
      }
    } else if (!strcmpstart(question, "extra-info/digest/")) {
@@@ -1584,10 -1635,8 +1584,10 @@@
   * current states of things we send events about. */
  static int
  getinfo_helper_events(control_connection_t *control_conn,
 -                      const char *question, char **answer)
 +                      const char *question, char **answer,
 +                      const char **errmsg)
  {
 +  (void) control_conn;
    if (!strcmp(question, "circuit-status")) {
      circuit_t *circ;
      smartlist_t *status = smartlist_create();
@@@ -1598,9 -1647,10 +1598,9 @@@
        const char *purpose;
        if (! CIRCUIT_IS_ORIGIN(circ) || circ->marked_for_close)
          continue;
 -      if (control_conn->use_long_names)
 -        path = circuit_list_path_for_controller(TO_ORIGIN_CIRCUIT(circ));
 -      else
 -        path = circuit_list_path(TO_ORIGIN_CIRCUIT(circ),0);
 +
 +      path = circuit_list_path_for_controller(TO_ORIGIN_CIRCUIT(circ));
 +
        if (circ->state == CIRCUIT_STATE_OPEN)
          state = "BUILT";
        else if (strlen(path))
@@@ -1678,7 -1728,8 +1678,7 @@@
    } else if (!strcmp(question, "orconn-status")) {
      smartlist_t *conns = get_connection_array();
      smartlist_t *status = smartlist_create();
 -    SMARTLIST_FOREACH(conns, connection_t *, base_conn,
 -    {
 +    SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
        const char *state;
        char *s;
        char name[128];
@@@ -1693,19 -1744,29 +1693,19 @@@
          state = "LAUNCHED";
        else
          state = "NEW";
 -      orconn_target_get_name(control_conn->use_long_names, name, sizeof(name),
 -                             conn);
 +      orconn_target_get_name(name, sizeof(name), conn);
        slen = strlen(name)+strlen(state)+2;
        s = tor_malloc(slen+1);
        tor_snprintf(s, slen, "%s %s", name, state);
        smartlist_add(status, s);
 -    });
 +    } SMARTLIST_FOREACH_END(base_conn);
      *answer = smartlist_join_strings(status, "\r\n", 0, NULL);
      SMARTLIST_FOREACH(status, char *, cp, tor_free(cp));
      smartlist_free(status);
 -  } else if (!strcmpstart(question, "addr-mappings/") ||
 -             !strcmpstart(question, "address-mappings/")) {
 +  } else if (!strcmpstart(question, "address-mappings/")) {
      time_t min_e, max_e;
      smartlist_t *mappings;
 -    int want_expiry = !strcmpstart(question, "address-mappings/");
 -    if (!strcmpstart(question, "addr-mappings/")) {
 -      /* XXXX022 This has been deprecated since 0.2.0.3-alpha, and has
 -         generated a warning since 0.2.1.10-alpha; remove late in 0.2.2.x. */
 -      log_warn(LD_CONTROL, "Controller used obsolete addr-mappings/ GETINFO "
 -               "key; use address-mappings/ instead.");
 -    }
 -    question += strlen(want_expiry ? "address-mappings/"
 -                                   : "addr-mappings/");
 +    question += strlen("address-mappings/");
      if (!strcmp(question, "all")) {
        min_e = 0; max_e = TIME_MAX;
      } else if (!strcmp(question, "cache")) {
@@@ -1718,7 -1779,7 +1718,7 @@@
        return 0;
      }
      mappings = smartlist_create();
 -    addressmap_get_mappings(mappings, min_e, max_e, want_expiry);
 +    addressmap_get_mappings(mappings, min_e, max_e, 1);
      *answer = smartlist_join_strings(mappings, "\r\n", 0, NULL);
      SMARTLIST_FOREACH(mappings, char *, cp, tor_free(cp));
      smartlist_free(mappings);
@@@ -1726,7 -1787,7 +1726,7 @@@
      /* Note that status/ is not a catch-all for events; there's only supposed
       * to be a status GETINFO if there's a corresponding STATUS event. */
      if (!strcmp(question, "status/circuit-established")) {
 -      *answer = tor_strdup(has_completed_circuit ? "1" : "0");
 +      *answer = tor_strdup(can_complete_circuit ? "1" : "0");
      } else if (!strcmp(question, "status/enough-dir-info")) {
        *answer = tor_strdup(router_have_minimum_dir_info() ? "1" : "0");
      } else if (!strcmp(question, "status/good-server-descriptor") ||
@@@ -1785,12 -1846,21 +1785,12 @@@
                   "information", question);
        }
      } else if (!strcmp(question, "status/clients-seen")) {
 -      char geoip_start[ISO_TIME_LEN+1];
 -      size_t answer_len;
 -      char *geoip_summary = extrainfo_get_client_geoip_summary(time(NULL));
 -
 -      if (!geoip_summary)
 +      const char *bridge_stats = geoip_get_bridge_stats_controller(time(NULL));
 +      if (!bridge_stats) {
 +        *errmsg = "No bridge-client stats available";
          return -1;
 -
 -      answer_len = strlen("TimeStarted=\"\" CountrySummary=") +
 -                   ISO_TIME_LEN + strlen(geoip_summary) + 1;
 -      *answer = tor_malloc(answer_len);
 -      format_iso_time(geoip_start, geoip_get_history_start());
 -      tor_snprintf(*answer, answer_len,
 -                   "TimeStarted=\"%s\" CountrySummary=%s",
 -                   geoip_start, geoip_summary);
 -      tor_free(geoip_summary);
 +      }
 +      *answer = tor_strdup(bridge_stats);
      } else {
        return 0;
      }
@@@ -1800,14 -1870,11 +1800,14 @@@
  
  /** Callback function for GETINFO: on a given control connection, try to
   * answer the question <b>q</b> and store the newly-allocated answer in
 - * *<b>a</b>.  If there's no answer, or an error occurs, just don't set
 - * <b>a</b>.  Return 0.
 + * *<b>a</b>. If an internal error occurs, return -1 and optionally set
 + * *<b>error_out</b> to point to an error message to be delivered to the
 + * controller. On success, _or if the key is not recognized_, return 0. Do not
 + * set <b>a</b> if the key is not recognized.
   */
  typedef int (*getinfo_helper_t)(control_connection_t *,
 -                                const char *q, char **a);
 +                                const char *q, char **a,
 +                                const char **error_out);
  
  /** A single item for the GETINFO question-to-answer-function table. */
  typedef struct getinfo_item_t {
@@@ -1827,8 -1894,6 +1827,8 @@@
  static const getinfo_item_t getinfo_items[] = {
    ITEM("version", misc, "The current version of Tor."),
    ITEM("config-file", misc, "Current location of the \"torrc\" file."),
 +  ITEM("config-text", misc,
 +       "Return the string that would be written by a saveconf command."),
    ITEM("accounting/bytes", accounting,
         "Number of bytes read/written so far in the accounting interval."),
    ITEM("accounting/bytes-left", accounting,
@@@ -1868,6 -1933,7 +1868,6 @@@
    PREFIX("ns/purpose/", networkstatus,
           "Brief summary of router status by purpose (v2 directory format)."),
  
 -  PREFIX("unregistered-servers-", dirserv_unregistered, NULL),
    ITEM("network-status", dir,
         "Brief summary of router status (v1 directory format)"),
    ITEM("circuit-status", events, "List of current circuits originating here."),
@@@ -1879,6 -1945,14 +1879,6 @@@
    DOC("address-mappings/config",
        "Current address mappings from configuration."),
    DOC("address-mappings/control", "Current address mappings from controller."),
 -  PREFIX("addr-mappings/", events, NULL),
 -  DOC("addr-mappings/all", "Current address mappings without expiry times."),
 -  DOC("addr-mappings/cache",
 -      "Current cached DNS replies without expiry times."),
 -  DOC("addr-mappings/config",
 -      "Current address mappings from configuration without expiry times."),
 -  DOC("addr-mappings/control",
 -      "Current address mappings from controller without expiry times."),
    PREFIX("status/", events, NULL),
    DOC("status/circuit-established",
        "Whether we think client functionality is working."),
@@@ -1914,18 -1988,18 +1914,18 @@@ static char 
  list_getinfo_options(void)
  {
    int i;
 -  char buf[300];
 +  char *buf=NULL;
    smartlist_t *lines = smartlist_create();
    char *ans;
    for (i = 0; getinfo_items[i].varname; ++i) {
      if (!getinfo_items[i].desc)
        continue;
  
 -    tor_snprintf(buf, sizeof(buf), "%s%s -- %s\n",
 +    tor_asprintf(&buf, "%s%s -- %s\n",
                   getinfo_items[i].varname,
                   getinfo_items[i].is_prefix ? "*" : "",
                   getinfo_items[i].desc);
 -    smartlist_add(lines, tor_strdup(buf));
 +    smartlist_add(lines, buf);
    }
    smartlist_sort_strings(lines);
  
@@@ -1942,8 -2016,7 +1942,8 @@@
   * internal error. */
  static int
  handle_getinfo_helper(control_connection_t *control_conn,
 -                      const char *question, char **answer)
 +                      const char *question, char **answer,
 +                      const char **err_out)
  {
    int i;
    *answer = NULL; /* unrecognized key by default */
@@@ -1956,7 -2029,7 +1956,7 @@@
        match = !strcmp(question, getinfo_items[i].varname);
      if (match) {
        tor_assert(getinfo_items[i].fn);
 -      return getinfo_items[i].fn(control_conn, question, answer);
 +      return getinfo_items[i].fn(control_conn, question, answer, err_out);
      }
    }
  
@@@ -1978,12 -2051,10 +1978,12 @@@ handle_control_getinfo(control_connecti
  
    smartlist_split_string(questions, body, " ",
                           SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
 -  SMARTLIST_FOREACH(questions, const char *, q,
 -  {
 -    if (handle_getinfo_helper(conn, q, &ans) < 0) {
 -      connection_write_str_to_buf("551 Internal error\r\n", conn);
 +  SMARTLIST_FOREACH_BEGIN(questions, const char *, q) {
 +    const char *errmsg = NULL;
 +    if (handle_getinfo_helper(conn, q, &ans, &errmsg) < 0) {
 +      if (!errmsg)
 +        errmsg = "Internal error";
 +      connection_printf_to_buf(conn, "551 %s\r\n", errmsg);
        goto done;
      }
      if (!ans) {
@@@ -1992,7 -2063,7 +1992,7 @@@
        smartlist_add(answers, tor_strdup(q));
        smartlist_add(answers, ans);
      }
 -  });
 +  } SMARTLIST_FOREACH_END(q);
    if (smartlist_len(unrecognized)) {
      for (i=0; i < smartlist_len(unrecognized)-1; ++i)
        connection_printf_to_buf(conn,
@@@ -2037,12 -2108,12 +2037,12 @@@
  static uint8_t
  circuit_purpose_from_string(const char *string)
  {
 -  if (!strcmpstart(string, "purpose="))
 +  if (!strcasecmpstart(string, "purpose="))
      string += strlen("purpose=");
  
 -  if (!strcmp(string, "general"))
 +  if (!strcasecmp(string, "general"))
      return CIRCUIT_PURPOSE_C_GENERAL;
 -  else if (!strcmp(string, "controller"))
 +  else if (!strcasecmp(string, "controller"))
      return CIRCUIT_PURPOSE_CONTROLLER;
    else
      return CIRCUIT_PURPOSE_UNKNOWN;
@@@ -2074,31 -2145,6 +2074,31 @@@ getargs_helper(const char *command, con
    return NULL;
  }
  
 +/** Helper.  Return the first element of <b>sl</b> at index <b>start_at</b> or
 + * higher that starts with <b>prefix</b>, case-insensitive.  Return NULL if no
 + * such element exists. */
 +static const char *
 +find_element_starting_with(smartlist_t *sl, int start_at, const char *prefix)
 +{
 +  int i;
 +  for (i = start_at; i < smartlist_len(sl); ++i) {
 +    const char *elt = smartlist_get(sl, i);
 +    if (!strcasecmpstart(elt, prefix))
 +      return elt;
 +  }
 +  return NULL;
 +}
 +
 +/** Helper.  Return true iff s is an argument that we should treat as a
 + * key-value pair. */
 +static int
 +is_keyval_pair(const char *s)
 +{
 +  /* An argument is a key-value pair if it has an =, and it isn't of the form
 +   * $fingeprint=name */
 +  return strchr(s, '=') && s[0] != '$';
 +}
 +
  /** Called when we get an EXTENDCIRCUIT message.  Try to extend the listed
   * circuit, and report success or failure. */
  static int
@@@ -2114,57 -2160,33 +2114,57 @@@ handle_control_extendcircuit(control_co
  
    router_nicknames = smartlist_create();
  
 -  args = getargs_helper("EXTENDCIRCUIT", conn, body, 2, -1);
 +  args = getargs_helper("EXTENDCIRCUIT", conn, body, 1, -1);
    if (!args)
      goto done;
  
    zero_circ = !strcmp("0", (char*)smartlist_get(args,0));
 -  if (!zero_circ && !(circ = get_circ(smartlist_get(args,0)))) {
 -    connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n",
 -                             (char*)smartlist_get(args, 0));
 -  }
 -  smartlist_split_string(router_nicknames, smartlist_get(args,1), ",", 0, 0);
  
 -  if (zero_circ && smartlist_len(args)>2) {
 -    char *purp = smartlist_get(args,2);
 -    intended_purpose = circuit_purpose_from_string(purp);
 -    if (intended_purpose == CIRCUIT_PURPOSE_UNKNOWN) {
 -      connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp);
 +  if (zero_circ) {
 +    const char *purp = find_element_starting_with(args, 1, "PURPOSE=");
 +
 +    if (purp) {
 +      intended_purpose = circuit_purpose_from_string(purp);
 +      if (intended_purpose == CIRCUIT_PURPOSE_UNKNOWN) {
 +        connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp);
 +        SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
 +        smartlist_free(args);
 +        goto done;
 +      }
 +    }
 +
 +    if ((smartlist_len(args) == 1) ||
 +        (smartlist_len(args) >= 2 && is_keyval_pair(smartlist_get(args, 1)))) {
 +      // "EXTENDCIRCUIT 0" || EXTENDCIRCUIT 0 foo=bar"
 +      circ = circuit_launch_by_router(intended_purpose, NULL,
 +                                      CIRCLAUNCH_NEED_CAPACITY);
 +      if (!circ) {
 +        connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn);
 +      } else {
 +        connection_printf_to_buf(conn, "250 EXTENDED %lu\r\n",
 +                  (unsigned long)circ->global_identifier);
 +      }
        SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
        smartlist_free(args);
        goto done;
      }
 +    // "EXTENDCIRCUIT 0 router1,router2" ||
 +    // "EXTENDCIRCUIT 0 router1,router2 PURPOSE=foo"
    }
 -  SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
 -  smartlist_free(args);
 -  if (!zero_circ && !circ) {
 +
 +  if (!zero_circ && !(circ = get_circ(smartlist_get(args,0)))) {
 +    connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n",
 +                             (char*)smartlist_get(args, 0));
 +    SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
 +    smartlist_free(args);
      goto done;
    }
  
 +  smartlist_split_string(router_nicknames, smartlist_get(args,1), ",", 0, 0);
 +
 +  SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
 +  smartlist_free(args);
 +
    routers = smartlist_create();
    SMARTLIST_FOREACH(router_nicknames, const char *, n,
    {
@@@ -2222,7 -2244,8 +2222,7 @@@
   done:
    SMARTLIST_FOREACH(router_nicknames, char *, n, tor_free(n));
    smartlist_free(router_nicknames);
 -  if (routers)
 -    smartlist_free(routers);
 +  smartlist_free(routers);
    return 0;
  }
  
@@@ -2248,7 -2271,7 +2248,7 @@@ handle_control_setcircuitpurpose(contro
    }
  
    {
 -    char *purp = smartlist_get(args,1);
 +    const char *purp = find_element_starting_with(args,1,"PURPOSE=");
      new_purpose = circuit_purpose_from_string(purp);
      if (new_purpose == CIRCUIT_PURPOSE_UNKNOWN) {
        connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp);
@@@ -2259,7 -2282,7 +2259,7 @@@
    circ->_base.purpose = new_purpose;
    connection_write_str_to_buf("250 OK\r\n", conn);
  
 -done:
 + done:
    if (args) {
      SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
      smartlist_free(args);
@@@ -2293,9 -2316,9 +2293,9 @@@ handle_control_attachstream(control_con
    } else if (!zero_circ && !(circ = get_circ(smartlist_get(args, 1)))) {
      connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n",
                               (char*)smartlist_get(args, 1));
 -  } else if (circ && smartlist_len(args) > 2) {
 -    char *hopstring = smartlist_get(args, 2);
 -    if (!strcasecmpstart(hopstring, "HOP=")) {
 +  } else if (circ) {
 +    const char *hopstring = find_element_starting_with(args,2,"HOP=");
 +    if (hopstring) {
        hopstring += strlen("HOP=");
        hop = (int) tor_parse_ulong(hopstring, 10, 0, INT_MAX,
                                    &hop_line_ok, NULL);
@@@ -2342,7 -2365,7 +2342,7 @@@
      char* exit_digest;
      if (circ->build_state &&
          circ->build_state->chosen_exit &&
 -        circ->build_state->chosen_exit->identity_digest) {
 +        !tor_digest_is_zero(circ->build_state->chosen_exit->identity_digest)) {
        exit_digest = circ->build_state->chosen_exit->identity_digest;
        r = router_get_by_digest(exit_digest);
      }
@@@ -2403,9 -2426,9 +2403,9 @@@ handle_control_postdescriptor(control_c
        }
      } else if (!strcasecmpstart(option, "cache=")) {
        option += strlen("cache=");
 -      if (!strcmp(option, "no"))
 +      if (!strcasecmp(option, "no"))
          cache = 0;
 -      else if (!strcmp(option, "yes"))
 +      else if (!strcasecmp(option, "yes"))
          cache = 1;
        else {
          connection_printf_to_buf(conn, "552 Unknown cache request \"%s\"\r\n",
@@@ -2587,17 -2610,17 +2587,17 @@@ handle_control_resolve(control_connecti
    args = smartlist_create();
    smartlist_split_string(args, body, " ",
                           SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
 -  if (smartlist_len(args) &&
 -      !strcasecmp(smartlist_get(args, 0), "mode=reverse")) {
 -    char *cp = smartlist_get(args, 0);
 -    smartlist_del_keeporder(args, 0);
 -    tor_free(cp);
 -    is_reverse = 1;
 +  {
 +    const char *modearg = find_element_starting_with(args, 0, "mode=");
 +    if (modearg && !strcasecmp(modearg, "mode=reverse"))
 +      is_reverse = 1;
    }
    failed = smartlist_create();
    SMARTLIST_FOREACH(args, const char *, arg, {
 -      if (dnsserv_launch_request(arg, is_reverse)<0)
 -        smartlist_add(failed, (char*)arg);
 +      if (!is_keyval_pair(arg)) {
 +          if (dnsserv_launch_request(arg, is_reverse)<0)
 +            smartlist_add(failed, (char*)arg);
 +      }
    });
  
    send_control_done(conn);
@@@ -2687,6 -2710,7 +2687,6 @@@ handle_control_usefeature(control_conne
                            const char *body)
  {
    smartlist_t *args;
 -  int verbose_names = 0, extended_events = 0;
    int bad = 0;
    (void) len; /* body is nul-terminated; it's safe to ignore the length */
    args = smartlist_create();
@@@ -2694,9 -2718,9 +2694,9 @@@
                           SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
    SMARTLIST_FOREACH(args, const char *, arg, {
        if (!strcasecmp(arg, "VERBOSE_NAMES"))
 -        verbose_names = 1;
 +        ;
        else if (!strcasecmp(arg, "EXTENDED_EVENTS"))
 -        extended_events = 1;
 +        ;
        else {
          connection_printf_to_buf(conn, "552 Unrecognized feature \"%s\"\r\n",
                                   arg);
@@@ -2706,6 -2730,12 +2706,6 @@@
      });
  
    if (!bad) {
 -    if (verbose_names) {
 -      conn->use_long_names = 1;
 -      control_update_global_event_mask();
 -    }
 -    if (extended_events)
 -      conn->use_extended_events = 1;
      send_control_done(conn);
    }
  
@@@ -2855,10 -2885,9 +2855,10 @@@ connection_control_process_inbuf(contro
           && !TOR_ISSPACE(conn->incoming_cmd[cmd_len]))
      ++cmd_len;
  
 -  data_len -= cmd_len;
    conn->incoming_cmd[cmd_len]='\0';
    args = conn->incoming_cmd+cmd_len+1;
 +  tor_assert(data_len>(size_t)cmd_len);
 +  data_len -= (cmd_len+1); /* skip the command and NUL we added after it */
    while (*args == ' ' || *args == '\t') {
      ++args;
      --data_len;
@@@ -3010,11 -3039,20 +3010,11 @@@ control_event_circuit_status(origin_cir
      tor_free(reason);
    }
  
 -  if (EVENT_IS_INTERESTING1S(EVENT_CIRCUIT_STATUS)) {
 -    char *path = circuit_list_path(circ,0);
 -    const char *sp = strlen(path) ? " " : "";
 -    send_control_event_extended(EVENT_CIRCUIT_STATUS, SHORT_NAMES,
 -                                "650 CIRC %lu %s%s%s@%s\r\n",
 -                                (unsigned long)circ->global_identifier,
 -                                status, sp, path, extended_buf);
 -    tor_free(path);
 -  }
 -  if (EVENT_IS_INTERESTING1L(EVENT_CIRCUIT_STATUS)) {
 +  {
      char *vpath = circuit_list_path_for_controller(circ);
      const char *sp = strlen(vpath) ? " " : "";
 -    send_control_event_extended(EVENT_CIRCUIT_STATUS, LONG_NAMES,
 -                                "650 CIRC %lu %s%s%s@%s\r\n",
 +    send_control_event(EVENT_CIRCUIT_STATUS, ALL_FORMATS,
 +                                "650 CIRC %lu %s%s%s %s\r\n",
                                  (unsigned long)circ->global_identifier,
                                  status, sp, vpath, extended_buf);
      tor_free(vpath);
@@@ -3093,26 -3131,26 +3093,26 @@@ control_event_stream_status(edge_connec
      char *r = NULL;
      if (!reason_str) {
        r = tor_malloc(16);
 -      tor_snprintf(r, 16, "UNKNOWN_%d", reason_code);
 +      tor_snprintf(r, 16, " UNKNOWN_%d", reason_code);
        reason_str = r;
      }
      if (reason_code & END_STREAM_REASON_FLAG_REMOTE)
        tor_snprintf(reason_buf, sizeof(reason_buf),
 -                   "REASON=END REMOTE_REASON=%s", reason_str);
 +                   " REASON=END REMOTE_REASON=%s", reason_str);
      else
        tor_snprintf(reason_buf, sizeof(reason_buf),
 -                   "REASON=%s", reason_str);
 +                   " REASON=%s", reason_str);
      tor_free(r);
    } else if (reason_code && tp == STREAM_EVENT_REMAP) {
      switch (reason_code) {
      case REMAP_STREAM_SOURCE_CACHE:
 -      strlcpy(reason_buf, "SOURCE=CACHE", sizeof(reason_buf));
 +      strlcpy(reason_buf, " SOURCE=CACHE", sizeof(reason_buf));
        break;
      case REMAP_STREAM_SOURCE_EXIT:
 -      strlcpy(reason_buf, "SOURCE=EXIT", sizeof(reason_buf));
 +      strlcpy(reason_buf, " SOURCE=EXIT", sizeof(reason_buf));
        break;
      default:
 -      tor_snprintf(reason_buf, sizeof(reason_buf), "REASON=UNKNOWN_%d",
 +      tor_snprintf(reason_buf, sizeof(reason_buf), " REASON=UNKNOWN_%d",
                     reason_code);
        /* XXX do we want SOURCE=UNKNOWN_%d above instead? -RD */
        break;
@@@ -3120,7 -3158,8 +3120,7 @@@
    }
  
    if (tp == STREAM_EVENT_NEW) {
 -    tor_snprintf(addrport_buf,sizeof(addrport_buf), "%sSOURCE_ADDR=%s:%d",
 -                 strlen(reason_buf) ? " " : "",
 +    tor_snprintf(addrport_buf,sizeof(addrport_buf), " SOURCE_ADDR=%s:%d",
                   TO_CONN(conn)->address, TO_CONN(conn)->port );
    } else {
      addrport_buf[0] = '\0';
@@@ -3149,8 -3188,8 +3149,8 @@@
    circ = circuit_get_by_edge_conn(conn);
    if (circ && CIRCUIT_IS_ORIGIN(circ))
      origin_circ = TO_ORIGIN_CIRCUIT(circ);
 -  send_control_event_extended(EVENT_STREAM_STATUS, ALL_NAMES,
 -                        "650 STREAM "U64_FORMAT" %s %lu %s@%s%s%s\r\n",
 +  send_control_event(EVENT_STREAM_STATUS, ALL_FORMATS,
 +                        "650 STREAM "U64_FORMAT" %s %lu %s%s%s%s\r\n",
                          U64_PRINTF_ARG(conn->_base.global_identifier), status,
                          origin_circ?
                             (unsigned long)origin_circ->global_identifier : 0ul,
@@@ -3163,21 -3202,30 +3163,21 @@@
  
  /** Figure out the best name for the target router of an OR connection
   * <b>conn</b>, and write it into the <b>len</b>-character buffer
 - * <b>name</b>.  Use verbose names if <b>long_names</b> is set. */
 + * <b>name</b>. */
  static void
 -orconn_target_get_name(int long_names,
 -                       char *name, size_t len, or_connection_t *conn)
 -{
 -  if (! long_names) {
 -    if (conn->nickname)
 -      strlcpy(name, conn->nickname, len);
 -    else
 -      tor_snprintf(name, len, "%s:%d",
 -                   conn->_base.address, conn->_base.port);
 +orconn_target_get_name(char *name, size_t len, or_connection_t *conn)
 +{
 +  routerinfo_t *ri = router_get_by_digest(conn->identity_digest);
 +  if (ri) {
 +    tor_assert(len > MAX_VERBOSE_NICKNAME_LEN);
 +    router_get_verbose_nickname(name, ri);
 +  } else if (! tor_digest_is_zero(conn->identity_digest)) {
 +    name[0] = '$';
 +    base16_encode(name+1, len-1, conn->identity_digest,
 +                  DIGEST_LEN);
    } else {
 -    routerinfo_t *ri = router_get_by_digest(conn->identity_digest);
 -    if (ri) {
 -      tor_assert(len > MAX_VERBOSE_NICKNAME_LEN);
 -      router_get_verbose_nickname(name, ri);
 -    } else if (! tor_digest_is_zero(conn->identity_digest)) {
 -      name[0] = '$';
 -      base16_encode(name+1, len-1, conn->identity_digest,
 -                    DIGEST_LEN);
 -    } else {
 -     tor_snprintf(name, len, "%s:%d",
 -                   conn->_base.address, conn->_base.port);
 -    }
 +    tor_snprintf(name, len, "%s:%d",
 +                 conn->_base.address, conn->_base.port);
    }
  }
  
@@@ -3216,13 -3264,24 +3216,13 @@@ control_event_or_conn_status(or_connect
                   reason ? " " : "", ncircs);
    }
  
 -  if (EVENT_IS_INTERESTING1S(EVENT_OR_CONN_STATUS)) {
 -    orconn_target_get_name(0, name, sizeof(name), conn);
 -    send_control_event_extended(EVENT_OR_CONN_STATUS, SHORT_NAMES,
 -                          "650 ORCONN %s %s@%s%s%s\r\n",
 -                          name, status,
 -                          reason ? "REASON=" : "",
 -                          orconn_end_reason_to_control_string(reason),
 -                          ncircs_buf);
 -  }
 -  if (EVENT_IS_INTERESTING1L(EVENT_OR_CONN_STATUS)) {
 -    orconn_target_get_name(1, name, sizeof(name), conn);
 -    send_control_event_extended(EVENT_OR_CONN_STATUS, LONG_NAMES,
 -                          "650 ORCONN %s %s@%s%s%s\r\n",
 -                          name, status,
 -                          reason ? "REASON=" : "",
 -                          orconn_end_reason_to_control_string(reason),
 -                          ncircs_buf);
 -  }
 +  orconn_target_get_name(name, sizeof(name), conn);
 +  send_control_event(EVENT_OR_CONN_STATUS, ALL_FORMATS,
 +                              "650 ORCONN %s %s %s%s%s\r\n",
 +                              name, status,
 +                              reason ? "REASON=" : "",
 +                              orconn_end_reason_to_control_string(reason),
 +                              ncircs_buf);
  
    return 0;
  }
@@@ -3237,7 -3296,7 +3237,7 @@@ control_event_stream_bandwidth(edge_con
      if (!edge_conn->n_read && !edge_conn->n_written)
        return 0;
  
 -    send_control_event(EVENT_STREAM_BANDWIDTH_USED, ALL_NAMES,
 +    send_control_event(EVENT_STREAM_BANDWIDTH_USED, ALL_FORMATS,
                         "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n",
                         U64_PRINTF_ARG(edge_conn->_base.global_identifier),
                         (unsigned long)edge_conn->n_read,
@@@ -3266,7 -3325,7 +3266,7 @@@ control_event_stream_bandwidth_used(voi
          if (!edge_conn->n_read && !edge_conn->n_written)
            continue;
  
 -        send_control_event(EVENT_STREAM_BANDWIDTH_USED, ALL_NAMES,
 +        send_control_event(EVENT_STREAM_BANDWIDTH_USED, ALL_FORMATS,
                             "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n",
                             U64_PRINTF_ARG(edge_conn->_base.global_identifier),
                             (unsigned long)edge_conn->n_read,
@@@ -3286,7 -3345,7 +3286,7 @@@ in
  control_event_bandwidth_used(uint32_t n_read, uint32_t n_written)
  {
    if (EVENT_IS_INTERESTING(EVENT_BANDWIDTH_USED)) {
 -    send_control_event(EVENT_BANDWIDTH_USED, ALL_NAMES,
 +    send_control_event(EVENT_BANDWIDTH_USED, ALL_FORMATS,
                         "650 BW %lu %lu\r\n",
                         (unsigned long)n_read,
                         (unsigned long)n_written);
@@@ -3356,7 -3415,7 +3356,7 @@@ control_event_logmsg(int severity, uint
        default: s = "UnknownLogSeverity"; break;
      }
      ++disable_log_messages;
 -    send_control_event(event, ALL_NAMES, "650 %s %s\r\n", s, b?b:msg);
 +    send_control_event(event, ALL_FORMATS, "650 %s %s\r\n", s, b?b:msg);
      --disable_log_messages;
      tor_free(b);
    }
@@@ -3369,12 -3428,31 +3369,12 @@@
  int
  control_event_descriptors_changed(smartlist_t *routers)
  {
 -  size_t len;
    char *msg;
  
    if (!EVENT_IS_INTERESTING(EVENT_NEW_DESC))
      return 0;
 -  if (EVENT_IS_INTERESTING1S(EVENT_NEW_DESC)) {
 -    identities = smartlist_create();
 -    SMARTLIST_FOREACH(routers, routerinfo_t *, r,
 -    {
 -      base16_encode(buf,sizeof(buf),r->cache_info.identity_digest,DIGEST_LEN);
 -      smartlist_add(identities, tor_strdup(buf));
 -    });
 -  }
 -  if (EVENT_IS_INTERESTING1S(EVENT_NEW_DESC)) {
 -    char *ids = smartlist_join_strings(identities, " ", 0, &len);
 -    size_t ids_len = strlen(ids)+32;
 -    msg = tor_malloc(ids_len);
 -    tor_snprintf(msg, ids_len, "650 NEWDESC %s\r\n", ids);
 -    send_control_event_string(EVENT_NEW_DESC, SHORT_NAMES|ALL_FORMATS, msg);
 -    tor_free(ids);
 -    tor_free(msg);
 -  }
 -  if (EVENT_IS_INTERESTING1L(EVENT_NEW_DESC)) {
 +
 +  {
      smartlist_t *names = smartlist_create();
      char *ids;
      size_t names_len;
@@@ -3387,12 -3465,16 +3387,12 @@@
      names_len = strlen(ids)+32;
      msg = tor_malloc(names_len);
      tor_snprintf(msg, names_len, "650 NEWDESC %s\r\n", ids);
 -    send_control_event_string(EVENT_NEW_DESC, LONG_NAMES|ALL_FORMATS, msg);
 +    send_control_event_string(EVENT_NEW_DESC, ALL_FORMATS, msg);
      tor_free(ids);
      tor_free(msg);
      SMARTLIST_FOREACH(names, char *, cp, tor_free(cp));
      smartlist_free(names);
    }
 -  if (identities) {
 -    SMARTLIST_FOREACH(identities, char *, cp, tor_free(cp));
 -    smartlist_free(identities);
 -  }
    return 0;
  }
  
@@@ -3409,17 -3491,17 +3409,17 @@@ control_event_address_mapped(const cha
      return 0;
  
    if (expires < 3 || expires == TIME_MAX)
 -    send_control_event_extended(EVENT_ADDRMAP, ALL_NAMES,
 -                                "650 ADDRMAP %s %s NEVER@%s\r\n", from, to,
 +    send_control_event(EVENT_ADDRMAP, ALL_FORMATS,
 +                                "650 ADDRMAP %s %s NEVER %s\r\n", from, to,
                                  error?error:"");
    else {
      char buf[ISO_TIME_LEN+1];
      char buf2[ISO_TIME_LEN+1];
      format_local_iso_time(buf,expires);
      format_iso_time(buf2,expires);
 -    send_control_event_extended(EVENT_ADDRMAP, ALL_NAMES,
 +    send_control_event(EVENT_ADDRMAP, ALL_FORMATS,
                                  "650 ADDRMAP %s %s \"%s\""
 -                                "@%s%sEXPIRES=\"%s\"\r\n",
 +                                " %s%sEXPIRES=\"%s\"\r\n",
                                  from, to, buf,
                                  error?error:"", error?" ":"",
                                  buf2);
@@@ -3459,9 -3541,9 +3459,9 @@@ control_event_or_authdir_new_descriptor
    buf = tor_malloc(totallen);
    strlcpy(buf, firstline, totallen);
    strlcpy(buf+strlen(firstline), esc, totallen);
 -  send_control_event_string(EVENT_AUTHDIR_NEWDESCS, ALL_NAMES|ALL_FORMATS,
 +  send_control_event_string(EVENT_AUTHDIR_NEWDESCS, ALL_FORMATS,
                              buf);
 -  send_control_event_string(EVENT_AUTHDIR_NEWDESCS, ALL_NAMES|ALL_FORMATS,
 +  send_control_event_string(EVENT_AUTHDIR_NEWDESCS, ALL_FORMATS,
                              "650 OK\r\n");
    tor_free(esc);
    tor_free(buf);
@@@ -3499,8 -3581,8 +3499,8 @@@ control_event_networkstatus_changed_hel
    SMARTLIST_FOREACH(strs, char *, cp, tor_free(cp));
    smartlist_free(strs);
    tor_free(s);
 -  send_control_event_string(event, ALL_NAMES|ALL_FORMATS, esc);
 -  send_control_event_string(event, ALL_NAMES|ALL_FORMATS,
 +  send_control_event_string(event, ALL_FORMATS, esc);
 +  send_control_event_string(event, ALL_FORMATS,
                              "650 OK\r\n");
  
    tor_free(esc);
@@@ -3526,55 -3608,6 +3526,55 @@@ control_event_newconsensus(const networ
             consensus->routerstatus_list, EVENT_NEWCONSENSUS, "NEWCONSENSUS");
  }
  
 +/** Called when we compute a new circuitbuildtimeout */
 +int
 +control_event_buildtimeout_set(const circuit_build_times_t *cbt,
 +                        buildtimeout_set_event_t type)
 +{
 +  const char *type_string = NULL;
 +  double qnt = circuit_build_times_quantile_cutoff();
 +
 +  if (!control_event_is_interesting(EVENT_BUILDTIMEOUT_SET))
 +    return 0;
 +
 +  switch (type) {
 +    case BUILDTIMEOUT_SET_EVENT_COMPUTED:
 +      type_string = "COMPUTED";
 +      break;
 +    case BUILDTIMEOUT_SET_EVENT_RESET:
 +      type_string = "RESET";
 +      qnt = 1.0;
 +      break;
 +    case BUILDTIMEOUT_SET_EVENT_SUSPENDED:
 +      type_string = "SUSPENDED";
 +      qnt = 1.0;
 +      break;
 +    case BUILDTIMEOUT_SET_EVENT_DISCARD:
 +      type_string = "DISCARD";
 +      qnt = 1.0;
 +      break;
 +    case BUILDTIMEOUT_SET_EVENT_RESUME:
 +      type_string = "RESUME";
 +      break;
 +    default:
 +      type_string = "UNKNOWN";
 +      break;
 +  }
 +
 +  send_control_event(EVENT_BUILDTIMEOUT_SET, ALL_FORMATS,
 +                     "650 BUILDTIMEOUT_SET %s TOTAL_TIMES=%lu "
 +                     "TIMEOUT_MS=%lu XM=%lu ALPHA=%lf CUTOFF_QUANTILE=%lf "
 +                     "TIMEOUT_RATE=%lf CLOSE_MS=%lu CLOSE_RATE=%lf\r\n",
 +                     type_string, (unsigned long)cbt->total_build_times,
 +                     (unsigned long)cbt->timeout_ms,
 +                     (unsigned long)cbt->Xm, cbt->alpha, qnt,
 +                     circuit_build_times_timeout_rate(cbt),
 +                     (unsigned long)cbt->close_ms,
 +                     circuit_build_times_close_rate(cbt));
 +
 +  return 0;
 +}
 +
  /** Called when a single local_routerstatus_t has changed: Sends an NS event
   * to any controller that cares. */
  int
@@@ -3598,7 -3631,7 +3598,7 @@@ control_event_networkstatus_changed_sin
  int
  control_event_my_descriptor_changed(void)
  {
 -  send_control_event(EVENT_DESCCHANGED, ALL_NAMES, "650 DESCCHANGED\r\n");
 +  send_control_event(EVENT_DESCCHANGED, ALL_FORMATS, "650 DESCCHANGED\r\n");
    return 0;
  }
  
@@@ -3646,7 -3679,7 +3646,7 @@@ control_event_status(int type, int seve
      return -1;
    }
  
 -  send_control_event_impl(type, ALL_NAMES|ALL_FORMATS, 0, format_buf, args);
 +  send_control_event_impl(type, ALL_FORMATS, format_buf, args);
    return 0;
  }
  
@@@ -3710,7 -3743,7 +3710,7 @@@ control_event_guard(const char *nicknam
    if (!EVENT_IS_INTERESTING(EVENT_GUARD))
      return 0;
  
 -  if (EVENT_IS_INTERESTING1L(EVENT_GUARD)) {
 +  {
      char buf[MAX_VERBOSE_NICKNAME_LEN+1];
      routerinfo_t *ri = router_get_by_digest(digest);
      if (ri) {
@@@ -3718,9 -3751,13 +3718,9 @@@
      } else {
        tor_snprintf(buf, sizeof(buf), "$%s~%s", hbuf, nickname);
      }
 -    send_control_event(EVENT_GUARD, LONG_NAMES,
 +    send_control_event(EVENT_GUARD, ALL_FORMATS,
                         "650 GUARD ENTRY %s %s\r\n", buf, status);
    }
 -  if (EVENT_IS_INTERESTING1S(EVENT_GUARD)) {
 -    send_control_event(EVENT_GUARD, SHORT_NAMES,
 -                       "650 GUARD ENTRY $%s %s\r\n", hbuf, status);
 -  }
    return 0;
  }
  
@@@ -3971,9 -4008,10 +3971,9 @@@ control_event_bootstrap_problem(const c
   * from recently. Send a copy to the controller in case it wants to
   * display it for the user. */
  void
 -control_event_clients_seen(const char *timestarted, const char *countries)
 +control_event_clients_seen(const char *controller_str)
  {
    send_control_event(EVENT_CLIENTS_SEEN, 0,
 -    "650 CLIENTS_SEEN TimeStarted=\"%s\" CountrySummary=%s\r\n",
 -    timestarted, countries);
 +    "650 CLIENTS_SEEN %s\r\n", controller_str);
  }
  
diff --combined src/or/cpuworker.c
index ae8d69f,91af5f1..29dfc90
--- a/src/or/cpuworker.c
+++ b/src/or/cpuworker.c
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -13,15 -13,6 +13,15 @@@
   **/
  
  #include "or.h"
 +#include "buffers.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "cpuworker.h"
 +#include "main.h"
 +#include "onion.h"
 +#include "router.h"
  
  /** The maximum number of cpuworker processes we will keep around. */
  #define MAX_CPUWORKERS 16
@@@ -192,7 -183,7 +192,7 @@@ connection_cpu_process_inbuf(connection
      tor_assert(0); /* don't ask me to do handshakes yet */
    }
  
 -done_processing:
 + done_processing:
    conn->state = CPUWORKER_STATE_IDLE;
    num_cpuworkers_busy--;
    if (conn->timestamp_created < last_rotation_time) {
@@@ -367,7 -358,7 +367,7 @@@ spawn_cpuworker(void
  static void
  spawn_enough_cpuworkers(void)
  {
 -  int num_cpuworkers_needed = get_options()->NumCpus;
 +  int num_cpuworkers_needed = get_options()->NumCPUs;
  
    if (num_cpuworkers_needed < MIN_CPUWORKERS)
      num_cpuworkers_needed = MIN_CPUWORKERS;
diff --combined src/or/directory.c
index fbdd496,09ebccd..7360a47
--- a/src/or/directory.c
+++ b/src/or/directory.c
@@@ -1,29 -1,9 +1,29 @@@
  /* Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  #include "or.h"
 +#include "buffers.h"
 +#include "circuitbuild.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "control.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "geoip.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "policies.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
 +
  #if defined(EXPORTMALLINFO) && defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO)
  #ifndef OPENBSD
  #include <malloc.h>
@@@ -67,10 -47,8 +67,10 @@@ static void http_set_address_origin(con
  static void connection_dir_download_networkstatus_failed(
                                 dir_connection_t *conn, int status_code);
  static void connection_dir_download_routerdesc_failed(dir_connection_t *conn);
 +static void connection_dir_bridge_routerdesc_failed(dir_connection_t *conn);
  static void connection_dir_download_cert_failed(
                                 dir_connection_t *conn, int status_code);
 +static void connection_dir_retry_bridges(smartlist_t *descs);
  static void dir_networkstatus_download_failed(smartlist_t *failed,
                                                int status_code);
  static void dir_routerdesc_download_failed(smartlist_t *failed,
@@@ -114,19 -92,17 +114,19 @@@ static void directory_initiate_command_
  #define ROUTERDESC_CACHE_LIFETIME (30*60)
  #define ROUTERDESC_BY_DIGEST_CACHE_LIFETIME (48*60*60)
  #define ROBOTS_CACHE_LIFETIME (24*60*60)
 +#define MICRODESC_CACHE_LIFETIME (48*60*60)
  
  /********* END VARIABLES ************/
  
 -/** Return true iff the directory purpose 'purpose' must use an
 - * anonymous connection to a directory. */
 +/** Return true iff the directory purpose <b>dir_purpose</b> (and if it's
 + * fetching descriptors, it's fetching them for <b>router_purpose</b>)
 + * must use an anonymous connection to a directory. */
  static int
  purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose)
  {
    if (get_options()->AllDirActionsPrivate)
      return 1;
 -  if (router_purpose == ROUTER_PURPOSE_BRIDGE && has_completed_circuit)
 +  if (router_purpose == ROUTER_PURPOSE_BRIDGE && can_complete_circuit)
      return 1; /* if no circuits yet, we may need this info to bootstrap. */
    if (dir_purpose == DIR_PURPOSE_UPLOAD_DIR ||
        dir_purpose == DIR_PURPOSE_UPLOAD_VOTE ||
@@@ -254,7 -230,7 +254,7 @@@ directories_have_accepted_server_descri
  
  /** Start a connection to every suitable directory authority, using
   * connection purpose 'purpose' and uploading the payload 'payload'
 - * (length 'payload_len').  The purpose should be one of
 + * (length 'payload_len').  dir_purpose should be one of
   * 'DIR_PURPOSE_UPLOAD_DIR' or 'DIR_PURPOSE_UPLOAD_RENDDESC'.
   *
   * <b>type</b> specifies what sort of dir authorities (V1, V2,
@@@ -584,7 -560,7 +584,7 @@@ connection_dir_request_failed(dir_conne
    if (directory_conn_is_self_reachability_test(conn)) {
      return; /* this was a test fetch. don't retry. */
    }
 -  if (entry_list_can_grow(get_options()))
 +  if (!entry_list_is_constrained(get_options()))
      router_set_status(conn->identity_digest, 0); /* don't try him again */
    if (conn->_base.purpose == DIR_PURPOSE_FETCH_V2_NETWORKSTATUS) {
      log_info(LD_DIR, "Giving up on directory server at '%s'; retrying",
@@@ -594,8 -570,6 +594,8 @@@
               conn->_base.purpose == DIR_PURPOSE_FETCH_EXTRAINFO) {
      log_info(LD_DIR, "Giving up on directory server at '%s'; retrying",
               conn->_base.address);
 +    if (conn->router_purpose == ROUTER_PURPOSE_BRIDGE)
 +      connection_dir_bridge_routerdesc_failed(conn);
      connection_dir_download_routerdesc_failed(conn);
    } else if (conn->_base.purpose == DIR_PURPOSE_FETCH_CONSENSUS) {
      networkstatus_consensus_download_failed(0);
@@@ -640,7 -614,7 +640,7 @@@ connection_dir_download_networkstatus_f
       * failed, and possibly retry them later.*/
      smartlist_t *failed = smartlist_create();
      dir_split_resource_into_fingerprints(conn->requested_resource+3,
 -                                         failed, NULL, 0, 0);
 +                                         failed, NULL, 0);
      if (smartlist_len(failed)) {
        dir_networkstatus_download_failed(failed, status_code);
        SMARTLIST_FOREACH(failed, char *, cp, tor_free(cp));
@@@ -649,24 -623,6 +649,24 @@@
    }
  }
  
 +/** Helper: Attempt to fetch directly the descriptors of each bridge
 + * listed in <b>failed</b>.
 + */
 +static void
 +connection_dir_retry_bridges(smartlist_t *descs)
 +{
 +  char digest[DIGEST_LEN];
 +  SMARTLIST_FOREACH(descs, const char *, cp,
 +  {
 +    if (base16_decode(digest, DIGEST_LEN, cp, strlen(cp))<0) {
 +      log_warn(LD_BUG, "Malformed fingerprint in list: %s",
 +              escaped(cp));
 +      continue;
 +    }
 +    retry_bridge_descriptor_fetch_directly(digest);
 +  });
 +}
 +
  /** Called when an attempt to download one or more router descriptors
   * or extra-info documents on connection <b>conn</b> failed.
   */
@@@ -684,33 -640,6 +684,33 @@@ connection_dir_download_routerdesc_fail
    (void) conn;
  }
  
 +/** Called when an attempt to download a bridge's routerdesc from
 + * one of the authorities failed due to a network error. If
 + * possible attempt to download descriptors from the bridge directly.
 + */
 +static void
 +connection_dir_bridge_routerdesc_failed(dir_connection_t *conn)
 +{
 +  smartlist_t *which = NULL;
 +
 +  /* Requests for bridge descriptors are in the form 'fp/', so ignore
 +     anything else. */
 +  if (!conn->requested_resource || strcmpstart(conn->requested_resource,"fp/"))
 +    return;
 +
 +  which = smartlist_create();
 +  dir_split_resource_into_fingerprints(conn->requested_resource
 +                                        + strlen("fp/"),
 +                                       which, NULL, 0);
 +
 +  tor_assert(conn->_base.purpose != DIR_PURPOSE_FETCH_EXTRAINFO);
 +  if (smartlist_len(which)) {
 +    connection_dir_retry_bridges(which);
 +    SMARTLIST_FOREACH(which, char *, cp, tor_free(cp));
 +  }
 +  smartlist_free(which);
 +}
 +
  /** Called when an attempt to fetch a certificate fails. */
  static void
  connection_dir_download_cert_failed(dir_connection_t *conn, int status)
@@@ -722,7 -651,7 +722,7 @@@
      return;
    failed = smartlist_create();
    dir_split_resource_into_fingerprints(conn->requested_resource+3,
 -                                       failed, NULL, 1, 0);
 +                                       failed, NULL, DSR_HEX);
    SMARTLIST_FOREACH(failed, char *, cp,
    {
      authority_cert_dl_failed(cp, status);
@@@ -738,7 -667,7 +738,7 @@@
   * 1) If or_port is 0, or it's a direct conn and or_port is firewalled
   *    or we're a dir mirror, no.
   * 2) If we prefer to avoid begindir conns, and we're not fetching or
 - * publishing a bridge relay descriptor, no.
 + *    publishing a bridge relay descriptor, no.
   * 3) Else yes.
   */
  static int
@@@ -817,15 -746,6 +817,15 @@@ directory_initiate_command_rend(const c
  
    log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose));
  
 +  /* ensure that we don't make direct connections when a SOCKS server is
 +   * configured. */
 +  if (!anonymized_connection && !use_begindir && !options->HTTPProxy &&
 +      (options->Socks4Proxy || options->Socks5Proxy)) {
 +    log_warn(LD_DIR, "Cannot connect to a directory server through a "
 +                     "SOCKS proxy!");
 +    return;
 +  }
 +
    conn = dir_connection_new(AF_INET);
  
    /* set up conn so it's got all the data we need to remember */
@@@ -850,9 -770,9 +850,9 @@@
    if (!anonymized_connection && !use_begindir) {
      /* then we want to connect to dirport directly */
  
 -    if (options->HttpProxy) {
 -      tor_addr_from_ipv4h(&addr, options->HttpProxyAddr);
 -      dir_port = options->HttpProxyPort;
 +    if (options->HTTPProxy) {
 +      tor_addr_copy(&addr, &options->HTTPProxyAddr);
 +      dir_port = options->HTTPProxyPort;
      }
  
      switch (connection_connect(TO_CONN(conn), conn->_base.address, &addr,
@@@ -873,7 -793,7 +873,7 @@@
                                 payload, payload_len,
                                 supports_conditional_consensus,
                                 if_modified_since);
 -        connection_watch_events(TO_CONN(conn), EV_READ | EV_WRITE);
 +        connection_watch_events(TO_CONN(conn), READ_EVENT | WRITE_EVENT);
          /* writable indicates finish, readable indicates broken link,
             error indicates broken link in windowsland. */
      }
@@@ -912,7 -832,7 +912,7 @@@
                             payload, payload_len,
                             supports_conditional_consensus,
                             if_modified_since);
 -    connection_watch_events(TO_CONN(conn), EV_READ | EV_WRITE);
 +    connection_watch_events(TO_CONN(conn), READ_EVENT|WRITE_EVENT);
      connection_start_reading(TO_CONN(linked_conn));
    }
  }
@@@ -960,7 -880,7 +960,7 @@@ directory_get_consensus_url(int support
  
    if (supports_conditional_consensus) {
      char *authority_id_list;
 -    smartlist_t *authority_digets = smartlist_create();
 +    smartlist_t *authority_digests = smartlist_create();
  
      SMARTLIST_FOREACH(router_get_trusted_dir_servers(),
                        trusted_dir_server_t *, ds,
@@@ -972,10 -892,10 +972,10 @@@
          hex = tor_malloc(2*CONDITIONAL_CONSENSUS_FPR_LEN+1);
          base16_encode(hex, 2*CONDITIONAL_CONSENSUS_FPR_LEN+1,
                        ds->v3_identity_digest, CONDITIONAL_CONSENSUS_FPR_LEN);
 -        smartlist_add(authority_digets, hex);
 +        smartlist_add(authority_digests, hex);
        });
 -    smartlist_sort(authority_digets, _compare_strs);
 -    authority_id_list = smartlist_join_strings(authority_digets,
 +    smartlist_sort(authority_digests, _compare_strs);
 +    authority_id_list = smartlist_join_strings(authority_digests,
                                                 "+", 0, NULL);
  
      len = strlen(authority_id_list)+64;
@@@ -983,8 -903,8 +983,8 @@@
      tor_snprintf(url, len, "/tor/status-vote/current/consensus/%s.z",
                   authority_id_list);
  
 -    SMARTLIST_FOREACH(authority_digets, char *, cp, tor_free(cp));
 -    smartlist_free(authority_digets);
 +    SMARTLIST_FOREACH(authority_digests, char *, cp, tor_free(cp));
 +    smartlist_free(authority_digests);
      tor_free(authority_id_list);
    } else {
      url = tor_strdup("/tor/status-vote/current/consensus.z");
@@@ -993,7 -913,7 +993,7 @@@
  }
  
  /** Queue an appropriate HTTP command on conn-\>outbuf.  The other args
 - * are as in directory_initiate_command.
 + * are as in directory_initiate_command().
   */
  static void
  directory_send_command(dir_connection_t *conn,
@@@ -1036,9 -956,9 +1036,9 @@@
    }
  
    /* come up with some proxy lines, if we're using one. */
 -  if (direct && get_options()->HttpProxy) {
 +  if (direct && get_options()->HTTPProxy) {
      char *base64_authenticator=NULL;
 -    const char *authenticator = get_options()->HttpProxyAuthenticator;
 +    const char *authenticator = get_options()->HTTPProxyAuthenticator;
  
      tor_snprintf(proxystring, sizeof(proxystring),"http://%s", hoststring);
      if (authenticator) {
@@@ -1129,10 -1049,31 +1129,10 @@@
        httpcommand = "POST";
        url = tor_strdup("/tor/post/consensus-signature");
        break;
 -    case DIR_PURPOSE_FETCH_RENDDESC:
 -      tor_assert(resource);
 -      tor_assert(!payload);
 -
 -      /* this must be true or we wouldn't be doing the lookup */
 -      tor_assert(strlen(resource) <= REND_SERVICE_ID_LEN_BASE32);
 -      /* This breaks the function abstraction. */
 -      conn->rend_data = tor_malloc_zero(sizeof(rend_data_t));
 -      strlcpy(conn->rend_data->onion_address, resource,
 -              sizeof(conn->rend_data->onion_address));
 -      conn->rend_data->rend_desc_version = 0;
 -
 -      httpcommand = "GET";
 -      /* Request the most recent versioned descriptor. */
 -      // (XXXX We were going to switch this to fetch rendezvous1 descriptors,
 -      // but that never got testing, and it wasn't a good design.)
 -      len = strlen(resource)+32;
 -      url = tor_malloc(len);
 -      tor_snprintf(url, len, "/tor/rendezvous/%s", resource);
 -      break;
      case DIR_PURPOSE_FETCH_RENDDESC_V2:
        tor_assert(resource);
        tor_assert(strlen(resource) <= REND_DESC_ID_V2_LEN_BASE32);
        tor_assert(!payload);
 -      conn->rend_data->rend_desc_version = 2;
        httpcommand = "GET";
        len = strlen(resource) + 32;
        url = tor_malloc(len);
@@@ -1221,7 -1162,7 +1221,7 @@@ parse_http_url(const char *headers, cha
      if (s-tmp >= 3 && !strcmpstart(tmp,"://")) {
        tmp = strchr(tmp+3, '/');
        if (tmp && tmp < s) {
 -        log_debug(LD_DIR,"Skipping over 'http[s]://hostname' string");
 +        log_debug(LD_DIR,"Skipping over 'http[s]://hostname/' string");
          start = tmp;
        }
      }
@@@ -1537,22 -1478,21 +1537,22 @@@ connection_dir_client_reached_eof(dir_c
    }
    (void) skewed; /* skewed isn't used yet. */
  
 -  if (status_code == 503 && body_len < 16) {
 -    routerstatus_t *rs;
 -    trusted_dir_server_t *ds;
 -    log_info(LD_DIR,"Received http status code %d (%s) from server "
 -             "'%s:%d'. I'll try again soon.",
 -             status_code, escaped(reason), conn->_base.address,
 -             conn->_base.port);
 -    if ((rs = router_get_consensus_status_by_id(conn->identity_digest)))
 -      rs->last_dir_503_at = now;
 -    if ((ds = router_get_trusteddirserver_by_digest(conn->identity_digest)))
 -      ds->fake_status.last_dir_503_at = now;
 +  if (status_code == 503) {
 +    if (body_len < 16) {
 +      routerstatus_t *rs;
 +      trusted_dir_server_t *ds;
 +      log_info(LD_DIR,"Received http status code %d (%s) from server "
 +               "'%s:%d'. I'll try again soon.",
 +               status_code, escaped(reason), conn->_base.address,
 +               conn->_base.port);
 +      if ((rs = router_get_consensus_status_by_id(conn->identity_digest)))
 +        rs->last_dir_503_at = now;
 +      if ((ds = router_get_trusteddirserver_by_digest(conn->identity_digest)))
 +        ds->fake_status.last_dir_503_at = now;
  
 -    tor_free(body); tor_free(headers); tor_free(reason);
 -    return -1;
 -  } else if (status_code == 503) {
 +      tor_free(body); tor_free(headers); tor_free(reason);
 +      return -1;
 +    }
      /* XXXX022 Remove this once every server with bug 539 is obsolete. */
      log_info(LD_DIR, "Server at '%s:%d' sent us a 503 response, but included "
               "a body anyway.  We'll pretend it gave us a 200.",
@@@ -1624,7 -1564,7 +1624,7 @@@
      v2_networkstatus_source_t source;
      char *cp;
      log_info(LD_DIR,"Received networkstatus objects (size %d) from server "
 -             "'%s:%d'",(int) body_len, conn->_base.address, conn->_base.port);
 +             "'%s:%d'", (int)body_len, conn->_base.address, conn->_base.port);
      if (status_code != 200) {
        log_warn(LD_DIR,
             "Received http status code %d (%s) from server "
@@@ -1640,7 -1580,7 +1640,7 @@@
        source = NS_FROM_DIR_BY_FP;
        which = smartlist_create();
        dir_split_resource_into_fingerprints(conn->requested_resource+3,
 -                                           which, NULL, 0, 0);
 +                                           which, NULL, 0);
      } else if (conn->requested_resource &&
                 !strcmpstart(conn->requested_resource, "all")) {
        source = NS_FROM_DIR_ALL;
@@@ -1698,8 -1638,8 +1698,8 @@@
        return -1;
      }
      log_info(LD_DIR,"Received consensus directory (size %d) from server "
 -             "'%s:%d'",(int) body_len, conn->_base.address, conn->_base.port);
 -    if ((r=networkstatus_set_current_consensus(body, 0))<0) {
 +             "'%s:%d'", (int)body_len, conn->_base.address, conn->_base.port);
 +    if ((r=networkstatus_set_current_consensus(body, "ns", 0))<0) {
        log_fn(r<-1?LOG_WARN:LOG_INFO, LD_DIR,
               "Unable to load consensus directory downloaded from "
               "server '%s:%d'. I'll try again soon.",
@@@ -1726,11 -1666,9 +1726,11 @@@
        return -1;
      }
      log_info(LD_DIR,"Received authority certificates (size %d) from server "
 -             "'%s:%d'",(int) body_len, conn->_base.address, conn->_base.port);
 +             "'%s:%d'", (int)body_len, conn->_base.address, conn->_base.port);
      if (trusted_dirs_load_certs_from_string(body, 0, 1)<0) {
        log_warn(LD_DIR, "Unable to parse fetched certificates");
 +      /* if we fetched more than one and only some failed, the successful
 +       * ones got flushed to disk so it's safe to call this on them */
        connection_dir_download_cert_failed(conn, status_code);
      } else {
        directory_info_has_arrived(now, 0);
@@@ -1741,7 -1679,7 +1741,7 @@@
      const char *msg;
      int st;
      log_info(LD_DIR,"Got votes (size %d) from server %s:%d",
 -             (int) body_len, conn->_base.address, conn->_base.port);
 +             (int)body_len, conn->_base.address, conn->_base.port);
      if (status_code != 200) {
        log_warn(LD_DIR,
               "Received http status code %d (%s) from server "
@@@ -1761,11 -1699,11 +1761,11 @@@
    if (conn->_base.purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES) {
      const char *msg = NULL;
      log_info(LD_DIR,"Got detached signatures (size %d) from server %s:%d",
 -             (int) body_len, conn->_base.address, conn->_base.port);
 +             (int)body_len, conn->_base.address, conn->_base.port);
      if (status_code != 200) {
        log_warn(LD_DIR,
 -        "Received http status code %d (%s) from server "
 -        "'%s:%d' while fetching \"/tor/status-vote/consensus-signatures.z\".",
 +        "Received http status code %d (%s) from server '%s:%d' while fetching "
 +        "\"/tor/status-vote/next/consensus-signatures.z\".",
               status_code, escaped(reason), conn->_base.address,
               conn->_base.port);
        tor_free(body); tor_free(headers); tor_free(reason);
@@@ -1793,7 -1731,7 +1793,7 @@@
        which = smartlist_create();
        dir_split_resource_into_fingerprints(conn->requested_resource +
                                               (descriptor_digests ? 2 : 3),
 -                                           which, NULL, 0, 0);
 +                                           which, NULL, 0);
        n_asked_for = smartlist_len(which);
      }
      if (status_code != 200) {
@@@ -1981,7 -1919,7 +1981,7 @@@
            /* Success, or at least there's a v2 descriptor already
             * present. Notify pending connections about this. */
            conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC;
 -          rend_client_desc_trynow(conn->rend_data->onion_address, -1);
 +          rend_client_desc_trynow(conn->rend_data->onion_address);
          }
          break;
        case 404:
@@@ -2028,7 -1966,7 +2028,7 @@@
              log_info(LD_REND, "Successfully fetched v2 rendezvous "
                       "descriptor.");
              conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC;
 -            rend_client_desc_trynow(conn->rend_data->onion_address, -1);
 +            rend_client_desc_trynow(conn->rend_data->onion_address);
              break;
          }
          break;
@@@ -2071,6 -2009,12 +2071,6 @@@
                   "'%s:%d'. Malformed rendezvous descriptor?",
                   escaped(reason), conn->_base.address, conn->_base.port);
          break;
 -      case 503:
 -        log_info(LD_REND,"http status 503 (%s) response from dirserver "
 -                 "'%s:%d'. Node is (currently) not acting as v2 hidden "
 -                 "service directory.",
 -                 escaped(reason), conn->_base.address, conn->_base.port);
 -        break;
        default:
          log_warn(LD_REND,"http status %d (%s) response unexpected (server "
                   "'%s:%d').",
@@@ -2377,7 -2321,7 +2377,7 @@@ directory_dump_request_log(void
  }
  #endif
  
 -/** Decide whether a client would accept the consensus we have
 +/** Decide whether a client would accept the consensus we have.
   *
   * Clients can say they only want a consensus if it's signed by more
   * than half the authorities in a list.  They pass this list in
@@@ -2398,32 -2342,31 +2398,32 @@@ client_likes_consensus(networkstatus_t 
    int need_at_least;
    int have = 0;
  
 -  dir_split_resource_into_fingerprints(want_url, want_authorities, NULL, 0, 0);
 +  dir_split_resource_into_fingerprints(want_url, want_authorities, NULL, 0);
    need_at_least = smartlist_len(want_authorities)/2+1;
 -  SMARTLIST_FOREACH(want_authorities, const char *, d, {
 +  SMARTLIST_FOREACH_BEGIN(want_authorities, const char *, d) {
      char want_digest[DIGEST_LEN];
      size_t want_len = strlen(d)/2;
      if (want_len > DIGEST_LEN)
        want_len = DIGEST_LEN;
  
      if (base16_decode(want_digest, DIGEST_LEN, d, want_len*2) < 0) {
 -      log_warn(LD_DIR,"Failed to decode requested authority digest %s.", d);
 +      log_fn(LOG_PROTOCOL_WARN, LD_DIR,
 +             "Failed to decode requested authority digest %s.", d);
        continue;
      };
  
 -    SMARTLIST_FOREACH(v->voters, networkstatus_voter_info_t *, vi, {
 -      if (vi->signature &&
 +    SMARTLIST_FOREACH_BEGIN(v->voters, networkstatus_voter_info_t *, vi) {
 +      if (smartlist_len(vi->sigs) &&
            !memcmp(vi->identity_digest, want_digest, want_len)) {
          have++;
          break;
        };
 -    });
 +    } SMARTLIST_FOREACH_END(vi);
  
      /* early exit, if we already have enough */
      if (have >= need_at_least)
        break;
 -  });
 +  } SMARTLIST_FOREACH_END(d);
  
    SMARTLIST_FOREACH(want_authorities, char *, d, tor_free(d));
    smartlist_free(want_authorities);
@@@ -2570,12 -2513,9 +2570,12 @@@ directory_handle_command_get(dir_connec
      /* v2 or v3 network status fetch. */
      smartlist_t *dir_fps = smartlist_create();
      int is_v3 = !strcmpstart(url, "/tor/status-vote");
 +    geoip_client_action_t act =
 +        is_v3 ? GEOIP_CLIENT_NETWORKSTATUS : GEOIP_CLIENT_NETWORKSTATUS_V2;
      const char *request_type = NULL;
      const char *key = url + strlen("/tor/status/");
      long lifetime = NETWORKSTATUS_CACHE_LIFETIME;
 +
      if (!is_v3) {
        dirserv_get_networkstatus_v2_fingerprints(dir_fps, key);
        if (!strcmpstart(key, "fp/"))
@@@ -2590,44 -2530,18 +2590,44 @@@
      } else {
        networkstatus_t *v = networkstatus_get_latest_consensus();
        time_t now = time(NULL);
 +      const char *want_fps = NULL;
 +      char *flavor = NULL;
        #define CONSENSUS_URL_PREFIX "/tor/status-vote/current/consensus/"
 -      if (v &&
 -          !strcmpstart(url, CONSENSUS_URL_PREFIX) &&
 -          !client_likes_consensus(v, url + strlen(CONSENSUS_URL_PREFIX))) {
 +      #define CONSENSUS_FLAVORED_PREFIX "/tor/status-vote/current/consensus-"
 +      /* figure out the flavor if any, and who we wanted to sign the thing */
 +      if (!strcmpstart(url, CONSENSUS_FLAVORED_PREFIX)) {
 +        const char *f, *cp;
 +        f = url + strlen(CONSENSUS_FLAVORED_PREFIX);
 +        cp = strchr(f, '/');
 +        if (cp) {
 +          want_fps = cp+1;
 +          flavor = tor_strndup(f, cp-f);
 +        } else {
 +          flavor = tor_strdup(f);
 +        }
 +      } else {
 +        if (!strcmpstart(url, CONSENSUS_URL_PREFIX))
 +          want_fps = url+strlen(CONSENSUS_URL_PREFIX);
 +      }
 +
 +      /* XXXX MICRODESC NM NM should check document of correct flavor */
 +      if (v && want_fps &&
 +          !client_likes_consensus(v, want_fps)) {
          write_http_status_line(conn, 404, "Consensus not signed by sufficient "
                                            "number of requested authorities");
          smartlist_free(dir_fps);
 +        geoip_note_ns_response(act, GEOIP_REJECT_NOT_ENOUGH_SIGS);
 +        tor_free(flavor);
          goto done;
        }
  
 -      smartlist_add(dir_fps, tor_memdup("\0\0\0\0\0\0\0\0\0\0"
 -                                        "\0\0\0\0\0\0\0\0\0\0", 20));
 +      {
 +        char *fp = tor_malloc_zero(DIGEST_LEN);
 +        if (flavor)
 +          strlcpy(fp, flavor, DIGEST_LEN);
 +        tor_free(flavor);
 +        smartlist_add(dir_fps, fp);
 +      }
        request_type = compressed?"v3.z":"v3";
        lifetime = (v && v->fresh_until > now) ? v->fresh_until - now : 0;
      }
@@@ -2635,7 -2549,6 +2635,7 @@@
      if (!smartlist_len(dir_fps)) { /* we failed to create/cache cp */
        write_http_status_line(conn, 503, "Network status object unavailable");
        smartlist_free(dir_fps);
 +      geoip_note_ns_response(act, GEOIP_REJECT_UNAVAILABLE);
        goto done;
      }
  
@@@ -2643,13 -2556,11 +2643,13 @@@
        write_http_status_line(conn, 404, "Not found");
        SMARTLIST_FOREACH(dir_fps, char *, cp, tor_free(cp));
        smartlist_free(dir_fps);
 +      geoip_note_ns_response(act, GEOIP_REJECT_NOT_FOUND);
        goto done;
      } else if (!smartlist_len(dir_fps)) {
        write_http_status_line(conn, 304, "Not modified");
        SMARTLIST_FOREACH(dir_fps, char *, cp, tor_free(cp));
        smartlist_free(dir_fps);
 +      geoip_note_ns_response(act, GEOIP_REJECT_NOT_MODIFIED);
        goto done;
      }
  
@@@ -2661,25 -2572,18 +2661,25 @@@
        write_http_status_line(conn, 503, "Directory busy, try again later");
        SMARTLIST_FOREACH(dir_fps, char *, fp, tor_free(fp));
        smartlist_free(dir_fps);
 +      geoip_note_ns_response(act, GEOIP_REJECT_BUSY);
        goto done;
      }
  
 -#ifdef ENABLE_GEOIP_STATS
      {
 -      geoip_client_action_t act =
 -        is_v3 ? GEOIP_CLIENT_NETWORKSTATUS : GEOIP_CLIENT_NETWORKSTATUS_V2;
        struct in_addr in;
 -      if (tor_inet_aton((TO_CONN(conn))->address, &in))
 +      if (tor_inet_aton((TO_CONN(conn))->address, &in)) {
          geoip_note_client_seen(act, ntohl(in.s_addr), time(NULL));
 +        geoip_note_ns_response(act, GEOIP_SUCCESS);
 +        /* Note that a request for a network status has started, so that we
 +         * can measure the download time later on. */
 +        if (TO_CONN(conn)->dirreq_id)
 +          geoip_start_dirreq(TO_CONN(conn)->dirreq_id, dlen, act,
 +                             DIRREQ_TUNNELED);
 +        else
 +          geoip_start_dirreq(TO_CONN(conn)->global_identifier, dlen, act,
 +                             DIRREQ_DIRECT);
 +      }
      }
 -#endif
  
      // note_request(request_type,dlen);
      (void) request_type;
@@@ -2715,7 -2619,7 +2715,7 @@@
        const char *item;
        tor_assert(!current); /* we handle current consensus specially above,
                               * since it wants to be spooled. */
 -      if ((item = dirvote_get_pending_consensus()))
 +      if ((item = dirvote_get_pending_consensus(FLAV_NS)))
          smartlist_add(items, (char*)item);
      } else if (!current && !strcmp(url, "consensus-signatures")) {
        /* XXXX the spec says that we should implement
@@@ -2741,8 -2645,7 +2741,8 @@@
          flags = DGV_BY_ID |
            (current ? DGV_INCLUDE_PREVIOUS : DGV_INCLUDE_PENDING);
        }
 -      dir_split_resource_into_fingerprints(url, fps, NULL, 1, 1);
 +      dir_split_resource_into_fingerprints(url, fps, NULL,
 +                                           DSR_HEX|DSR_SORT_UNIQ);
        SMARTLIST_FOREACH(fps, char *, fp, {
            if ((d = dirvote_get_vote(fp, flags)))
              smartlist_add(dir_items, (cached_dir_t*)d);
@@@ -2795,41 -2698,6 +2795,41 @@@
      goto done;
    }
  
 +  if (!strcmpstart(url, "/tor/micro/d/")) {
 +    smartlist_t *fps = smartlist_create();
 +
 +    dir_split_resource_into_fingerprints(url+strlen("/tor/micro/d/"),
 +                                      fps, NULL,
 +                                      DSR_DIGEST256|DSR_BASE64|DSR_SORT_UNIQ);
 +
 +    if (!dirserv_have_any_microdesc(fps)) {
 +      write_http_status_line(conn, 404, "Not found");
 +      SMARTLIST_FOREACH(fps, char *, fp, tor_free(fp));
 +      smartlist_free(fps);
 +      goto done;
 +    }
 +    dlen = dirserv_estimate_microdesc_size(fps, compressed);
 +    if (global_write_bucket_low(TO_CONN(conn), dlen, 2)) {
 +      log_info(LD_DIRSERV,
 +               "Client asked for server descriptors, but we've been "
 +               "writing too many bytes lately. Sending 503 Dir busy.");
 +      write_http_status_line(conn, 503, "Directory busy, try again later");
 +      SMARTLIST_FOREACH(fps, char *, fp, tor_free(fp));
 +      smartlist_free(fps);
 +      goto done;
 +    }
 +
 +    write_http_response_header(conn, -1, compressed, MICRODESC_CACHE_LIFETIME);
 +    conn->dir_spool_src = DIR_SPOOL_MICRODESC;
 +    conn->fingerprint_stack = fps;
 +
 +    if (compressed)
 +      conn->zlib_state = tor_zlib_new(1, ZLIB_METHOD);
 +
 +    connection_dirserv_flushed_some(conn);
 +    goto done;
 +  }
 +
    if (!strcmpstart(url,"/tor/server/") ||
        (!options->BridgeAuthoritativeDir &&
         !options->BridgeRelay && !strcmpstart(url,"/tor/extra/"))) {
@@@ -2911,8 -2779,7 +2911,8 @@@
      } else if (!strcmpstart(url, "/tor/keys/fp/")) {
        smartlist_t *fps = smartlist_create();
        dir_split_resource_into_fingerprints(url+strlen("/tor/keys/fp/"),
 -                                           fps, NULL, 1, 1);
 +                                           fps, NULL,
 +                                           DSR_HEX|DSR_SORT_UNIQ);
        SMARTLIST_FOREACH(fps, char *, d, {
            authority_cert_t *c = authority_cert_get_newest_by_id(d);
            if (c) smartlist_add(certs, c);
@@@ -2922,8 -2789,7 +2922,8 @@@
      } else if (!strcmpstart(url, "/tor/keys/sk/")) {
        smartlist_t *fps = smartlist_create();
        dir_split_resource_into_fingerprints(url+strlen("/tor/keys/sk/"),
 -                                           fps, NULL, 1, 1);
 +                                           fps, NULL,
 +                                           DSR_HEX|DSR_SORT_UNIQ);
        SMARTLIST_FOREACH(fps, char *, d, {
            authority_cert_t *c = authority_cert_get_by_sk_digest(d);
            if (c) smartlist_add(certs, c);
@@@ -3025,9 -2891,18 +3025,9 @@@
          note_request("/tor/rendezvous?/", desc_len);
          /* need to send descp separately, because it may include NULs */
          connection_write_to_buf(descp, desc_len, TO_CONN(conn));
          break;
        case 0: /* well-formed but not present */
          write_http_status_line(conn, 404, "Not found");
 -        /* report (unsuccessful) fetch to statistic */
 -        if (options->HSAuthorityRecordStats) {
 -          hs_usage_note_fetch_total(query, time(NULL));
 -        }
          break;
        case -1: /* not well-formed */
          write_http_status_line(conn, 400, "Bad request");
@@@ -3304,8 -3179,8 +3304,8 @@@ directory_handle_command(dir_connection
                                &body, &body_len, MAX_DIR_UL_SIZE, 0)) {
      case -1: /* overflow */
        log_warn(LD_DIRSERV,
 -               "Invalid input from address '%s'. Closing.",
 -               conn->_base.address);
 +               "Request too large from address '%s' to DirPort. Closing.",
 +               safe_str(conn->_base.address));
        return -1;
      case 0:
        log_debug(LD_DIRSERV,"command not all here yet.");
@@@ -3341,16 -3216,6 +3341,16 @@@ connection_dir_finished_flushing(dir_co
    tor_assert(conn);
    tor_assert(conn->_base.type == CONN_TYPE_DIR);
  
 +  /* Note that we have finished writing the directory response. For direct
 +   * connections this means we're done, for tunneled connections its only
 +   * an intermediate step. */
 +  if (TO_CONN(conn)->dirreq_id)
 +    geoip_change_dirreq_state(TO_CONN(conn)->dirreq_id, DIRREQ_TUNNELED,
 +                              DIRREQ_FLUSHING_DIR_CONN_FINISHED);
 +  else
 +    geoip_change_dirreq_state(TO_CONN(conn)->global_identifier,
 +                              DIRREQ_DIRECT,
 +                              DIRREQ_FLUSHING_DIR_CONN_FINISHED);
    switch (conn->_base.state) {
      case DIR_CONN_STATE_CLIENT_SENDING:
        log_debug(LD_DIR,"client finished sending command.");
@@@ -3533,14 -3398,6 +3533,14 @@@ download_status_reset(download_status_
    dls->next_attempt_at = time(NULL) + schedule[0];
  }
  
 +/** Return the number of failures on <b>dls</b> since the last success (if
 + * any). */
 +int
 +download_status_get_n_failures(const download_status_t *dls)
 +{
 +  return dls->n_download_failures;
 +}
 +
  /** Called when one or more routerdesc (or extrainfo, if <b>was_extrainfo</b>)
   * fetches have failed (with uppercase fingerprints listed in <b>failed</b>,
   * either as descriptor digests or as identity digests based on
@@@ -3556,8 -3413,16 +3556,8 @@@ dir_routerdesc_download_failed(smartlis
    int server = directory_fetches_from_authorities(get_options());
    if (!was_descriptor_digests) {
      if (router_purpose == ROUTER_PURPOSE_BRIDGE) {
 -      tor_assert(!was_extrainfo); /* not supported yet */
 -      SMARTLIST_FOREACH(failed, const char *, cp,
 -      {
 -        if (base16_decode(digest, DIGEST_LEN, cp, strlen(cp))<0) {
 -          log_warn(LD_BUG, "Malformed fingerprint in list: %s",
 -                   escaped(cp));
 -          continue;
 -        }
 -        retry_bridge_descriptor_fetch_directly(digest);
 -      });
 +      tor_assert(!was_extrainfo);
 +      connection_dir_retry_bridges(failed);
      }
      return; /* FFFF should implement for other-than-router-purpose someday */
    }
@@@ -3650,37 -3515,19 +3650,37 @@@ dir_split_resource_into_fingerprint_pai
  /** Given a directory <b>resource</b> request, containing zero
   * or more strings separated by plus signs, followed optionally by ".z", store
   * the strings, in order, into <b>fp_out</b>.  If <b>compressed_out</b> is
 - * non-NULL, set it to 1 if the resource ends in ".z", else set it to 0.  If
 - * decode_hex is true, then delete all elements that aren't hex digests, and
 - * decode the rest.  If sort_uniq is true, then sort the list and remove
 - * all duplicates.
 + * non-NULL, set it to 1 if the resource ends in ".z", else set it to 0.
 + *
 + * If (flags & DSR_HEX), then delete all elements that aren't hex digests, and
 + * decode the rest.  If (flags & DSR_BASE64), then use "-" rather than "+" as
 + * a separator, delete all the elements that aren't base64-encoded digests,
 + * and decode the rest.  If (flags & DSR_DIGEST256), these digests should be
 + * 256 bits long; else they should be 160.
 + *
 + * If (flags & DSR_SORT_UNIQ), then sort the list and remove all duplicates.
   */
  int
  dir_split_resource_into_fingerprints(const char *resource,
                                       smartlist_t *fp_out, int *compressed_out,
 -                                     int decode_hex, int sort_uniq)
 +                                     int flags)
  {
 +  const int decode_hex = flags & DSR_HEX;
 +  const int decode_base64 = flags & DSR_BASE64;
 +  const int digests_are_256 = flags & DSR_DIGEST256;
 +  const int sort_uniq = flags & DSR_SORT_UNIQ;
 +
 +  const int digest_len = digests_are_256 ? DIGEST256_LEN : DIGEST_LEN;
 +  const int hex_digest_len = digests_are_256 ?
 +    HEX_DIGEST256_LEN : HEX_DIGEST_LEN;
 +  const int base64_digest_len = digests_are_256 ?
 +    BASE64_DIGEST256_LEN : BASE64_DIGEST_LEN;
    smartlist_t *fp_tmp = smartlist_create();
 +
 +  tor_assert(!(decode_hex && decode_base64));
    tor_assert(fp_out);
 -  smartlist_split_string(fp_tmp, resource, "+", 0, 0);
 +
 +  smartlist_split_string(fp_tmp, resource, decode_base64?"-":"+", 0, 0);
    if (compressed_out)
      *compressed_out = 0;
    if (smartlist_len(fp_tmp)) {
@@@ -3692,25 -3539,22 +3692,25 @@@
          *compressed_out = 1;
      }
    }
 -  if (decode_hex) {
 +  if (decode_hex || decode_base64) {
 +    const size_t encoded_len = decode_hex ? hex_digest_len : base64_digest_len;
      int i;
      char *cp, *d = NULL;
      for (i = 0; i < smartlist_len(fp_tmp); ++i) {
        cp = smartlist_get(fp_tmp, i);
 -      if (strlen(cp) != HEX_DIGEST_LEN) {
 +      if (strlen(cp) != encoded_len) {
          log_info(LD_DIR,
                   "Skipping digest %s with non-standard length.", escaped(cp));
          smartlist_del_keeporder(fp_tmp, i--);
          goto again;
        }
 -      d = tor_malloc_zero(DIGEST_LEN);
 -      if (base16_decode(d, DIGEST_LEN, cp, HEX_DIGEST_LEN)<0) {
 -        log_info(LD_DIR, "Skipping non-decodable digest %s", escaped(cp));
 -        smartlist_del_keeporder(fp_tmp, i--);
 -        goto again;
 +      d = tor_malloc_zero(digest_len);
 +      if (decode_hex ?
 +          (base16_decode(d, digest_len, cp, hex_digest_len)<0) :
 +          (base64_decode(d, digest_len, cp, base64_digest_len)<0)) {
 +          log_info(LD_DIR, "Skipping non-decodable digest %s", escaped(cp));
 +          smartlist_del_keeporder(fp_tmp, i--);
 +          goto again;
        }
        smartlist_set(fp_tmp, i, d);
        d = NULL;
@@@ -3720,18 -3564,26 +3720,18 @@@
      }
    }
    if (sort_uniq) {
 -    smartlist_t *fp_tmp2 = smartlist_create();
 -    int i;
 -    if (decode_hex)
 -      smartlist_sort_digests(fp_tmp);
 -    else
 +    if (decode_hex || decode_base64) {
 +      if (digests_are_256) {
 +        smartlist_sort_digests256(fp_tmp);
 +        smartlist_uniq_digests256(fp_tmp);
 +      } else {
 +        smartlist_sort_digests(fp_tmp);
 +        smartlist_uniq_digests(fp_tmp);
 +      }
 +    } else {
        smartlist_sort_strings(fp_tmp);
 -    if (smartlist_len(fp_tmp))
 -      smartlist_add(fp_tmp2, smartlist_get(fp_tmp, 0));
 -    for (i = 1; i < smartlist_len(fp_tmp); ++i) {
 -      char *cp = smartlist_get(fp_tmp, i);
 -      char *last = smartlist_get(fp_tmp2, smartlist_len(fp_tmp2)-1);
 -
 -      if ((decode_hex && memcmp(cp, last, DIGEST_LEN))
 -          || (!decode_hex && strcasecmp(cp, last)))
 -        smartlist_add(fp_tmp2, cp);
 -      else
 -        tor_free(cp);
 +      smartlist_uniq_strings(fp_tmp);
      }
 -    smartlist_free(fp_tmp);
 -    fp_tmp = fp_tmp2;
    }
    smartlist_add_all(fp_out, fp_tmp);
    smartlist_free(fp_tmp);
diff --combined src/or/dirserv.c
index 841c866,1649bd7..876698a
--- a/src/or/dirserv.c
+++ b/src/or/dirserv.c
@@@ -1,26 -1,10 +1,26 @@@
  /* Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  #define DIRSERV_PRIVATE
  #include "or.h"
 +#include "buffers.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "hibernate.h"
 +#include "microdesc.h"
 +#include "networkstatus.h"
 +#include "policies.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  /**
   * \file dirserv.c
@@@ -57,7 -41,7 +57,7 @@@ static time_t the_v2_networkstatus_is_d
  static cached_dir_t *the_directory = NULL;
  
  /** For authoritative directories: the current (v1) network status. */
 -static cached_dir_t the_runningrouters = { NULL, NULL, 0, 0, 0, -1 };
 +static cached_dir_t the_runningrouters;
  
  static void directory_remove_invalid(void);
  static cached_dir_t *dirserv_regenerate_directory(void);
@@@ -79,16 -63,13 +79,16 @@@ static signed_descriptor_t *get_signed_
                                                          time_t publish_cutoff);
  static int dirserv_add_extrainfo(extrainfo_t *ei, const char **msg);
  
 +/************** Measured Bandwidth parsing code ******/
 +#define MAX_MEASUREMENT_AGE (3*24*60*60) /* 3 days */
 +
  /************** Fingerprint handling code ************/
  
  #define FP_NAMED   1  /**< Listed in fingerprint file. */
  #define FP_INVALID 2  /**< Believed invalid. */
  #define FP_REJECT  4  /**< We will not publish this router. */
  #define FP_BADDIR  8  /**< We'll tell clients to avoid using this as a dir. */
 -#define FP_BADEXIT 16  /**< We'll tell clients not to use this as an exit. */
 +#define FP_BADEXIT 16 /**< We'll tell clients not to use this as an exit. */
  #define FP_UNNAMED 32 /**< Another router has this name in fingerprint file. */
  
  /** Encapsulate a nickname and an FP_* status; target of status_by_digest
@@@ -118,7 -99,7 +118,7 @@@ authdir_config_new(void
    return list;
  }
  
 -/** Add the fingerprint <b>fp</b> for the nickname <b>nickname</b> to
 +/** Add the fingerprint <b>fp</b> for <b>nickname</b> to
   * the smartlist of fingerprint_entry_t's <b>list</b>. Return 0 if it's
   * new, or 1 if we replaced the old value.
   */
@@@ -200,7 -181,8 +200,7 @@@ dirserv_add_own_fingerprint(const char 
   * file.  The file format is line-based, with each non-blank holding one
   * nickname, some space, and a fingerprint for that nickname.  On success,
   * replace the current fingerprint list with the new list and return 0.  On
 - * failure, leave the current fingerprint list untouched, and
 - * return -1. */
 + * failure, leave the current fingerprint list untouched, and return -1. */
  int
  dirserv_load_fingerprint_file(void)
  {
@@@ -386,19 -368,13 +386,19 @@@ dirserv_get_status_impl(const char *id_
                strmap_size(fingerprint_list->fp_by_name),
                digestmap_size(fingerprint_list->status_by_digest));
  
 -  /* 0.1.1.17-rc was the first version that claimed to be stable, doesn't
 -   * crash and drop circuits all the time, and is even vaguely compatible with
 -   * the current network */
 -  if (platform && !tor_version_as_new_as(platform,"0.1.1.17-rc")) {
 +  /* Tor 0.2.0.26-rc is the oldest version that currently caches the right
 +   * directory information.  Once more of them die off, we should raise this
 +   * minimum. */
 +  if (platform && !tor_version_as_new_as(platform,"0.2.0.26-rc")) {
      if (msg)
        *msg = "Tor version is far too old to work.";
      return FP_REJECT;
 +  } else if (platform && tor_version_as_new_as(platform,"0.2.1.3-alpha")
 +                      && !tor_version_as_new_as(platform, "0.2.1.19")) {
 +    /* These versions mishandled RELAY_EARLY cells on rend circuits. */
 +    if (msg)
 +      *msg = "Tor version is too buggy to work.";
 +    return FP_REJECT;
    }
  
    result = dirserv_get_name_status(id_digest, nickname);
@@@ -544,7 -520,7 +544,7 @@@ authdir_wants_to_reject_router(routerin
    /* Okay.  Now check whether the fingerprint is recognized. */
    uint32_t status = dirserv_router_get_status(ri, msg);
    time_t now;
 -  int severity = complain ? LOG_NOTICE : LOG_INFO;
 +  int severity = (complain && ri->contact_info) ? LOG_NOTICE : LOG_INFO;
    tor_assert(msg);
    if (status & FP_REJECT)
      return -1; /* msg is already set. */
@@@ -736,10 -712,6 +736,10 @@@ dirserv_add_descriptor(routerinfo_t *ri
    desc = tor_strndup(ri->cache_info.signed_descriptor_body, desclen);
    nickname = tor_strdup(ri->nickname);
  
 +  /* Tell if we're about to need to launch a test if we add this. */
 +  ri->needs_retest_if_added =
 +    dirserv_should_launch_reachability_test(ri, ri_old);
 +
    r = router_add_to_routerlist(ri, msg, 0, 0);
    if (!WRA_WAS_ADDED(r)) {
      /* unless the routerinfo was fine, just out-of-date */
@@@ -754,7 -726,7 +754,7 @@@
  
      changed = smartlist_create();
      smartlist_add(changed, ri);
 -    control_event_descriptors_changed(changed);
 +    routerlist_descriptors_added(changed, 0);
      smartlist_free(changed);
      if (!*msg) {
        *msg =  ri->is_valid ? "Descriptor for valid server accepted" :
@@@ -863,6 -835,46 +863,6 @@@ directory_remove_invalid(void
    routerlist_assert_ok(rl);
  }
  
 -/** Write a list of unregistered descriptors into a newly allocated
 - * string and return it. Used by dirserv operators to keep track of
 - * fast nodes that haven't registered.
 - */
 -int
 -getinfo_helper_dirserv_unregistered(control_connection_t *control_conn,
 -                                    const char *question, char **answer_out)
 -{
 -  smartlist_t *answerlist;
 -  char buf[1024];
 -  char *answer;
 -  int min_bw = atoi(question);
 -  routerlist_t *rl = router_get_routerlist();
 -
 -  (void) control_conn;
 -
 -  if (strcmpstart(question, "unregistered-servers-"))
 -    return 0;
 -  question += strlen("unregistered-servers-");
 -
 -  answerlist = smartlist_create();
 -  SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ent, {
 -    uint32_t r = dirserv_router_get_status(ent, NULL);
 -    if (router_get_advertised_bandwidth(ent) >= (size_t)min_bw &&
 -        !(r & FP_NAMED)) {
 -      /* then log this one */
 -      tor_snprintf(buf, sizeof(buf),
 -                   "%s: BW %d on '%s'.",
 -                   ent->nickname, router_get_advertised_bandwidth(ent),
 -                   ent->platform ? ent->platform : "");
 -      smartlist_add(answerlist, tor_strdup(buf));
 -    }
 -  });
 -  answer = smartlist_join_strings(answerlist, "\r\n", 0, NULL);
 -  SMARTLIST_FOREACH(answerlist, char *, cp, tor_free(cp));
 -  smartlist_free(answerlist);
 -  *answer_out = answer;
 -  return 0;
 -}
 -
  /** Mark the directory as <b>dirty</b> -- when we're next asked for a
   * directory, we will rebuild it instead of reusing the most recently
   * generated one.
@@@ -921,54 -933,26 +921,54 @@@ list_single_server_status(routerinfo_t 
    return tor_strdup(buf);
  }
  
 +static INLINE int
 +running_long_enough_to_decide_unreachable(void)
 +{
 +  return time_of_process_start
 +    + get_options()->TestingAuthDirTimeToLearnReachability < approx_time();
 +}
 +
  /** Each server needs to have passed a reachability test no more
   * than this number of seconds ago, or he is listed as down in
   * the directory. */
  #define REACHABLE_TIMEOUT (45*60)
  
 +/** If we tested a router and found it reachable _at least this long_ after it
 + * declared itself hibernating, it is probably done hibernating and we just
 + * missed a descriptor from it. */
 +#define HIBERNATION_PUBLICATION_SKEW (60*60)
 +
  /** Treat a router as alive if
   *    - It's me, and I'm not hibernating.
   * or - We've found it reachable recently. */
  void
  dirserv_set_router_is_running(routerinfo_t *router, time_t now)
  {
 +  /*XXXX022 This function is a mess.  Separate out the part that calculates
 +    whether it's reachable and the part that tells rephist that the router was
 +    unreachable.
 +   */
    int answer;
  
 -  if (router_is_me(router) && !we_are_hibernating())
 +  if (router_is_me(router)) {
 +    /* We always know if we are down ourselves. */
 +    answer = ! we_are_hibernating();
 +  } else if (router->is_hibernating &&
 +             (router->cache_info.published_on +
 +              HIBERNATION_PUBLICATION_SKEW) > router->last_reachable) {
 +    /* A hibernating router is down unless we (somehow) had contact with it
 +     * since it declared itself to be hibernating. */
 +    answer = 0;
 +  } else if (get_options()->AssumeReachable) {
 +    /* If AssumeReachable, everybody is up unless they say they are down! */
      answer = 1;
 -  else
 -    answer = get_options()->AssumeReachable ||
 -             now < router->last_reachable + REACHABLE_TIMEOUT;
 +  } else {
 +    /* Otherwise, a router counts as up if we found it reachable in the last
 +       REACHABLE_TIMEOUT seconds. */
 +    answer = (now < router->last_reachable + REACHABLE_TIMEOUT);
 +  }
  
 -  if (!answer) {
 +  if (!answer && running_long_enough_to_decide_unreachable()) {
      /* not considered reachable. tell rephist. */
      rep_hist_note_router_unreachable(router->cache_info.identity_digest, now);
    }
@@@ -981,6 -965,7 +981,6 @@@
   * *<b>router_status_out</b>.  Return 0 on success, -1 on failure.
   *
   * If for_controller is true, include the routers with very old descriptors.
 - * If for_controller is &gt;1, use the verbose nickname format.
   */
  int
  list_server_status_v1(smartlist_t *routers, char **router_status_out,
@@@ -1000,22 -985,23 +1000,22 @@@
  
    rs_entries = smartlist_create();
  
 -  SMARTLIST_FOREACH(routers, routerinfo_t *, ri,
 -  {
 +  SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) {
      if (authdir) {
        /* Update router status in routerinfo_t. */
        dirserv_set_router_is_running(ri, now);
      }
 -    if (for_controller == 1 || ri->cache_info.published_on >= cutoff)
 -      smartlist_add(rs_entries, list_single_server_status(ri, ri->is_running));
 -    else if (for_controller > 2) {
 +    if (for_controller) {
        char name_buf[MAX_VERBOSE_NICKNAME_LEN+2];
        char *cp = name_buf;
        if (!ri->is_running)
          *cp++ = '!';
        router_get_verbose_nickname(cp, ri);
        smartlist_add(rs_entries, tor_strdup(name_buf));
 +    } else if (ri->cache_info.published_on >= cutoff) {
 +      smartlist_add(rs_entries, list_single_server_status(ri, ri->is_running));
      }
 -  });
 +  } SMARTLIST_FOREACH_END(ri);
  
    *router_status_out = smartlist_join_strings(rs_entries, " ", 0, NULL);
  
@@@ -1133,8 -1119,7 +1133,8 @@@ dirserv_dump_directory_to_string(char *
      return -1;
    }
    note_crypto_pk_op(SIGN_DIR);
 -  if (router_append_dirobj_signature(buf,buf_len,digest,private_key)<0) {
 +  if (router_append_dirobj_signature(buf,buf_len,digest,DIGEST_LEN,
 +                                     private_key)<0) {
      tor_free(buf);
      return -1;
    }
@@@ -1159,21 -1144,18 +1159,21 @@@ directory_fetches_from_authorities(or_o
  {
    routerinfo_t *me;
    uint32_t addr;
 +  int refuseunknown;
    if (options->FetchDirInfoEarly)
      return 1;
    if (options->BridgeRelay == 1)
      return 0;
    if (server_mode(options) && router_pick_published_address(options, &addr)<0)
      return 1; /* we don't know our IP address; ask an authority. */
 -  if (options->DirPort == 0)
 +  refuseunknown = ! router_my_exit_policy_is_reject_star() &&
 +    should_refuse_unknown_exits(options);
 +  if (options->DirPort == 0 && !refuseunknown)
      return 0;
    if (!server_mode(options) || !advertised_server_mode())
      return 0;
    me = router_get_my_routerinfo();
 -  if (!me || !me->dir_port)
 +  if (!me || (!me->dir_port && !refuseunknown))
      return 0; /* if dirport not advertised, return 0 too */
    return 1;
  }
@@@ -1213,14 -1195,7 +1213,14 @@@ directory_caches_v2_dir_info(or_options
  int
  directory_caches_dir_info(or_options_t *options)
  {
 -  return options->BridgeRelay != 0 || options->DirPort != 0;
 +  if (options->BridgeRelay || options->DirPort)
 +    return 1;
 +  if (!server_mode(options) || !advertised_server_mode())
 +    return 0;
 +  /* We need an up-to-date view of network info if we're going to try to
 +   * block exit attempts from unknown relays. */
 +  return ! router_my_exit_policy_is_reject_star() &&
 +    should_refuse_unknown_exits(options);
  }
  
  /** Return 1 if we want to allow remote people to ask us directory
@@@ -1263,14 -1238,14 +1263,14 @@@ directory_too_idle_to_fetch_descriptors
  static cached_dir_t *cached_directory = NULL;
  /** The v1 runningrouters document we'll serve (as a cache or as an authority)
   * if requested. */
 -static cached_dir_t cached_runningrouters = { NULL, NULL, 0, 0, 0, -1 };
 +static cached_dir_t cached_runningrouters;
  
  /** Used for other dirservers' v2 network statuses.  Map from hexdigest to
   * cached_dir_t. */
  static digestmap_t *cached_v2_networkstatus = NULL;
  
 -/** The v3 consensus network status that we're currently serving. */
 -static cached_dir_t *cached_v3_networkstatus = NULL;
 +/** Map from flavor name to the v3 consensuses that we're currently serving. */
 +static strmap_t *cached_consensuses = NULL;
  
  /** Possibly replace the contents of <b>d</b> with the value of
   * <b>directory</b> published on <b>when</b>, unless <b>when</b> is older than
@@@ -1344,11 -1319,7 +1344,11 @@@ clear_cached_dir(cached_dir_t *d
  static void
  _free_cached_dir(void *_d)
  {
 -  cached_dir_t *d = (cached_dir_t *)_d;
 +  cached_dir_t *d;
 +  if (!_d)
 +    return;
 +
 +  d = (cached_dir_t *)_d;
    cached_dir_decref(d);
  }
  
@@@ -1442,26 -1413,17 +1442,26 @@@ dirserv_set_cached_networkstatus_v2(con
    }
  }
  
 -/** Replace the v3 consensus networkstatus that we're serving with
 - * <b>networkstatus</b>, published at <b>published</b>.  No validation is
 - * performed. */
 +/** Replace the v3 consensus networkstatus of type <b>flavor_name</b> that
 + * we're serving with <b>networkstatus</b>, published at <b>published</b>.  No
 + * validation is performed. */
  void
 -dirserv_set_cached_networkstatus_v3(const char *networkstatus,
 -                                    time_t published)
 +dirserv_set_cached_consensus_networkstatus(const char *networkstatus,
 +                                           const char *flavor_name,
 +                                           const digests_t *digests,
 +                                           time_t published)
  {
 -  if (cached_v3_networkstatus)
 -    cached_dir_decref(cached_v3_networkstatus);
 -  cached_v3_networkstatus = new_cached_dir(
 -                                  tor_strdup(networkstatus), published);
 +  cached_dir_t *new_networkstatus;
 +  cached_dir_t *old_networkstatus;
 +  if (!cached_consensuses)
 +    cached_consensuses = strmap_new();
 +
 +  new_networkstatus = new_cached_dir(tor_strdup(networkstatus), published);
 +  memcpy(&new_networkstatus->digests, digests, sizeof(digests_t));
 +  old_networkstatus = strmap_set(cached_consensuses, flavor_name,
 +                                 new_networkstatus);
 +  if (old_networkstatus)
 +    cached_dir_decref(old_networkstatus);
  }
  
  /** Remove any v2 networkstatus from the directory cache that was published
@@@ -1558,8 -1520,7 +1558,8 @@@ dirserv_regenerate_directory(void
  {
    char *new_directory=NULL;
  
 -  if (dirserv_dump_directory_to_string(&new_directory, get_identity_key())) {
 +  if (dirserv_dump_directory_to_string(&new_directory,
 +                                       get_server_identity_key())) {
      log_warn(LD_BUG, "Error creating directory.");
      tor_free(new_directory);
      return NULL;
@@@ -1589,7 -1550,7 +1589,7 @@@ generate_runningrouters(void
    char digest[DIGEST_LEN];
    char published[ISO_TIME_LEN+1];
    size_t len;
 -  crypto_pk_env_t *private_key = get_identity_key();
 +  crypto_pk_env_t *private_key = get_server_identity_key();
    char *identity_pkey; /* Identity key, DER64-encoded. */
    size_t identity_pkey_len;
  
@@@ -1616,8 -1577,7 +1616,8 @@@
      goto err;
    }
    note_crypto_pk_op(SIGN_DIR);
 -  if (router_append_dirobj_signature(s, len, digest, private_key)<0)
 +  if (router_append_dirobj_signature(s, len, digest, DIGEST_LEN,
 +                                     private_key)<0)
      goto err;
  
    set_cached_dir(&the_runningrouters, s, time(NULL));
@@@ -1645,11 -1605,9 +1645,11 @@@ dirserv_get_runningrouters(void
  /** Return the latest downloaded consensus networkstatus in encoded, signed,
   * optionally compressed format, suitable for sending to clients. */
  cached_dir_t *
 -dirserv_get_consensus(void)
 +dirserv_get_consensus(const char *flavor_name)
  {
 -  return cached_v3_networkstatus;
 +  if (!cached_consensuses)
 +    return NULL;
 +  return strmap_get(cached_consensuses, flavor_name);
  }
  
  /** For authoritative directories: the current (v2) network status. */
@@@ -1687,7 -1645,7 +1687,7 @@@ should_generate_v2_networkstatus(void
  #define TIME_KNOWN_TO_GUARANTEE_FAMILIAR (8*24*60*60)
  /** Similarly, every node with sufficient WFU is around enough to be a guard.
   */
 -#define WFU_TO_GUARANTEE_GUARD (0.995)
 +#define WFU_TO_GUARANTEE_GUARD (0.98)
  
  /* Thresholds for server performance: set by
   * dirserv_compute_performance_thresholds, and used by
@@@ -1767,7 -1725,7 +1767,7 @@@ dirserv_thinks_router_is_unreliable(tim
  
  /** Return true iff <b>router</b> should be assigned the "HSDir" flag.
   * Right now this means it advertises support for it, it has a high
 - * uptime, and it's currently considered Running.
 + * uptime, it has a DirPort open, and it's currently considered Running.
   *
   * This function needs to be called after router-\>is_running has
   * been set.
@@@ -1777,11 -1735,7 +1777,11 @@@ dirserv_thinks_router_is_hs_dir(routeri
  {
    long uptime = real_uptime(router, now);
  
 -  return (router->wants_to_be_hs_dir &&
 +  /* XXX We shouldn't need to check dir_port, but we do because of
 +   * bug 1693. In the future, once relays set wants_to_be_hs_dir
 +   * correctly, we can revert to only checking dir_port if router's
 +   * version is too old. */
 +  return (router->wants_to_be_hs_dir && router->dir_port &&
            uptime > get_options()->MinUptimeHidServDirectoryV2 &&
            router->is_running);
  }
@@@ -1836,8 -1790,7 +1836,8 @@@ dirserv_compute_performance_thresholds(
      if (router_is_active(ri, now)) {
        const char *id = ri->cache_info.identity_digest;
        uint32_t bw;
 -      ri->is_exit = exit_policy_is_general_exit(ri->exit_policy);
 +      ri->is_exit = (!router_exit_policy_rejects_all(ri) &&
 +                    exit_policy_is_general_exit(ri->exit_policy));
        uptimes[n_active] = (uint32_t)real_uptime(ri, now);
        mtbfs[n_active] = rep_hist_get_stability(id, now);
        tks  [n_active] = rep_hist_get_weighted_time_known(id, now);
@@@ -1944,20 -1897,16 +1944,20 @@@ version_from_platform(const char *platf
   * which has at least <b>buf_len</b> free characters.  Do NUL-termination.
   * Use the same format as in network-status documents.  If <b>version</b> is
   * non-NULL, add a "v" line for the platform.  Return 0 on success, -1 on
 - * failure.  If <b>first_line_only</b> is true, don't include any flags
 - * or version line.
 + * failure.
 + *
 + * The format argument has three possible values:
 + *   NS_V2 - Output an entry suitable for a V2 NS opinion document
 + *   NS_V3_CONSENSUS - Output the first portion of a V3 NS consensus entry
 + *   NS_V3_CONSENSUS_MICRODESC - Output the first portion of a V3 microdesc
 + *        consensus entry.
 + *   NS_V3_VOTE - Output a complete V3 NS vote
 + *   NS_CONTROL_PORT - Output a NS document for the control port
   */
  int
  routerstatus_format_entry(char *buf, size_t buf_len,
                            routerstatus_t *rs, const char *version,
 -                          int first_line_only, int v2_format)
 -/* XXX: first_line_only and v2_format should probably be be both
 - *      replaced by a single purpose parameter.
 - */
 +                          routerstatus_format_type_t format)
  {
    int r;
    struct in_addr in;
@@@ -1976,11 -1925,10 +1976,11 @@@
    tor_inet_ntoa(&in, ipaddr, sizeof(ipaddr));
  
    r = tor_snprintf(buf, buf_len,
 -                   "r %s %s %s %s %s %d %d\n",
 +                   "r %s %s %s%s%s %s %d %d\n",
                     rs->nickname,
                     identity64,
 -                   digest64,
 +                   (format==NS_V3_CONSENSUS_MICRODESC)?"":digest64,
 +                   (format==NS_V3_CONSENSUS_MICRODESC)?"":" ",
                     published,
                     ipaddr,
                     (int)rs->or_port,
@@@ -1989,12 -1937,7 +1989,12 @@@
      log_warn(LD_BUG, "Not enough space in buffer.");
      return -1;
    }
 -  if (first_line_only)
 +
 +  /* TODO: Maybe we want to pass in what we need to build the rest of
 +   * this here, instead of in the caller. Then we could use the
 +   * networkstatus_type_t values, with an additional control port value
 +   * added -MP */
 +  if (format == NS_V3_CONSENSUS || format == NS_V3_CONSENSUS_MICRODESC)
      return 0;
  
    cp = buf + strlen(buf);
@@@ -2031,87 -1974,62 +2031,87 @@@
      cp += strlen(cp);
    }
  
 -  if (!v2_format) {
 +  if (format != NS_V2) {
      routerinfo_t* desc = router_get_by_digest(rs->identity_digest);
 +    uint32_t bw;
 +
 +    if (format != NS_CONTROL_PORT) {
 +      /* Blow up more or less nicely if we didn't get anything or not the
 +       * thing we expected.
 +       */
 +      if (!desc) {
 +        char id[HEX_DIGEST_LEN+1];
 +        char dd[HEX_DIGEST_LEN+1];
 +
 +        base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
 +        base16_encode(dd, sizeof(dd), rs->descriptor_digest, DIGEST_LEN);
 +        log_warn(LD_BUG, "Cannot get any descriptor for %s "
 +            "(wanted descriptor %s).",
 +            id, dd);
 +        return -1;
 +      };
 +
 +      /* This assert can fire for the control port, because
 +       * it can request NS documents before all descriptors
 +       * have been fetched. */
 +      if (memcmp(desc->cache_info.signed_descriptor_digest,
 +            rs->descriptor_digest,
 +            DIGEST_LEN)) {
 +        char rl_d[HEX_DIGEST_LEN+1];
 +        char rs_d[HEX_DIGEST_LEN+1];
 +        char id[HEX_DIGEST_LEN+1];
 +
 +        base16_encode(rl_d, sizeof(rl_d),
 +            desc->cache_info.signed_descriptor_digest, DIGEST_LEN);
 +        base16_encode(rs_d, sizeof(rs_d), rs->descriptor_digest, DIGEST_LEN);
 +        base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
 +        log_err(LD_BUG, "descriptor digest in routerlist does not match "
 +            "the one in routerstatus: %s vs %s "
 +            "(router %s)\n",
 +            rl_d, rs_d, id);
 +
 +        tor_assert(!memcmp(desc->cache_info.signed_descriptor_digest,
 +              rs->descriptor_digest,
 +              DIGEST_LEN));
 +      };
 +    }
  
 -    /* Blow up more or less nicely if we didn't get anything or not the
 -     * thing we expected.
 -     */
 -    if (!desc) {
 -      char id[HEX_DIGEST_LEN+1];
 -      char dd[HEX_DIGEST_LEN+1];
 -
 -      base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
 -      base16_encode(dd, sizeof(dd), rs->descriptor_digest, DIGEST_LEN);
 -      log_warn(LD_BUG, "Cannot get any descriptor for %s "
 -                       "(wanted descriptor %s).",
 -               id, dd);
 -      return -1;
 -    };
 -    if (memcmp(desc->cache_info.signed_descriptor_digest,
 -               rs->descriptor_digest,
 -               DIGEST_LEN)) {
 -      char rl_d[HEX_DIGEST_LEN+1];
 -      char rs_d[HEX_DIGEST_LEN+1];
 -      char id[HEX_DIGEST_LEN+1];
 -
 -      base16_encode(rl_d, sizeof(rl_d),
 -                    desc->cache_info.signed_descriptor_digest, DIGEST_LEN);
 -      base16_encode(rs_d, sizeof(rs_d), rs->descriptor_digest, DIGEST_LEN);
 -      base16_encode(id, sizeof(id), rs->identity_digest, DIGEST_LEN);
 -      log_err(LD_BUG, "descriptor digest in routerlist does not match "
 -                      "the one in routerstatus: %s vs %s "
 -                      "(router %s)\n",
 -              rl_d, rs_d, id);
 -
 -      tor_assert(!memcmp(desc->cache_info.signed_descriptor_digest,
 -                       rs->descriptor_digest,
 -                       DIGEST_LEN));
 -    };
 -
 +    if (format == NS_CONTROL_PORT && rs->has_bandwidth) {
 +      bw = rs->bandwidth;
 +    } else {
 +      tor_assert(desc);
 +      bw = router_get_advertised_bandwidth_capped(desc) / 1000;
 +    }
      r = tor_snprintf(cp, buf_len - (cp-buf),
 -                     "w Bandwidth=%d\n",
 -                     router_get_advertised_bandwidth_capped(desc) / 1024);
 +                     "w Bandwidth=%d\n", bw);
 +
      if (r<0) {
        log_warn(LD_BUG, "Not enough space in buffer.");
        return -1;
      }
      cp += strlen(cp);
 +    if (format == NS_V3_VOTE && rs->has_measured_bw) {
 +      *--cp = '\0'; /* Kill "\n" */
 +      r = tor_snprintf(cp, buf_len - (cp-buf),
 +                       " Measured=%d\n", rs->measured_bw);
 +      if (r<0) {
 +        log_warn(LD_BUG, "Not enough space in buffer for weight line.");
 +        return -1;
 +      }
 +      cp += strlen(cp);
 +    }
  
 -    summary = policy_summarize(desc->exit_policy);
 -    r = tor_snprintf(cp, buf_len - (cp-buf), "p %s\n", summary);
 -    if (r<0) {
 -      log_warn(LD_BUG, "Not enough space in buffer.");
 +    if (desc) {
 +      summary = policy_summarize(desc->exit_policy);
 +      r = tor_snprintf(cp, buf_len - (cp-buf), "p %s\n", summary);
 +      if (r<0) {
 +        log_warn(LD_BUG, "Not enough space in buffer.");
 +        tor_free(summary);
 +        return -1;
 +      }
 +      cp += strlen(cp);
        tor_free(summary);
 -      return -1;
      }
 -    cp += strlen(cp);
 -    tor_free(summary);
    }
  
    return 0;
@@@ -2214,7 -2132,9 +2214,7 @@@ get_possible_sybil_list(const smartlist
  
  /** Extract status information from <b>ri</b> and from other authority
   * functions and store it in <b>rs</b>>.  If <b>naming</b>, consider setting
 - * the named flag in <b>rs</b>. If not <b>exits_can_be_guards</b>, never mark
 - * an exit as a guard.  If <b>listbadexits</b>, consider setting the badexit
 - * flag.
 + * the named flag in <b>rs</b>.
   *
   * We assume that ri-\>is_running has already been set, e.g. by
   *   dirserv_set_router_is_running(ri, now);
@@@ -2222,8 -2142,8 +2222,8 @@@
  void
  set_routerstatus_from_routerinfo(routerstatus_t *rs,
                                   routerinfo_t *ri, time_t now,
 -                                 int naming, int exits_can_be_guards,
 -                                 int listbadexits, int listbaddirs)
 +                                 int naming, int listbadexits,
 +                                 int listbaddirs)
  {
    int unstable_version =
      !tor_version_as_new_as(ri->platform,"0.1.1.16-rc-cvs");
@@@ -2252,10 -2172,11 +2252,10 @@@
    rs->is_valid = ri->is_valid;
  
    if (rs->is_fast &&
 -      (!rs->is_exit || exits_can_be_guards) &&
        (router_get_advertised_bandwidth(ri) >= BANDWIDTH_TO_GUARANTEE_GUARD ||
         router_get_advertised_bandwidth(ri) >=
 -       (exits_can_be_guards ? guard_bandwidth_including_exits :
 -        guard_bandwidth_excluding_exits))) {
 +                              MIN(guard_bandwidth_including_exits,
 +                                  guard_bandwidth_excluding_exits))) {
      long tk = rep_hist_get_weighted_time_known(
                                        ri->cache_info.identity_digest, now);
      double wfu = rep_hist_get_weighted_fractional_uptime(
@@@ -2311,177 -2232,6 +2311,177 @@@ router_clear_status_flags(routerinfo_t 
      router->is_bad_exit = router->is_bad_directory = 0;
  }
  
 +/**
 + * Helper function to parse out a line in the measured bandwidth file
 + * into a measured_bw_line_t output structure. Returns -1 on failure
 + * or 0 on success.
 + */
 +int
 +measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line)
 +{
 +  char *line = tor_strdup(orig_line);
 +  char *cp = line;
 +  int got_bw = 0;
 +  int got_node_id = 0;
 +  char *strtok_state; /* lame sauce d'jour */
 +  cp = tor_strtok_r(cp, " \t", &strtok_state);
 +
 +  if (!cp) {
 +    log_warn(LD_DIRSERV, "Invalid line in bandwidth file: %s",
 +             escaped(orig_line));
 +    tor_free(line);
 +    return -1;
 +  }
 +
 +  if (orig_line[strlen(orig_line)-1] != '\n') {
 +    log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
 +             escaped(orig_line));
 +    tor_free(line);
 +    return -1;
 +  }
 +
 +  do {
 +    if (strcmpstart(cp, "bw=") == 0) {
 +      int parse_ok = 0;
 +      char *endptr;
 +      if (got_bw) {
 +        log_warn(LD_DIRSERV, "Double bw= in bandwidth file line: %s",
 +                 escaped(orig_line));
 +        tor_free(line);
 +        return -1;
 +      }
 +      cp+=strlen("bw=");
 +
 +      out->bw = tor_parse_long(cp, 0, 0, LONG_MAX, &parse_ok, &endptr);
 +      if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) {
 +        log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s",
 +                 escaped(orig_line));
 +        tor_free(line);
 +        return -1;
 +      }
 +      got_bw=1;
 +    } else if (strcmpstart(cp, "node_id=$") == 0) {
 +      if (got_node_id) {
 +        log_warn(LD_DIRSERV, "Double node_id= in bandwidth file line: %s",
 +                 escaped(orig_line));
 +        tor_free(line);
 +        return -1;
 +      }
 +      cp+=strlen("node_id=$");
 +
 +      if (strlen(cp) != HEX_DIGEST_LEN ||
 +          base16_decode(out->node_id, DIGEST_LEN, cp, HEX_DIGEST_LEN)) {
 +        log_warn(LD_DIRSERV, "Invalid node_id in bandwidth file line: %s",
 +                 escaped(orig_line));
 +        tor_free(line);
 +        return -1;
 +      }
 +      strncpy(out->node_hex, cp, sizeof(out->node_hex));
 +      got_node_id=1;
 +    }
 +  } while ((cp = tor_strtok_r(NULL, " \t", &strtok_state)));
 +
 +  if (got_bw && got_node_id) {
 +    tor_free(line);
 +    return 0;
 +  } else {
 +    log_warn(LD_DIRSERV, "Incomplete line in bandwidth file: %s",
 +             escaped(orig_line));
 +    tor_free(line);
 +    return -1;
 +  }
 +}
 +
 +/**
 + * Helper function to apply a parsed measurement line to a list
 + * of bandwidth statuses. Returns true if a line is found,
 + * false otherwise.
 + */
 +int
 +measured_bw_line_apply(measured_bw_line_t *parsed_line,
 +                       smartlist_t *routerstatuses)
 +{
 +  routerstatus_t *rs = NULL;
 +  if (!routerstatuses)
 +    return 0;
 +
 +  rs = smartlist_bsearch(routerstatuses, parsed_line->node_id,
 +                         compare_digest_to_routerstatus_entry);
 +
 +  if (rs) {
 +    rs->has_measured_bw = 1;
 +    rs->measured_bw = (uint32_t)parsed_line->bw;
 +  } else {
 +    log_info(LD_DIRSERV, "Node ID %s not found in routerstatus list",
 +             parsed_line->node_hex);
 +  }
 +
 +  return rs != NULL;
 +}
 +
 +/**
 + * Read the measured bandwidth file and apply it to the list of
 + * routerstatuses. Returns -1 on error, 0 otherwise.
 + */
 +int
 +dirserv_read_measured_bandwidths(const char *from_file,
 +                                 smartlist_t *routerstatuses)
 +{
 +  char line[256];
 +  FILE *fp = fopen(from_file, "r");
 +  int applied_lines = 0;
 +  time_t file_time;
 +  int ok;
 +  if (fp == NULL) {
 +    log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s",
 +             from_file);
 +    return -1;
 +  }
 +
 +  if (!fgets(line, sizeof(line), fp)
 +          || !strlen(line) || line[strlen(line)-1] != '\n') {
 +    log_warn(LD_DIRSERV, "Long or truncated time in bandwidth file: %s",
 +             escaped(line));
 +    fclose(fp);
 +    return -1;
 +  }
 +
 +  line[strlen(line)-1] = '\0';
 +  file_time = tor_parse_ulong(line, 10, 0, ULONG_MAX, &ok, NULL);
 +  if (!ok) {
 +    log_warn(LD_DIRSERV, "Non-integer time in bandwidth file: %s",
 +             escaped(line));
 +    fclose(fp);
 +    return -1;
 +  }
 +
 +  if ((time(NULL) - file_time) > MAX_MEASUREMENT_AGE) {
 +    log_warn(LD_DIRSERV, "Bandwidth measurement file stale. Age: %u",
 +             (unsigned)(time(NULL) - file_time));
 +    fclose(fp);
 +    return -1;
 +  }
 +
 +  if (routerstatuses)
 +    smartlist_sort(routerstatuses, compare_routerstatus_entries);
 +
 +  while (!feof(fp)) {
 +    measured_bw_line_t parsed_line;
 +    if (fgets(line, sizeof(line), fp) && strlen(line)) {
 +      if (measured_bw_line_parse(&parsed_line, line) != -1) {
 +        if (measured_bw_line_apply(&parsed_line, routerstatuses) > 0)
 +          applied_lines++;
 +      }
 +    }
 +  }
 +
 +  fclose(fp);
 +  log_info(LD_DIRSERV,
 +           "Bandwidth measurement file successfully read. "
 +           "Applied %d measurements.", applied_lines);
 +  return 0;
 +}
 +
  /** Return a new networkstatus_t* containing our current opinion. (For v3
   * authorities) */
  networkstatus_t *
@@@ -2499,18 -2249,22 +2499,18 @@@ dirserv_generate_networkstatus_vote_obj
    int naming = options->NamingAuthoritativeDir;
    int listbadexits = options->AuthDirListBadExits;
    int listbaddirs = options->AuthDirListBadDirs;
 -  int exits_can_be_guards;
    routerlist_t *rl = router_get_routerlist();
    time_t now = time(NULL);
    time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH;
    networkstatus_voter_info_t *voter = NULL;
    vote_timing_t timing;
    digestmap_t *omit_as_sybil = NULL;
 -  int vote_on_reachability = 1;
 +  const int vote_on_reachability = running_long_enough_to_decide_unreachable();
 +  smartlist_t *microdescriptors = NULL;
  
    tor_assert(private_key);
    tor_assert(cert);
  
 -  if (now - time_of_process_start <
 -      options->TestingAuthDirTimeToLearnReachability)
 -    vote_on_reachability = 0;
 -
    if (resolve_my_address(LOG_WARN, options, &addr, &hostname)<0) {
      log_warn(LD_NET, "Couldn't resolve my hostname");
      return NULL;
@@@ -2545,24 -2299,27 +2545,24 @@@
  
    dirserv_compute_performance_thresholds(rl);
  
 -  /* XXXX We should take steps to keep this from oscillating if
 -   * total_exit_bandwidth is close to total_bandwidth/3. */
 -  exits_can_be_guards = total_exit_bandwidth >= (total_bandwidth / 3);
 -
    routers = smartlist_create();
    smartlist_add_all(routers, rl->routers);
    routers_sort_by_identity(routers);
    omit_as_sybil = get_possible_sybil_list(routers);
  
    routerstatuses = smartlist_create();
 +  microdescriptors = smartlist_create();
  
 -  SMARTLIST_FOREACH(routers, routerinfo_t *, ri, {
 +  SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) {
      if (ri->cache_info.published_on >= cutoff) {
        routerstatus_t *rs;
        vote_routerstatus_t *vrs;
 +      microdesc_t *md;
  
        vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
        rs = &vrs->status;
        set_routerstatus_from_routerinfo(rs, ri, now,
 -                                       naming, exits_can_be_guards,
 -                                       listbadexits, listbaddirs);
 +                                       naming, listbadexits, listbaddirs);
  
        if (digestmap_get(omit_as_sybil, ri->cache_info.identity_digest))
          clear_status_flags_on_sybil(rs);
@@@ -2571,39 -2328,12 +2571,39 @@@
          rs->is_running = 0;
  
        vrs->version = version_from_platform(ri->platform);
 +      md = dirvote_create_microdescriptor(ri);
 +      if (md) {
 +        char buf[128];
 +        vote_microdesc_hash_t *h;
 +        dirvote_format_microdesc_vote_line(buf, sizeof(buf), md);
 +        h = tor_malloc(sizeof(vote_microdesc_hash_t));
 +        h->microdesc_hash_line = tor_strdup(buf);
 +        h->next = NULL;
 +        vrs->microdesc = h;
 +        md->last_listed = now;
 +        smartlist_add(microdescriptors, md);
 +      }
 +
        smartlist_add(routerstatuses, vrs);
      }
 -  });
 +  } SMARTLIST_FOREACH_END(ri);
 +
 +  {
 +    smartlist_t *added =
 +      microdescs_add_list_to_cache(get_microdesc_cache(),
 +                                   microdescriptors, SAVED_NOWHERE, 0);
 +    smartlist_free(added);
 +    smartlist_free(microdescriptors);
 +  }
 +
    smartlist_free(routers);
    digestmap_free(omit_as_sybil, NULL);
  
 +  if (options->V3BandwidthsFile) {
 +    dirserv_read_measured_bandwidths(options->V3BandwidthsFile,
 +                                     routerstatuses);
 +  }
 +
    v3_out = tor_malloc_zero(sizeof(networkstatus_t));
  
    v3_out->type = NS_TYPE_VOTE;
@@@ -2653,22 -2383,15 +2653,22 @@@
    }
    smartlist_sort_strings(v3_out->known_flags);
  
 +  if (options->ConsensusParams) {
 +    v3_out->net_params = smartlist_create();
 +    smartlist_split_string(v3_out->net_params,
 +                           options->ConsensusParams, NULL, 0, 0);
 +    smartlist_sort_strings(v3_out->net_params);
 +  }
 +
    voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
    voter->nickname = tor_strdup(options->Nickname);
    memcpy(voter->identity_digest, identity_digest, DIGEST_LEN);
 +  voter->sigs = smartlist_create();
    voter->address = hostname;
    voter->addr = addr;
    voter->dir_port = options->DirPort;
    voter->or_port = options->ORPort;
    voter->contact = tor_strdup(contact);
 -  memcpy(voter->signing_key_digest, signing_key_digest, DIGEST_LEN);
    if (options->V3AuthUseLegacyKey) {
      authority_cert_t *c = get_my_v3_legacy_cert();
      if (c) {
@@@ -2712,12 -2435,13 +2712,12 @@@ generate_v2_networkstatus_opinion(void
    int versioning = options->VersioningAuthoritativeDir;
    int listbaddirs = options->AuthDirListBadDirs;
    int listbadexits = options->AuthDirListBadExits;
 -  int exits_can_be_guards;
    const char *contact;
    char *version_lines = NULL;
    smartlist_t *routers = NULL;
    digestmap_t *omit_as_sybil = NULL;
  
 -  private_key = get_identity_key();
 +  private_key = get_server_identity_key();
  
    if (resolve_my_address(LOG_WARN, options, &addr, &hostname)<0) {
      log_warn(LD_NET, "Couldn't resolve my hostname");
@@@ -2791,6 -2515,10 +2791,6 @@@
  
    dirserv_compute_performance_thresholds(rl);
  
 -  /* XXXX We should take steps to keep this from oscillating if
 -   * total_exit_bandwidth is close to total_bandwidth/3. */
 -  exits_can_be_guards = total_exit_bandwidth >= (total_bandwidth / 3);
 -
    routers = smartlist_create();
    smartlist_add_all(routers, rl->routers);
    routers_sort_by_identity(routers);
@@@ -2803,12 -2531,13 +2803,12 @@@
        char *version = version_from_platform(ri->platform);
  
        set_routerstatus_from_routerinfo(&rs, ri, now,
 -                                       naming, exits_can_be_guards,
 -                                       listbadexits, listbaddirs);
 +                                       naming, listbadexits, listbaddirs);
  
        if (digestmap_get(omit_as_sybil, ri->cache_info.identity_digest))
          clear_status_flags_on_sybil(&rs);
  
 -      if (routerstatus_format_entry(outp, endp-outp, &rs, version, 0, 1)) {
 +      if (routerstatus_format_entry(outp, endp-outp, &rs, version, NS_V2)) {
          log_warn(LD_BUG, "Unable to print router status.");
          tor_free(version);
          goto done;
@@@ -2830,8 -2559,7 +2830,8 @@@
    outp += strlen(outp);
  
    note_crypto_pk_op(SIGN_DIR);
 -  if (router_append_dirobj_signature(outp,endp-outp,digest,private_key)<0) {
 +  if (router_append_dirobj_signature(outp,endp-outp,digest,DIGEST_LEN,
 +                                     private_key)<0) {
      log_warn(LD_BUG, "Unable to sign router status.");
      goto done;
    }
@@@ -2863,8 -2591,10 +2863,8 @@@
    tor_free(status);
    tor_free(hostname);
    tor_free(identity_pkey);
 -  if (routers)
 -    smartlist_free(routers);
 -  if (omit_as_sybil)
 -    digestmap_free(omit_as_sybil, NULL);
 +  smartlist_free(routers);
 +  digestmap_free(omit_as_sybil, NULL);
    return r;
  }
  
@@@ -2912,8 -2642,7 +2912,8 @@@ dirserv_get_networkstatus_v2_fingerprin
        log_info(LD_DIRSERV,
                 "Client requested 'all' network status objects; we have none.");
    } else if (!strcmpstart(key, "fp/")) {
 -    dir_split_resource_into_fingerprints(key+3, result, NULL, 1, 1);
 +    dir_split_resource_into_fingerprints(key+3, result, NULL,
 +                                         DSR_HEX|DSR_SORT_UNIQ);
    }
  }
  
@@@ -2978,12 -2707,10 +2978,12 @@@ dirserv_get_routerdesc_fingerprints(sma
    } else if (!strcmpstart(key, "d/")) {
      by_id = 0;
      key += strlen("d/");
 -    dir_split_resource_into_fingerprints(key, fps_out, NULL, 1, 1);
 +    dir_split_resource_into_fingerprints(key, fps_out, NULL,
 +                                         DSR_HEX|DSR_SORT_UNIQ);
    } else if (!strcmpstart(key, "fp/")) {
      key += strlen("fp/");
 -    dir_split_resource_into_fingerprints(key, fps_out, NULL, 1, 1);
 +    dir_split_resource_into_fingerprints(key, fps_out, NULL,
 +                                         DSR_HEX|DSR_SORT_UNIQ);
    } else {
      *msg = "Key not recognized";
      return -1;
@@@ -3048,8 -2775,7 +3048,8 @@@ dirserv_get_routerdescs(smartlist_t *de
    } else if (!strcmpstart(key, "/tor/server/d/")) {
      smartlist_t *digests = smartlist_create();
      key += strlen("/tor/server/d/");
 -    dir_split_resource_into_fingerprints(key, digests, NULL, 1, 1);
 +    dir_split_resource_into_fingerprints(key, digests, NULL,
 +                                         DSR_HEX|DSR_SORT_UNIQ);
      SMARTLIST_FOREACH(digests, const char *, d,
         {
           signed_descriptor_t *sd = router_get_by_descriptor_digest(d);
@@@ -3062,8 -2788,7 +3062,8 @@@
      smartlist_t *digests = smartlist_create();
      time_t cutoff = time(NULL) - ROUTER_MAX_AGE_TO_PUBLISH;
      key += strlen("/tor/server/fp/");
 -    dir_split_resource_into_fingerprints(key, digests, NULL, 1, 1);
 +    dir_split_resource_into_fingerprints(key, digests, NULL,
 +                                         DSR_HEX|DSR_SORT_UNIQ);
      SMARTLIST_FOREACH(digests, const char *, d,
         {
           if (router_digest_is_me(d)) {
@@@ -3132,48 -2857,8 +3132,48 @@@ dirserv_orconn_tls_done(const char *add
     * skip testing. */
  }
  
 -/** Auth dir server only: if <b>try_all</b> is 1, launch connections to
 - * all known routers; else we want to load balance such that we only
 +/** Called when we, as an authority, receive a new router descriptor either as
 + * an upload or a download.  Used to decide whether to relaunch reachability
 + * testing for the server. */
 +int
 +dirserv_should_launch_reachability_test(routerinfo_t *ri, routerinfo_t *ri_old)
 +{
 +  if (!authdir_mode_handles_descs(get_options(), ri->purpose))
 +    return 0;
 +  if (!ri_old) {
 +    /* New router: Launch an immediate reachability test, so we will have an
 +     * opinion soon in case we're generating a consensus soon */
 +    return 1;
 +  }
 +  if (ri_old->is_hibernating && !ri->is_hibernating) {
 +    /* It just came out of hibernation; launch a reachability test */
 +    return 1;
 +  }
 +  if (! routers_have_same_or_addr(ri, ri_old)) {
 +    /* Address or port changed; launch a reachability test */
 +    return 1;
 +  }
 +  return 0;
 +}
 +
 +/** Helper function for dirserv_test_reachability(). Start a TLS
 + * connection to <b>router</b>, and annotate it with when we started
 + * the test. */
 +void
 +dirserv_single_reachability_test(time_t now, routerinfo_t *router)
 +{
 +  tor_addr_t router_addr;
 +  log_debug(LD_OR,"Testing reachability of %s at %s:%u.",
 +            router->nickname, router->address, router->or_port);
 +  /* Remember when we started trying to determine reachability */
 +  if (!router->testing_since)
 +    router->testing_since = now;
 +  tor_addr_from_ipv4h(&router_addr, router->addr);
 +  connection_or_connect(&router_addr, router->or_port,
 +                        router->cache_info.identity_digest);
 +}
 +
 +/** Auth dir server only: load balance such that we only
   * try a few connections per call.
   *
   * The load balancing is such that if we get called once every ten
@@@ -3181,7 -2866,7 +3181,7 @@@
   * bit over 20 minutes).
   */
  void
 -dirserv_test_reachability(time_t now, int try_all)
 +dirserv_test_reachability(time_t now)
  {
    /* XXX decide what to do here; see or-talk thread "purging old router
     * information, revocation." -NM
@@@ -3198,33 -2883,38 +3198,33 @@@
  
    SMARTLIST_FOREACH_BEGIN(rl->routers, routerinfo_t *, router) {
      const char *id_digest = router->cache_info.identity_digest;
 -    tor_addr_t router_addr;
      if (router_is_me(router))
        continue;
      if (bridge_auth && router->purpose != ROUTER_PURPOSE_BRIDGE)
        continue; /* bridge authorities only test reachability on bridges */
  //    if (router->cache_info.published_on > cutoff)
  //      continue;
 -    if (try_all || (((uint8_t)id_digest[0]) % 128) == ctr) {
 -      log_debug(LD_OR,"Testing reachability of %s at %s:%u.",
 -                router->nickname, router->address, router->or_port);
 -      /* Remember when we started trying to determine reachability */
 -      if (!router->testing_since)
 -        router->testing_since = now;
 -      tor_addr_from_ipv4h(&router_addr, router->addr);
 -      connection_or_connect(&router_addr, router->or_port, id_digest);
 +    if ((((uint8_t)id_digest[0]) % 128) == ctr) {
 +      dirserv_single_reachability_test(now, router);
      }
    } SMARTLIST_FOREACH_END(router);
 -  if (!try_all) /* increment ctr */
 -    ctr = (ctr + 1) % 128;
 +  ctr = (ctr + 1) % 128; /* increment ctr */
  }
  
 -/** Given a fingerprint <b>fp</b> which is either set if we're looking
 - * for a v2 status, or zeroes if we're looking for a v3 status, return
 - * a pointer to the appropriate cached dir object, or NULL if there isn't
 - * one available. */
 +/** Given a fingerprint <b>fp</b> which is either set if we're looking for a
 + * v2 status, or zeroes if we're looking for a v3 status, or a NUL-padded
 + * flavor name if we want a flavored v3 status, return a pointer to the
 + * appropriate cached dir object, or NULL if there isn't one available. */
  static cached_dir_t *
  lookup_cached_dir_by_fp(const char *fp)
  {
    cached_dir_t *d = NULL;
 -  if (tor_digest_is_zero(fp) && cached_v3_networkstatus)
 -    d = cached_v3_networkstatus;
 -  else if (router_digest_is_me(fp) && the_v2_networkstatus)
 +  if (tor_digest_is_zero(fp) && cached_consensuses)
 +    d = strmap_get(cached_consensuses, "ns");
 +  else if (memchr(fp, '\0', DIGEST_LEN) && cached_consensuses &&
 +           (d = strmap_get(cached_consensuses, fp))) {
 +    /* this here interface is a nasty hack XXXX022 */;
 +  } else if (router_digest_is_me(fp) && the_v2_networkstatus)
      d = the_v2_networkstatus;
    else if (cached_v2_networkstatus)
      d = digestmap_get(cached_v2_networkstatus, fp);
@@@ -3310,18 -3000,6 +3310,18 @@@ dirserv_have_any_serverdesc(smartlist_
    return 0;
  }
  
 +/** Return true iff any of the 256-bit elements in <b>fps</b> is the digest of
 + * a microdescriptor we have. */
 +int
 +dirserv_have_any_microdesc(const smartlist_t *fps)
 +{
 +  microdesc_cache_t *cache = get_microdesc_cache();
 +  SMARTLIST_FOREACH(fps, const char *, fp,
 +                    if (microdesc_cache_lookup_by_digest256(cache, fp))
 +                      return 1);
 +  return 0;
 +}
 +
  /** Return an approximate estimate of the number of bytes that will
   * be needed to transmit the server descriptors (if is_serverdescs --
   * they can be either d/ or fp/ queries) or networkstatus objects (if
@@@ -3353,17 -3031,6 +3353,17 @@@ dirserv_estimate_data_size(smartlist_t 
    return result;
  }
  
 +/** Given a list of microdescriptor hashes, guess how many bytes will be
 + * needed to transmit them, and return the guess. */
 +size_t
 +dirserv_estimate_microdesc_size(const smartlist_t *fps, int compressed)
 +{
 +  size_t result = smartlist_len(fps) * microdesc_average_size(NULL);
 +  if (compressed)
 +    result /= 2;
 +  return result;
 +}
 +
  /** When we're spooling data onto our outbuf, add more whenever we dip
   * below this threshold. */
  #define DIRSERV_BUFFER_MIN 16384
@@@ -3427,8 -3094,6 +3427,8 @@@ connection_dirserv_add_servers_to_outbu
  #endif
      body = signed_descriptor_get_body(sd);
      if (conn->zlib_state) {
 +      /* XXXX022 This 'last' business should actually happen on the last
 +       * routerinfo, not on the last fingerprint. */
        int last = ! smartlist_len(conn->fingerprint_stack);
        connection_write_to_buf_zlib(body, sd->signed_descriptor_len, conn,
                                     last);
@@@ -3452,44 -3117,6 +3452,44 @@@
    return 0;
  }
  
 +/** Spooling helper: called when we're sending a bunch of microdescriptors,
 + * and the outbuf has become too empty. Pulls some entries from
 + * fingerprint_stack, and writes the corresponding microdescs onto outbuf.  If
 + * we run out of entries, flushes the zlib state and sets the spool source to
 + * NONE.  Returns 0 on success, negative on failure.
 + */
 +static int
 +connection_dirserv_add_microdescs_to_outbuf(dir_connection_t *conn)
 +{
 +  microdesc_cache_t *cache = get_microdesc_cache();
 +  while (smartlist_len(conn->fingerprint_stack) &&
 +         buf_datalen(conn->_base.outbuf) < DIRSERV_BUFFER_MIN) {
 +    char *fp256 = smartlist_pop_last(conn->fingerprint_stack);
 +    microdesc_t *md = microdesc_cache_lookup_by_digest256(cache, fp256);
 +    tor_free(fp256);
 +    if (!md)
 +      continue;
 +    if (conn->zlib_state) {
 +      /* XXXX022 This 'last' business should actually happen on the last
 +       * routerinfo, not on the last fingerprint. */
 +      int last = !smartlist_len(conn->fingerprint_stack);
 +      connection_write_to_buf_zlib(md->body, md->bodylen, conn, last);
 +      if (last) {
 +        tor_zlib_free(conn->zlib_state);
 +        conn->zlib_state = NULL;
 +      }
 +    } else {
 +      connection_write_to_buf(md->body, md->bodylen, TO_CONN(conn));
 +    }
 +  }
 +  if (!smartlist_len(conn->fingerprint_stack)) {
 +    conn->dir_spool_src = DIR_SPOOL_NONE;
 +    smartlist_free(conn->fingerprint_stack);
 +    conn->fingerprint_stack = NULL;
 +  }
 +  return 0;
 +}
 +
  /** Spooling helper: Called when we're sending a directory or networkstatus,
   * and the outbuf has become too empty.  Pulls some bytes from
   * <b>conn</b>-\>cached_dir-\>dir_z, uncompresses them if appropriate, and
@@@ -3572,7 -3199,8 +3572,7 @@@ connection_dirserv_add_networkstatus_by
        }
      } else {
        connection_dirserv_finish_spooling(conn);
 -      if (conn->fingerprint_stack)
 -        smartlist_free(conn->fingerprint_stack);
 +      smartlist_free(conn->fingerprint_stack);
        conn->fingerprint_stack = NULL;
        return 0;
      }
@@@ -3596,8 -3224,6 +3596,8 @@@ connection_dirserv_flushed_some(dir_con
      case DIR_SPOOL_SERVER_BY_DIGEST:
      case DIR_SPOOL_SERVER_BY_FP:
        return connection_dirserv_add_servers_to_outbuf(conn);
 +    case DIR_SPOOL_MICRODESC:
 +      return connection_dirserv_add_microdescs_to_outbuf(conn);
      case DIR_SPOOL_CACHED_DIR:
        return connection_dirserv_add_dir_bytes_to_outbuf(conn);
      case DIR_SPOOL_NETWORKSTATUS:
@@@ -3619,10 -3245,10 +3619,10 @@@ dirserv_free_all(void
    cached_dir_decref(the_v2_networkstatus);
    cached_dir_decref(cached_directory);
    clear_cached_dir(&cached_runningrouters);
 -  if (cached_v2_networkstatus) {
 -    digestmap_free(cached_v2_networkstatus, _free_cached_dir);
 -    cached_v2_networkstatus = NULL;
 -  }
 -  cached_dir_decref(cached_v3_networkstatus);
 +
 +  digestmap_free(cached_v2_networkstatus, _free_cached_dir);
 +  cached_v2_networkstatus = NULL;
 +  strmap_free(cached_consensuses, _free_cached_dir);
 +  cached_consensuses = NULL;
  }
  
diff --combined src/or/dirvote.c
index dd36a0f,656cb8a..41985d1
--- a/src/or/dirvote.c
+++ b/src/or/dirvote.c
@@@ -1,76 -1,31 +1,76 @@@
  /* Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  #define DIRVOTE_PRIVATE
  #include "or.h"
 +#include "config.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "microdesc.h"
 +#include "networkstatus.h"
 +#include "policies.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  /**
   * \file dirvote.c
   * \brief Functions to compute directory consensus, and schedule voting.
   **/
  
 -static int dirvote_add_signatures_to_pending_consensus(
 +/** A consensus that we have built and are appending signatures to.  Once it's
 + * time to publish it, it will become an active consensus if it accumulates
 + * enough signatures. */
 +typedef struct pending_consensus_t {
 +  /** The body of the consensus that we're currently building.  Once we
 +   * have it built, it goes into dirserv.c */
 +  char *body;
 +  /** The parsed in-progress consensus document. */
 +  networkstatus_t *consensus;
 +} pending_consensus_t;
 +
 +static int dirvote_add_signatures_to_all_pending_consensuses(
                         const char *detached_signatures_body,
                         const char **msg_out);
 +static int dirvote_add_signatures_to_pending_consensus(
 +                       pending_consensus_t *pc,
 +                       ns_detached_signatures_t *sigs,
 +                       const char **msg_out);
  static char *list_v3_auth_ids(void);
  static void dirvote_fetch_missing_votes(void);
  static void dirvote_fetch_missing_signatures(void);
  static int dirvote_perform_vote(void);
  static void dirvote_clear_votes(int all_votes);
 -static int dirvote_compute_consensus(void);
 +static int dirvote_compute_consensuses(void);
  static int dirvote_publish_consensus(void);
 +static char *make_consensus_method_list(int low, int high, const char *sep);
 +
 +/** The highest consensus method that we currently support. */
 +#define MAX_SUPPORTED_CONSENSUS_METHOD 10
 +
 +/** Lowest consensus method that contains a 'directory-footer' marker */
 +#define MIN_METHOD_FOR_FOOTER 9
 +
 +/** Lowest consensus method that contains bandwidth weights */
 +#define MIN_METHOD_FOR_BW_WEIGHTS 9
 +
 +/** Lowest consensus method that contains consensus params */
 +#define MIN_METHOD_FOR_PARAMS 7
 +
 +/** Lowest consensus method that generates microdescriptors */
 +#define MIN_METHOD_FOR_MICRODESC 8
  
  /* =====
   * Voting
   * =====*/
  
 +/* Overestimated. */
 +#define MICRODESC_LINE_LEN 80
 +
  /** Return a new string containing the string representation of the vote in
   * <b>v3_ns</b>, signed with our v3 signing key <b>private_signing_key</b>.
   * For v3 authorities. */
@@@ -89,7 -44,6 +89,7 @@@ format_networkstatus_vote(crypto_pk_env
    uint32_t addr;
    routerlist_t *rl = router_get_routerlist();
    char *version_lines = NULL;
 +  int r;
    networkstatus_voter_info_t *voter;
  
    tor_assert(private_signing_key);
@@@ -116,30 -70,20 +116,30 @@@
      version_lines = tor_malloc(v_len);
      cp = version_lines;
      if (client_versions) {
 -      tor_snprintf(cp, v_len-(cp-version_lines),
 +      r = tor_snprintf(cp, v_len-(cp-version_lines),
                     "client-versions %s\n", client_versions);
 +      if (r < 0) {
 +        log_err(LD_BUG, "Insufficient memory for client-versions line");
 +        tor_assert(0);
 +      }
        cp += strlen(cp);
      }
 -    if (server_versions)
 -      tor_snprintf(cp, v_len-(cp-version_lines),
 +    if (server_versions) {
 +      r = tor_snprintf(cp, v_len-(cp-version_lines),
                     "server-versions %s\n", server_versions);
 +      if (r < 0) {
 +        log_err(LD_BUG, "Insufficient memory for server-versions line");
 +        tor_assert(0);
 +      }
 +    }
    } else {
      version_lines = tor_strdup("");
    }
  
    len = 8192;
    len += strlen(version_lines);
 -  len += (RS_ENTRY_LEN)*smartlist_len(rl->routers);
 +  len += (RS_ENTRY_LEN+MICRODESC_LINE_LEN)*smartlist_len(rl->routers);
 +  len += strlen("\ndirectory-footer\n");
    len += v3_ns->cert->cache_info.signed_descriptor_len;
  
    status = tor_malloc(len);
@@@ -149,25 -93,17 +149,25 @@@
      char fu[ISO_TIME_LEN+1];
      char vu[ISO_TIME_LEN+1];
      char *flags = smartlist_join_strings(v3_ns->known_flags, " ", 0, NULL);
 +    char *params;
      authority_cert_t *cert = v3_ns->cert;
 +    char *methods =
 +      make_consensus_method_list(1, MAX_SUPPORTED_CONSENSUS_METHOD, " ");
      format_iso_time(published, v3_ns->published);
      format_iso_time(va, v3_ns->valid_after);
      format_iso_time(fu, v3_ns->fresh_until);
      format_iso_time(vu, v3_ns->valid_until);
  
 +    if (v3_ns->net_params)
 +      params = smartlist_join_strings(v3_ns->net_params, " ", 0, NULL);
 +    else
 +      params = tor_strdup("");
 +
      tor_assert(cert);
 -    tor_snprintf(status, len,
 +    r = tor_snprintf(status, len,
                   "network-status-version 3\n"
                   "vote-status %s\n"
 -                 "consensus-methods 1 2 3 4 5\n"
 +                 "consensus-methods %s\n"
                   "published %s\n"
                   "valid-after %s\n"
                   "fresh-until %s\n"
@@@ -175,38 -111,24 +175,38 @@@
                   "voting-delay %d %d\n"
                   "%s" /* versions */
                   "known-flags %s\n"
 +                 "params %s\n"
                   "dir-source %s %s %s %s %d %d\n"
                   "contact %s\n",
                   v3_ns->type == NS_TYPE_VOTE ? "vote" : "opinion",
 +                 methods,
                   published, va, fu, vu,
                   v3_ns->vote_seconds, v3_ns->dist_seconds,
                   version_lines,
                   flags,
 +                 params,
                   voter->nickname, fingerprint, voter->address,
 -                   ipaddr, voter->dir_port, voter->or_port, voter->contact);
 +                 ipaddr, voter->dir_port, voter->or_port, voter->contact);
  
 +    if (r < 0) {
 +      log_err(LD_BUG, "Insufficient memory for network status line");
 +      tor_assert(0);
 +    }
 +
 +    tor_free(params);
      tor_free(flags);
 +    tor_free(methods);
      outp = status + strlen(status);
      endp = status + len;
  
      if (!tor_digest_is_zero(voter->legacy_id_digest)) {
        char fpbuf[HEX_DIGEST_LEN+1];
        base16_encode(fpbuf, sizeof(fpbuf), voter->legacy_id_digest, DIGEST_LEN);
 -      tor_snprintf(outp, endp-outp, "legacy-dir-key %s\n", fpbuf);
 +      r = tor_snprintf(outp, endp-outp, "legacy-dir-key %s\n", fpbuf);
 +      if (r < 0) {
 +        log_err(LD_BUG, "Insufficient memory for legacy-dir-key line");
 +        tor_assert(0);
 +      }
        outp += strlen(outp);
      }
  
@@@ -217,32 -139,15 +217,32 @@@
      outp += cert->cache_info.signed_descriptor_len;
    }
  
 -  SMARTLIST_FOREACH(v3_ns->routerstatus_list, vote_routerstatus_t *, vrs,
 -  {
 +  SMARTLIST_FOREACH_BEGIN(v3_ns->routerstatus_list, vote_routerstatus_t *,
 +                          vrs) {
 +    vote_microdesc_hash_t *h;
      if (routerstatus_format_entry(outp, endp-outp, &vrs->status,
 -                                  vrs->version, 0, 0) < 0) {
 +                                  vrs->version, NS_V3_VOTE) < 0) {
        log_warn(LD_BUG, "Unable to print router status.");
        goto err;
      }
      outp += strlen(outp);
 -  });
 +
 +    for (h = vrs->microdesc; h; h = h->next) {
 +      size_t mlen = strlen(h->microdesc_hash_line);
 +      if (outp+mlen >= endp) {
 +        log_warn(LD_BUG, "Can't fit microdesc line in vote.");
 +      }
 +      memcpy(outp, h->microdesc_hash_line, mlen+1);
 +      outp += strlen(outp);
 +    }
 +  } SMARTLIST_FOREACH_END(vrs);
 +
 +  r = tor_snprintf(outp, endp-outp, "directory-footer\n");
 +  if (r < 0) {
 +    log_err(LD_BUG, "Insufficient memory for directory-footer line");
 +    tor_assert(0);
 +  }
 +  outp += strlen(outp);
  
    {
      char signing_key_fingerprint[FINGERPRINT_LEN+1];
@@@ -265,10 -170,10 +265,10 @@@
      outp += strlen(outp);
    }
  
 -  if (router_get_networkstatus_v3_hash(status, digest)<0)
 +  if (router_get_networkstatus_v3_hash(status, digest, DIGEST_SHA1)<0)
      goto err;
    note_crypto_pk_op(SIGN_DIR);
 -  if (router_append_dirobj_signature(outp,endp-outp,digest,
 +  if (router_append_dirobj_signature(outp,endp-outp,digest, DIGEST_LEN,
                                       private_signing_key)<0) {
      log_warn(LD_BUG, "Unable to sign networkstatus vote.");
      goto err;
@@@ -311,20 -216,6 +311,20 @@@ get_voter(const networkstatus_t *vote
    return smartlist_get(vote->voters, 0);
  }
  
 +/** Return the signature made by <b>voter</b> using the algorithm
 + * <b>alg</b>, or NULL if none is found. */
 +document_signature_t *
 +voter_get_sig_by_algorithm(const networkstatus_voter_info_t *voter,
 +                           digest_algorithm_t alg)
 +{
 +  if (!voter->sigs)
 +    return NULL;
 +  SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig,
 +    if (sig->alg == alg)
 +      return sig);
 +  return NULL;
 +}
 +
  /** Temporary structure used in constructing a list of dir-source entries
   * for a consensus.  One of these is generated for every vote, and one more
   * for every legacy key in each vote. */
@@@ -384,8 -275,34 +384,8 @@@ get_frequent_members(smartlist_t *out, 
  
  /** Given a sorted list of strings <b>lst</b>, return the member that appears
   * most.  Break ties in favor of later-occurring members. */
 -static const char *
 -get_most_frequent_member(smartlist_t *lst)
 -{
 -  const char *most_frequent = NULL;
 -  int most_frequent_count = 0;
 -
 -  const char *cur = NULL;
 -  int count = 0;
 -
 -  SMARTLIST_FOREACH(lst, const char *, s,
 -  {
 -    if (cur && !strcmp(s, cur)) {
 -      ++count;
 -    } else {
 -      if (count >= most_frequent_count) {
 -        most_frequent = cur;
 -        most_frequent_count = count;
 -      }
 -      cur = s;
 -      count = 1;
 -    }
 -  });
 -  if (count >= most_frequent_count) {
 -    most_frequent = cur;
 -    most_frequent_count = count;
 -  }
 -  return most_frequent;
 -}
 +#define get_most_frequent_member(lst)           \
 +  smartlist_get_most_frequent_string(lst)
  
  /** Return 0 if and only if <b>a</b> and <b>b</b> are routerstatuses
   * that come from the same routerinfo, with the same derived elements.
@@@ -427,8 -344,7 +427,8 @@@ _compare_vote_rs(const void **_a, cons
   * in favor of smaller descriptor digest.
   */
  static vote_routerstatus_t *
 -compute_routerstatus_consensus(smartlist_t *votes)
 +compute_routerstatus_consensus(smartlist_t *votes, int consensus_method,
 +                               char *microdesc_digest256_out)
  {
    vote_routerstatus_t *most = NULL, *cur = NULL;
    int most_n = 0, cur_n = 0;
@@@ -464,45 -380,18 +464,45 @@@
    }
  
    tor_assert(most);
 +
 +  if (consensus_method >= MIN_METHOD_FOR_MICRODESC &&
 +      microdesc_digest256_out) {
 +    smartlist_t *digests = smartlist_create();
 +    const char *best_microdesc_digest;
 +    SMARTLIST_FOREACH_BEGIN(votes, vote_routerstatus_t *, rs) {
 +        char d[DIGEST256_LEN];
 +        if (compare_vote_rs(rs, most))
 +          continue;
 +        if (!vote_routerstatus_find_microdesc_hash(d, rs, consensus_method,
 +                                                   DIGEST_SHA256))
 +          smartlist_add(digests, tor_memdup(d, sizeof(d)));
 +    } SMARTLIST_FOREACH_END(rs);
 +    smartlist_sort_digests256(digests);
 +    best_microdesc_digest = smartlist_get_most_frequent_digest256(digests);
 +    if (best_microdesc_digest)
 +      memcpy(microdesc_digest256_out, best_microdesc_digest, DIGEST256_LEN);
 +    SMARTLIST_FOREACH(digests, char *, cp, tor_free(cp));
 +    smartlist_free(digests);
 +  }
 +
    return most;
  }
  
 -/** Given a list of strings in <b>lst</b>, set the DIGEST_LEN-byte digest at
 - * <b>digest_out</b> to the hash of the concatenation of those strings. */
 +/** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest
 + * at <b>digest_out</b> to the hash of the concatenation of those strings,
 + * computed with the algorithm <b>alg</b>. */
  static void
 -hash_list_members(char *digest_out, smartlist_t *lst)
 +hash_list_members(char *digest_out, size_t len_out,
 +                  smartlist_t *lst, digest_algorithm_t alg)
  {
 -  crypto_digest_env_t *d = crypto_new_digest_env();
 +  crypto_digest_env_t *d;
 +  if (alg == DIGEST_SHA1)
 +    d = crypto_new_digest_env();
 +  else
 +    d = crypto_new_digest256_env(alg);
    SMARTLIST_FOREACH(lst, const char *, cp,
                      crypto_digest_add_bytes(d, cp, strlen(cp)));
 -  crypto_digest_get_digest(d, digest_out, DIGEST_LEN);
 +  crypto_digest_get_digest(d, digest_out, len_out);
    crypto_free_digest_env(d);
  }
  
@@@ -566,31 -455,7 +566,31 @@@ compute_consensus_method(smartlist_t *v
  static int
  consensus_method_is_supported(int method)
  {
 -  return (method >= 1) && (method <= 5);
 +  return (method >= 1) && (method <= MAX_SUPPORTED_CONSENSUS_METHOD);
 +}
 +
 +/** Return a newly allocated string holding the numbers between low and high
 + * (inclusive) that are supported consensus methods. */
 +static char *
 +make_consensus_method_list(int low, int high, const char *separator)
 +{
 +  char *list;
 +
 +  char b[32];
 +  int i;
 +  smartlist_t *lst;
 +  lst = smartlist_create();
 +  for (i = low; i <= high; ++i) {
 +    if (!consensus_method_is_supported(i))
 +      continue;
 +    tor_snprintf(b, sizeof(b), "%d", i);
 +    smartlist_add(lst, tor_strdup(b));
 +  }
 +  list = smartlist_join_strings(lst, separator, 0, NULL);
 +  tor_assert(list);
 +  SMARTLIST_FOREACH(lst, char *, cp, tor_free(cp));
 +  smartlist_free(lst);
 +  return list;
  }
  
  /** Helper: given <b>lst</b>, a list of version strings such that every
@@@ -610,728 -475,6 +610,728 @@@ compute_consensus_versions_list(smartli
    return result;
  }
  
 +/** Helper: given a list of valid networkstatus_t, return a new string
 + * containing the contents of the consensus network parameter set.
 + */
 +/* private */ char *
 +dirvote_compute_params(smartlist_t *votes)
 +{
 +  int i;
 +  int32_t *vals;
 +
 +  int cur_param_len;
 +  const char *cur_param;
 +  const char *eq;
 +  char *result;
 +
 +  const int n_votes = smartlist_len(votes);
 +  smartlist_t *output;
 +  smartlist_t *param_list = smartlist_create();
 +
 +  /* We require that the parameter lists in the votes are well-formed: that
 +     is, that their keywords are unique and sorted, and that their values are
 +     between INT32_MIN and INT32_MAX inclusive.  This should be guaranteed by
 +     the parsing code. */
 +
 +  vals = tor_malloc(sizeof(int)*n_votes);
 +
 +  SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
 +    if (!v->net_params)
 +      continue;
 +    smartlist_add_all(param_list, v->net_params);
 +  } SMARTLIST_FOREACH_END(v);
 +
 +  if (smartlist_len(param_list) == 0) {
 +    tor_free(vals);
 +    smartlist_free(param_list);
 +    return NULL;
 +  }
 +
 +  smartlist_sort_strings(param_list);
 +  i = 0;
 +  cur_param = smartlist_get(param_list, 0);
 +  eq = strchr(cur_param, '=');
 +  tor_assert(eq);
 +  cur_param_len = (int)(eq+1 - cur_param);
 +
 +  output = smartlist_create();
 +
 +  SMARTLIST_FOREACH_BEGIN(param_list, const char *, param) {
 +    const char *next_param;
 +    int ok=0;
 +    eq = strchr(param, '=');
 +    tor_assert(i<n_votes);
 +    vals[i++] = (int32_t)
 +      tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL);
 +    tor_assert(ok);
 +
 +    if (param_sl_idx+1 == smartlist_len(param_list))
 +      next_param = NULL;
 +    else
 +      next_param = smartlist_get(param_list, param_sl_idx+1);
 +    if (!next_param || strncmp(next_param, param, cur_param_len)) {
 +      /* We've reached the end of a series. */
 +      int32_t median = median_int32(vals, i);
 +      char *out_string = tor_malloc(64+cur_param_len);
 +      memcpy(out_string, param, cur_param_len);
 +      tor_snprintf(out_string+cur_param_len,64, "%ld", (long)median);
 +      smartlist_add(output, out_string);
 +
 +      i = 0;
 +      if (next_param) {
 +        eq = strchr(next_param, '=');
 +        cur_param_len = (int)(eq+1 - next_param);
 +      }
 +    }
 +  } SMARTLIST_FOREACH_END(param);
 +
 +  result = smartlist_join_strings(output, " ", 0, NULL);
 +  SMARTLIST_FOREACH(output, char *, cp, tor_free(cp));
 +  smartlist_free(output);
 +  smartlist_free(param_list);
 +  tor_free(vals);
 +  return result;
 +}
 +
 +#define RANGE_CHECK(a,b,c,d,e,f,g,mx) \
 +       ((a) >= 0 && (a) <= (mx) && (b) >= 0 && (b) <= (mx) && \
 +        (c) >= 0 && (c) <= (mx) && (d) >= 0 && (d) <= (mx) && \
 +        (e) >= 0 && (e) <= (mx) && (f) >= 0 && (f) <= (mx) && \
 +        (g) >= 0 && (g) <= (mx))
 +
 +#define CHECK_EQ(a, b, margin) \
 +     ((a)-(b) >= 0 ? (a)-(b) <= (margin) : (b)-(a) <= (margin))
 +
 +typedef enum {
 + BW_WEIGHTS_NO_ERROR = 0,
 + BW_WEIGHTS_RANGE_ERROR = 1,
 + BW_WEIGHTS_SUMG_ERROR = 2,
 + BW_WEIGHTS_SUME_ERROR = 3,
 + BW_WEIGHTS_SUMD_ERROR = 4,
 + BW_WEIGHTS_BALANCE_MID_ERROR = 5,
 + BW_WEIGHTS_BALANCE_EG_ERROR = 6
 +} bw_weights_error_t;
 +
 +/**
 + * Verify that any weightings satisfy the balanced formulas.
 + */
 +static bw_weights_error_t
 +networkstatus_check_weights(int64_t Wgg, int64_t Wgd, int64_t Wmg,
 +                            int64_t Wme, int64_t Wmd, int64_t Wee,
 +                            int64_t Wed, int64_t scale, int64_t G,
 +                            int64_t M, int64_t E, int64_t D, int64_t T,
 +                            int64_t margin, int do_balance) {
 +  bw_weights_error_t berr = BW_WEIGHTS_NO_ERROR;
 +
 +  // Wed + Wmd + Wgd == 1
 +  if (!CHECK_EQ(Wed + Wmd + Wgd, scale, margin)) {
 +    berr = BW_WEIGHTS_SUMD_ERROR;
 +    goto out;
 +  }
 +
 +  // Wmg + Wgg == 1
 +  if (!CHECK_EQ(Wmg + Wgg, scale, margin)) {
 +    berr = BW_WEIGHTS_SUMG_ERROR;
 +    goto out;
 +  }
 +
 +  // Wme + Wee == 1
 +  if (!CHECK_EQ(Wme + Wee, scale, margin)) {
 +    berr = BW_WEIGHTS_SUME_ERROR;
 +    goto out;
 +  }
 +
 +  // Verify weights within range 0->1
 +  if (!RANGE_CHECK(Wgg, Wgd, Wmg, Wme, Wmd, Wed, Wee, scale)) {
 +    berr = BW_WEIGHTS_RANGE_ERROR;
 +    goto out;
 +  }
 +
 +  if (do_balance) {
 +    // Wgg*G + Wgd*D == Wee*E + Wed*D, already scaled
 +    if (!CHECK_EQ(Wgg*G + Wgd*D, Wee*E + Wed*D, (margin*T)/3)) {
 +      berr = BW_WEIGHTS_BALANCE_EG_ERROR;
 +      goto out;
 +    }
 +
 +    // Wgg*G + Wgd*D == M*scale + Wmd*D + Wme*E + Wmg*G, already scaled
 +    if (!CHECK_EQ(Wgg*G + Wgd*D, M*scale + Wmd*D + Wme*E + Wmg*G,
 +                (margin*T)/3)) {
 +      berr = BW_WEIGHTS_BALANCE_MID_ERROR;
 +      goto out;
 +    }
 +  }
 +
 + out:
 +  if (berr) {
 +    log_info(LD_DIR,
 +             "Bw weight mismatch %d. G="I64_FORMAT" M="I64_FORMAT
 +             " E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT
 +             " Wmd=%d Wme=%d Wmg=%d Wed=%d Wee=%d"
 +             " Wgd=%d Wgg=%d Wme=%d Wmg=%d",
 +             berr,
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +             (int)Wmd, (int)Wme, (int)Wmg, (int)Wed, (int)Wee,
 +             (int)Wgd, (int)Wgg, (int)Wme, (int)Wmg);
 +  }
 +
 +  return berr;
 +}
 +
 +/**
 + * This function computes the bandwidth weights for consensus method 10.
 + *
 + * It returns true if weights could be computed, false otherwise.
 + */
 +static int
 +networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
 +                                     int64_t M, int64_t E, int64_t D,
 +                                     int64_t T, int64_t weight_scale)
 +{
 +  bw_weights_error_t berr = 0;
 +  int64_t Wgg = -1, Wgd = -1;
 +  int64_t Wmg = -1, Wme = -1, Wmd = -1;
 +  int64_t Wed = -1, Wee = -1;
 +  const char *casename;
 +  char buf[512];
 +  int r;
 +
 +  if (G <= 0 || M <= 0 || E <= 0 || D <= 0) {
 +    log_warn(LD_DIR, "Consensus with empty bandwidth: "
 +                     "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                     " D="I64_FORMAT" T="I64_FORMAT,
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    return 0;
 +  }
 +
 +  /*
 +   * Computed from cases in 3.4.3 of dir-spec.txt
 +   *
 +   * 1. Neither are scarce
 +   * 2. Both Guard and Exit are scarce
 +   *    a. R+D <= S
 +   *    b. R+D > S
 +   * 3. One of Guard or Exit is scarce
 +   *    a. S+D < T/3
 +   *    b. S+D >= T/3
 +   */
 +  if (3*E >= T && 3*G >= T) { // E >= T/3 && G >= T/3
 +    /* Case 1: Neither are scarce.  */
 +    casename = "Case 1 (Wgd=Wmd=Wed)";
 +    Wgd = weight_scale/3;
 +    Wed = weight_scale/3;
 +    Wmd = weight_scale/3;
 +    Wee = (weight_scale*(E+G+M))/(3*E);
 +    Wme = weight_scale - Wee;
 +    Wmg = (weight_scale*(2*G-E-M))/(3*G);
 +    Wgg = weight_scale - Wmg;
 +
 +    berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed,
 +                                       weight_scale, G, M, E, D, T, 10, 1);
 +
 +    if (berr) {
 +      log_warn(LD_DIR,
 +             "Bw Weights error %d for %s v10. G="I64_FORMAT" M="I64_FORMAT
 +             " E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT
 +             " Wmd=%d Wme=%d Wmg=%d Wed=%d Wee=%d"
 +             " Wgd=%d Wgg=%d Wme=%d Wmg=%d weight_scale=%d",
 +             berr, casename,
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +             (int)Wmd, (int)Wme, (int)Wmg, (int)Wed, (int)Wee,
 +             (int)Wgd, (int)Wgg, (int)Wme, (int)Wmg, (int)weight_scale);
 +      return 0;
 +    }
 +  } else if (3*E < T && 3*G < T) { // E < T/3 && G < T/3
 +    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.
 +     */
 +    if (R+D < S) { // Subcase a
 +      Wgg = weight_scale;
 +      Wee = weight_scale;
 +      Wmg = 0;
 +      Wme = 0;
 +      Wmd = 0;
 +      if (E < G) {
 +        casename = "Case 2a (E scarce)";
 +        Wed = weight_scale;
 +        Wgd = 0;
 +      } else { /* E >= G */
 +        casename = "Case 2a (G scarce)";
 +        Wed = 0;
 +        Wgd = weight_scale;
 +      }
 +    } else { // Subcase b: R+D >= S
 +      casename = "Case 2b1 (Wgg=1, Wmd=Wgd)";
 +      Wee = (weight_scale*(E - G + M))/E;
 +      Wed = (weight_scale*(D - 2*E + 4*G - 2*M))/(3*D);
 +      Wme = (weight_scale*(G-M))/E;
 +      Wmg = 0;
 +      Wgg = weight_scale;
 +      Wmd = (weight_scale - Wed)/2;
 +      Wgd = (weight_scale - Wed)/2;
 +
 +      berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed,
 +                                       weight_scale, G, M, E, D, T, 10, 1);
 +
 +      if (berr) {
 +        casename = "Case 2b2 (Wgg=1, Wee=1)";
 +        Wgg = weight_scale;
 +        Wee = weight_scale;
 +        Wed = (weight_scale*(D - 2*E + G + M))/(3*D);
 +        Wmd = (weight_scale*(D - 2*M + G + E))/(3*D);
 +        Wme = 0;
 +        Wmg = 0;
 +
 +        if (Wmd < 0) { // Can happen if M > T/3
 +          casename = "Case 2b3 (Wmd=0)";
 +          Wmd = 0;
 +          log_warn(LD_DIR,
 +                   "Too much Middle bandwidth on the network to calculate "
 +                   "balanced bandwidth-weights. Consider increasing the "
 +                   "number of Guard nodes by lowering the requirements.");
 +        }
 +        Wgd = weight_scale - Wed - Wmd;
 +        berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
 +                  Wed, weight_scale, G, M, E, D, T, 10, 1);
 +      }
 +      if (berr != BW_WEIGHTS_NO_ERROR &&
 +              berr != BW_WEIGHTS_BALANCE_MID_ERROR) {
 +        log_warn(LD_DIR,
 +             "Bw Weights error %d for %s v10. G="I64_FORMAT" M="I64_FORMAT
 +             " E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT
 +             " Wmd=%d Wme=%d Wmg=%d Wed=%d Wee=%d"
 +             " Wgd=%d Wgg=%d Wme=%d Wmg=%d weight_scale=%d",
 +             berr, casename,
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +             (int)Wmd, (int)Wme, (int)Wmg, (int)Wed, (int)Wee,
 +             (int)Wgd, (int)Wgg, (int)Wme, (int)Wmg, (int)weight_scale);
 +        return 0;
 +      }
 +    }
 +  } else { // if (E < T/3 || G < T/3) {
 +    int64_t S = MIN(E, G);
 +    // Case 3: Exactly one of Guard or Exit is scarce
 +    if (!(3*E < T || 3*G < T) || !(3*G >= T || 3*E >= T)) {
 +      log_warn(LD_BUG,
 +           "Bw-Weights Case 3 v10 but with G="I64_FORMAT" M="
 +           I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    }
 +
 +    if (3*(S+D) < T) { // Subcase a: S+D < T/3
 +      if (G < E) {
 +        casename = "Case 3a (G scarce)";
 +        Wgg = Wgd = weight_scale;
 +        Wmd = Wed = Wmg = 0;
 +        // Minor subcase, if E is more scarce than M,
 +        // keep its bandwidth in place.
 +        if (E < M) Wme = 0;
 +        else Wme = (weight_scale*(E-M))/(2*E);
 +        Wee = weight_scale-Wme;
 +      } else { // G >= E
 +        casename = "Case 3a (E scarce)";
 +        Wee = Wed = weight_scale;
 +        Wmd = Wgd = Wme = 0;
 +        // Minor subcase, if G is more scarce than M,
 +        // keep its bandwidth in place.
 +        if (G < M) Wmg = 0;
 +        else Wmg = (weight_scale*(G-M))/(2*G);
 +        Wgg = weight_scale-Wmg;
 +      }
 +    } else { // Subcase b: S+D >= T/3
 +      // D != 0 because S+D >= T/3
 +      if (G < E) {
 +        casename = "Case 3bg (G scarce, Wgg=1, Wmd == Wed)";
 +        Wgg = weight_scale;
 +        Wgd = (weight_scale*(D - 2*G + E + M))/(3*D);
 +        Wmg = 0;
 +        Wee = (weight_scale*(E+M))/(2*E);
 +        Wme = weight_scale - Wee;
 +        Wmd = (weight_scale - Wgd)/2;
 +        Wed = (weight_scale - Wgd)/2;
 +
 +        berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
 +                    Wed, weight_scale, G, M, E, D, T, 10, 1);
 +      } else { // G >= E
 +        casename = "Case 3be (E scarce, Wee=1, Wmd == Wgd)";
 +        Wee = weight_scale;
 +        Wed = (weight_scale*(D - 2*E + G + M))/(3*D);
 +        Wme = 0;
 +        Wgg = (weight_scale*(G+M))/(2*G);
 +        Wmg = weight_scale - Wgg;
 +        Wmd = (weight_scale - Wed)/2;
 +        Wgd = (weight_scale - Wed)/2;
 +
 +        berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
 +                      Wed, weight_scale, G, M, E, D, T, 10, 1);
 +      }
 +      if (berr) {
 +        log_warn(LD_DIR,
 +             "Bw Weights error %d for %s v10. G="I64_FORMAT" M="I64_FORMAT
 +             " E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT
 +             " Wmd=%d Wme=%d Wmg=%d Wed=%d Wee=%d"
 +             " Wgd=%d Wgg=%d Wme=%d Wmg=%d weight_scale=%d",
 +             berr, casename,
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +             (int)Wmd, (int)Wme, (int)Wmg, (int)Wed, (int)Wee,
 +             (int)Wgd, (int)Wgg, (int)Wme, (int)Wmg, (int)weight_scale);
 +        return 0;
 +      }
 +    }
 +  }
 +
 +  /* We cast down the weights to 32 bit ints on the assumption that
 +   * weight_scale is ~= 10000. We need to ensure a rogue authority
 +   * doesn't break this assumption to rig our weights */
 +  tor_assert(0 < weight_scale && weight_scale < INT32_MAX);
 +
 +  /*
 +   * Provide Wgm=Wgg, Wmm=1, Wem=Wee, Weg=Wed. May later determine
 +   * that middle nodes need different bandwidth weights for dirport traffic,
 +   * or that weird exit policies need special weight, or that bridges
 +   * need special weight.
 +   *
 +   * NOTE: This list is sorted.
 +   */
 +  r = tor_snprintf(buf, sizeof(buf),
 +     "bandwidth-weights Wbd=%d Wbe=%d Wbg=%d Wbm=%d "
 +     "Wdb=%d "
 +     "Web=%d Wed=%d Wee=%d Weg=%d Wem=%d "
 +     "Wgb=%d Wgd=%d Wgg=%d Wgm=%d "
 +     "Wmb=%d Wmd=%d Wme=%d Wmg=%d Wmm=%d\n",
 +     (int)Wmd, (int)Wme, (int)Wmg, (int)weight_scale,
 +     (int)weight_scale,
 +     (int)weight_scale, (int)Wed, (int)Wee, (int)Wed, (int)Wee,
 +     (int)weight_scale, (int)Wgd, (int)Wgg, (int)Wgg,
 +     (int)weight_scale, (int)Wmd, (int)Wme, (int)Wmg, (int)weight_scale);
 +  if (r<0) {
 +    log_warn(LD_BUG,
 +             "Not enough space in buffer for bandwidth-weights line.");
 +    *buf = '\0';
 +    return 0;
 +  }
 +  smartlist_add(chunks, tor_strdup(buf));
 +
 +  log_notice(LD_CIRC, "Computed bandwidth weights for %s with v10: "
 +             "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +             " T="I64_FORMAT,
 +             casename,
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +  return 1;
 +}
 +/**
 + * This function computes the bandwidth weights for consensus method 9.
 + *
 + * It has been obsoleted in favor of consensus method 10.
 + */
 +static void
 +networkstatus_compute_bw_weights_v9(smartlist_t *chunks, int64_t G, int64_t M,
 +                              int64_t E, int64_t D, int64_t T,
 +                              int64_t weight_scale)
 +{
 +  int64_t Wgg = -1, Wgd = -1;
 +  int64_t Wmg = -1, Wme = -1, Wmd = -1;
 +  int64_t Wed = -1, Wee = -1;
 +  const char *casename;
 +  char buf[512];
 +  int r;
 +
 +  if (G <= 0 || M <= 0 || E <= 0 || D <= 0) {
 +    log_warn(LD_DIR, "Consensus with empty bandwidth: "
 +                     "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                     " D="I64_FORMAT" T="I64_FORMAT,
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    return;
 +  }
 +
 +  /*
 +   * Computed from cases in 3.4.3 of dir-spec.txt
 +   *
 +   * 1. Neither are scarce
 +   * 2. Both Guard and Exit are scarce
 +   *    a. R+D <= S
 +   *    b. R+D > S
 +   * 3. One of Guard or Exit is scarce
 +   *    a. S+D < T/3
 +   *    b. S+D >= T/3
 +   */
 +  if (3*E >= T && 3*G >= T) { // E >= T/3 && G >= T/3
 +    bw_weights_error_t berr = 0;
 +    /* Case 1: Neither are scarce.
 +     *
 +     * Attempt to ensure that we have a large amount of exit bandwidth
 +     * in the middle position.
 +     */
 +    casename = "Case 1 (Wme*E = Wmd*D)";
 +    Wgg = (weight_scale*(D+E+G+M))/(3*G);
 +    if (D==0) Wmd = 0;
 +    else Wmd = (weight_scale*(2*D + 2*E - G - M))/(6*D);
 +    Wme = (weight_scale*(2*D + 2*E - G - M))/(6*E);
 +    Wee = (weight_scale*(-2*D + 4*E + G + M))/(6*E);
 +    Wgd = 0;
 +    Wmg = weight_scale - Wgg;
 +    Wed = weight_scale - Wmd;
 +
 +    berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed,
 +                                       weight_scale, G, M, E, D, T, 10, 1);
 +
 +    if (berr) {
 +      log_warn(LD_DIR, "Bw Weights error %d for case %s. "
 +                       "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                       " D="I64_FORMAT" T="I64_FORMAT,
 +               berr, casename,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    }
 +  } else if (3*E < T && 3*G < T) { // E < T/3 && G < T/3
 +    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.
 +     */
 +    if (R+D < S) { // Subcase a
 +      Wgg = weight_scale;
 +      Wee = weight_scale;
 +      Wmg = 0;
 +      Wme = 0;
 +      Wmd = 0;
 +      if (E < G) {
 +        casename = "Case 2a (E scarce)";
 +        Wed = weight_scale;
 +        Wgd = 0;
 +      } else { /* E >= G */
 +        casename = "Case 2a (G scarce)";
 +        Wed = 0;
 +        Wgd = weight_scale;
 +      }
 +    } else { // Subcase b: R+D > S
 +      bw_weights_error_t berr = 0;
 +      casename = "Case 2b (Wme*E == Wmd*D)";
 +      if (D != 0) {
 +        Wgg = weight_scale;
 +        Wgd = (weight_scale*(D + E - 2*G + M))/(3*D); // T/3 >= G (Ok)
 +        Wmd = (weight_scale*(D + E + G - 2*M))/(6*D); // T/3 >= M
 +        Wme = (weight_scale*(D + E + G - 2*M))/(6*E);
 +        Wee = (weight_scale*(-D + 5*E - G + 2*M))/(6*E); // 2E+M >= T/3
 +        Wmg = 0;
 +        Wed = weight_scale - Wgd - Wmd;
 +
 +        berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed,
 +                                       weight_scale, G, M, E, D, T, 10, 1);
 +      }
 +
 +      if (D == 0 || berr) { // Can happen if M > T/3
 +        casename = "Case 2b (E=G)";
 +        Wgg = weight_scale;
 +        Wee = weight_scale;
 +        Wmg = 0;
 +        Wme = 0;
 +        Wmd = 0;
 +        if (D == 0) Wgd = 0;
 +        else Wgd = (weight_scale*(D+E-G))/(2*D);
 +        Wed = weight_scale - Wgd;
 +        berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
 +                Wed, weight_scale, G, M, E, D, T, 10, 1);
 +      }
 +      if (berr != BW_WEIGHTS_NO_ERROR &&
 +              berr != BW_WEIGHTS_BALANCE_MID_ERROR) {
 +        log_warn(LD_DIR, "Bw Weights error %d for case %s. "
 +                         "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                         " D="I64_FORMAT" T="I64_FORMAT,
 +                 berr, casename,
 +                 I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                 I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +      }
 +    }
 +  } else { // if (E < T/3 || G < T/3) {
 +    int64_t S = MIN(E, G);
 +    // Case 3: Exactly one of Guard or Exit is scarce
 +    if (!(3*E < T || 3*G < T) || !(3*G >= T || 3*E >= T)) {
 +      log_warn(LD_BUG,
 +           "Bw-Weights Case 3 but with G="I64_FORMAT" M="
 +           I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    }
 +
 +    if (3*(S+D) < T) { // Subcase a: S+D < T/3
 +      if (G < E) {
 +        casename = "Case 3a (G scarce)";
 +        Wgg = Wgd = weight_scale;
 +        Wmd = Wed = Wmg = 0;
 +        // Minor subcase, if E is more scarce than M,
 +        // keep its bandwidth in place.
 +        if (E < M) Wme = 0;
 +        else Wme = (weight_scale*(E-M))/(2*E);
 +        Wee = weight_scale-Wme;
 +      } else { // G >= E
 +        casename = "Case 3a (E scarce)";
 +        Wee = Wed = weight_scale;
 +        Wmd = Wgd = Wme = 0;
 +        // Minor subcase, if G is more scarce than M,
 +        // keep its bandwidth in place.
 +        if (G < M) Wmg = 0;
 +        else Wmg = (weight_scale*(G-M))/(2*G);
 +        Wgg = weight_scale-Wmg;
 +      }
 +    } else { // Subcase b: S+D >= T/3
 +      bw_weights_error_t berr = 0;
 +      // D != 0 because S+D >= T/3
 +      if (G < E) {
 +        casename = "Case 3b (G scarce, Wme*E == Wmd*D)";
 +        Wgd = (weight_scale*(D + E - 2*G + M))/(3*D);
 +        Wmd = (weight_scale*(D + E + G - 2*M))/(6*D);
 +        Wme = (weight_scale*(D + E + G - 2*M))/(6*E);
 +        Wee = (weight_scale*(-D + 5*E - G + 2*M))/(6*E);
 +        Wgg = weight_scale;
 +        Wmg = 0;
 +        Wed = weight_scale - Wgd - Wmd;
 +
 +        berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
 +                    Wed, weight_scale, G, M, E, D, T, 10, 1);
 +      } else { // G >= E
 +        casename = "Case 3b (E scarce, Wme*E == Wmd*D)";
 +        Wgg = (weight_scale*(D + E + G + M))/(3*G);
 +        Wmd = (weight_scale*(2*D + 2*E - G - M))/(6*D);
 +        Wme = (weight_scale*(2*D + 2*E - G - M))/(6*E);
 +        Wee = (weight_scale*(-2*D + 4*E + G + M))/(6*E);
 +        Wgd = 0;
 +        Wmg = weight_scale - Wgg;
 +        Wed = weight_scale - Wmd;
 +
 +        berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
 +                      Wed, weight_scale, G, M, E, D, T, 10, 1);
 +      }
 +      if (berr) {
 +        log_warn(LD_DIR, "Bw Weights error %d for case %s. "
 +                         "G="I64_FORMAT" M="I64_FORMAT
 +                         " E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT,
 +                 berr, casename,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +      }
 +    }
 +  }
 +
 +  /* We cast down the weights to 32 bit ints on the assumption that
 +   * weight_scale is ~= 10000. We need to ensure a rogue authority
 +   * doesn't break this assumption to rig our weights */
 +  tor_assert(0 < weight_scale && weight_scale < INT32_MAX);
 +
 +  if (Wgg < 0 || Wgg > weight_scale) {
 +    log_warn(LD_DIR, "Bw %s: Wgg="I64_FORMAT"! G="I64_FORMAT
 +            " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +            " T="I64_FORMAT,
 +             casename, I64_PRINTF_ARG(Wgg),
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +
 +    Wgg = MAX(MIN(Wgg, weight_scale), 0);
 +  }
 +  if (Wgd < 0 || Wgd > weight_scale) {
 +    log_warn(LD_DIR, "Bw %s: Wgd="I64_FORMAT"! G="I64_FORMAT
 +            " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +            " T="I64_FORMAT,
 +             casename, I64_PRINTF_ARG(Wgd),
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    Wgd = MAX(MIN(Wgd, weight_scale), 0);
 +  }
 +  if (Wmg < 0 || Wmg > weight_scale) {
 +    log_warn(LD_DIR, "Bw %s: Wmg="I64_FORMAT"! G="I64_FORMAT
 +            " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +            " T="I64_FORMAT,
 +             casename, I64_PRINTF_ARG(Wmg),
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    Wmg = MAX(MIN(Wmg, weight_scale), 0);
 +  }
 +  if (Wme < 0 || Wme > weight_scale) {
 +    log_warn(LD_DIR, "Bw %s: Wme="I64_FORMAT"! G="I64_FORMAT
 +            " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +            " T="I64_FORMAT,
 +             casename, I64_PRINTF_ARG(Wme),
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    Wme = MAX(MIN(Wme, weight_scale), 0);
 +  }
 +  if (Wmd < 0 || Wmd > weight_scale) {
 +    log_warn(LD_DIR, "Bw %s: Wmd="I64_FORMAT"! G="I64_FORMAT
 +            " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +            " T="I64_FORMAT,
 +             casename, I64_PRINTF_ARG(Wmd),
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    Wmd = MAX(MIN(Wmd, weight_scale), 0);
 +  }
 +  if (Wee < 0 || Wee > weight_scale) {
 +    log_warn(LD_DIR, "Bw %s: Wee="I64_FORMAT"! G="I64_FORMAT
 +            " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +            " T="I64_FORMAT,
 +             casename, I64_PRINTF_ARG(Wee),
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    Wee = MAX(MIN(Wee, weight_scale), 0);
 +  }
 +  if (Wed < 0 || Wed > weight_scale) {
 +    log_warn(LD_DIR, "Bw %s: Wed="I64_FORMAT"! G="I64_FORMAT
 +            " M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +            " T="I64_FORMAT,
 +             casename, I64_PRINTF_ARG(Wed),
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +    Wed = MAX(MIN(Wed, weight_scale), 0);
 +  }
 +
 +  // Add consensus weight keywords
 +  smartlist_add(chunks, tor_strdup("bandwidth-weights "));
 +  /*
 +   * Provide Wgm=Wgg, Wmm=1, Wem=Wee, Weg=Wed. May later determine
 +   * that middle nodes need different bandwidth weights for dirport traffic,
 +   * or that weird exit policies need special weight, or that bridges
 +   * need special weight.
 +   *
 +   * NOTE: This list is sorted.
 +   */
 +  r = tor_snprintf(buf, sizeof(buf),
 +     "Wbd=%d Wbe=%d Wbg=%d Wbm=%d "
 +     "Wdb=%d "
 +     "Web=%d Wed=%d Wee=%d Weg=%d Wem=%d "
 +     "Wgb=%d Wgd=%d Wgg=%d Wgm=%d "
 +     "Wmb=%d Wmd=%d Wme=%d Wmg=%d Wmm=%d\n",
 +     (int)Wmd, (int)Wme, (int)Wmg, (int)weight_scale,
 +     (int)weight_scale,
 +     (int)weight_scale, (int)Wed, (int)Wee, (int)Wed, (int)Wee,
 +     (int)weight_scale, (int)Wgd, (int)Wgg, (int)Wgg,
 +     (int)weight_scale, (int)Wmd, (int)Wme, (int)Wmg, (int)weight_scale);
 +  if (r<0) {
 +    log_warn(LD_BUG,
 +             "Not enough space in buffer for bandwidth-weights line.");
 +    *buf = '\0';
 +  }
 +  smartlist_add(chunks, tor_strdup(buf));
 +  log_notice(LD_CIRC, "Computed bandwidth weights for %s with v9: "
 +             "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +             " T="I64_FORMAT,
 +             casename,
 +             I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +             I64_PRINTF_ARG(D), I64_PRINTF_ARG(T));
 +}
 +
  /** Given a list of vote networkstatus_t in <b>votes</b>, our public
   * authority <b>identity_key</b>, our private authority <b>signing_key</b>,
   * and the number of <b>total_authorities</b> that we believe exist in our
@@@ -1346,27 -489,18 +1346,27 @@@ networkstatus_compute_consensus(smartli
                                  crypto_pk_env_t *identity_key,
                                  crypto_pk_env_t *signing_key,
                                  const char *legacy_id_key_digest,
 -                                crypto_pk_env_t *legacy_signing_key)
 +                                crypto_pk_env_t *legacy_signing_key,
 +                                consensus_flavor_t flavor)
  {
    smartlist_t *chunks;
    char *result = NULL;
    int consensus_method;
 -
    time_t valid_after, fresh_until, valid_until;
    int vote_seconds, dist_seconds;
    char *client_versions = NULL, *server_versions = NULL;
    smartlist_t *flags;
 +  const char *flavor_name;
 +  int64_t G=0, M=0, E=0, D=0, T=0; /* For bandwidth weights */
 +  const routerstatus_format_type_t rs_format =
 +    flavor == FLAV_NS ? NS_V3_CONSENSUS : NS_V3_CONSENSUS_MICRODESC;
 +  char *params = NULL;
 +  int added_weights = 0;
 +  tor_assert(flavor == FLAV_NS || flavor == FLAV_MICRODESC);
    tor_assert(total_authorities >= smartlist_len(votes));
  
 +  flavor_name = networkstatus_get_flavor_name(flavor);
 +
    if (!smartlist_len(votes)) {
      log_warn(LD_DIR, "Can't compute a consensus from no votes.");
      return NULL;
@@@ -1459,7 -593,7 +1459,7 @@@
    chunks = smartlist_create();
  
    {
 -    char buf[1024];
 +    char *buf=NULL;
      char va_buf[ISO_TIME_LEN+1], fu_buf[ISO_TIME_LEN+1],
        vu_buf[ISO_TIME_LEN+1];
      char *flaglist;
@@@ -1468,20 -602,16 +1468,20 @@@
      format_iso_time(vu_buf, valid_until);
      flaglist = smartlist_join_strings(flags, " ", 0, NULL);
  
 -    smartlist_add(chunks, tor_strdup("network-status-version 3\n"
 -                                     "vote-status consensus\n"));
 +    tor_asprintf(&buf, "network-status-version 3%s%s\n"
 +                 "vote-status consensus\n",
 +                 flavor == FLAV_NS ? "" : " ",
 +                 flavor == FLAV_NS ? "" : flavor_name);
 +
 +    smartlist_add(chunks, buf);
  
      if (consensus_method >= 2) {
 -      tor_snprintf(buf, sizeof(buf), "consensus-method %d\n",
 +      tor_asprintf(&buf, "consensus-method %d\n",
                     consensus_method);
 -      smartlist_add(chunks, tor_strdup(buf));
 +      smartlist_add(chunks, buf);
      }
  
 -    tor_snprintf(buf, sizeof(buf),
 +    tor_asprintf(&buf,
                   "valid-after %s\n"
                   "fresh-until %s\n"
                   "valid-until %s\n"
@@@ -1492,26 -622,18 +1492,26 @@@
                   va_buf, fu_buf, vu_buf,
                   vote_seconds, dist_seconds,
                   client_versions, server_versions, flaglist);
 -    smartlist_add(chunks, tor_strdup(buf));
 +    smartlist_add(chunks, buf);
  
      tor_free(flaglist);
    }
  
 +  if (consensus_method >= MIN_METHOD_FOR_PARAMS) {
 +    params = dirvote_compute_params(votes);
 +    if (params) {
 +      smartlist_add(chunks, tor_strdup("params "));
 +      smartlist_add(chunks, params);
 +      smartlist_add(chunks, tor_strdup("\n"));
 +    }
 +  }
 +
    /* Sort the votes. */
    smartlist_sort(votes, _compare_votes_by_authority_id);
    /* Add the authority sections. */
    {
      smartlist_t *dir_sources = smartlist_create();
 -    SMARTLIST_FOREACH(votes, networkstatus_t *, v,
 -    {
 +    SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
        dir_src_ent_t *e = tor_malloc_zero(sizeof(dir_src_ent_t));
        e->v = v;
        e->digest = get_voter(v)->identity_digest;
@@@ -1525,17 -647,18 +1525,17 @@@
          e_legacy->is_legacy = 1;
          smartlist_add(dir_sources, e_legacy);
        }
 -    });
 +    } SMARTLIST_FOREACH_END(v);
      smartlist_sort(dir_sources, _compare_dir_src_ents_by_authority_id);
  
 -    SMARTLIST_FOREACH(dir_sources, const dir_src_ent_t *, e,
 -    {
 -      char buf[1024];
 +    SMARTLIST_FOREACH_BEGIN(dir_sources, const dir_src_ent_t *, e) {
        struct in_addr in;
        char ip[INET_NTOA_BUF_LEN];
        char fingerprint[HEX_DIGEST_LEN+1];
        char votedigest[HEX_DIGEST_LEN+1];
        networkstatus_t *v = e->v;
        networkstatus_voter_info_t *voter = get_voter(v);
 +      char *buf = NULL;
  
        if (e->is_legacy)
          tor_assert(consensus_method >= 2);
@@@ -1546,22 -669,22 +1546,22 @@@
        base16_encode(votedigest, sizeof(votedigest), voter->vote_digest,
                      DIGEST_LEN);
  
 -      tor_snprintf(buf, sizeof(buf),
 +      tor_asprintf(&buf,
                     "dir-source %s%s %s %s %s %d %d\n",
                     voter->nickname, e->is_legacy ? "-legacy" : "",
                     fingerprint, voter->address, ip,
                     voter->dir_port,
                     voter->or_port);
 -      smartlist_add(chunks, tor_strdup(buf));
 +      smartlist_add(chunks, buf);
        if (! e->is_legacy) {
 -        tor_snprintf(buf, sizeof(buf),
 +        tor_asprintf(&buf,
                       "contact %s\n"
                       "vote-digest %s\n",
                       voter->contact,
                       votedigest);
 -        smartlist_add(chunks, tor_strdup(buf));
 +        smartlist_add(chunks, buf);
        }
 -    });
 +    } SMARTLIST_FOREACH_END(e);
      SMARTLIST_FOREACH(dir_sources, dir_src_ent_t *, e, tor_free(e));
      smartlist_free(dir_sources);
    }
@@@ -1578,10 -701,7 +1578,10 @@@
      smartlist_t *versions = smartlist_create();
      smartlist_t *exitsummaries = smartlist_create();
      uint32_t *bandwidths = tor_malloc(sizeof(uint32_t) * smartlist_len(votes));
 +    uint32_t *measured_bws = tor_malloc(sizeof(uint32_t) *
 +                                        smartlist_len(votes));
      int num_bandwidths;
 +    int num_mbws;
  
      int *n_voter_flags; /* n_voter_flags[j] is the number of flags that
                           * votes[j] knows about. */
@@@ -1693,12 -813,10 +1693,12 @@@
        const char *chosen_name = NULL;
        int exitsummary_disagreement = 0;
        int is_named = 0, is_unnamed = 0, is_running = 0;
 +      int is_guard = 0, is_exit = 0;
        int naming_conflict = 0;
        int n_listing = 0;
        int i;
 -      char buf[256];
 +      char *buf=NULL;
 +      char microdesc_digest[DIGEST256_LEN];
  
        /* Of the next-to-be-considered digest in each voter, which is first? */
        SMARTLIST_FOREACH(votes, networkstatus_t *, v, {
@@@ -1717,7 -835,6 +1717,7 @@@
        smartlist_clear(chosen_flags);
        smartlist_clear(versions);
        num_bandwidths = 0;
 +      num_mbws = 0;
  
        /* Okay, go through all the entries for this digest. */
        SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
@@@ -1751,9 -868,6 +1751,9 @@@
          }
  
          /* count bandwidths */
 +        if (rs->status.has_measured_bw)
 +          measured_bws[num_mbws++] = rs->status.measured_bw;
 +
          if (rs->status.has_bandwidth)
            bandwidths[num_bandwidths++] = rs->status.bandwidth;
        } SMARTLIST_FOREACH_END(v);
@@@ -1765,9 -879,7 +1765,9 @@@
  
        /* Figure out the most popular opinion of what the most recent
         * routerinfo and its contents are. */
 -      rs = compute_routerstatus_consensus(matching_descs);
 +      memset(microdesc_digest, 0, sizeof(microdesc_digest));
 +      rs = compute_routerstatus_consensus(matching_descs, consensus_method,
 +                                          microdesc_digest);
        /* Copy bits of that into rs_out. */
        tor_assert(!memcmp(lowest_id, rs->status.identity_digest, DIGEST_LEN));
        memcpy(rs_out.identity_digest, lowest_id, DIGEST_LEN);
@@@ -1813,11 -925,7 +1813,11 @@@
          } else {
            if (flag_counts[fl_sl_idx] > n_flag_voters[fl_sl_idx]/2) {
              smartlist_add(chosen_flags, (char*)fl);
 -            if (!strcmp(fl, "Running"))
 +            if (!strcmp(fl, "Exit"))
 +              is_exit = 1;
 +            else if (!strcmp(fl, "Guard"))
 +              is_guard = 1;
 +            else if (!strcmp(fl, "Running"))
                is_running = 1;
            }
          }
@@@ -1837,31 -945,11 +1837,31 @@@
        }
  
        /* Pick a bandwidth */
 -      if (consensus_method >= 5 && num_bandwidths > 0) {
 +      if (consensus_method >= 6 && num_mbws > 2) {
 +        rs_out.has_bandwidth = 1;
 +        rs_out.bandwidth = median_uint32(measured_bws, num_mbws);
 +      } else if (consensus_method >= 5 && num_bandwidths > 0) {
          rs_out.has_bandwidth = 1;
          rs_out.bandwidth = median_uint32(bandwidths, num_bandwidths);
        }
  
 +      if (consensus_method >= MIN_METHOD_FOR_BW_WEIGHTS) {
 +        if (rs_out.has_bandwidth) {
 +          T += rs_out.bandwidth;
 +          if (is_exit && is_guard)
 +            D += rs_out.bandwidth;
 +          else if (is_exit)
 +            E += rs_out.bandwidth;
 +          else if (is_guard)
 +            G += rs_out.bandwidth;
 +          else
 +            M += rs_out.bandwidth;
 +        } else {
 +          log_warn(LD_BUG, "Missing consensus bandwidth for router %s",
 +              rs_out.nickname);
 +        }
 +      }
 +
        /* Ok, we already picked a descriptor digest we want to list
         * previously.  Now we want to use the exit policy summary from
         * that descriptor.  If everybody plays nice all the voters who
@@@ -1946,23 -1034,11 +1946,23 @@@
          }
        }
  
 -      /* Okay!! Now we can write the descriptor... */
 -      /*     First line goes into "buf". */
 -      routerstatus_format_entry(buf, sizeof(buf), &rs_out, NULL, 1, 0);
 -      smartlist_add(chunks, tor_strdup(buf));
 -      /*     Second line is all flags.  The "\n" is missing. */
 +      {
 +        char buf[4096];
 +        /* Okay!! Now we can write the descriptor... */
 +        /*     First line goes into "buf". */
 +        routerstatus_format_entry(buf, sizeof(buf), &rs_out, NULL,
 +                                  rs_format);
 +        smartlist_add(chunks, tor_strdup(buf));
 +      }
 +      /*     Now an m line, if applicable. */
 +      if (flavor == FLAV_MICRODESC &&
 +          !tor_digest256_is_zero(microdesc_digest)) {
 +        char m[BASE64_DIGEST256_LEN+1], *cp;
 +        digest256_to_base64(m, microdesc_digest);
 +        tor_asprintf(&cp, "m %s\n", m);
 +        smartlist_add(chunks, cp);
 +      }
 +      /*     Next line is all flags.  The "\n" is missing. */
        smartlist_add(chunks,
                      smartlist_join_strings(chosen_flags, " ", 0, NULL));
        /*     Now the version line. */
@@@ -1973,16 -1049,24 +1973,16 @@@
        smartlist_add(chunks, tor_strdup("\n"));
        /*     Now the weight line. */
        if (rs_out.has_bandwidth) {
 -        int r = tor_snprintf(buf, sizeof(buf),
 -                             "w Bandwidth=%d\n", rs_out.bandwidth);
 -        if (r<0) {
 -          log_warn(LD_BUG, "Not enough space in buffer for weight line.");
 -          *buf = '\0';
 -        }
 -        smartlist_add(chunks, tor_strdup(buf));
 -      };
 +        char *cp=NULL;
 +        tor_asprintf(&cp, "w Bandwidth=%d\n", rs_out.bandwidth);
 +        smartlist_add(chunks, cp);
 +      }
 +
        /*     Now the exitpolicy summary line. */
 -      if (rs_out.has_exitsummary) {
 -        char buf[MAX_POLICY_LINE_LEN+1];
 -        int r = tor_snprintf(buf, sizeof(buf), "p %s\n", rs_out.exitsummary);
 -        if (r<0) {
 -          log_warn(LD_BUG, "Not enough space in buffer for exitpolicy line.");
 -          *buf = '\0';
 -        }
 -        smartlist_add(chunks, tor_strdup(buf));
 -      };
 +      if (rs_out.has_exitsummary && flavor == FLAV_NS) {
 +        tor_asprintf(&buf, "p %s\n", rs_out.exitsummary);
 +        smartlist_add(chunks, buf);
 +      }
  
        /* And the loop is over and we move on to the next router */
      }
@@@ -2003,97 -1087,34 +2003,97 @@@
      smartlist_free(versions);
      smartlist_free(exitsummaries);
      tor_free(bandwidths);
 +    tor_free(measured_bws);
 +  }
 +
 +  if (consensus_method >= MIN_METHOD_FOR_FOOTER) {
 +    /* Starting with consensus method 9, we clearly mark the directory
 +     * footer region */
 +    smartlist_add(chunks, tor_strdup("directory-footer\n"));
 +  }
 +
 +  if (consensus_method >= MIN_METHOD_FOR_BW_WEIGHTS) {
 +    int64_t weight_scale = BW_WEIGHT_SCALE;
 +    char *bw_weight_param = NULL;
 +
 +    // Parse params, extract BW_WEIGHT_SCALE if present
 +    // DO NOT use consensus_param_bw_weight_scale() in this code!
 +    // The consensus is not formed yet!
 +    if (params) {
 +      if (strcmpstart(params, "bwweightscale=") == 0)
 +        bw_weight_param = params;
 +      else
 +        bw_weight_param = strstr(params, " bwweightscale=");
 +    }
 +
 +    if (bw_weight_param) {
 +      int ok=0;
 +      char *eq = strchr(bw_weight_param, '=');
 +      if (eq) {
 +        weight_scale = tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok,
 +                                         NULL);
 +        if (!ok) {
 +          log_warn(LD_DIR, "Bad element '%s' in bw weight param",
 +              escaped(bw_weight_param));
 +          weight_scale = BW_WEIGHT_SCALE;
 +        }
 +      } else {
 +        log_warn(LD_DIR, "Bad element '%s' in bw weight param",
 +            escaped(bw_weight_param));
 +        weight_scale = BW_WEIGHT_SCALE;
 +      }
 +    }
 +
 +    if (consensus_method < 10) {
 +      networkstatus_compute_bw_weights_v9(chunks, G, M, E, D, T, weight_scale);
 +      added_weights = 1;
 +    } else {
 +      added_weights = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D,
 +                                                           T, weight_scale);
 +    }
    }
  
    /* Add a signature. */
    {
 -    char digest[DIGEST_LEN];
 +    char digest[DIGEST256_LEN];
      char fingerprint[HEX_DIGEST_LEN+1];
      char signing_key_fingerprint[HEX_DIGEST_LEN+1];
 +    digest_algorithm_t digest_alg =
 +      flavor == FLAV_NS ? DIGEST_SHA1 : DIGEST_SHA256;
 +    size_t digest_len =
 +      flavor == FLAV_NS ? DIGEST_LEN : DIGEST256_LEN;
 +    const char *algname = crypto_digest_algorithm_get_name(digest_alg);
 +    char *buf = NULL;
 +    char sigbuf[4096];
  
      smartlist_add(chunks, tor_strdup("directory-signature "));
  
      /* Compute the hash of the chunks. */
 -    hash_list_members(digest, chunks);
 +    hash_list_members(digest, digest_len, chunks, digest_alg);
  
      /* Get the fingerprints */
      crypto_pk_get_fingerprint(identity_key, fingerprint, 0);
      crypto_pk_get_fingerprint(signing_key, signing_key_fingerprint, 0);
  
      /* add the junk that will go at the end of the line. */
 -    tor_snprintf(buf, sizeof(buf), "%s %s\n", fingerprint,
 -                 signing_key_fingerprint);
 +    if (flavor == FLAV_NS) {
 +      tor_asprintf(&buf, "%s %s\n", fingerprint,
 +                   signing_key_fingerprint);
 +    } else {
 +      tor_asprintf(&buf, "%s %s %s\n",
 +                   algname, fingerprint,
 +                   signing_key_fingerprint);
 +    }
 +    smartlist_add(chunks, buf);
      /* And the signature. */
 -    if (router_append_dirobj_signature(buf, sizeof(buf), digest,
 +    sigbuf[0] = '\0';
 +    if (router_append_dirobj_signature(sigbuf, sizeof(sigbuf),
 +                                       digest, digest_len,
                                         signing_key)) {
        log_warn(LD_BUG, "Couldn't sign consensus networkstatus.");
        return NULL; /* This leaks, but it should never happen. */
      }
 -    smartlist_add(chunks, tor_strdup(buf));
 +    smartlist_add(chunks, tor_strdup(sigbuf));
  
      if (legacy_id_key_digest && legacy_signing_key && consensus_method >= 3) {
        smartlist_add(chunks, tor_strdup("directory-signature "));
@@@ -2101,23 -1122,14 +2101,23 @@@
                      legacy_id_key_digest, DIGEST_LEN);
        crypto_pk_get_fingerprint(legacy_signing_key,
                                  signing_key_fingerprint, 0);
 -      tor_snprintf(buf, sizeof(buf), "%s %s\n", fingerprint,
 -                   signing_key_fingerprint);
 -      if (router_append_dirobj_signature(buf, sizeof(buf), digest,
 +      if (flavor == FLAV_NS) {
 +        tor_asprintf(&buf, "%s %s\n", fingerprint,
 +                     signing_key_fingerprint);
 +      } else {
 +        tor_asprintf(&buf, "%s %s %s\n",
 +                     algname, fingerprint,
 +                     signing_key_fingerprint);
 +      }
 +      smartlist_add(chunks, buf);
 +      sigbuf[0] = '\0';
 +      if (router_append_dirobj_signature(sigbuf, sizeof(sigbuf),
 +                                         digest, digest_len,
                                           legacy_signing_key)) {
          log_warn(LD_BUG, "Couldn't sign consensus networkstatus.");
          return NULL; /* This leaks, but it should never happen. */
        }
 -      smartlist_add(chunks, tor_strdup(buf));
 +      smartlist_add(chunks, tor_strdup(sigbuf));
      }
    }
  
@@@ -2134,15 -1146,11 +2134,15 @@@
      networkstatus_t *c;
      if (!(c = networkstatus_parse_vote_from_string(result, NULL,
                                                     NS_TYPE_CONSENSUS))) {
 -      log_err(LD_BUG,"Generated a networkstatus consensus we couldn't "
 +      log_err(LD_BUG, "Generated a networkstatus consensus we couldn't "
                "parse.");
        tor_free(result);
        return NULL;
      }
 +    // Verify balancing parameters
 +    if (consensus_method >= MIN_METHOD_FOR_BW_WEIGHTS && added_weights) {
 +      networkstatus_verify_bw_weights(c);
 +    }
      networkstatus_vote_free(c);
    }
  
@@@ -2163,14 -1171,10 +2163,14 @@@ networkstatus_add_detached_signatures(n
                                        const char **msg_out)
  {
    int r = 0;
 +  const char *flavor;
 +  smartlist_t *siglist;
    tor_assert(sigs);
    tor_assert(target);
    tor_assert(target->type == NS_TYPE_CONSENSUS);
  
 +  flavor = networkstatus_get_flavor_name(target->flavor);
 +
    /* Do the times seem right? */
    if (target->valid_after != sigs->valid_after) {
      *msg_out = "Valid-After times do not match "
@@@ -2187,179 -1191,79 +2187,179 @@@
        "when adding detached signatures to consensus";
      return -1;
    }
 -  /* Are they the same consensus? */
 -  if (memcmp(target->networkstatus_digest, sigs->networkstatus_digest,
 -             DIGEST_LEN)) {
 -    *msg_out = "Digest mismatch when adding detached signatures to consensus";
 +  siglist = strmap_get(sigs->signatures, flavor);
 +  if (!siglist) {
 +    *msg_out = "No signatures for given consensus flavor";
      return -1;
    }
  
 -  /* For each voter in src... */
 -  SMARTLIST_FOREACH_BEGIN(sigs->signatures, networkstatus_voter_info_t *,
 -                          src_voter) {
 -      char voter_identity[HEX_DIGEST_LEN+1];
 -      networkstatus_voter_info_t *target_voter =
 -        networkstatus_get_voter_by_id(target, src_voter->identity_digest);
 -      authority_cert_t *cert = NULL;
 -
 -      base16_encode(voter_identity, sizeof(voter_identity),
 -                    src_voter->identity_digest, DIGEST_LEN);
 -      log_info(LD_DIR, "Looking at signature from %s", voter_identity);
 -      /* If the target doesn't know about this voter, then forget it. */
 -      if (!target_voter) {
 -        log_info(LD_DIR, "We do not know about %s", voter_identity);
 -        continue;
 +  /** Make sure all the digests we know match, and at least one matches. */
 +  {
 +    digests_t *digests = strmap_get(sigs->digests, flavor);
 +    int n_matches = 0;
 +    digest_algorithm_t alg;
 +    if (!digests) {
 +      *msg_out = "No digests for given consensus flavor";
 +      return -1;
 +    }
 +    for (alg = DIGEST_SHA1; alg < N_DIGEST_ALGORITHMS; ++alg) {
 +      if (!tor_mem_is_zero(digests->d[alg], DIGEST256_LEN)) {
 +        if (!memcmp(target->digests.d[alg], digests->d[alg], DIGEST256_LEN)) {
 +          ++n_matches;
 +        } else {
 +          *msg_out = "Mismatched digest.";
 +          return -1;
 +        }
        }
 +    }
 +    if (!n_matches) {
 +      *msg_out = "No regognized digests for given consensus flavor";
 +    }
 +  }
  
 -      /* If the target already has a good signature from this voter, then skip
 -       * this one. */
 -      if (target_voter->good_signature) {
 -        log_info(LD_DIR, "We already have a good signature from %s",
 -                         voter_identity);
 -        continue;
 -      }
 +  /* For each voter in src... */
 +  SMARTLIST_FOREACH_BEGIN(siglist, document_signature_t *, sig) {
 +    char voter_identity[HEX_DIGEST_LEN+1];
 +    networkstatus_voter_info_t *target_voter =
 +      networkstatus_get_voter_by_id(target, sig->identity_digest);
 +    authority_cert_t *cert = NULL;
 +    const char *algorithm;
 +    document_signature_t *old_sig = NULL;
 +
 +    algorithm = crypto_digest_algorithm_get_name(sig->alg);
 +
 +    base16_encode(voter_identity, sizeof(voter_identity),
 +                  sig->identity_digest, DIGEST_LEN);
 +    log_info(LD_DIR, "Looking at signature from %s using %s", voter_identity,
 +             algorithm);
 +    /* If the target doesn't know about this voter, then forget it. */
 +    if (!target_voter) {
 +      log_info(LD_DIR, "We do not know any voter with ID %s", voter_identity);
 +      continue;
 +    }
  
 -      /* Try checking the signature if we haven't already. */
 -      if (!src_voter->good_signature && !src_voter->bad_signature) {
 -        cert = authority_cert_get_by_digests(src_voter->identity_digest,
 -                                             src_voter->signing_key_digest);
 -        if (cert) {
 -          networkstatus_check_voter_signature(target, src_voter, cert);
 -        }
 +    old_sig = voter_get_sig_by_algorithm(target_voter, sig->alg);
 +
 +    /* If the target already has a good signature from this voter, then skip
 +     * this one. */
 +    if (old_sig && old_sig->good_signature) {
 +      log_info(LD_DIR, "We already have a good signature from %s using %s",
 +               voter_identity, algorithm);
 +      continue;
 +    }
 +
 +    /* Try checking the signature if we haven't already. */
 +    if (!sig->good_signature && !sig->bad_signature) {
 +      cert = authority_cert_get_by_digests(sig->identity_digest,
 +                                           sig->signing_key_digest);
 +      if (cert)
 +        networkstatus_check_document_signature(target, sig, cert);
 +    }
 +
 +    /* If this signature is good, or we don't have any signature yet,
 +     * then maybe add it. */
 +    if (sig->good_signature || !old_sig || old_sig->bad_signature) {
 +      log_info(LD_DIR, "Adding signature from %s with %s", voter_identity,
 +               algorithm);
 +      ++r;
 +      if (old_sig) {
 +        smartlist_remove(target_voter->sigs, old_sig);
 +        document_signature_free(old_sig);
        }
 +      smartlist_add(target_voter->sigs, document_signature_dup(sig));
 +    } else {
 +      log_info(LD_DIR, "Not adding signature from %s", voter_identity);
 +    }
 +  } SMARTLIST_FOREACH_END(sig);
 +
 +  return r;
 +}
 +
 +/** Return a newly allocated string containing all the signatures on
 + * <b>consensus</b> by all voters. If <b>for_detached_signatures</b> is true,
 + * then the signatures will be put in a detached signatures document, so
 + * prefix any non-NS-flavored signatures with "additional-signature" rather
 + * than "directory-signature". */
 +static char *
 +networkstatus_format_signatures(networkstatus_t *consensus,
 +                                int for_detached_signatures)
 +{
 +  smartlist_t *elements;
 +  char buf[4096];
 +  char *result = NULL;
 +  int n_sigs = 0;
 +  const consensus_flavor_t flavor = consensus->flavor;
 +  const char *flavor_name = networkstatus_get_flavor_name(flavor);
 +  const char *keyword;
 +
 +  if (for_detached_signatures && flavor != FLAV_NS)
 +    keyword = "additional-signature";
 +  else
 +    keyword = "directory-signature";
 +
 +  elements = smartlist_create();
  
 -      /* If this signature is good, or we don't have any signature yet,
 -       * then add it. */
 -      if (src_voter->good_signature || !target_voter->signature) {
 -        log_info(LD_DIR, "Adding signature from %s", voter_identity);
 -        ++r;
 -        tor_free(target_voter->signature);
 -        target_voter->signature =
 -          tor_memdup(src_voter->signature, src_voter->signature_len);
 -        memcpy(target_voter->signing_key_digest, src_voter->signing_key_digest,
 -               DIGEST_LEN);
 -        target_voter->signature_len = src_voter->signature_len;
 -        target_voter->good_signature = src_voter->good_signature;
 -        target_voter->bad_signature = src_voter->bad_signature;
 +  SMARTLIST_FOREACH_BEGIN(consensus->voters, networkstatus_voter_info_t *, v) {
 +    SMARTLIST_FOREACH_BEGIN(v->sigs, document_signature_t *, sig) {
 +      char sk[HEX_DIGEST_LEN+1];
 +      char id[HEX_DIGEST_LEN+1];
 +      if (!sig->signature || sig->bad_signature)
 +        continue;
 +      ++n_sigs;
 +      base16_encode(sk, sizeof(sk), sig->signing_key_digest, DIGEST_LEN);
 +      base16_encode(id, sizeof(id), sig->identity_digest, DIGEST_LEN);
 +      if (flavor == FLAV_NS) {
 +        tor_snprintf(buf, sizeof(buf),
 +                     "%s %s %s\n-----BEGIN SIGNATURE-----\n",
 +                     keyword, id, sk);
        } else {
 -        log_info(LD_DIR, "Not adding signature from %s", voter_identity);
 +        const char *digest_name =
 +          crypto_digest_algorithm_get_name(sig->alg);
 +        tor_snprintf(buf, sizeof(buf),
 +                     "%s%s%s %s %s %s\n-----BEGIN SIGNATURE-----\n",
 +                     keyword,
 +                     for_detached_signatures ? " " : "",
 +                     for_detached_signatures ? flavor_name : "",
 +                     digest_name, id, sk);
        }
 -  } SMARTLIST_FOREACH_END(src_voter);
 +      smartlist_add(elements, tor_strdup(buf));
 +      base64_encode(buf, sizeof(buf), sig->signature, sig->signature_len);
 +      strlcat(buf, "-----END SIGNATURE-----\n", sizeof(buf));
 +      smartlist_add(elements, tor_strdup(buf));
 +    } SMARTLIST_FOREACH_END(sig);
 +  } SMARTLIST_FOREACH_END(v);
  
 -  return r;
 +  result = smartlist_join_strings(elements, "", 0, NULL);
 +  SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp));
 +  smartlist_free(elements);
 +  if (!n_sigs)
 +    tor_free(result);
 +  return result;
  }
  
  /** Return a newly allocated string holding the detached-signatures document
 - * corresponding to the signatures on <b>consensus</b>. */
 + * corresponding to the signatures on <b>consensuses</b>, which must contain
 + * exactly one FLAV_NS consensus, and no more than one consensus for each
 + * other flavor. */
  char *
 -networkstatus_get_detached_signatures(networkstatus_t *consensus)
 +networkstatus_get_detached_signatures(smartlist_t *consensuses)
  {
    smartlist_t *elements;
    char buf[4096];
 -  char *result = NULL;
 -  int n_sigs = 0;
 -  tor_assert(consensus);
 -  tor_assert(consensus->type == NS_TYPE_CONSENSUS);
 +  char *result = NULL, *sigs = NULL;
 +  networkstatus_t *consensus_ns = NULL;
 +  tor_assert(consensuses);
 +
 +  SMARTLIST_FOREACH(consensuses, networkstatus_t *, ns, {
 +      tor_assert(ns);
 +      tor_assert(ns->type == NS_TYPE_CONSENSUS);
 +      if (ns && ns->flavor == FLAV_NS)
 +        consensus_ns = ns;
 +  });
 +  if (!consensus_ns) {
 +    log_warn(LD_BUG, "No NS consensus given.");
 +    return NULL;
 +  }
  
    elements = smartlist_create();
  
@@@ -2368,11 -1272,10 +2368,11 @@@
        vu_buf[ISO_TIME_LEN+1];
      char d[HEX_DIGEST_LEN+1];
  
 -    base16_encode(d, sizeof(d), consensus->networkstatus_digest, DIGEST_LEN);
 -    format_iso_time(va_buf, consensus->valid_after);
 -    format_iso_time(fu_buf, consensus->fresh_until);
 -    format_iso_time(vu_buf, consensus->valid_until);
 +    base16_encode(d, sizeof(d),
 +                  consensus_ns->digests.d[DIGEST_SHA1], DIGEST_LEN);
 +    format_iso_time(va_buf, consensus_ns->valid_after);
 +    format_iso_time(fu_buf, consensus_ns->fresh_until);
 +    format_iso_time(vu_buf, consensus_ns->valid_until);
  
      tor_snprintf(buf, sizeof(buf),
                   "consensus-digest %s\n"
@@@ -2382,89 -1285,45 +2382,89 @@@
      smartlist_add(elements, tor_strdup(buf));
    }
  
 -  SMARTLIST_FOREACH(consensus->voters, networkstatus_voter_info_t *, v,
 -    {
 -      char sk[HEX_DIGEST_LEN+1];
 -      char id[HEX_DIGEST_LEN+1];
 -      if (!v->signature || v->bad_signature)
 +  /* Get all the digests for the non-FLAV_NS consensuses */
 +  SMARTLIST_FOREACH_BEGIN(consensuses, networkstatus_t *, ns) {
 +    const char *flavor_name = networkstatus_get_flavor_name(ns->flavor);
 +    int alg;
 +    if (ns->flavor == FLAV_NS)
 +      continue;
 +
 +    /* start with SHA256; we don't include SHA1 for anything but the basic
 +     * consensus. */
 +    for (alg = DIGEST_SHA256; alg < N_DIGEST_ALGORITHMS; ++alg) {
 +      char d[HEX_DIGEST256_LEN+1];
 +      const char *alg_name =
 +        crypto_digest_algorithm_get_name(alg);
 +      if (tor_mem_is_zero(ns->digests.d[alg], DIGEST256_LEN))
          continue;
 -      ++n_sigs;
 -      base16_encode(sk, sizeof(sk), v->signing_key_digest, DIGEST_LEN);
 -      base16_encode(id, sizeof(id), v->identity_digest, DIGEST_LEN);
 -      tor_snprintf(buf, sizeof(buf),
 -                   "directory-signature %s %s\n-----BEGIN SIGNATURE-----\n",
 -                   id, sk);
 -      smartlist_add(elements, tor_strdup(buf));
 -      base64_encode(buf, sizeof(buf), v->signature, v->signature_len);
 -      strlcat(buf, "-----END SIGNATURE-----\n", sizeof(buf));
 +      base16_encode(d, sizeof(d), ns->digests.d[alg], DIGEST256_LEN);
 +      tor_snprintf(buf, sizeof(buf), "additional-digest %s %s %s\n",
 +                   flavor_name, alg_name, d);
        smartlist_add(elements, tor_strdup(buf));
 -    });
 +    }
 +  } SMARTLIST_FOREACH_END(ns);
 +
 +  /* Now get all the sigs for non-FLAV_NS consensuses */
 +  SMARTLIST_FOREACH_BEGIN(consensuses, networkstatus_t *, ns) {
 +    char *sigs;
 +    if (ns->flavor == FLAV_NS)
 +      continue;
 +    sigs = networkstatus_format_signatures(ns, 1);
 +    if (!sigs) {
 +      log_warn(LD_DIR, "Couldn't format signatures");
 +      goto err;
 +    }
 +    smartlist_add(elements, sigs);
 +  } SMARTLIST_FOREACH_END(ns);
  
 -  result = smartlist_join_strings(elements, "", 0, NULL);
 +  /* Now add the FLAV_NS consensus signatrures. */
 +  sigs = networkstatus_format_signatures(consensus_ns, 1);
 +  if (!sigs)
 +    goto err;
 +  smartlist_add(elements, sigs);
  
 +  result = smartlist_join_strings(elements, "", 0, NULL);
 + err:
    SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp));
    smartlist_free(elements);
    return result;
  }
  
 +/** Return a newly allocated string holding a detached-signatures document for
 + * all of the in-progress consensuses in the <b>n_flavors</b>-element array at
 + * <b>pending</b>. */
 +static char *
 +get_detached_signatures_from_pending_consensuses(pending_consensus_t *pending,
 +                                                 int n_flavors)
 +{
 +  int flav;
 +  char *signatures;
 +  smartlist_t *c = smartlist_create();
 +  for (flav = 0; flav < n_flavors; ++flav) {
 +    if (pending[flav].consensus)
 +      smartlist_add(c, pending[flav].consensus);
 +  }
 +  signatures = networkstatus_get_detached_signatures(c);
 +  smartlist_free(c);
 +  return signatures;
 +}
 +
  /** Release all storage held in <b>s</b>. */
  void
  ns_detached_signatures_free(ns_detached_signatures_t *s)
  {
 +  if (!s)
 +    return;
    if (s->signatures) {
 -    SMARTLIST_FOREACH(s->signatures, networkstatus_voter_info_t *, v,
 -      {
 -        tor_free(v->signature);
 -        tor_free(v);
 -      });
 -    smartlist_free(s->signatures);
 +    STRMAP_FOREACH(s->signatures, flavor, smartlist_t *, sigs) {
 +      SMARTLIST_FOREACH(sigs, document_signature_t *, sig,
 +                        document_signature_free(sig));
 +      smartlist_free(sigs);
 +    } STRMAP_FOREACH_END;
 +    strmap_free(s->signatures, NULL);
 +    strmap_free(s->digests, _tor_free);
    }
 +
    tor_free(s);
  }
  
@@@ -2654,7 -1513,7 +2654,7 @@@ dirvote_act(or_options_t *options, time
    if (voting_schedule.voting_ends < now &&
        !voting_schedule.have_built_consensus) {
      log_notice(LD_DIR, "Time to compute a consensus.");
 -    dirvote_compute_consensus();
 +    dirvote_compute_consensuses();
      /* XXXX We will want to try again later if we haven't got enough
       * votes yet.  Implement this if it turns out to ever happen. */
      voting_schedule.have_built_consensus = 1;
@@@ -2691,13 -1550,14 +2691,13 @@@ static smartlist_t *pending_vote_list 
  /** List of pending_vote_t for the previous vote.  After we've used them to
   * build a consensus, the votes go here for the next period. */
  static smartlist_t *previous_vote_list = NULL;
 -/** The body of the consensus that we're currently building.  Once we
 - * have it built, it goes into dirserv.c */
 -static char *pending_consensus_body = NULL;
 +
 +static pending_consensus_t pending_consensuses[N_CONSENSUS_FLAVORS];
 +
  /** The detached signatures for the consensus that we're currently
   * building. */
  static char *pending_consensus_signatures = NULL;
 -/** The parsed in-progress consensus document. */
 -static networkstatus_t *pending_consensus = NULL;
 +
  /** List of ns_detached_signatures_t: hold signatures that get posted to us
   * before we have generated the consensus on our own. */
  static smartlist_t *pending_consensus_signature_list = NULL;
@@@ -2791,39 -1651,15 +2791,39 @@@ dirvote_fetch_missing_votes(void
  static void
  dirvote_fetch_missing_signatures(void)
  {
 -  if (!pending_consensus)
 +  int need_any = 0;
 +  int i;
 +  for (i=0; i < N_CONSENSUS_FLAVORS; ++i) {
 +    networkstatus_t *consensus = pending_consensuses[i].consensus;
 +    if (!consensus ||
 +        networkstatus_check_consensus_signature(consensus, -1) == 1) {
 +      /* We have no consensus, or we have one that's signed by everybody. */
 +      continue;
 +    }
 +    need_any = 1;
 +  }
 +  if (!need_any)
      return;
 -  if (networkstatus_check_consensus_signature(pending_consensus, -1) == 1)
 -    return; /* we have a signature from everybody. */
  
    directory_get_from_all_authorities(DIR_PURPOSE_FETCH_DETACHED_SIGNATURES,
                                       0, NULL);
  }
  
 +/** Release all storage held by pending consensuses (those waiting for
 + * signatures). */
 +static void
 +dirvote_clear_pending_consensuses(void)
 +{
 +  int i;
 +  for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
 +    pending_consensus_t *pc = &pending_consensuses[i];
 +    tor_free(pc->body);
 +
 +    networkstatus_vote_free(pc->consensus);
 +    pc->consensus = NULL;
 +  }
 +}
 +
  /** Drop all currently pending votes, consensus, and detached signatures. */
  static void
  dirvote_clear_votes(int all_votes)
@@@ -2861,8 -1697,12 +2861,8 @@@
                        tor_free(cp));
      smartlist_clear(pending_consensus_signature_list);
    }
 -  tor_free(pending_consensus_body);
    tor_free(pending_consensus_signatures);
 -  if (pending_consensus) {
 -    networkstatus_vote_free(pending_consensus);
 -    pending_consensus = NULL;
 -  }
 +  dirvote_clear_pending_consensuses();
  }
  
  /** Return a newly allocated string containing the hex-encoded v3 authority
@@@ -2920,13 -1760,7 +2920,13 @@@ dirvote_add_vote(const char *vote_body
    }
    tor_assert(smartlist_len(vote->voters) == 1);
    vi = get_voter(vote);
 -  tor_assert(vi->good_signature == 1);
 +  {
 +    int any_sig_good = 0;
 +    SMARTLIST_FOREACH(vi->sigs, document_signature_t *, sig,
 +                      if (sig->good_signature)
 +                        any_sig_good = 1);
 +    tor_assert(any_sig_good);
 +  }
    ds = trusteddirserver_get_by_v3_auth_digest(vi->identity_digest);
    if (!ds) {
      char *keys = list_v3_auth_ids();
@@@ -2963,9 -1797,6 +2963,9 @@@
      goto err;
    }
  
 +  /* Fetch any new router descriptors we just learned about */
 +  update_consensus_router_descriptor_downloads(time(NULL), 1, vote);
 +
    /* Now see whether we already have a vote from this authority. */
    SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, v, {
        if (! memcmp(v->vote->cert->cache_info.identity_digest,
@@@ -2974,8 -1805,7 +2974,8 @@@
          networkstatus_voter_info_t *vi_old = get_voter(v->vote);
          if (!memcmp(vi_old->vote_digest, vi->vote_digest, DIGEST_LEN)) {
            /* Ah, it's the same vote. Not a problem. */
 -          log_info(LD_DIR, "Discarding a vote we already have.");
 +          log_info(LD_DIR, "Discarding a vote we already have (from %s).",
 +                   vi->address);
            if (*status_out < 200)
              *status_out = 200;
            goto discard;
@@@ -3002,7 -1832,7 +3002,7 @@@
            goto err;
          }
        }
 -    });
 +  });
  
    pending_vote = tor_malloc_zero(sizeof(pending_vote_t));
    pending_vote->vote_body = new_cached_dir(tor_strndup(vote_body,
@@@ -3026,7 -1856,8 +3026,7 @@@
      *status_out = 400;
  
   discard:
 -  if (vote)
 -    networkstatus_vote_free(vote);
 +  networkstatus_vote_free(vote);
  
    if (end_of_vote && !strcmpstart(end_of_vote, "network-status-version ")) {
      vote_body = end_of_vote;
@@@ -3053,18 -1884,14 +3053,18 @@@
   * pending_consensus: it won't be ready to be published until we have
   * everybody else's signatures collected too. (V3 Authority only) */
  static int
 -dirvote_compute_consensus(void)
 +dirvote_compute_consensuses(void)
  {
    /* Have we got enough votes to try? */
 -  int n_votes, n_voters;
 +  int n_votes, n_voters, n_vote_running = 0;
    smartlist_t *votes = NULL, *votestrings = NULL;
    char *consensus_body = NULL, *signatures = NULL, *votefile;
    networkstatus_t *consensus = NULL;
    authority_cert_t *my_cert;
 +  pending_consensus_t pending[N_CONSENSUS_FLAVORS];
 +  int flav;
 +
 +  memset(pending, 0, sizeof(pending));
  
    if (!pending_vote_list)
      pending_vote_list = smartlist_create();
@@@ -3076,19 -1903,6 +3076,19 @@@
               "%d of %d", n_votes, n_voters/2);
      goto err;
    }
 +  tor_assert(pending_vote_list);
 +  SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, v, {
 +    if (smartlist_string_isin(v->vote->known_flags, "Running"))
 +      n_vote_running++;
 +  });
 +  if (!n_vote_running) {
 +    /* See task 1066. */
 +    log_warn(LD_DIR, "Nobody has voted on the Running flag. Generating "
 +                     "and publishing a consensus without Running nodes "
 +                     "would make many clients stop working. Not "
 +                     "generating a consensus!");
 +    goto err;
 +  }
  
    if (!(my_cert = get_my_v3_authority_cert())) {
      log_warn(LD_DIR, "Can't generate consensus without a certificate.");
@@@ -3117,7 -1931,6 +3117,7 @@@
      char legacy_dbuf[DIGEST_LEN];
      crypto_pk_env_t *legacy_sign=NULL;
      char *legacy_id_digest = NULL;
 +    int n_generated = 0;
      if (get_options()->V3AuthUseLegacyKey) {
        authority_cert_t *cert = get_my_v3_legacy_cert();
        legacy_sign = get_my_v3_legacy_signing_key();
@@@ -3126,58 -1939,39 +3126,58 @@@
          legacy_id_digest = legacy_dbuf;
        }
      }
 -    consensus_body = networkstatus_compute_consensus(
 +
 +    for (flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
 +      const char *flavor_name = networkstatus_get_flavor_name(flav);
 +      consensus_body = networkstatus_compute_consensus(
          votes, n_voters,
          my_cert->identity_key,
 -        get_my_v3_authority_signing_key(), legacy_id_digest, legacy_sign);
 -  }
 -  if (!consensus_body) {
 -    log_warn(LD_DIR, "Couldn't generate a consensus at all!");
 -    goto err;
 -  }
 -  consensus = networkstatus_parse_vote_from_string(consensus_body, NULL,
 -                                                   NS_TYPE_CONSENSUS);
 -  if (!consensus) {
 -    log_warn(LD_DIR, "Couldn't parse consensus we generated!");
 -    goto err;
 +        get_my_v3_authority_signing_key(), legacy_id_digest, legacy_sign,
 +        flav);
 +
 +      if (!consensus_body) {
 +        log_warn(LD_DIR, "Couldn't generate a %s consensus at all!",
 +                 flavor_name);
 +        continue;
 +      }
 +      consensus = networkstatus_parse_vote_from_string(consensus_body, NULL,
 +                                                       NS_TYPE_CONSENSUS);
 +      if (!consensus) {
 +        log_warn(LD_DIR, "Couldn't parse %s consensus we generated!",
 +                 flavor_name);
 +        tor_free(consensus_body);
 +        continue;
 +      }
 +
 +      /* 'Check' our own signature, to mark it valid. */
 +      networkstatus_check_consensus_signature(consensus, -1);
 +
 +      pending[flav].body = consensus_body;
 +      pending[flav].consensus = consensus;
 +      n_generated++;
 +      consensus_body = NULL;
 +      consensus = NULL;
 +    }
 +    if (!n_generated) {
 +      log_warn(LD_DIR, "Couldn't generate any consensus flavors at all.");
 +      goto err;
 +    }
    }
 -  /* 'Check' our own signature, to mark it valid. */
 -  networkstatus_check_consensus_signature(consensus, -1);
  
 -  signatures = networkstatus_get_detached_signatures(consensus);
 +  signatures = get_detached_signatures_from_pending_consensuses(
 +       pending, N_CONSENSUS_FLAVORS);
 +
    if (!signatures) {
      log_warn(LD_DIR, "Couldn't extract signatures.");
      goto err;
    }
  
 -  tor_free(pending_consensus_body);
 -  pending_consensus_body = consensus_body;
 +  dirvote_clear_pending_consensuses();
 +  memcpy(pending_consensuses, pending, sizeof(pending));
 +
    tor_free(pending_consensus_signatures);
    pending_consensus_signatures = signatures;
  
 -  if (pending_consensus)
 -    networkstatus_vote_free(pending_consensus);
 -  pending_consensus = consensus;
 -
    if (pending_consensus_signature_list) {
      int n_sigs = 0;
      /* we may have gotten signatures for this consensus before we built
@@@ -3185,7 -1979,7 +3185,7 @@@
      SMARTLIST_FOREACH(pending_consensus_signature_list, char *, sig,
        {
          const char *msg = NULL;
 -        int r = dirvote_add_signatures_to_pending_consensus(sig, &msg);
 +        int r = dirvote_add_signatures_to_all_pending_consensuses(sig, &msg);
          if (r >= 0)
            n_sigs += r;
          else
@@@ -3209,10 -2003,10 +3209,10 @@@
                                 strlen(pending_consensus_signatures), 0);
    log_notice(LD_DIR, "Signature(s) posted.");
  
 +  smartlist_free(votes);
    return 0;
   err:
 -  if (votes)
 -    smartlist_free(votes);
 +  smartlist_free(votes);
    tor_free(consensus_body);
    tor_free(signatures);
    networkstatus_vote_free(consensus);
@@@ -3221,62 -2015,76 +3221,62 @@@
  }
  
  /** Helper: we just got the <b>detached_signatures_body</b> sent to us as
 - * signatures on the currently pending consensus.  Add them to the consensus
 + * signatures on the currently pending consensus.  Add them to <b>pc</b>
   * as appropriate.  Return the number of signatures added. (?) */
  static int
  dirvote_add_signatures_to_pending_consensus(
 -                       const char *detached_signatures_body,
 +                       pending_consensus_t *pc,
 +                       ns_detached_signatures_t *sigs,
                         const char **msg_out)
  {
 -  ns_detached_signatures_t *sigs = NULL;
 +  const char *flavor_name;
    int r = -1;
  
 -  tor_assert(detached_signatures_body);
 -  tor_assert(msg_out);
 -
    /* Only call if we have a pending consensus right now. */
 -  tor_assert(pending_consensus);
 -  tor_assert(pending_consensus_body);
 +  tor_assert(pc->consensus);
 +  tor_assert(pc->body);
    tor_assert(pending_consensus_signatures);
  
 +  flavor_name = networkstatus_get_flavor_name(pc->consensus->flavor);
    *msg_out = NULL;
  
 -  if (!(sigs = networkstatus_parse_detached_signatures(
 -                               detached_signatures_body, NULL))) {
 -    *msg_out = "Couldn't parse detached signatures.";
 -    goto err;
 +  {
 +    smartlist_t *sig_list = strmap_get(sigs->signatures, flavor_name);
 +    log_info(LD_DIR, "Have %d signatures for adding to %s consensus.",
 +             sig_list ? smartlist_len(sig_list) : 0, flavor_name);
    }
 -
 -  log_info(LD_DIR, "Have %d signatures for adding to consensus.",
 -                   smartlist_len(sigs->signatures));
 -  r = networkstatus_add_detached_signatures(pending_consensus,
 -                                            sigs, msg_out);
 +  r = networkstatus_add_detached_signatures(pc->consensus, sigs, msg_out);
    log_info(LD_DIR,"Added %d signatures to consensus.", r);
  
    if (r >= 1) {
 -    char *new_detached =
 -      networkstatus_get_detached_signatures(pending_consensus);
 -    const char *src;
 +    char *new_signatures =
 +      networkstatus_format_signatures(pc->consensus, 0);
      char *dst, *dst_end;
      size_t new_consensus_len;
 -    if (!new_detached) {
 +    if (!new_signatures) {
        *msg_out = "No signatures to add";
        goto err;
      }
      new_consensus_len =
 -      strlen(pending_consensus_body) + strlen(new_detached) + 1;
 -    pending_consensus_body = tor_realloc(pending_consensus_body,
 -                                         new_consensus_len);
 -    dst_end = pending_consensus_body + new_consensus_len;
 -    dst = strstr(pending_consensus_body, "directory-signature ");
 +      strlen(pc->body) + strlen(new_signatures) + 1;
 +    pc->body = tor_realloc(pc->body, new_consensus_len);
 +    dst_end = pc->body + new_consensus_len;
 +    dst = strstr(pc->body, "directory-signature ");
      tor_assert(dst);
 -    src = strstr(new_detached, "directory-signature ");
 -    tor_assert(src);
 -    strlcpy(dst, src, dst_end-dst);
 +    strlcpy(dst, new_signatures, dst_end-dst);
  
      /* We remove this block once it has failed to crash for a while.  But
       * unless it shows up in profiles, we're probably better leaving it in,
       * just in case we break detached signature processing at some point. */
      {
 -      ns_detached_signatures_t *sigs =
 -        networkstatus_parse_detached_signatures(new_detached, NULL);
        networkstatus_t *v = networkstatus_parse_vote_from_string(
 -                                             pending_consensus_body, NULL,
 +                                             pc->body, NULL,
                                               NS_TYPE_CONSENSUS);
        tor_assert(v);
        networkstatus_vote_free(v);
      }
 -    tor_free(pending_consensus_signatures);
 -    pending_consensus_signatures = new_detached;
      *msg_out = "Signatures added";
 +    tor_free(new_signatures);
    } else if (r == 0) {
      *msg_out = "Signatures ignored";
    } else {
@@@ -3288,62 -2096,8 +3288,62 @@@
    if (!*msg_out)
      *msg_out = "Unrecognized error while adding detached signatures.";
   done:
 -  if (sigs)
 -    ns_detached_signatures_free(sigs);
 +  return r;
 +}
 +
 +static int
 +dirvote_add_signatures_to_all_pending_consensuses(
 +                       const char *detached_signatures_body,
 +                       const char **msg_out)
 +{
 +  int r=0, i, n_added = 0, errors = 0;
 +  ns_detached_signatures_t *sigs;
 +  tor_assert(detached_signatures_body);
 +  tor_assert(msg_out);
 +  tor_assert(pending_consensus_signatures);
 +
 +  if (!(sigs = networkstatus_parse_detached_signatures(
 +                               detached_signatures_body, NULL))) {
 +    *msg_out = "Couldn't parse detached signatures.";
 +    goto err;
 +  }
 +
 +  for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
 +    int res;
 +    pending_consensus_t *pc = &pending_consensuses[i];
 +    if (!pc->consensus)
 +      continue;
 +    res = dirvote_add_signatures_to_pending_consensus(pc, sigs, msg_out);
 +    if (res < 0)
 +      errors++;
 +    else
 +      n_added += res;
 +  }
 +
 +  if (errors && !n_added) {
 +    r = -1;
 +    goto err;
 +  }
 +
 +  if (n_added && pending_consensuses[FLAV_NS].consensus) {
 +    char *new_detached =
 +      get_detached_signatures_from_pending_consensuses(
 +                      pending_consensuses, N_CONSENSUS_FLAVORS);
 +    if (new_detached) {
 +      tor_free(pending_consensus_signatures);
 +      pending_consensus_signatures = new_detached;
 +    }
 +  }
 +
 +  r = n_added;
 +  goto done;
 + err:
 +  if (!*msg_out)
 +    *msg_out = "Unrecognized error while adding detached signatures.";
 + done:
 +  ns_detached_signatures_free(sigs);
 +  /* XXXX NM Check how return is used.  We can now have an error *and*
 +     signatures added. */
    return r;
  }
  
@@@ -3356,10 -2110,10 +3356,10 @@@ dirvote_add_signatures(const char *deta
                         const char *source,
                         const char **msg)
  {
 -  if (pending_consensus) {
 +  if (pending_consensuses[FLAV_NS].consensus) {
      log_notice(LD_DIR, "Got a signature from %s. "
                         "Adding it to the pending consensus.", source);
 -    return dirvote_add_signatures_to_pending_consensus(
 +    return dirvote_add_signatures_to_all_pending_consensuses(
                                       detached_signatures_body, msg);
    } else {
      log_notice(LD_DIR, "Got a signature from %s. "
@@@ -3378,25 -2132,17 +3378,25 @@@
  static int
  dirvote_publish_consensus(void)
  {
 -  /* Can we actually publish it yet? */
 -  if (!pending_consensus ||
 -      networkstatus_check_consensus_signature(pending_consensus, 1)<0) {
 -    log_warn(LD_DIR, "Not enough info to publish pending consensus");
 -    return -1;
 -  }
 +  int i;
 +
 +  /* Now remember all the other consensuses as if we were a directory cache. */
 +  for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
 +    pending_consensus_t *pending = &pending_consensuses[i];
 +    const char *name;
 +    name = networkstatus_get_flavor_name(i);
 +    tor_assert(name);
 +    if (!pending->consensus ||
 +      networkstatus_check_consensus_signature(pending->consensus, 1)<0) {
 +      log_warn(LD_DIR, "Not enough info to publish pending %s consensus",name);
 +      continue;
 +    }
  
 -  if (networkstatus_set_current_consensus(pending_consensus_body, 0))
 -    log_warn(LD_DIR, "Error publishing consensus");
 -  else
 -    log_notice(LD_DIR, "Consensus published.");
 +    if (networkstatus_set_current_consensus(pending->body, name, 0))
 +      log_warn(LD_DIR, "Error publishing %s consensus", name);
 +    else
 +      log_notice(LD_DIR, "Published %s consensus", name);
 +  }
  
    return 0;
  }
@@@ -3406,16 -2152,20 +3406,16 @@@ voi
  dirvote_free_all(void)
  {
    dirvote_clear_votes(1);
 -  /* now empty as a result of clear_pending_votes. */
 +  /* now empty as a result of dirvote_clear_votes(). */
    smartlist_free(pending_vote_list);
    pending_vote_list = NULL;
    smartlist_free(previous_vote_list);
    previous_vote_list = NULL;
  
 -  tor_free(pending_consensus_body);
 +  dirvote_clear_pending_consensuses();
    tor_free(pending_consensus_signatures);
 -  if (pending_consensus) {
 -    networkstatus_vote_free(pending_consensus);
 -    pending_consensus = NULL;
 -  }
    if (pending_consensus_signature_list) {
 -    /* now empty as a result of clear_pending_votes. */
 +    /* now empty as a result of dirvote_clear_votes(). */
      smartlist_free(pending_consensus_signature_list);
      pending_consensus_signature_list = NULL;
    }
@@@ -3427,14 -2177,13 +3427,14 @@@
  
  /** Return the body of the consensus that we're currently trying to build. */
  const char *
 -dirvote_get_pending_consensus(void)
 +dirvote_get_pending_consensus(consensus_flavor_t flav)
  {
 -  return pending_consensus_body;
 +  tor_assert(((int)flav) >= 0 && flav < N_CONSENSUS_FLAVORS);
 +  return pending_consensuses[flav].body;
  }
  
  /** Return the signatures that we know for the consensus that we're currently
 - * trying to build */
 + * trying to build. */
  const char *
  dirvote_get_pending_detached_signatures(void)
  {
@@@ -3480,147 -2229,15 +3480,147 @@@ dirvote_get_vote(const char *fp, int fl
    } else {
      if (pending_vote_list && include_pending) {
        SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, pv,
 -        if (!memcmp(pv->vote->networkstatus_digest, fp, DIGEST_LEN))
 +        if (!memcmp(pv->vote->digests.d[DIGEST_SHA1], fp, DIGEST_LEN))
            return pv->vote_body);
      }
      if (previous_vote_list && include_previous) {
        SMARTLIST_FOREACH(previous_vote_list, pending_vote_t *, pv,
 -        if (!memcmp(pv->vote->networkstatus_digest, fp, DIGEST_LEN))
 +        if (!memcmp(pv->vote->digests.d[DIGEST_SHA1], fp, DIGEST_LEN))
            return pv->vote_body);
      }
    }
    return NULL;
  }
  
 +/** Construct and return a new microdescriptor from a routerinfo <b>ri</b>.
 + *
 + * XXX Right now, there is only one way to generate microdescriptors from
 + * router descriptors.  This may change in future consensus methods.  If so,
 + * we'll need an internal way to remember which method we used, and ask for a
 + * particular method.
 + **/
 +microdesc_t *
 +dirvote_create_microdescriptor(const routerinfo_t *ri)
 +{
 +  microdesc_t *result = NULL;
 +  char *key = NULL, *summary = NULL, *family = NULL;
 +  char buf[1024];
 +  size_t keylen;
 +  char *out = buf, *end = buf+sizeof(buf);
 +
 +  if (crypto_pk_write_public_key_to_string(ri->onion_pkey, &key, &keylen)<0)
 +    goto done;
 +  summary = policy_summarize(ri->exit_policy);
 +  if (ri->declared_family)
 +    family = smartlist_join_strings(ri->declared_family, " ", 0, NULL);
 +
 +  if (tor_snprintf(out, end-out, "onion-key\n%s", key)<0)
 +    goto done;
 +  out += strlen(out);
 +  if (family) {
 +    if (tor_snprintf(out, end-out, "family %s\n", family)<0)
 +      goto done;
 +    out += strlen(out);
 +  }
 +  if (summary && strcmp(summary, "reject 1-65535")) {
 +    if (tor_snprintf(out, end-out, "p %s\n", summary)<0)
 +      goto done;
 +    out += strlen(out);
 +  }
 +  *out = '\0'; /* Make sure it's nul-terminated.  This should be a no-op */
 +
 +  {
 +    smartlist_t *lst = microdescs_parse_from_string(buf, out, 0, 1);
 +    if (smartlist_len(lst) != 1) {
 +      log_warn(LD_DIR, "We generated a microdescriptor we couldn't parse.");
 +      SMARTLIST_FOREACH(lst, microdesc_t *, md, microdesc_free(md));
 +      smartlist_free(lst);
 +      goto done;
 +    }
 +    result = smartlist_get(lst, 0);
 +    smartlist_free(lst);
 +  }
 +
 + done:
 +  tor_free(key);
 +  tor_free(summary);
 +  tor_free(family);
 +  return result;
 +}
 +
 +/** Cached space-separated string to hold */
 +static char *microdesc_consensus_methods = NULL;
 +
 +/** Format the appropriate vote line to describe the microdescriptor <b>md</b>
 + * in a consensus vote document.  Write it into the <b>out_len</b>-byte buffer
 + * in <b>out</b>.  Return -1 on failure and the number of characters written
 + * on success. */
 +ssize_t
 +dirvote_format_microdesc_vote_line(char *out, size_t out_len,
 +                                   const microdesc_t *md)
 +{
 +  char d64[BASE64_DIGEST256_LEN+1];
 +  if (!microdesc_consensus_methods) {
 +    microdesc_consensus_methods =
 +      make_consensus_method_list(MIN_METHOD_FOR_MICRODESC,
 +                                 MAX_SUPPORTED_CONSENSUS_METHOD,
 +                                 ",");
 +    tor_assert(microdesc_consensus_methods);
 +  }
 +  if (digest256_to_base64(d64, md->digest)<0)
 +    return -1;
 +
 +  if (tor_snprintf(out, out_len, "m %s sha256=%s\n",
 +                   microdesc_consensus_methods, d64)<0)
 +    return -1;
 +
 +  return strlen(out);
 +}
 +
 +/** If <b>vrs</b> has a hash made for the consensus method <b>method</b> with
 + * the digest algorithm <b>alg</b>, decode it and copy it into
 + * <b>digest256_out</b> and return 0.  Otherwise return -1. */
 +int
 +vote_routerstatus_find_microdesc_hash(char *digest256_out,
 +                                      const vote_routerstatus_t *vrs,
 +                                      int method,
 +                                      digest_algorithm_t alg)
 +{
 +  /* XXXX only returns the sha256 method. */
 +  const vote_microdesc_hash_t *h;
 +  char mstr[64];
 +  size_t mlen;
 +  char dstr[64];
 +
 +  tor_snprintf(mstr, sizeof(mstr), "%d", method);
 +  mlen = strlen(mstr);
 +  tor_snprintf(dstr, sizeof(dstr), " %s=",
 +               crypto_digest_algorithm_get_name(alg));
 +
 +  for (h = vrs->microdesc; h; h = h->next) {
 +    const char *cp = h->microdesc_hash_line;
 +    size_t num_len;
 +    /* cp looks like \d+(,\d+)* (digesttype=val )+ .  Let's hunt for mstr in
 +     * the first part. */
 +    while (1) {
 +      num_len = strspn(cp, "1234567890");
 +      if (num_len == mlen && !memcmp(mstr, cp, mlen)) {
 +        /* This is the line. */
 +        char buf[BASE64_DIGEST256_LEN+1];
 +        /* XXXX ignores extraneous stuff if the digest is too long.  This
 +         * seems harmless enough, right? */
 +        cp = strstr(cp, dstr);
 +        if (!cp)
 +          return -1;
 +        cp += strlen(dstr);
 +        strlcpy(buf, cp, sizeof(buf));
 +        return digest256_from_base64(digest256_out, buf);
 +      }
 +      if (num_len == 0 || cp[num_len] != ',')
 +        break;
 +      cp += num_len + 1;
 +    }
 +  }
 +  return -1;
 +}
 +
diff --combined src/or/dns.c
index 4e319b7,3bb9c34..5c76301
--- a/src/or/dns.c
+++ b/src/or/dns.c
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2003-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -12,63 -12,8 +12,63 @@@
   **/
  
  #include "or.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "control.h"
 +#include "dns.h"
 +#include "main.h"
 +#include "policies.h"
 +#include "relay.h"
 +#include "router.h"
  #include "ht.h"
 +#ifdef HAVE_EVENT2_DNS_H
 +#include <event2/event.h>
 +#include <event2/dns.h>
 +#else
 +#include <event.h>
  #include "eventdns.h"
 +#ifndef HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS
 +#define HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS
 +#endif
 +#endif
 +
 +#ifndef HAVE_EVENT2_DNS_H
 +struct evdns_base;
 +struct evdns_request;
 +#define evdns_base_new(x,y) tor_malloc(1)
 +#define evdns_base_clear_nameservers_and_suspend(base) \
 +  evdns_clear_nameservers_and_suspend()
 +#define evdns_base_search_clear(base) evdns_search_clear()
 +#define evdns_base_set_default_outgoing_bind_address(base, a, len)  \
 +  evdns_set_default_outgoing_bind_address((a),(len))
 +#define evdns_base_resolv_conf_parse(base, options, fname) \
 +  evdns_resolv_conf_parse((options), (fname))
 +#define evdns_base_count_nameservers(base)      \
 +  evdns_count_nameservers()
 +#define evdns_base_resume(base)                 \
 +  evdns_resume()
 +#define evdns_base_config_windows_nameservers(base)     \
 +  evdns_config_windows_nameservers()
 +#define evdns_base_set_option_(base, opt, val) \
 +  evdns_set_option((opt),(val),DNS_OPTIONS_ALL)
 +#define evdns_base_resolve_ipv4(base, addr, options, cb, ptr) \
 +  ((evdns_resolve_ipv4(addr, options, cb, ptr)<0) ? NULL : ((void*)1))
 +#define evdns_base_resolve_reverse(base, addr, options, cb, ptr) \
 +  ((evdns_resolve_reverse(addr, options, cb, ptr)<0) ? NULL : ((void*)1))
 +#define evdns_base_resolve_reverse_ipv6(base, addr, options, cb, ptr) \
 +  ((evdns_resolve_reverse_ipv6(addr, options, cb, ptr)<0) ? NULL : ((void*)1))
 +
 +#elif defined(LIBEVENT_VERSION_NUMBER) && LIBEVENT_VERSION_NUMBER < 0x02000303
 +#define evdns_base_set_option_(base, opt, val) \
 +  evdns_base_set_option((base), (opt),(val),DNS_OPTIONS_ALL)
 +
 +#else
 +#define evdns_base_set_option_ evdns_base_set_option
 +
 +#endif
  
  /** Longest hostname we're willing to resolve. */
  #define MAX_ADDRESSLEN 256
@@@ -83,9 -28,6 +83,9 @@@
  #define DNS_RESOLVE_FAILED_PERMANENT 2
  #define DNS_RESOLVE_SUCCEEDED 3
  
 +/** Our evdns_base; this structure handles all our name lookups. */
 +static struct evdns_base *the_evdns_base = NULL;
 +
  /** Have we currently configured nameservers with eventdns? */
  static int nameservers_configured = 0;
  /** Did our most recent attempt to configure nameservers with eventdns fail? */
@@@ -147,8 -89,6 +147,8 @@@ typedef struct cached_resolve_t 
    uint32_t ttl; /**< What TTL did the nameserver tell us? */
    /** Connections that want to know when we get an answer for this resolve. */
    pending_connection_t *pending_connections;
 +  /** Position of this element in the heap*/
 +  int minheap_idx;
  } cached_resolve_t;
  
  static void purge_expired_resolves(time_t now);
@@@ -271,16 -211,8 +271,16 @@@ dns_reset(void
  {
    or_options_t *options = get_options();
    if (! server_mode(options)) {
 -    evdns_clear_nameservers_and_suspend();
 -    evdns_search_clear();
 +
 +    if (!the_evdns_base) {
 +      if (!(the_evdns_base = evdns_base_new(tor_libevent_get_base(), 0))) {
 +        log_err(LD_BUG, "Couldn't create an evdns_base");
 +        return -1;
 +      }
 +    }
 +
 +    evdns_base_clear_nameservers_and_suspend(the_evdns_base);
 +    evdns_base_search_clear(the_evdns_base);
      nameservers_configured = 0;
      tor_free(resolv_conf_fname);
      resolv_conf_mtime = 0;
@@@ -330,8 -262,6 +330,8 @@@ dns_get_expiry_ttl(uint32_t ttl
  static void
  _free_cached_resolve(cached_resolve_t *r)
  {
 +  if (!r)
 +    return;
    while (r->pending_connections) {
      pending_connection_t *victim = r->pending_connections;
      r->pending_connections = victim->next;
@@@ -373,7 -303,6 +373,7 @@@ set_expiry(cached_resolve_t *resolve, t
    resolve->expire = expires;
    smartlist_pqueue_add(cached_resolve_pqueue,
                         _compare_cached_resolves_by_expiry,
 +                       STRUCT_OFFSET(cached_resolve_t, minheap_idx),
                         resolve);
  }
  
@@@ -396,7 -325,8 +396,7 @@@ dns_free_all(void
      _free_cached_resolve(item);
    }
    HT_CLEAR(cache_map, &cache_root);
 -  if (cached_resolve_pqueue)
 -    smartlist_free(cached_resolve_pqueue);
 +  smartlist_free(cached_resolve_pqueue);
    cached_resolve_pqueue = NULL;
    tor_free(resolv_conf_fname);
  }
@@@ -419,8 -349,7 +419,8 @@@ purge_expired_resolves(time_t now
      if (resolve->expire > now)
        break;
      smartlist_pqueue_pop(cached_resolve_pqueue,
 -                         _compare_cached_resolves_by_expiry);
 +                         _compare_cached_resolves_by_expiry,
 +                         STRUCT_OFFSET(cached_resolve_t, minheap_idx));
  
      if (resolve->state == CACHE_STATE_PENDING) {
        log_debug(LD_EXIT,
@@@ -464,7 -393,7 +464,7 @@@
          log_err(LD_BUG, "The expired resolve we purged didn't match any in"
                  " the cache. Tried to purge %s (%p); instead got %s (%p).",
                  resolve->address, (void*)resolve,
 -                removed ? removed->address : "NULL", (void*)remove);
 +                removed ? removed->address : "NULL", (void*)removed);
        }
        tor_assert(removed == resolve);
      } else {
@@@ -682,7 -611,7 +682,7 @@@ dns_resolve_impl(edge_connection_t *exi
     * know the answer. */
    if (tor_addr_from_str(&addr, exitconn->_base.address) >= 0) {
      if (tor_addr_family(&addr) == AF_INET) {
 -      tor_addr_assign(&exitconn->_base.addr, &addr);
 +      tor_addr_copy(&exitconn->_base.addr, &addr);
        exitconn->address_ttl = DEFAULT_DNS_TTL;
        return 1;
      } else {
@@@ -782,7 -711,6 +782,7 @@@
    resolve = tor_malloc_zero(sizeof(cached_resolve_t));
    resolve->magic = CACHED_RESOLVE_MAGIC;
    resolve->state = CACHE_STATE_PENDING;
 +  resolve->minheap_idx = -1;
    resolve->is_reverse = is_reverse;
    strlcpy(resolve->address, exitconn->_base.address, sizeof(resolve->address));
  
@@@ -879,8 -807,7 +879,8 @@@ connection_dns_remove(edge_connection_
      tor_free(pend);
      log_debug(LD_EXIT, "First connection (fd %d) no longer waiting "
                "for resolve of %s",
 -              conn->_base.s, escaped_safe_str(conn->_base.address));
 +              conn->_base.s,
 +              escaped_safe_str(conn->_base.address));
      return;
    } else {
      for ( ; pend->next; pend = pend->next) {
@@@ -1181,14 -1108,6 +1181,14 @@@ configure_nameservers(int force
      conf_fname = "/etc/resolv.conf";
  #endif
  
 +  if (!the_evdns_base) {
 +    if (!(the_evdns_base = evdns_base_new(tor_libevent_get_base(), 0))) {
 +      log_err(LD_BUG, "Couldn't create an evdns_base");
 +      return -1;
 +    }
 +  }
 +
 +#ifdef HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS
    if (options->OutboundBindAddress) {
      tor_addr_t addr;
      if (tor_addr_from_str(&addr, options->OutboundBindAddress) < 0) {
@@@ -1203,13 -1122,16 +1203,13 @@@
          log_warn(LD_BUG, "Couldn't convert outbound bind address to sockaddr."
                   " Ignoring.");
        } else {
 -        evdns_set_default_outgoing_bind_address((struct sockaddr *)&ss,
 -                                                socklen);
 +        evdns_base_set_default_outgoing_bind_address(the_evdns_base,
 +                                                     (struct sockaddr *)&ss,
 +                                                     socklen);
        }
      }
    }
 -
 -  if (options->ServerDNSRandomizeCase)
 -    evdns_set_option("randomize-case:", "1", DNS_OPTIONS_ALL);
 -  else
 -    evdns_set_option("randomize-case:", "0", DNS_OPTIONS_ALL);
 +#endif
  
    evdns_set_log_fn(evdns_log_cb);
    if (conf_fname) {
@@@ -1224,17 -1146,16 +1224,17 @@@
        return 0;
      }
      if (nameservers_configured) {
 -      evdns_search_clear();
 -      evdns_clear_nameservers_and_suspend();
 +      evdns_base_search_clear(the_evdns_base);
 +      evdns_base_clear_nameservers_and_suspend(the_evdns_base);
      }
      log_info(LD_EXIT, "Parsing resolver configuration in '%s'", conf_fname);
 -    if ((r = evdns_resolv_conf_parse(DNS_OPTIONS_ALL, conf_fname))) {
 +    if ((r = evdns_base_resolv_conf_parse(the_evdns_base,
 +                                          DNS_OPTIONS_ALL, conf_fname))) {
        log_warn(LD_EXIT, "Unable to parse '%s', or no nameservers in '%s' (%d)",
                 conf_fname, conf_fname, r);
        goto err;
      }
 -    if (evdns_count_nameservers() == 0) {
 +    if (evdns_base_count_nameservers(the_evdns_base) == 0) {
        log_warn(LD_EXIT, "Unable to find any nameservers in '%s'.", conf_fname);
        goto err;
      }
@@@ -1242,47 -1163,38 +1242,47 @@@
      resolv_conf_fname = tor_strdup(conf_fname);
      resolv_conf_mtime = st.st_mtime;
      if (nameservers_configured)
 -      evdns_resume();
 +      evdns_base_resume(the_evdns_base);
    }
  #ifdef MS_WINDOWS
    else {
      if (nameservers_configured) {
 -      evdns_search_clear();
 -      evdns_clear_nameservers_and_suspend();
 +      evdns_base_search_clear(the_evdns_base);
 +      evdns_base_clear_nameservers_and_suspend(the_evdns_base);
      }
 -    if (evdns_config_windows_nameservers())  {
 +    if (evdns_base_config_windows_nameservers(the_evdns_base))  {
        log_warn(LD_EXIT,"Could not config nameservers.");
        goto err;
      }
 -    if (evdns_count_nameservers() == 0) {
 +    if (evdns_base_count_nameservers(the_evdns_base) == 0) {
        log_warn(LD_EXIT, "Unable to find any platform nameservers in "
                 "your Windows configuration.");
        goto err;
      }
      if (nameservers_configured)
 -      evdns_resume();
 +      evdns_base_resume(the_evdns_base);
      tor_free(resolv_conf_fname);
      resolv_conf_mtime = 0;
    }
  #endif
  
 -  if (evdns_count_nameservers() == 1) {
 -    evdns_set_option("max-timeouts:", "16", DNS_OPTIONS_ALL);
 -    evdns_set_option("timeout:", "10", DNS_OPTIONS_ALL);
 +#define SET(k,v)  evdns_base_set_option_(the_evdns_base, (k), (v))
 +
 +  if (evdns_base_count_nameservers(the_evdns_base) == 1) {
 +    SET("max-timeouts:", "16");
 +    SET("timeout:", "10");
    } else {
 -    evdns_set_option("max-timeouts:", "3", DNS_OPTIONS_ALL);
 -    evdns_set_option("timeout:", "5", DNS_OPTIONS_ALL);
 +    SET("max-timeouts:", "3");
 +    SET("timeout:", "5");
    }
  
 +  if (options->ServerDNSRandomizeCase)
 +    SET("randomize-case:", "1");
 +  else
 +    SET("randomize-case:", "0");
 +
 +#undef SET
 +
    dns_servers_relaunch_checks();
  
    nameservers_configured = 1;
@@@ -1380,7 -1292,6 +1380,7 @@@ static in
  launch_resolve(edge_connection_t *exitconn)
  {
    char *addr = tor_strdup(exitconn->_base.address);
 +  struct evdns_request *req = NULL;
    tor_addr_t a;
    int r;
    int options = get_options()->ServerDNSSearchDomains ? 0
@@@ -1396,34 -1307,28 +1396,34 @@@
  
    r = tor_addr_parse_reverse_lookup_name(
                              &a, exitconn->_base.address, AF_UNSPEC, 0);
 +
 +  tor_assert(the_evdns_base);
    if (r == 0) {
      log_info(LD_EXIT, "Launching eventdns request for %s",
               escaped_safe_str(exitconn->_base.address));
 -    r = evdns_resolve_ipv4(exitconn->_base.address, options,
 -                           evdns_callback, addr);
 +    req = evdns_base_resolve_ipv4(the_evdns_base,
 +                                exitconn->_base.address, options,
 +                                evdns_callback, addr);
    } else if (r == 1) {
      log_info(LD_EXIT, "Launching eventdns reverse request for %s",
               escaped_safe_str(exitconn->_base.address));
      if (tor_addr_family(&a) == AF_INET)
 -      r = evdns_resolve_reverse(tor_addr_to_in(&a), DNS_QUERY_NO_SEARCH,
 +      req = evdns_base_resolve_reverse(the_evdns_base,
 +                                tor_addr_to_in(&a), DNS_QUERY_NO_SEARCH,
                                  evdns_callback, addr);
      else
 -      r = evdns_resolve_reverse_ipv6(tor_addr_to_in6(&a), DNS_QUERY_NO_SEARCH,
 +      req = evdns_base_resolve_reverse_ipv6(the_evdns_base,
 +                                     tor_addr_to_in6(&a), DNS_QUERY_NO_SEARCH,
                                       evdns_callback, addr);
    } else if (r == -1) {
      log_warn(LD_BUG, "Somehow a malformed in-addr.arpa address reached here.");
    }
  
 -  if (r) {
 -    log_warn(LD_EXIT, "eventdns rejected address %s: error %d.",
 -             escaped_safe_str(addr), r);
 -    r = evdns_err_is_transient(r) ? -2 : -1;
 +  r = 0;
 +  if (!req) {
 +    log_warn(LD_EXIT, "eventdns rejected address %s.",
 +             escaped_safe_str(addr));
 +    r = -1;
      tor_free(addr); /* There is no evdns request in progress; stop
                       * addr from getting leaked. */
    }
@@@ -1544,8 -1449,8 +1544,8 @@@ evdns_wildcard_check_callback(int resul
      }
      log(dns_wildcard_one_notice_given ? LOG_INFO : LOG_NOTICE, LD_EXIT,
          "Your DNS provider gave an answer for \"%s\", which "
 -        "is not supposed to exist.  Apparently they are hijacking "
 -        "DNS failures. Trying to correct for this.  We've noticed %d "
 +        "is not supposed to exist. Apparently they are hijacking "
 +        "DNS failures. Trying to correct for this. We've noticed %d "
          "possibly bad address%s so far.",
          string_address, strmap_size(dns_wildcard_response_count),
          (strmap_size(dns_wildcard_response_count) == 1) ? "" : "es");
@@@ -1561,20 -1466,17 +1561,20 @@@ static voi
  launch_wildcard_check(int min_len, int max_len, const char *suffix)
  {
    char *addr;
 -  int r;
 +  struct evdns_request *req;
  
    addr = crypto_random_hostname(min_len, max_len, "", suffix);
    log_info(LD_EXIT, "Testing whether our DNS server is hijacking nonexistent "
             "domains with request for bogus hostname \"%s\"", addr);
  
 -  r = evdns_resolve_ipv4(/* This "addr" tells us which address to resolve */
 +  tor_assert(the_evdns_base);
 +  req = evdns_base_resolve_ipv4(
 +                         the_evdns_base,
 +                         /* This "addr" tells us which address to resolve */
                           addr,
                           DNS_QUERY_NO_SEARCH, evdns_wildcard_check_callback,
                           /* This "addr" is an argument to the callback*/ addr);
 -  if (r) {
 +  if (!req) {
      /* There is no evdns request in progress; stop addr from getting leaked */
      tor_free(addr);
    }
@@@ -1586,7 -1488,6 +1586,7 @@@ static voi
  launch_test_addresses(int fd, short event, void *args)
  {
    or_options_t *options = get_options();
 +  struct evdns_request *req;
    (void)fd;
    (void)event;
    (void)args;
@@@ -1598,19 -1499,14 +1598,19 @@@
     * be an exit server.*/
    if (!options->ServerDNSTestAddresses)
      return;
 -  SMARTLIST_FOREACH(options->ServerDNSTestAddresses, const char *, address,
 -    {
 -      int r = evdns_resolve_ipv4(address, DNS_QUERY_NO_SEARCH, evdns_callback,
 -                                 tor_strdup(address));
 -      if (r)
 -        log_info(LD_EXIT, "eventdns rejected test address %s: error %d",
 -                 escaped_safe_str(address), r);
 -    });
 +  tor_assert(the_evdns_base);
 +  SMARTLIST_FOREACH_BEGIN(options->ServerDNSTestAddresses,
 +                          const char *, address) {
 +    char *a = tor_strdup(address);
 +    req = evdns_base_resolve_ipv4(the_evdns_base,
 +                              address, DNS_QUERY_NO_SEARCH, evdns_callback, a);
 +
 +    if (!req) {
 +      log_info(LD_EXIT, "eventdns rejected test address %s",
 +               escaped_safe_str(address));
 +      tor_free(a);
 +    }
 +  } SMARTLIST_FOREACH_END(address);
  }
  
  #define N_WILDCARD_CHECKS 2
@@@ -1651,7 -1547,7 +1651,7 @@@ dns_launch_wildcard_checks(void
  void
  dns_launch_correctness_checks(void)
  {
 -  static struct event launch_event;
 +  static struct event *launch_event = NULL;
    struct timeval timeout;
    if (!get_options()->ServerDNSDetectHijacking)
      return;
@@@ -1659,12 -1555,10 +1659,12 @@@
  
    /* Wait a while before launching requests for test addresses, so we can
     * get the results from checking for wildcarding. */
 -  evtimer_set(&launch_event, launch_test_addresses, NULL);
 +  if (! launch_event)
 +    launch_event = tor_evtimer_new(tor_libevent_get_base(),
 +                                   launch_test_addresses, NULL);
    timeout.tv_sec = 30;
    timeout.tv_usec = 0;
 -  if (evtimer_add(&launch_event, &timeout)<0) {
 +  if (evtimer_add(launch_event, &timeout)<0) {
      log_warn(LD_BUG, "Couldn't add timer for checking for dns hijacking");
    }
  }
@@@ -1680,9 -1574,10 +1680,9 @@@ dns_seems_to_be_broken(void
  void
  dns_reset_correctness_checks(void)
  {
 -  if (dns_wildcard_response_count) {
 -    strmap_free(dns_wildcard_response_count, _tor_free);
 -    dns_wildcard_response_count = NULL;
 -  }
 +  strmap_free(dns_wildcard_response_count, _tor_free);
 +  dns_wildcard_response_count = NULL;
 +
    n_wildcard_requests = 0;
  
    if (dns_wildcard_list) {
@@@ -1727,30 -1622,6 +1727,30 @@@ assert_resolve_ok(cached_resolve_t *res
    }
  }
  
 +/** Return the number of DNS cache entries as an int */
 +static int
 +dns_cache_entry_count(void)
 +{
 +   return HT_SIZE(&cache_root);
 +}
 +
 +/** Log memory information about our internal DNS cache at level 'severity'. */
 +void
 +dump_dns_mem_usage(int severity)
 +{
 +  /* This should never be larger than INT_MAX. */
 +  int hash_count = dns_cache_entry_count();
 +  size_t hash_mem = sizeof(struct cached_resolve_t) * hash_count;
 +  hash_mem += HT_MEM_USAGE(&cache_root);
 +
 +  /* Print out the count and estimated size of our &cache_root.  It undercounts
 +     hostnames in cached reverse resolves.
 +   */
 +  log(severity, LD_MM, "Our DNS cache has %d entries.", hash_count);
 +  log(severity, LD_MM, "Our DNS cache size is approximately %u bytes.",
 +      (unsigned)hash_mem);
 +}
 +
  #ifdef DEBUG_DNS_CACHE
  /** Exit with an assertion if the DNS cache is corrupt. */
  static void
@@@ -1771,8 -1642,7 +1771,8 @@@ _assert_cache_ok(void
      return;
  
    smartlist_pqueue_assert_ok(cached_resolve_pqueue,
 -                             _compare_cached_resolves_by_expiry);
 +                             _compare_cached_resolves_by_expiry,
 +                             STRUCT_OFFSET(cached_resolve_t, minheap_idx));
  
    SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res,
      {
diff --combined src/or/dnsserv.c
index ad4f412,579080b..c491656
--- a/src/or/dnsserv.c
+++ b/src/or/dnsserv.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Copyright (c) 2007-2010, The Tor Project, Inc. */
+ /* Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -9,21 -9,7 +9,21 @@@
   **/
  
  #include "or.h"
 +#include "dnsserv.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "control.h"
 +#include "main.h"
 +#include "policies.h"
 +#ifdef HAVE_EVENT2_DNS_H
 +#include <event2/dns.h>
 +#include <event2/dns_compat.h>
 +/* XXXX022 this implies we want an improved evdns  */
 +#include <event2/dns_struct.h>
 +#else
  #include "eventdns.h"
 +#endif
  
  /** Helper function: called by evdns whenever the client sends a request to our
   * DNSPort.  We need to eventually answer the request <b>req</b>.
@@@ -99,7 -85,12 +99,7 @@@ evdns_server_callback(struct evdns_serv
      evdns_server_request_respond(req, DNS_ERR_NONE);
      return;
    }
 -  if (q->type == EVDNS_TYPE_A) {
 -    /* Refuse any attempt to resolve a noconnect address, right now. */
 -    if (hostname_is_noconnect_address(q->name)) {
 -      err = DNS_ERR_REFUSED;
 -    }
 -  } else {
 +  if (q->type != EVDNS_TYPE_A) {
      tor_assert(q->type == EVDNS_TYPE_PTR);
    }
  
@@@ -141,18 -132,17 +141,18 @@@
  
    control_event_stream_status(conn, STREAM_EVENT_NEW, 0);
  
 -  /* Now, throw the connection over to get rewritten (which will answer it
 -  * immediately if it's in the cache, or completely bogus, or automapped),
 -  * and then attached to a circuit. */
 +  /* Now, unless a controller asked us to leave streams unattached,
 +  * throw the connection over to get rewritten (which will
 +  * answer it immediately if it's in the cache, or completely bogus, or
 +  * automapped), and then attached to a circuit. */
    log_info(LD_APP, "Passing request for %s to rewrite_and_attach.",
 -           escaped_safe_str(q->name));
 +           escaped_safe_str_client(q->name));
    q_name = tor_strdup(q->name); /* q could be freed in rewrite_and_attach */
 -  connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
 +  connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL);
    /* Now, the connection is marked if it was bad. */
  
 -  log_info(LD_APP, "Passed request for %s to rewrite_and_attach.",
 -           escaped_safe_str(q_name));
 +  log_info(LD_APP, "Passed request for %s to rewrite_and_attach_if_allowed.",
 +           escaped_safe_str_client(q_name));
    tor_free(q_name);
  }
  
@@@ -187,18 -177,17 +187,18 @@@ dnsserv_launch_request(const char *name
      return -1;
    }
  
 -  /* Now, throw the connection over to get rewritten (which will answer it
 -  * immediately if it's in the cache, or completely bogus, or automapped),
 -  * and then attached to a circuit. */
 +  /* Now, unless a controller asked us to leave streams unattached,
 +  * throw the connection over to get rewritten (which will
 +  * answer it immediately if it's in the cache, or completely bogus, or
 +  * automapped), and then attached to a circuit. */
    log_info(LD_APP, "Passing request for %s to rewrite_and_attach.",
 -           escaped_safe_str(name));
 +           escaped_safe_str_client(name));
    q_name = tor_strdup(name); /* q could be freed in rewrite_and_attach */
 -  connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
 +  connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL);
    /* Now, the connection is marked if it was bad. */
  
 -  log_info(LD_APP, "Passed request for %s to rewrite_and_attach.",
 -           escaped_safe_str(q_name));
 +  log_info(LD_APP, "Passed request for %s to rewrite_and_attach_if_allowed.",
 +           escaped_safe_str_client(q_name));
    tor_free(q_name);
    return 0;
  }
@@@ -308,8 -297,8 +308,8 @@@ dnsserv_configure_listener(connection_
    tor_assert(conn->s >= 0);
    tor_assert(conn->type == CONN_TYPE_AP_DNS_LISTENER);
  
 -  conn->dns_server_port = evdns_add_server_port(conn->s, 0,
 -                                                evdns_server_callback, NULL);
 +  conn->dns_server_port =
 +    tor_evdns_add_server_port(conn->s, 0, evdns_server_callback, NULL);
  }
  
  /** Free the evdns server port for <b>conn</b>, which must be an
diff --combined src/or/geoip.c
index ee8d72e,360b65f..d4e279f
--- a/src/or/geoip.c
+++ b/src/or/geoip.c
@@@ -1,25 -1,17 +1,25 @@@
- /* Copyright (c) 2007-2010, The Tor Project, Inc. */
+ /* Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
   * \file geoip.c
 - * \brief Functions related to maintaining an IP-to-country database and to
 - *    summarizing client connections by country.
 + * \brief Functions related to maintaining an IP-to-country database;
 + * to summarizing client connections by country to entry guards, bridges,
 + * and directory servers; and for statistics on answering network status
 + * requests.
   */
  
  #define GEOIP_PRIVATE
  #include "or.h"
  #include "ht.h"
 +#include "config.h"
 +#include "control.h"
 +#include "dnsserv.h"
 +#include "geoip.h"
 +#include "routerlist.h"
  
  static void clear_geoip_db(void);
 +static void init_geoip_countries(void);
  
  /** An entry from the GeoIP file: maps an IP range to a country. */
  typedef struct geoip_entry_t {
@@@ -28,11 -20,16 +28,11 @@@
    intptr_t country; /**< An index into geoip_countries */
  } geoip_entry_t;
  
  /** A per-country record for GeoIP request history. */
  typedef struct geoip_country_t {
    char countrycode[3];
 -  uint32_t n_v2_ns_requests[REQUEST_HIST_LEN];
 -  uint32_t n_v3_ns_requests[REQUEST_HIST_LEN];
 +  uint32_t n_v2_ns_requests;
 +  uint32_t n_v3_ns_requests;
  } geoip_country_t;
  
  /** A list of geoip_country_t */
@@@ -45,7 -42,7 +45,7 @@@ static strmap_t *country_idxplus1_by_lc
  static smartlist_t *geoip_entries = NULL;
  
  /** Return the index of the <b>country</b>'s entry in the GeoIP DB
 - * if it is a valid 2-letter country code, otherwise return zero.
 + * if it is a valid 2-letter country code, otherwise return -1.
   */
  country_t
  geoip_get_country(const char *country)
@@@ -104,11 -101,11 +104,11 @@@ geoip_parse_entry(const char *line
  {
    unsigned int low, high;
    char b[3];
 -  if (!geoip_countries) {
 -    geoip_countries = smartlist_create();
 +  if (!geoip_countries)
 +    init_geoip_countries();
 +  if (!geoip_entries)
      geoip_entries = smartlist_create();
 -    country_idxplus1_by_lc_code = strmap_new();
 -  }
 +
    while (TOR_ISSPACE(*line))
      ++line;
    if (*line == '#')
@@@ -145,7 -142,6 +145,7 @@@ _geoip_compare_entries(const void **_a
  static int
  _geoip_compare_key_to_entry(const void *_key, const void **_member)
  {
 +  /* No alignment issue here, since _key really is a pointer to uint32_t */
    const uint32_t addr = *(uint32_t *)_key;
    const geoip_entry_t *entry = *_member;
    if (addr < entry->ip_low)
@@@ -164,24 -160,6 +164,24 @@@ should_record_bridge_info(or_options_t 
    return options->BridgeRelay && options->BridgeRecordUsageByCountry;
  }
  
 +/** Set up a new list of geoip countries with no countries (yet) set in it,
 + * except for the unknown country.
 + */
 +static void
 +init_geoip_countries(void)
 +{
 +  geoip_country_t *geoip_unresolved;
 +  geoip_countries = smartlist_create();
 +  /* Add a geoip_country_t for requests that could not be resolved to a
 +   * country as first element (index 0) to geoip_countries. */
 +  geoip_unresolved = tor_malloc_zero(sizeof(geoip_country_t));
 +  strlcpy(geoip_unresolved->countrycode, "??",
 +          sizeof(geoip_unresolved->countrycode));
 +  smartlist_add(geoip_countries, geoip_unresolved);
 +  country_idxplus1_by_lc_code = strmap_new();
 +  strmap_set_lc(country_idxplus1_by_lc_code, "??", (void*)(1));
 +}
 +
  /** Clear the GeoIP database and reload it from the file
   * <b>filename</b>. Return 0 on success, -1 on failure.
   *
@@@ -207,8 -185,10 +207,8 @@@ geoip_load_file(const char *filename, o
             filename, msg);
      return -1;
    }
 -  if (!geoip_countries) {
 -    geoip_countries = smartlist_create();
 -    country_idxplus1_by_lc_code = strmap_new();
 -  }
 +  if (!geoip_countries)
 +    init_geoip_countries();
    if (geoip_entries) {
      SMARTLIST_FOREACH(geoip_entries, geoip_entry_t *, e, tor_free(e));
      smartlist_free(geoip_entries);
@@@ -235,10 -215,9 +235,10 @@@
  }
  
  /** Given an IP address in host order, return a number representing the
 - * country to which that address belongs, or -1 for unknown.  The return value
 - * will always be less than geoip_get_n_countries().  To decode it,
 - * call geoip_get_country_name().
 + * country to which that address belongs, -1 for "No geoip information
 + * available", or 0 for the 'unknown country'.  The return value will always
 + * be less than geoip_get_n_countries().  To decode it, call
 + * geoip_get_country_name().
   */
  int
  geoip_get_country_by_ip(uint32_t ipaddr)
@@@ -247,15 -226,13 +247,15 @@@
    if (!geoip_entries)
      return -1;
    ent = smartlist_bsearch(geoip_entries, &ipaddr, _geoip_compare_key_to_entry);
 -  return ent ? (int)ent->country : -1;
 +  return ent ? (int)ent->country : 0;
  }
  
  /** Return the number of countries recognized by the GeoIP database. */
  int
  geoip_get_n_countries(void)
  {
 +  if (!geoip_countries)
 +    init_geoip_countries();
    return (int) smartlist_len(geoip_countries);
  }
  
@@@ -284,8 -261,8 +284,8 @@@ geoip_is_loaded(void
  typedef struct clientmap_entry_t {
    HT_ENTRY(clientmap_entry_t) node;
    uint32_t ipaddr;
 -  time_t last_seen; /* The last 2 bits of this value hold the client
 -                     * operation. */
 +  unsigned int last_seen_in_minutes:30;
 +  unsigned int action:2;
  } clientmap_entry_t;
  
  #define ACTION_MASK 3
@@@ -293,6 -270,14 +293,6 @@@
  /** Map from client IP address to last time seen. */
  static HT_HEAD(clientmap, clientmap_entry_t) client_history =
       HT_INITIALIZER();
 -/** Time at which we started tracking client IP history. */
 -static time_t client_history_starts = 0;
 -
 -/** When did the current period of checking per-country request history
 - * start? */
 -static time_t current_request_period_starts = 0;
 -/** How many older request periods are we remembering? */
 -static int n_old_request_periods = 0;
  
  /** Hashtable helper: compute a hash of a clientmap_entry_t. */
  static INLINE unsigned
@@@ -304,7 -289,7 +304,7 @@@ clientmap_entry_hash(const clientmap_en
  static INLINE int
  clientmap_entries_eq(const clientmap_entry_t *a, const clientmap_entry_t *b)
  {
 -  return a->ipaddr == b->ipaddr;
 +  return a->ipaddr == b->ipaddr && a->action == b->action;
  }
  
  HT_PROTOTYPE(clientmap, clientmap_entry_t, node, clientmap_entry_hash,
@@@ -312,87 -297,8 +312,87 @@@
  HT_GENERATE(clientmap, clientmap_entry_t, node, clientmap_entry_hash,
              clientmap_entries_eq, 0.6, malloc, realloc, free);
  
 +/** Clear history of connecting clients used by entry and bridge stats. */
 +static void
 +client_history_clear(void)
 +{
 +  clientmap_entry_t **ent, **next, *this;
 +  for (ent = HT_START(clientmap, &client_history); ent != NULL;
 +       ent = next) {
 +    if ((*ent)->action == GEOIP_CLIENT_CONNECT) {
 +      this = *ent;
 +      next = HT_NEXT_RMV(clientmap, &client_history, ent);
 +      tor_free(this);
 +    } else {
 +      next = HT_NEXT(clientmap, &client_history, ent);
 +    }
 +  }
 +}
 +
 +/** How often do we update our estimate which share of v2 and v3 directory
 + * requests is sent to us? We could as well trigger updates of shares from
 + * network status updates, but that means adding a lot of calls into code
 + * that is independent from geoip stats (and keeping them up-to-date). We
 + * are perfectly fine with an approximation of 15-minute granularity. */
 +#define REQUEST_SHARE_INTERVAL (15 * 60)
 +
 +/** When did we last determine which share of v2 and v3 directory requests
 + * is sent to us? */
 +static time_t last_time_determined_shares = 0;
 +
 +/** Sum of products of v2 shares times the number of seconds for which we
 + * consider these shares as valid. */
 +static double v2_share_times_seconds;
 +
 +/** Sum of products of v3 shares times the number of seconds for which we
 + * consider these shares as valid. */
 +static double v3_share_times_seconds;
 +
 +/** Number of seconds we are determining v2 and v3 shares. */
 +static int share_seconds;
 +
 +/** Try to determine which fraction of v2 and v3 directory requests aimed at
 + * caches will be sent to us at time <b>now</b> and store that value in
 + * order to take a mean value later on. */
 +static void
 +geoip_determine_shares(time_t now)
 +{
 +  double v2_share = 0.0, v3_share = 0.0;
 +  if (router_get_my_share_of_directory_requests(&v2_share, &v3_share) < 0)
 +    return;
 +  if (last_time_determined_shares) {
 +    v2_share_times_seconds += v2_share *
 +        ((double) (now - last_time_determined_shares));
 +    v3_share_times_seconds += v3_share *
 +        ((double) (now - last_time_determined_shares));
 +    share_seconds += (int)(now - last_time_determined_shares);
 +  }
 +  last_time_determined_shares = now;
 +}
 +
 +/** Calculate which fraction of v2 and v3 directory requests aimed at caches
 + * have been sent to us since the last call of this function up to time
 + * <b>now</b>. Set *<b>v2_share_out</b> and *<b>v3_share_out</b> to the
 + * fractions of v2 and v3 protocol shares we expect to have seen. Reset
 + * counters afterwards. Return 0 on success, -1 on failure (e.g. when zero
 + * seconds have passed since the last call).*/
 +static int
 +geoip_get_mean_shares(time_t now, double *v2_share_out,
 +                         double *v3_share_out)
 +{
 +  geoip_determine_shares(now);
 +  if (!share_seconds)
 +    return -1;
 +  *v2_share_out = v2_share_times_seconds / ((double) share_seconds);
 +  *v3_share_out = v3_share_times_seconds / ((double) share_seconds);
 +  v2_share_times_seconds = v3_share_times_seconds = 0.0;
 +  share_seconds = 0;
 +  return 0;
 +}
 +
  /** Note that we've seen a client connect from the IP <b>addr</b> (host order)
 - * at time <b>now</b>. Ignored by all but bridges. */
 + * at time <b>now</b>. Ignored by all but bridges and directories if
 + * configured accordingly. */
  void
  geoip_note_client_seen(geoip_client_action_t action,
                         uint32_t addr, time_t now)
@@@ -400,45 -306,72 +400,45 @@@
    or_options_t *options = get_options();
    clientmap_entry_t lookup, *ent;
    if (action == GEOIP_CLIENT_CONNECT) {
 -    if (!(options->BridgeRelay && options->BridgeRecordUsageByCountry))
 -      return;
 -    /* Did we recently switch from bridge to relay or back? */
 -    if (client_history_starts > now)
 +    /* Only remember statistics as entry guard or as bridge. */
 +    if (!options->EntryStatistics &&
 +        (!(options->BridgeRelay && options->BridgeRecordUsageByCountry)))
        return;
    } else {
 -#ifndef ENABLE_GEOIP_STATS
 -    return;
 -#else
      if (options->BridgeRelay || options->BridgeAuthoritativeDir ||
 -        !options->DirRecordUsageByCountry)
 +        !options->DirReqStatistics)
        return;
 -#endif
    }
  
 -  /* Rotate the current request period. */
 -  while (current_request_period_starts + REQUEST_HIST_PERIOD < now) {
 -    if (!geoip_countries)
 -      geoip_countries = smartlist_create();
 -    if (!current_request_period_starts) {
 -      current_request_period_starts = now;
 -      break;
 -    }
 -    SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, {
 -        memmove(&c->n_v2_ns_requests[0], &c->n_v2_ns_requests[1],
 -                sizeof(uint32_t)*(REQUEST_HIST_LEN-1));
 -        memmove(&c->n_v3_ns_requests[0], &c->n_v3_ns_requests[1],
 -                sizeof(uint32_t)*(REQUEST_HIST_LEN-1));
 -        c->n_v2_ns_requests[REQUEST_HIST_LEN-1] = 0;
 -        c->n_v3_ns_requests[REQUEST_HIST_LEN-1] = 0;
 -      });
 -    current_request_period_starts += REQUEST_HIST_PERIOD;
 -    if (n_old_request_periods < REQUEST_HIST_LEN-1)
 -      ++n_old_request_periods;
 -   }
 -
 -  /* We use the low 3 bits of the time to encode the action. Since we're
 -   * potentially remembering tons of clients, we don't want to make
 -   * clientmap_entry_t larger than it has to be. */
 -  now = (now & ~ACTION_MASK) | (((int)action) & ACTION_MASK);
    lookup.ipaddr = addr;
 +  lookup.action = (int)action;
    ent = HT_FIND(clientmap, &client_history, &lookup);
    if (ent) {
 -    ent->last_seen = now;
 +    ent->last_seen_in_minutes = now / 60;
    } else {
      ent = tor_malloc_zero(sizeof(clientmap_entry_t));
      ent->ipaddr = addr;
 -    ent->last_seen = now;
 +    ent->last_seen_in_minutes = now / 60;
 +    ent->action = (int)action;
      HT_INSERT(clientmap, &client_history, ent);
    }
  
    if (action == GEOIP_CLIENT_NETWORKSTATUS ||
        action == GEOIP_CLIENT_NETWORKSTATUS_V2) {
      int country_idx = geoip_get_country_by_ip(addr);
 +    if (country_idx < 0)
 +      country_idx = 0; /** unresolved requests are stored at index 0. */
      if (country_idx >= 0 && country_idx < smartlist_len(geoip_countries)) {
        geoip_country_t *country = smartlist_get(geoip_countries, country_idx);
        if (action == GEOIP_CLIENT_NETWORKSTATUS)
 -        ++country->n_v3_ns_requests[REQUEST_HIST_LEN-1];
 +        ++country->n_v3_ns_requests;
        else
 -        ++country->n_v2_ns_requests[REQUEST_HIST_LEN-1];
 +        ++country->n_v2_ns_requests;
      }
 -  }
  
 -  if (!client_history_starts) {
 -    client_history_starts = now;
 -    current_request_period_starts = now;
 +    /* Periodically determine share of requests that we should see */
 +    if (last_time_determined_shares + REQUEST_SHARE_INTERVAL < now)
 +      geoip_determine_shares(now);
    }
  }
  
@@@ -447,8 -380,8 +447,8 @@@
  static int
  _remove_old_client_helper(struct clientmap_entry_t *ent, void *_cutoff)
  {
 -  time_t cutoff = *(time_t*)_cutoff;
 -  if (ent->last_seen < cutoff) {
 +  time_t cutoff = *(time_t*)_cutoff / 60;
 +  if (ent->last_seen_in_minutes < cutoff) {
      tor_free(ent);
      return 1;
    } else {
@@@ -456,45 -389,18 +456,45 @@@
    }
  }
  
 -/** Forget about all clients that haven't connected since <b>cutoff</b>.
 - * If <b>cutoff</b> is in the future, clients won't be added to the history
 - * until this time is reached. This is useful to prevent relays that switch
 - * to bridges from reporting unbelievable numbers of clients. */
 +/** Forget about all clients that haven't connected since <b>cutoff</b>. */
  void
  geoip_remove_old_clients(time_t cutoff)
  {
    clientmap_HT_FOREACH_FN(&client_history,
                            _remove_old_client_helper,
                            &cutoff);
 -  if (client_history_starts < cutoff)
 -    client_history_starts = cutoff;
 +}
 +
 +/** How many responses are we giving to clients requesting v2 network
 + * statuses? */
 +static uint32_t ns_v2_responses[GEOIP_NS_RESPONSE_NUM];
 +
 +/** How many responses are we giving to clients requesting v3 network
 + * statuses? */
 +static uint32_t ns_v3_responses[GEOIP_NS_RESPONSE_NUM];
 +
 +/** Note that we've rejected a client's request for a v2 or v3 network
 + * status, encoded in <b>action</b> for reason <b>reason</b> at time
 + * <b>now</b>. */
 +void
 +geoip_note_ns_response(geoip_client_action_t action,
 +                       geoip_ns_response_t response)
 +{
 +  static int arrays_initialized = 0;
 +  if (!get_options()->DirReqStatistics)
 +    return;
 +  if (!arrays_initialized) {
 +    memset(ns_v2_responses, 0, sizeof(ns_v2_responses));
 +    memset(ns_v3_responses, 0, sizeof(ns_v3_responses));
 +    arrays_initialized = 1;
 +  }
 +  tor_assert(action == GEOIP_CLIENT_NETWORKSTATUS ||
 +             action == GEOIP_CLIENT_NETWORKSTATUS_V2);
 +  tor_assert(response < GEOIP_NS_RESPONSE_NUM);
 +  if (action == GEOIP_CLIENT_NETWORKSTATUS)
 +    ns_v3_responses[response]++;
 +  else
 +    ns_v2_responses[response]++;
  }
  
  /** Do not mention any country from which fewer than this number of IPs have
@@@ -508,6 -414,13 +508,6 @@@
   * multiple of this value. */
  #define IP_GRANULARITY 8
  
 -/** Return the time at which we started recording geoip data. */
 -time_t
 -geoip_get_history_start(void)
 -{
 -  return client_history_starts;
 -}
 -
  /** Helper type: used to sort per-country totals by value. */
  typedef struct c_hist_t {
    char country[3]; /**< Two-letter country code. */
@@@ -529,319 -442,97 +529,319 @@@ _c_hist_compare(const void **_a, const 
      return strcmp(a->country, b->country);
  }
  
 -/** How long do we have to have observed per-country request history before we
 - * are willing to talk about it? */
 -#define GEOIP_MIN_OBSERVATION_TIME (12*60*60)
 +/** When there are incomplete directory requests at the end of a 24-hour
 + * period, consider those requests running for longer than this timeout as
 + * failed, the others as still running. */
 +#define DIRREQ_TIMEOUT (10*60)
  
 -/** Return the lowest x such that x is at least <b>number</b>, and x modulo
 - * <b>divisor</b> == 0. */
 -static INLINE unsigned
 -round_to_next_multiple_of(unsigned number, unsigned divisor)
 +/** Entry in a map from either conn->global_identifier for direct requests
 + * or a unique circuit identifier for tunneled requests to request time,
 + * response size, and completion time of a network status request. Used to
 + * measure download times of requests to derive average client
 + * bandwidths. */
 +typedef struct dirreq_map_entry_t {
 +  HT_ENTRY(dirreq_map_entry_t) node;
 +  /** Unique identifier for this network status request; this is either the
 +   * conn->global_identifier of the dir conn (direct request) or a new
 +   * locally unique identifier of a circuit (tunneled request). This ID is
 +   * only unique among other direct or tunneled requests, respectively. */
 +  uint64_t dirreq_id;
 +  unsigned int state:3; /**< State of this directory request. */
 +  unsigned int type:1; /**< Is this a direct or a tunneled request? */
 +  unsigned int completed:1; /**< Is this request complete? */
 +  unsigned int action:2; /**< Is this a v2 or v3 request? */
 +  /** When did we receive the request and started sending the response? */
 +  struct timeval request_time;
 +  size_t response_size; /**< What is the size of the response in bytes? */
 +  struct timeval completion_time; /**< When did the request succeed? */
 +} dirreq_map_entry_t;
 +
 +/** Map of all directory requests asking for v2 or v3 network statuses in
 + * the current geoip-stats interval. Values are
 + * of type *<b>dirreq_map_entry_t</b>. */
 +static HT_HEAD(dirreqmap, dirreq_map_entry_t) dirreq_map =
 +     HT_INITIALIZER();
 +
 +static int
 +dirreq_map_ent_eq(const dirreq_map_entry_t *a,
 +                  const dirreq_map_entry_t *b)
  {
 -  number += divisor - 1;
 -  number -= number % divisor;
 -  return number;
 +  return a->dirreq_id == b->dirreq_id && a->type == b->type;
  }
  
 -/** Return a newly allocated comma-separated string containing entries for all
 - * the countries from which we've seen enough clients connect. The entry
 - * format is cc=num where num is the number of IPs we've seen connecting from
 - * that country, and cc is a lowercased country code. Returns NULL if we don't
 - * want to export geoip data yet. */
 -char *
 -geoip_get_client_history(time_t now, geoip_client_action_t action)
 +static unsigned
 +dirreq_map_ent_hash(const dirreq_map_entry_t *entry)
 +{
 +  unsigned u = (unsigned) entry->dirreq_id;
 +  u += entry->type << 20;
 +  return u;
 +}
 +
 +HT_PROTOTYPE(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash,
 +             dirreq_map_ent_eq);
 +HT_GENERATE(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash,
 +            dirreq_map_ent_eq, 0.6, malloc, realloc, free);
 +
 +/** Helper: Put <b>entry</b> into map of directory requests using
 + * <b>type</b> and <b>dirreq_id</b> as key parts. If there is
 + * already an entry for that key, print out a BUG warning and return. */
 +static void
 +_dirreq_map_put(dirreq_map_entry_t *entry, dirreq_type_t type,
 +               uint64_t dirreq_id)
 +{
 +  dirreq_map_entry_t *old_ent;
 +  tor_assert(entry->type == type);
 +  tor_assert(entry->dirreq_id == dirreq_id);
 +
 +  /* XXXX022 once we're sure the bug case never happens, we can switch
 +   * to HT_INSERT */
 +  old_ent = HT_REPLACE(dirreqmap, &dirreq_map, entry);
 +  if (old_ent && old_ent != entry) {
 +    log_warn(LD_BUG, "Error when putting directory request into local "
 +             "map. There was already an entry for the same identifier.");
 +    return;
 +  }
 +}
 +
 +/** Helper: Look up and return an entry in the map of directory requests
 + * using <b>type</b> and <b>dirreq_id</b> as key parts. If there
 + * is no such entry, return NULL. */
 +static dirreq_map_entry_t *
 +_dirreq_map_get(dirreq_type_t type, uint64_t dirreq_id)
 +{
 +  dirreq_map_entry_t lookup;
 +  lookup.type = type;
 +  lookup.dirreq_id = dirreq_id;
 +  return HT_FIND(dirreqmap, &dirreq_map, &lookup);
 +}
 +
 +/** Note that an either direct or tunneled (see <b>type</b>) directory
 + * request for a network status with unique ID <b>dirreq_id</b> of size
 + * <b>response_size</b> and action <b>action</b> (either v2 or v3) has
 + * started. */
 +void
 +geoip_start_dirreq(uint64_t dirreq_id, size_t response_size,
 +                   geoip_client_action_t action, dirreq_type_t type)
 +{
 +  dirreq_map_entry_t *ent;
 +  if (!get_options()->DirReqStatistics)
 +    return;
 +  ent = tor_malloc_zero(sizeof(dirreq_map_entry_t));
 +  ent->dirreq_id = dirreq_id;
 +  tor_gettimeofday(&ent->request_time);
 +  ent->response_size = response_size;
 +  ent->action = action;
 +  ent->type = type;
 +  _dirreq_map_put(ent, type, dirreq_id);
 +}
 +
 +/** Change the state of the either direct or tunneled (see <b>type</b>)
 + * directory request with <b>dirreq_id</b> to <b>new_state</b> and
 + * possibly mark it as completed. If no entry can be found for the given
 + * key parts (e.g., if this is a directory request that we are not
 + * measuring, or one that was started in the previous measurement period),
 + * or if the state cannot be advanced to <b>new_state</b>, do nothing. */
 +void
 +geoip_change_dirreq_state(uint64_t dirreq_id, dirreq_type_t type,
 +                          dirreq_state_t new_state)
 +{
 +  dirreq_map_entry_t *ent;
 +  if (!get_options()->DirReqStatistics)
 +    return;
 +  ent = _dirreq_map_get(type, dirreq_id);
 +  if (!ent)
 +    return;
 +  if (new_state == DIRREQ_IS_FOR_NETWORK_STATUS)
 +    return;
 +  if (new_state - 1 != ent->state)
 +    return;
 +  ent->state = new_state;
 +  if ((type == DIRREQ_DIRECT &&
 +         new_state == DIRREQ_FLUSHING_DIR_CONN_FINISHED) ||
 +      (type == DIRREQ_TUNNELED &&
 +         new_state == DIRREQ_OR_CONN_BUFFER_FLUSHED)) {
 +    tor_gettimeofday(&ent->completion_time);
 +    ent->completed = 1;
 +  }
 +}
 +
 +/** Return a newly allocated comma-separated string containing statistics
 + * on network status downloads. The string contains the number of completed
 + * requests, timeouts, and still running requests as well as the download
 + * times by deciles and quartiles. Return NULL if we have not observed
 + * requests for long enough. */
 +static char *
 +geoip_get_dirreq_history(geoip_client_action_t action,
 +                           dirreq_type_t type)
  {
    char *result = NULL;
 -  if (!geoip_is_loaded())
 +  smartlist_t *dirreq_completed = NULL;
 +  uint32_t complete = 0, timeouts = 0, running = 0;
 +  int bufsize = 1024, written;
 +  dirreq_map_entry_t **ptr, **next, *ent;
 +  struct timeval now;
 +
 +  tor_gettimeofday(&now);
 +  if (action != GEOIP_CLIENT_NETWORKSTATUS &&
 +      action != GEOIP_CLIENT_NETWORKSTATUS_V2)
      return NULL;
 -  if (client_history_starts < (now - GEOIP_MIN_OBSERVATION_TIME)) {
 -    char buf[32];
 -    smartlist_t *chunks = NULL;
 -    smartlist_t *entries = NULL;
 -    int n_countries = geoip_get_n_countries();
 -    int i;
 -    clientmap_entry_t **ent;
 -    unsigned *counts = tor_malloc_zero(sizeof(unsigned)*n_countries);
 -    unsigned total = 0;
 -    unsigned granularity = IP_GRANULARITY;
 -#ifdef ENABLE_GEOIP_STATS
 -    if (get_options()->DirRecordUsageByCountry)
 -      granularity = get_options()->DirRecordUsageGranularity;
 -#endif
 -    HT_FOREACH(ent, clientmap, &client_history) {
 -      int country;
 -      if (((*ent)->last_seen & ACTION_MASK) != (int)action)
 -        continue;
 -      country = geoip_get_country_by_ip((*ent)->ipaddr);
 -      if (country < 0)
 -        continue;
 -      tor_assert(0 <= country && country < n_countries);
 -      ++counts[country];
 -      ++total;
 -    }
 -    /* Don't record anything if we haven't seen enough IPs. */
 -    if (total < MIN_IPS_TO_NOTE_ANYTHING)
 -      goto done;
 -    /* Make a list of c_hist_t */
 -    entries = smartlist_create();
 -    for (i = 0; i < n_countries; ++i) {
 -      unsigned c = counts[i];
 -      const char *countrycode;
 -      c_hist_t *ent;
 -      /* Only report a country if it has a minimum number of IPs. */
 -      if (c >= MIN_IPS_TO_NOTE_COUNTRY) {
 -        c = round_to_next_multiple_of(c, granularity);
 -        countrycode = geoip_get_country_name(i);
 -        ent = tor_malloc(sizeof(c_hist_t));
 -        strlcpy(ent->country, countrycode, sizeof(ent->country));
 -        ent->total = c;
 -        smartlist_add(entries, ent);
 +  dirreq_completed = smartlist_create();
 +  for (ptr = HT_START(dirreqmap, &dirreq_map); ptr; ptr = next) {
 +    ent = *ptr;
 +    if (ent->action != action || ent->type != type) {
 +      next = HT_NEXT(dirreqmap, &dirreq_map, ptr);
 +      continue;
 +    } else {
 +      if (ent->completed) {
 +        smartlist_add(dirreq_completed, ent);
 +        complete++;
 +        next = HT_NEXT_RMV(dirreqmap, &dirreq_map, ptr);
 +      } else {
 +        if (tv_mdiff(&ent->request_time, &now) / 1000 > DIRREQ_TIMEOUT)
 +          timeouts++;
 +        else
 +          running++;
 +        next = HT_NEXT_RMV(dirreqmap, &dirreq_map, ptr);
 +        tor_free(ent);
        }
      }
 -    /* Sort entries. Note that we must do this _AFTER_ rounding, or else
 -     * the sort order could leak info. */
 -    smartlist_sort(entries, _c_hist_compare);
 -
 -    /* Build the result. */
 -    chunks = smartlist_create();
 -    SMARTLIST_FOREACH(entries, c_hist_t *, ch, {
 -        tor_snprintf(buf, sizeof(buf), "%s=%u", ch->country, ch->total);
 -        smartlist_add(chunks, tor_strdup(buf));
 -      });
 -    result = smartlist_join_strings(chunks, ",", 0, NULL);
 -  done:
 -    tor_free(counts);
 -    if (chunks) {
 -      SMARTLIST_FOREACH(chunks, char *, c, tor_free(c));
 -      smartlist_free(chunks);
 -    }
 -    if (entries) {
 -      SMARTLIST_FOREACH(entries, c_hist_t *, c, tor_free(c));
 -      smartlist_free(entries);
 +  }
 +#define DIR_REQ_GRANULARITY 4
 +  complete = round_uint32_to_next_multiple_of(complete,
 +                                              DIR_REQ_GRANULARITY);
 +  timeouts = round_uint32_to_next_multiple_of(timeouts,
 +                                              DIR_REQ_GRANULARITY);
 +  running = round_uint32_to_next_multiple_of(running,
 +                                             DIR_REQ_GRANULARITY);
 +  result = tor_malloc_zero(bufsize);
 +  written = tor_snprintf(result, bufsize, "complete=%u,timeout=%u,"
 +                         "running=%u", complete, timeouts, running);
 +  if (written < 0) {
 +    tor_free(result);
 +    goto done;
 +  }
 +
 +#define MIN_DIR_REQ_RESPONSES 16
 +  if (complete >= MIN_DIR_REQ_RESPONSES) {
 +    uint32_t *dltimes;
 +    /* We may have rounded 'completed' up.  Here we want to use the
 +     * real value. */
 +    complete = smartlist_len(dirreq_completed);
 +    dltimes = tor_malloc_zero(sizeof(uint32_t) * complete);
 +    SMARTLIST_FOREACH_BEGIN(dirreq_completed, dirreq_map_entry_t *, ent) {
 +      uint32_t bytes_per_second;
 +      uint32_t time_diff = (uint32_t) tv_mdiff(&ent->request_time,
 +                                               &ent->completion_time);
 +      if (time_diff == 0)
 +        time_diff = 1; /* Avoid DIV/0; "instant" answers are impossible
 +                        * by law of nature or something, but a milisecond
 +                        * is a bit greater than "instantly" */
 +      bytes_per_second = (uint32_t)(1000 * ent->response_size / time_diff);
 +      dltimes[ent_sl_idx] = bytes_per_second;
 +    } SMARTLIST_FOREACH_END(ent);
 +    median_uint32(dltimes, complete); /* sorts as a side effect. */
 +    written = tor_snprintf(result + written, bufsize - written,
 +                           ",min=%u,d1=%u,d2=%u,q1=%u,d3=%u,d4=%u,md=%u,"
 +                           "d6=%u,d7=%u,q3=%u,d8=%u,d9=%u,max=%u",
 +                           dltimes[0],
 +                           dltimes[1*complete/10-1],
 +                           dltimes[2*complete/10-1],
 +                           dltimes[1*complete/4-1],
 +                           dltimes[3*complete/10-1],
 +                           dltimes[4*complete/10-1],
 +                           dltimes[5*complete/10-1],
 +                           dltimes[6*complete/10-1],
 +                           dltimes[7*complete/10-1],
 +                           dltimes[3*complete/4-1],
 +                           dltimes[8*complete/10-1],
 +                           dltimes[9*complete/10-1],
 +                           dltimes[complete-1]);
 +    if (written<0)
 +      tor_free(result);
 +    tor_free(dltimes);
 +  }
 + done:
 +  SMARTLIST_FOREACH(dirreq_completed, dirreq_map_entry_t *, ent,
 +                    tor_free(ent));
 +  smartlist_free(dirreq_completed);
 +  return result;
 +}
 +
 +/** Return a newly allocated comma-separated string containing entries for
 + * all the countries from which we've seen enough clients connect as a
 + * bridge, directory server, or entry guard. The entry format is cc=num
 + * where num is the number of IPs we've seen connecting from that country,
 + * and cc is a lowercased country code. Returns NULL if we don't want
 + * to export geoip data yet. */
 +char *
 +geoip_get_client_history(geoip_client_action_t action)
 +{
 +  char *result = NULL;
 +  unsigned granularity = IP_GRANULARITY;
 +  smartlist_t *chunks = NULL;
 +  smartlist_t *entries = NULL;
 +  int n_countries = geoip_get_n_countries();
 +  int i;
 +  clientmap_entry_t **ent;
 +  unsigned *counts = NULL;
 +  unsigned total = 0;
 +
 +  if (!geoip_is_loaded())
 +    return NULL;
 +
 +  counts = tor_malloc_zero(sizeof(unsigned)*n_countries);
 +  HT_FOREACH(ent, clientmap, &client_history) {
 +    int country;
 +    if ((*ent)->action != (int)action)
 +      continue;
 +    country = geoip_get_country_by_ip((*ent)->ipaddr);
 +    if (country < 0)
 +      country = 0; /** unresolved requests are stored at index 0. */
 +    tor_assert(0 <= country && country < n_countries);
 +    ++counts[country];
 +    ++total;
 +  }
 +  /* Don't record anything if we haven't seen enough IPs. */
 +  if (total < MIN_IPS_TO_NOTE_ANYTHING)
 +    goto done;
 +  /* Make a list of c_hist_t */
 +  entries = smartlist_create();
 +  for (i = 0; i < n_countries; ++i) {
 +    unsigned c = counts[i];
 +    const char *countrycode;
 +    c_hist_t *ent;
 +    /* Only report a country if it has a minimum number of IPs. */
 +    if (c >= MIN_IPS_TO_NOTE_COUNTRY) {
 +      c = round_to_next_multiple_of(c, granularity);
 +      countrycode = geoip_get_country_name(i);
 +      ent = tor_malloc(sizeof(c_hist_t));
 +      strlcpy(ent->country, countrycode, sizeof(ent->country));
 +      ent->total = c;
 +      smartlist_add(entries, ent);
      }
    }
 +  /* Sort entries. Note that we must do this _AFTER_ rounding, or else
 +   * the sort order could leak info. */
 +  smartlist_sort(entries, _c_hist_compare);
 +
 +  /* Build the result. */
 +  chunks = smartlist_create();
 +  SMARTLIST_FOREACH(entries, c_hist_t *, ch, {
 +      char *buf=NULL;
 +      tor_asprintf(&buf, "%s=%u", ch->country, ch->total);
 +      smartlist_add(chunks, buf);
 +  });
 +  result = smartlist_join_strings(chunks, ",", 0, NULL);
 + done:
 +  tor_free(counts);
 +  if (chunks) {
 +    SMARTLIST_FOREACH(chunks, char *, c, tor_free(c));
 +    smartlist_free(chunks);
 +  }
 +  if (entries) {
 +    SMARTLIST_FOREACH(entries, c_hist_t *, c, tor_free(c));
 +    smartlist_free(entries);
 +  }
    return result;
  }
  
@@@ -849,12 -540,18 +849,12 @@@
   * for <b>action</b> in a format suitable for an extra-info document, or NULL
   * on failure. */
  char *
 -geoip_get_request_history(time_t now, geoip_client_action_t action)
 +geoip_get_request_history(geoip_client_action_t action)
  {
    smartlist_t *entries, *strings;
    char *result;
    unsigned granularity = IP_GRANULARITY;
 -#ifdef ENABLE_GEOIP_STATS
 -  if (get_options()->DirRecordUsageByCountry)
 -    granularity = get_options()->DirRecordUsageGranularity;
 -#endif
  
 -  if (client_history_starts >= (now - GEOIP_MIN_OBSERVATION_TIME))
 -    return NULL;
    if (action != GEOIP_CLIENT_NETWORKSTATUS &&
        action != GEOIP_CLIENT_NETWORKSTATUS_V2)
      return NULL;
@@@ -863,10 -560,13 +863,10 @@@
  
    entries = smartlist_create();
    SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, {
 -      uint32_t *n = (action == GEOIP_CLIENT_NETWORKSTATUS)
 -        ? c->n_v3_ns_requests : c->n_v2_ns_requests;
        uint32_t tot = 0;
 -      int i;
        c_hist_t *ent;
 -      for (i=0; i < REQUEST_HIST_LEN; ++i)
 -        tot += n[i];
 +      tot = (action == GEOIP_CLIENT_NETWORKSTATUS) ?
 +            c->n_v3_ns_requests : c->n_v2_ns_requests;
        if (!tot)
          continue;
        ent = tor_malloc_zero(sizeof(c_hist_t));
@@@ -878,9 -578,9 +878,9 @@@
  
    strings = smartlist_create();
    SMARTLIST_FOREACH(entries, c_hist_t *, ent, {
 -      char buf[32];
 -      tor_snprintf(buf, sizeof(buf), "%s=%u", ent->country, ent->total);
 -      smartlist_add(strings, tor_strdup(buf));
 +      char *buf = NULL;
 +      tor_asprintf(&buf, "%s=%u", ent->country, ent->total);
 +      smartlist_add(strings, buf);
      });
    result = smartlist_join_strings(strings, ",", 0, NULL);
    SMARTLIST_FOREACH(strings, char *, cp, tor_free(cp));
@@@ -890,434 -590,69 +890,434 @@@
    return result;
  }
  
 -/** Store all our geoip statistics into $DATADIR/geoip-stats. */
 +/** Start time of directory request stats or 0 if we're not collecting
 + * directory request statistics. */
 +static time_t start_of_dirreq_stats_interval;
 +
 +/** Initialize directory request stats. */
  void
 -dump_geoip_stats(void)
 +geoip_dirreq_stats_init(time_t now)
  {
 -#ifdef ENABLE_GEOIP_STATS
 -  time_t now = time(NULL);
 -  time_t request_start;
 -  char *filename = get_datadir_fname("geoip-stats");
 +  start_of_dirreq_stats_interval = now;
 +}
 +
 +/** Stop collecting directory request stats in a way that we can re-start
 + * doing so in geoip_dirreq_stats_init(). */
 +void
 +geoip_dirreq_stats_term(void)
 +{
 +  SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, {
 +      c->n_v2_ns_requests = c->n_v3_ns_requests = 0;
 +  });
 +  {
 +    clientmap_entry_t **ent, **next, *this;
 +    for (ent = HT_START(clientmap, &client_history); ent != NULL;
 +         ent = next) {
 +      if ((*ent)->action == GEOIP_CLIENT_NETWORKSTATUS ||
 +          (*ent)->action == GEOIP_CLIENT_NETWORKSTATUS_V2) {
 +        this = *ent;
 +        next = HT_NEXT_RMV(clientmap, &client_history, ent);
 +        tor_free(this);
 +      } else {
 +        next = HT_NEXT(clientmap, &client_history, ent);
 +      }
 +    }
 +  }
 +  v2_share_times_seconds = v3_share_times_seconds = 0.0;
 +  last_time_determined_shares = 0;
 +  share_seconds = 0;
 +  memset(ns_v2_responses, 0, sizeof(ns_v2_responses));
 +  memset(ns_v3_responses, 0, sizeof(ns_v3_responses));
 +  {
 +    dirreq_map_entry_t **ent, **next, *this;
 +    for (ent = HT_START(dirreqmap, &dirreq_map); ent != NULL; ent = next) {
 +      this = *ent;
 +      next = HT_NEXT_RMV(dirreqmap, &dirreq_map, ent);
 +      tor_free(this);
 +    }
 +  }
 +  start_of_dirreq_stats_interval = 0;
 +}
 +
 +/** Write dirreq statistics to $DATADIR/stats/dirreq-stats and return when
 + * we would next want to write. */
 +time_t
 +geoip_dirreq_stats_write(time_t now)
 +{
 +  char *statsdir = NULL, *filename = NULL;
    char *data_v2 = NULL, *data_v3 = NULL;
 -  char since[ISO_TIME_LEN+1], written[ISO_TIME_LEN+1];
 +  char written[ISO_TIME_LEN+1];
    open_file_t *open_file = NULL;
    double v2_share = 0.0, v3_share = 0.0;
    FILE *out;
 +  int i;
 +
 +  if (!start_of_dirreq_stats_interval)
 +    return 0; /* Not initialized. */
 +  if (start_of_dirreq_stats_interval + WRITE_STATS_INTERVAL > now)
 +    goto done; /* Not ready to write. */
 +
 +  /* Discard all items in the client history that are too old. */
 +  geoip_remove_old_clients(start_of_dirreq_stats_interval);
  
 -  data_v2 = geoip_get_client_history(now, GEOIP_CLIENT_NETWORKSTATUS_V2);
 -  data_v3 = geoip_get_client_history(now, GEOIP_CLIENT_NETWORKSTATUS);
 -  format_iso_time(since, geoip_get_history_start());
 +  statsdir = get_datadir_fname("stats");
 +  if (check_private_dir(statsdir, CPD_CREATE) < 0)
 +    goto done;
 +  filename = get_datadir_fname2("stats", "dirreq-stats");
 +  data_v2 = geoip_get_client_history(GEOIP_CLIENT_NETWORKSTATUS_V2);
 +  data_v3 = geoip_get_client_history(GEOIP_CLIENT_NETWORKSTATUS);
    format_iso_time(written, now);
 -  out = start_writing_to_stdio_file(filename, OPEN_FLAGS_REPLACE,
 +  out = start_writing_to_stdio_file(filename, OPEN_FLAGS_APPEND,
                                      0600, &open_file);
    if (!out)
      goto done;
 -  if (fprintf(out, "written %s\nstarted-at %s\nns-ips %s\nns-v2-ips %s\n",
 -              written, since,
 +  if (fprintf(out, "dirreq-stats-end %s (%d s)\ndirreq-v3-ips %s\n"
 +              "dirreq-v2-ips %s\n", written,
 +              (unsigned) (now - start_of_dirreq_stats_interval),
                data_v3 ? data_v3 : "", data_v2 ? data_v2 : "") < 0)
      goto done;
    tor_free(data_v2);
    tor_free(data_v3);
  
 -  request_start = current_request_period_starts -
 -    (n_old_request_periods * REQUEST_HIST_PERIOD);
 -  format_iso_time(since, request_start);
 -  data_v2 = geoip_get_request_history(now, GEOIP_CLIENT_NETWORKSTATUS_V2);
 -  data_v3 = geoip_get_request_history(now, GEOIP_CLIENT_NETWORKSTATUS);
 -  if (fprintf(out, "requests-start %s\nn-ns-reqs %s\nn-v2-ns-reqs %s\n",
 -              since,
 +  data_v2 = geoip_get_request_history(GEOIP_CLIENT_NETWORKSTATUS_V2);
 +  data_v3 = geoip_get_request_history(GEOIP_CLIENT_NETWORKSTATUS);
 +  if (fprintf(out, "dirreq-v3-reqs %s\ndirreq-v2-reqs %s\n",
                data_v3 ? data_v3 : "", data_v2 ? data_v2 : "") < 0)
      goto done;
 -  if (!router_get_my_share_of_directory_requests(&v2_share, &v3_share)) {
 -    if (fprintf(out, "v2-ns-share %0.2lf%%\n", v2_share*100) < 0)
 +  tor_free(data_v2);
 +  tor_free(data_v3);
 +  SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, {
 +      c->n_v2_ns_requests = c->n_v3_ns_requests = 0;
 +  });
 +#define RESPONSE_GRANULARITY 8
 +  for (i = 0; i < GEOIP_NS_RESPONSE_NUM; i++) {
 +    ns_v2_responses[i] = round_uint32_to_next_multiple_of(
 +                               ns_v2_responses[i], RESPONSE_GRANULARITY);
 +    ns_v3_responses[i] = round_uint32_to_next_multiple_of(
 +                               ns_v3_responses[i], RESPONSE_GRANULARITY);
 +  }
 +#undef RESPONSE_GRANULARITY
 +  if (fprintf(out, "dirreq-v3-resp ok=%u,not-enough-sigs=%u,unavailable=%u,"
 +                   "not-found=%u,not-modified=%u,busy=%u\n",
 +                   ns_v3_responses[GEOIP_SUCCESS],
 +                   ns_v3_responses[GEOIP_REJECT_NOT_ENOUGH_SIGS],
 +                   ns_v3_responses[GEOIP_REJECT_UNAVAILABLE],
 +                   ns_v3_responses[GEOIP_REJECT_NOT_FOUND],
 +                   ns_v3_responses[GEOIP_REJECT_NOT_MODIFIED],
 +                   ns_v3_responses[GEOIP_REJECT_BUSY]) < 0)
 +    goto done;
 +  if (fprintf(out, "dirreq-v2-resp ok=%u,unavailable=%u,"
 +                   "not-found=%u,not-modified=%u,busy=%u\n",
 +                   ns_v2_responses[GEOIP_SUCCESS],
 +                   ns_v2_responses[GEOIP_REJECT_UNAVAILABLE],
 +                   ns_v2_responses[GEOIP_REJECT_NOT_FOUND],
 +                   ns_v2_responses[GEOIP_REJECT_NOT_MODIFIED],
 +                   ns_v2_responses[GEOIP_REJECT_BUSY]) < 0)
 +    goto done;
 +  memset(ns_v2_responses, 0, sizeof(ns_v2_responses));
 +  memset(ns_v3_responses, 0, sizeof(ns_v3_responses));
 +  if (!geoip_get_mean_shares(now, &v2_share, &v3_share)) {
 +    if (fprintf(out, "dirreq-v2-share %0.2lf%%\n", v2_share*100) < 0)
        goto done;
 -    if (fprintf(out, "v3-ns-share %0.2lf%%\n", v3_share*100) < 0)
 +    if (fprintf(out, "dirreq-v3-share %0.2lf%%\n", v3_share*100) < 0)
        goto done;
    }
  
 +  data_v2 = geoip_get_dirreq_history(GEOIP_CLIENT_NETWORKSTATUS_V2,
 +                                       DIRREQ_DIRECT);
 +  data_v3 = geoip_get_dirreq_history(GEOIP_CLIENT_NETWORKSTATUS,
 +                                       DIRREQ_DIRECT);
 +  if (fprintf(out, "dirreq-v3-direct-dl %s\ndirreq-v2-direct-dl %s\n",
 +              data_v3 ? data_v3 : "", data_v2 ? data_v2 : "") < 0)
 +    goto done;
 +  tor_free(data_v2);
 +  tor_free(data_v3);
 +  data_v2 = geoip_get_dirreq_history(GEOIP_CLIENT_NETWORKSTATUS_V2,
 +                                       DIRREQ_TUNNELED);
 +  data_v3 = geoip_get_dirreq_history(GEOIP_CLIENT_NETWORKSTATUS,
 +                                       DIRREQ_TUNNELED);
 +  if (fprintf(out, "dirreq-v3-tunneled-dl %s\ndirreq-v2-tunneled-dl %s\n",
 +              data_v3 ? data_v3 : "", data_v2 ? data_v2 : "") < 0)
 +    goto done;
 +
    finish_writing_to_file(open_file);
    open_file = NULL;
 +
 +  start_of_dirreq_stats_interval = now;
 +
   done:
    if (open_file)
      abort_writing_to_file(open_file);
    tor_free(filename);
 +  tor_free(statsdir);
    tor_free(data_v2);
    tor_free(data_v3);
 -#endif
 +  return start_of_dirreq_stats_interval + WRITE_STATS_INTERVAL;
 +}
 +
 +/** Start time of bridge stats or 0 if we're not collecting bridge
 + * statistics. */
 +static time_t start_of_bridge_stats_interval;
 +
 +/** Initialize bridge stats. */
 +void
 +geoip_bridge_stats_init(time_t now)
 +{
 +  start_of_bridge_stats_interval = now;
 +}
 +
 +/** Stop collecting bridge stats in a way that we can re-start doing so in
 + * geoip_bridge_stats_init(). */
 +void
 +geoip_bridge_stats_term(void)
 +{
 +  client_history_clear();
 +  start_of_bridge_stats_interval = 0;
 +}
 +
 +/** Parse the bridge statistics as they are written to extra-info
 + * descriptors for being returned to controller clients. Return the
 + * controller string if successful, or NULL otherwise. */
 +static char *
 +parse_bridge_stats_controller(const char *stats_str, time_t now)
 +{
 +  char stats_end_str[ISO_TIME_LEN+1], stats_start_str[ISO_TIME_LEN+1],
 +       *controller_str, *eos, *eol, *summary;
 +
 +  const char *BRIDGE_STATS_END = "bridge-stats-end ";
 +  const char *BRIDGE_IPS = "bridge-ips ";
 +  const char *BRIDGE_IPS_EMPTY_LINE = "bridge-ips\n";
 +  const char *tmp;
 +  time_t stats_end_time;
 +  int seconds;
 +  tor_assert(stats_str);
 +
 +  /* Parse timestamp and number of seconds from
 +    "bridge-stats-end YYYY-MM-DD HH:MM:SS (N s)" */
 +  tmp = find_str_at_start_of_line(stats_str, BRIDGE_STATS_END);
 +  if (!tmp)
 +    return NULL;
 +  tmp += strlen(BRIDGE_STATS_END);
 +
 +  if (strlen(tmp) < ISO_TIME_LEN + 6)
 +    return NULL;
 +  strlcpy(stats_end_str, tmp, sizeof(stats_end_str));
 +  if (parse_iso_time(stats_end_str, &stats_end_time) < 0)
 +    return NULL;
 +  if (stats_end_time < now - (25*60*60) ||
 +      stats_end_time > now + (1*60*60))
 +    return NULL;
 +  seconds = (int)strtol(tmp + ISO_TIME_LEN + 2, &eos, 10);
 +  if (!eos || seconds < 23*60*60)
 +    return NULL;
 +  format_iso_time(stats_start_str, stats_end_time - seconds);
 +
 +  /* Parse: "bridge-ips CC=N,CC=N,..." */
 +  tmp = find_str_at_start_of_line(stats_str, BRIDGE_IPS);
 +  if (tmp) {
 +    tmp += strlen(BRIDGE_IPS);
 +    tmp = eat_whitespace_no_nl(tmp);
 +    eol = strchr(tmp, '\n');
 +    if (eol)
 +      summary = tor_strndup(tmp, eol-tmp);
 +    else
 +      summary = tor_strdup(tmp);
 +  } else {
 +    /* Look if there is an empty "bridge-ips" line */
 +    tmp = find_str_at_start_of_line(stats_str, BRIDGE_IPS_EMPTY_LINE);
 +    if (!tmp)
 +      return NULL;
 +    summary = tor_strdup("");
 +  }
 +
 +  tor_asprintf(&controller_str,
 +               "TimeStarted=\"%s\" CountrySummary=%s",
 +               stats_start_str, summary);
 +  tor_free(summary);
 +  return controller_str;
 +}
 +
 +/** Most recent bridge statistics formatted to be written to extra-info
 + * descriptors. */
 +static char *bridge_stats_extrainfo = NULL;
 +
 +/** Most recent bridge statistics formatted to be returned to controller
 + * clients. */
 +static char *bridge_stats_controller = NULL;
 +
 +/** Write bridge statistics to $DATADIR/stats/bridge-stats and return
 + * when we should next try to write statistics. */
 +time_t
 +geoip_bridge_stats_write(time_t now)
 +{
 +  char *statsdir = NULL, *filename = NULL, *data = NULL,
 +       written[ISO_TIME_LEN+1], *out = NULL, *controller_str;
 +  size_t len;
 +
 +  /* Check if 24 hours have passed since starting measurements. */
 +  if (now < start_of_bridge_stats_interval + WRITE_STATS_INTERVAL)
 +    return start_of_bridge_stats_interval + WRITE_STATS_INTERVAL;
 +
 +  /* Discard all items in the client history that are too old. */
 +  geoip_remove_old_clients(start_of_bridge_stats_interval);
 +
 +  statsdir = get_datadir_fname("stats");
 +  if (check_private_dir(statsdir, CPD_CREATE) < 0)
 +    goto done;
 +  filename = get_datadir_fname2("stats", "bridge-stats");
 +  data = geoip_get_client_history(GEOIP_CLIENT_CONNECT);
 +  format_iso_time(written, now);
 +  len = strlen("bridge-stats-end  (999999 s)\nbridge-ips \n") +
 +        ISO_TIME_LEN + (data ? strlen(data) : 0) + 42;
 +  out = tor_malloc(len);
 +  if (tor_snprintf(out, len, "bridge-stats-end %s (%u s)\nbridge-ips %s\n",
 +              written, (unsigned) (now - start_of_bridge_stats_interval),
 +              data ? data : "") < 0)
 +    goto done;
 +  write_str_to_file(filename, out, 0);
 +  controller_str = parse_bridge_stats_controller(out, now);
 +  if (!controller_str)
 +    goto done;
 +  start_of_bridge_stats_interval = now;
 +  tor_free(bridge_stats_extrainfo);
 +  tor_free(bridge_stats_controller);
 +  bridge_stats_extrainfo = out;
 +  out = NULL;
 +  bridge_stats_controller = controller_str;
 +  control_event_clients_seen(controller_str);
 + done:
 +  tor_free(filename);
 +  tor_free(statsdir);
 +  tor_free(data);
 +  tor_free(out);
 +  return start_of_bridge_stats_interval +
 +         WRITE_STATS_INTERVAL;
 +}
 +
 +/** Try to load the most recent bridge statistics from disk, unless we
 + * have finished a measurement interval lately. */
 +static void
 +load_bridge_stats(time_t now)
 +{
 +  char *statsdir, *fname=NULL, *contents, *controller_str;
 +  if (bridge_stats_extrainfo)
 +    return;
 +  statsdir = get_datadir_fname("stats");
 +  if (check_private_dir(statsdir, CPD_CREATE) < 0)
 +    goto done;
 +  fname = get_datadir_fname2("stats", "bridge-stats");
 +  contents = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL);
 +  if (contents) {
 +    controller_str = parse_bridge_stats_controller(contents, now);
 +    if (controller_str) {
 +      bridge_stats_extrainfo = contents;
 +      bridge_stats_controller = controller_str;
 +    } else {
 +      tor_free(contents);
 +    }
 +  }
 + done:
 +  tor_free(fname);
 +  tor_free(statsdir);
 +}
 +
 +/** Return most recent bridge statistics for inclusion in extra-info
 + * descriptors, or NULL if we don't have recent bridge statistics. */
 +const char *
 +geoip_get_bridge_stats_extrainfo(time_t now)
 +{
 +  load_bridge_stats(now);
 +  return bridge_stats_extrainfo;
 +}
 +
 +/** Return most recent bridge statistics to be returned to controller
 + * clients, or NULL if we don't have recent bridge statistics. */
 +const char *
 +geoip_get_bridge_stats_controller(time_t now)
 +{
 +  load_bridge_stats(now);
 +  return bridge_stats_controller;
 +}
 +
 +/** Start time of entry stats or 0 if we're not collecting entry
 + * statistics. */
 +static time_t start_of_entry_stats_interval;
 +
 +/** Initialize entry stats. */
 +void
 +geoip_entry_stats_init(time_t now)
 +{
 +  start_of_entry_stats_interval = now;
 +}
 +
 +/** Stop collecting entry stats in a way that we can re-start doing so in
 + * geoip_entry_stats_init(). */
 +void
 +geoip_entry_stats_term(void)
 +{
 +  client_history_clear();
 +  start_of_entry_stats_interval = 0;
 +}
 +
 +/** Write entry statistics to $DATADIR/stats/entry-stats and return time
 + * when we would next want to write. */
 +time_t
 +geoip_entry_stats_write(time_t now)
 +{
 +  char *statsdir = NULL, *filename = NULL;
 +  char *data = NULL;
 +  char written[ISO_TIME_LEN+1];
 +  open_file_t *open_file = NULL;
 +  FILE *out;
 +
 +  if (!start_of_entry_stats_interval)
 +    return 0; /* Not initialized. */
 +  if (start_of_entry_stats_interval + WRITE_STATS_INTERVAL > now)
 +    goto done; /* Not ready to write. */
 +
 +  /* Discard all items in the client history that are too old. */
 +  geoip_remove_old_clients(start_of_entry_stats_interval);
 +
 +  statsdir = get_datadir_fname("stats");
 +  if (check_private_dir(statsdir, CPD_CREATE) < 0)
 +    goto done;
 +  filename = get_datadir_fname2("stats", "entry-stats");
 +  data = geoip_get_client_history(GEOIP_CLIENT_CONNECT);
 +  format_iso_time(written, now);
 +  out = start_writing_to_stdio_file(filename, OPEN_FLAGS_APPEND,
 +                                    0600, &open_file);
 +  if (!out)
 +    goto done;
 +  if (fprintf(out, "entry-stats-end %s (%u s)\nentry-ips %s\n",
 +              written, (unsigned) (now - start_of_entry_stats_interval),
 +              data ? data : "") < 0)
 +    goto done;
 +
 +  start_of_entry_stats_interval = now;
 +
 +  finish_writing_to_file(open_file);
 +  open_file = NULL;
 + done:
 +  if (open_file)
 +    abort_writing_to_file(open_file);
 +  tor_free(filename);
 +  tor_free(statsdir);
 +  tor_free(data);
 +  return start_of_entry_stats_interval + WRITE_STATS_INTERVAL;
  }
  
  /** Helper used to implement GETINFO ip-to-country/... controller command. */
  int
  getinfo_helper_geoip(control_connection_t *control_conn,
 -                     const char *question, char **answer)
 +                     const char *question, char **answer,
 +                     const char **errmsg)
  {
    (void)control_conn;
 -  if (geoip_is_loaded() && !strcmpstart(question, "ip-to-country/")) {
 +  if (!geoip_is_loaded()) {
 +    *errmsg = "GeoIP data not loaded";
 +    return -1;
 +  }
 +  if (!strcmpstart(question, "ip-to-country/")) {
      int c;
      uint32_t ip;
      struct in_addr in;
@@@ -1339,8 -674,8 +1339,8 @@@ clear_geoip_db(void
      SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, tor_free(c));
      smartlist_free(geoip_countries);
    }
 -  if (country_idxplus1_by_lc_code)
 -    strmap_free(country_idxplus1_by_lc_code, NULL);
 +
 +  strmap_free(country_idxplus1_by_lc_code, NULL);
    if (geoip_entries) {
      SMARTLIST_FOREACH(geoip_entries, geoip_entry_t *, ent, tor_free(ent));
      smartlist_free(geoip_entries);
@@@ -1354,24 -689,13 +1354,24 @@@
  void
  geoip_free_all(void)
  {
 -  clientmap_entry_t **ent, **next, *this;
 -  for (ent = HT_START(clientmap, &client_history); ent != NULL; ent = next) {
 -    this = *ent;
 -    next = HT_NEXT_RMV(clientmap, &client_history, ent);
 -    tor_free(this);
 +  {
 +    clientmap_entry_t **ent, **next, *this;
 +    for (ent = HT_START(clientmap, &client_history); ent != NULL; ent = next) {
 +      this = *ent;
 +      next = HT_NEXT_RMV(clientmap, &client_history, ent);
 +      tor_free(this);
 +    }
 +    HT_CLEAR(clientmap, &client_history);
 +  }
 +  {
 +    dirreq_map_entry_t **ent, **next, *this;
 +    for (ent = HT_START(dirreqmap, &dirreq_map); ent != NULL; ent = next) {
 +      this = *ent;
 +      next = HT_NEXT_RMV(dirreqmap, &dirreq_map, ent);
 +      tor_free(this);
 +    }
 +    HT_CLEAR(dirreqmap, &dirreq_map);
    }
 -  HT_CLEAR(clientmap, &client_history);
  
    clear_geoip_db();
  }
diff --combined src/or/hibernate.c
index 929c199,9ec6375..48a7ce7
--- a/src/or/hibernate.c
+++ b/src/or/hibernate.c
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -22,12 -22,6 +22,12 @@@ hibernating, phase 2
  */
  
  #include "or.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "hibernate.h"
 +#include "main.h"
 +#include "router.h"
  
  /** Possible values of hibernate_state */
  typedef enum {
@@@ -95,13 -89,6 +95,13 @@@ static uint64_t n_bytes_read_in_interva
  static uint64_t n_bytes_written_in_interval = 0;
  /** How many seconds have we been running this interval? */
  static uint32_t n_seconds_active_in_interval = 0;
 +/** How many seconds were we active in this interval before we hit our soft
 + * limit? */
 +static int n_seconds_to_hit_soft_limit = 0;
 +/** When in this interval was the soft limit hit. */
 +static time_t soft_limit_hit_at = 0;
 +/** How many bytes had we read/written when we hit the soft limit? */
 +static uint64_t n_bytes_at_soft_limit = 0;
  /** When did this accounting interval start? */
  static time_t interval_start_time = 0;
  /** When will this accounting interval end? */
@@@ -195,9 -182,6 +195,9 @@@ accounting_parse_options(or_options_t *
    case UNIT_DAY:
      d = 0;
      break;
 +    /* Coverity dislikes unreachable default cases; some compilers warn on
 +     * switch statements missing a case.  Tell Coverity not to worry. */
 +    /* coverity[dead_error_begin] */
    default:
      tor_assert(0);
    }
@@@ -348,57 -332,29 +348,57 @@@ start_of_accounting_period_after(time_
    return edge_of_accounting_period_containing(now, 1);
  }
  
 +/** Return the length of the accounting period containing the time
 + * <b>now</b>. */
 +static long
 +length_of_accounting_period_containing(time_t now)
 +{
 +  return edge_of_accounting_period_containing(now, 1) -
 +    edge_of_accounting_period_containing(now, 0);
 +}
 +
  /** Initialize the accounting subsystem. */
  void
  configure_accounting(time_t now)
  {
 +  time_t s_now;
    /* Try to remember our recorded usage. */
    if (!interval_start_time)
      read_bandwidth_usage(); /* If we fail, we'll leave values at zero, and
                               * reset below.*/
 -  if (!interval_start_time ||
 -      start_of_accounting_period_after(interval_start_time) <= now) {
 -    /* We didn't have recorded usage, or we don't have recorded usage
 -     * for this interval. Start a new interval. */
 +
 +  s_now = start_of_accounting_period_containing(now);
 +
 +  if (!interval_start_time) {
 +    /* We didn't have recorded usage; Start a new interval. */
      log_info(LD_ACCT, "Starting new accounting interval.");
      reset_accounting(now);
 -  } else if (interval_start_time ==
 -        start_of_accounting_period_containing(interval_start_time)) {
 +  } else if (s_now == interval_start_time) {
      log_info(LD_ACCT, "Continuing accounting interval.");
      /* We are in the interval we thought we were in. Do nothing.*/
      interval_end_time = start_of_accounting_period_after(interval_start_time);
    } else {
 -    log_warn(LD_ACCT,
 -             "Mismatched accounting interval; starting a fresh one.");
 -    reset_accounting(now);
 +    long duration = length_of_accounting_period_containing(now);
 +    double delta = ((double)(s_now - interval_start_time)) / duration;
 +    if (-0.50 <= delta && delta <= 0.50) {
 +      /* The start of the period is now a little later or earlier than we
 +       * remembered.  That's fine; we might lose some bytes we could otherwise
 +       * have written, but better to err on the side of obeying people's
 +       * accounting settings. */
 +      log_info(LD_ACCT, "Accounting interval moved by %.02f%%; "
 +               "that's fine.", delta*100);
 +      interval_end_time = start_of_accounting_period_after(now);
 +    } else if (delta >= 0.99) {
 +      /* This is the regular time-moved-forward case; don't be too noisy
 +       * about it or people will complain */
 +      log_info(LD_ACCT, "Accounting interval elapsed; starting a new one");
 +      reset_accounting(now);
 +    } else {
 +      log_warn(LD_ACCT,
 +               "Mismatched accounting interval: moved by %.02f%%. "
 +               "Starting a fresh one.", delta*100);
 +      reset_accounting(now);
 +    }
    }
    accounting_set_wakeup_time();
  }
@@@ -409,42 -365,23 +409,42 @@@
  static void
  update_expected_bandwidth(void)
  {
 -  uint64_t used, expected;
 -  uint64_t max_configured = (get_options()->BandwidthRate * 60);
 -
 -  if (n_seconds_active_in_interval < 1800) {
 +  uint64_t expected;
 +  or_options_t *options= get_options();
 +  uint64_t max_configured = (options->RelayBandwidthRate > 0 ?
 +                             options->RelayBandwidthRate :
 +                             options->BandwidthRate) * 60;
 +
 +#define MIN_TIME_FOR_MEASUREMENT (1800)
 +
 +  if (soft_limit_hit_at > interval_start_time && n_bytes_at_soft_limit &&
 +      (soft_limit_hit_at - interval_start_time) > MIN_TIME_FOR_MEASUREMENT) {
 +    /* If we hit our soft limit last time, only count the bytes up to that
 +     * time. This is a better predictor of our actual bandwidth than
 +     * considering the entirety of the last interval, since we likely started
 +     * using bytes very slowly once we hit our soft limit. */
 +    expected = n_bytes_at_soft_limit /
 +      (soft_limit_hit_at - interval_start_time);
 +    expected /= 60;
 +  } else if (n_seconds_active_in_interval >= MIN_TIME_FOR_MEASUREMENT) {
 +    /* Otherwise, we either measured enough time in the last interval but
 +     * never hit our soft limit, or we're using a state file from a Tor that
 +     * doesn't know to store soft-limit info.  Just take rate at which
 +     * we were reading/writing in the last interval as our expected rate.
 +     */
 +    uint64_t used = MAX(n_bytes_written_in_interval,
 +                        n_bytes_read_in_interval);
 +    expected = used / (n_seconds_active_in_interval / 60);
 +  } else {
      /* If we haven't gotten enough data last interval, set 'expected'
       * to 0.  This will set our wakeup to the start of the interval.
       * Next interval, we'll choose our starting time based on how much
       * we sent this interval.
       */
      expected = 0;
 -  } else {
 -    used = n_bytes_written_in_interval < n_bytes_read_in_interval ?
 -      n_bytes_read_in_interval : n_bytes_written_in_interval;
 -    expected = used / (n_seconds_active_in_interval / 60);
 -    if (expected > max_configured)
 -      expected = max_configured;
    }
 +  if (expected > max_configured)
 +    expected = max_configured;
    expected_bandwidth_usage = expected;
  }
  
@@@ -462,9 -399,6 +462,9 @@@ reset_accounting(time_t now
    n_bytes_read_in_interval = 0;
    n_bytes_written_in_interval = 0;
    n_seconds_active_in_interval = 0;
 +  n_bytes_at_soft_limit = 0;
 +  soft_limit_hit_at = 0;
 +  n_seconds_to_hit_soft_limit = 0;
  }
  
  /** Return true iff we should save our bandwidth usage to disk. */
@@@ -515,39 -449,35 +515,39 @@@ accounting_run_housekeeping(time_t now
  static void
  accounting_set_wakeup_time(void)
  {
 -  char buf[ISO_TIME_LEN+1];
    char digest[DIGEST_LEN];
    crypto_digest_env_t *d_env;
    int time_in_interval;
    uint64_t time_to_exhaust_bw;
    int time_to_consider;
  
 -  if (! identity_key_is_set()) {
 +  if (! server_identity_key_is_set()) {
      if (init_keys() < 0) {
        log_err(LD_BUG, "Error initializing keys");
        tor_assert(0);
      }
    }
  
 -  format_iso_time(buf, interval_start_time);
 -  crypto_pk_get_digest(get_identity_key(), digest);
 +  if (server_identity_key_is_set()) {
 +    char buf[ISO_TIME_LEN+1];
 +    format_iso_time(buf, interval_start_time);
  
 -  d_env = crypto_new_digest_env();
 -  crypto_digest_add_bytes(d_env, buf, ISO_TIME_LEN);
 -  crypto_digest_add_bytes(d_env, digest, DIGEST_LEN);
 -  crypto_digest_get_digest(d_env, digest, DIGEST_LEN);
 -  crypto_free_digest_env(d_env);
 +    crypto_pk_get_digest(get_server_identity_key(), digest);
 +
 +    d_env = crypto_new_digest_env();
 +    crypto_digest_add_bytes(d_env, buf, ISO_TIME_LEN);
 +    crypto_digest_add_bytes(d_env, digest, DIGEST_LEN);
 +    crypto_digest_get_digest(d_env, digest, DIGEST_LEN);
 +    crypto_free_digest_env(d_env);
 +  } else {
 +    crypto_rand(digest, DIGEST_LEN);
 +  }
  
    if (!expected_bandwidth_usage) {
      char buf1[ISO_TIME_LEN+1];
      char buf2[ISO_TIME_LEN+1];
      format_local_iso_time(buf1, interval_start_time);
      format_local_iso_time(buf2, interval_end_time);
 -    time_to_exhaust_bw = GUESS_TIME_TO_USE_BANDWIDTH;
      interval_wakeup_time = interval_start_time;
  
      log_notice(LD_ACCT,
@@@ -562,8 -492,8 +562,8 @@@
  
    time_to_exhaust_bw =
      (get_options()->AccountingMax/expected_bandwidth_usage)*60;
 -  if (time_to_exhaust_bw > TIME_MAX) {
 -    time_to_exhaust_bw = TIME_MAX;
 +  if (time_to_exhaust_bw > INT_MAX) {
 +    time_to_exhaust_bw = INT_MAX;
      time_to_consider = 0;
    } else {
      time_to_consider = time_in_interval - (int)time_to_exhaust_bw;
@@@ -581,6 -511,8 +581,6 @@@
       * to be chosen than the last half. */
      interval_wakeup_time = interval_start_time +
        (get_uint32(digest) % time_to_consider);
 -
 -    format_iso_time(buf, interval_wakeup_time);
    }
  
    {
@@@ -627,10 -559,6 +627,10 @@@ accounting_record_bandwidth_usage(time_
    state->AccountingSecondsActive = n_seconds_active_in_interval;
    state->AccountingExpectedUsage = expected_bandwidth_usage;
  
 +  state->AccountingSecondsToReachSoftLimit = n_seconds_to_hit_soft_limit;
 +  state->AccountingSoftLimitHitAt = soft_limit_hit_at;
 +  state->AccountingBytesAtSoftLimit = n_bytes_at_soft_limit;
 +
    or_state_mark_dirty(state,
                        now+(get_options()->AvoidDiskWrites ? 7200 : 60));
  
@@@ -654,6 -582,10 +654,6 @@@ read_bandwidth_usage(void
    if (!state)
      return -1;
  
 -  /* Okay; it looks like the state file is more up-to-date than the
 -   * bw_accounting file, or the bw_accounting file is nonexistent,
 -   * or the bw_accounting file is corrupt.
 -   */
    log_info(LD_ACCT, "Reading bandwidth accounting data from state file");
    n_bytes_read_in_interval = state->AccountingBytesReadInInterval;
    n_bytes_written_in_interval = state->AccountingBytesWrittenInInterval;
@@@ -661,21 -593,6 +661,21 @@@
    interval_start_time = state->AccountingIntervalStart;
    expected_bandwidth_usage = state->AccountingExpectedUsage;
  
 +  /* Older versions of Tor (before 0.2.2.17-alpha or so) didn't generate these
 +   * fields. If you switch back and forth, you might get an
 +   * AccountingSoftLimitHitAt value from long before the most recent
 +   * interval_start_time.  If that's so, then ignore the softlimit-related
 +   * values. */
 +  if (state->AccountingSoftLimitHitAt > interval_start_time) {
 +    soft_limit_hit_at =  state->AccountingSoftLimitHitAt;
 +    n_bytes_at_soft_limit = state->AccountingBytesAtSoftLimit;
 +    n_seconds_to_hit_soft_limit = state->AccountingSecondsToReachSoftLimit;
 +  } else {
 +    soft_limit_hit_at = 0;
 +    n_bytes_at_soft_limit = 0;
 +    n_seconds_to_hit_soft_limit = 0;
 +  }
 +
    {
      char tbuf1[ISO_TIME_LEN+1];
      char tbuf2[ISO_TIME_LEN+1];
@@@ -715,27 -632,8 +715,27 @@@ hibernate_hard_limit_reached(void
  static int
  hibernate_soft_limit_reached(void)
  {
 -  uint64_t soft_limit = DBL_TO_U64(U64_TO_DBL(get_options()->AccountingMax)
 -                                   * .95);
 +  const uint64_t acct_max = get_options()->AccountingMax;
 +#define SOFT_LIM_PCT (.95)
 +#define SOFT_LIM_BYTES (500*1024*1024)
 +#define SOFT_LIM_MINUTES (3*60)
 +  /* The 'soft limit' is a fair bit more complicated now than once it was.
 +   * We want to stop accepting connections when ALL of the following are true:
 +   *   - We expect to use up the remaining bytes in under 3 hours
 +   *   - We have used up 95% of our bytes.
 +   *   - We have less than 500MB of bytes left.
 +   */
 +  uint64_t soft_limit = DBL_TO_U64(U64_TO_DBL(acct_max) * SOFT_LIM_PCT);
 +  if (acct_max > SOFT_LIM_BYTES && acct_max - SOFT_LIM_BYTES > soft_limit) {
 +    soft_limit = acct_max - SOFT_LIM_BYTES;
 +  }
 +  if (expected_bandwidth_usage) {
 +    const uint64_t expected_usage =
 +      expected_bandwidth_usage * SOFT_LIM_MINUTES;
 +    if (acct_max > expected_usage && acct_max - expected_usage > soft_limit)
 +      soft_limit = acct_max - expected_usage;
 +  }
 +
    if (!soft_limit)
      return 0;
    return n_bytes_read_in_interval >= soft_limit
@@@ -760,14 -658,6 +760,14 @@@ hibernate_begin(hibernate_state_t new_s
      exit(0);
    }
  
 +  if (new_state == HIBERNATE_STATE_LOWBANDWIDTH &&
 +      hibernate_state == HIBERNATE_STATE_LIVE) {
 +    soft_limit_hit_at = now;
 +    n_seconds_to_hit_soft_limit = n_seconds_active_in_interval;
 +    n_bytes_at_soft_limit = MAX(n_bytes_read_in_interval,
 +                                n_bytes_written_in_interval);
 +  }
 +
    /* close listeners. leave control listener(s). */
    while ((conn = connection_get_by_type(CONN_TYPE_OR_LISTENER)) ||
           (conn = connection_get_by_type(CONN_TYPE_AP_LISTENER)) ||
@@@ -970,11 -860,9 +970,11 @@@ consider_hibernation(time_t now
   * NULL. */
  int
  getinfo_helper_accounting(control_connection_t *conn,
 -                          const char *question, char **answer)
 +                          const char *question, char **answer,
 +                          const char **errmsg)
  {
    (void) conn;
 +  (void) errmsg;
    if (!strcmp(question, "accounting/enabled")) {
      *answer = tor_strdup(accounting_is_enabled(get_options()) ? "1" : "0");
    } else if (!strcmp(question, "accounting/hibernating")) {
diff --combined src/or/main.c
index e13215b,96b7b45..4b51290
--- a/src/or/main.c
+++ b/src/or/main.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -12,50 -12,12 +12,50 @@@
  
  #define MAIN_PRIVATE
  #include "or.h"
 +#include "buffers.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "command.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "cpuworker.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "dns.h"
 +#include "dnsserv.h"
 +#include "geoip.h"
 +#include "hibernate.h"
 +#include "main.h"
 +#include "microdesc.h"
 +#include "networkstatus.h"
 +#include "ntmain.h"
 +#include "onion.h"
 +#include "policies.h"
 +#include "relay.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rendservice.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  #ifdef USE_DMALLOC
  #include <dmalloc.h>
  #include <openssl/crypto.h>
  #endif
  #include "memarea.h"
  
 +#ifdef HAVE_EVENT2_EVENT_H
 +#include <event2/event.h>
 +#else
 +#include <event.h>
 +#endif
 +
  void evdns_shutdown(int);
  
  /********* PROTOTYPES **********/
@@@ -65,7 -27,7 +65,7 @@@ static void dumpstats(int severity); /
  static void conn_read_callback(int fd, short event, void *_conn);
  static void conn_write_callback(int fd, short event, void *_conn);
  static void signal_callback(int fd, short events, void *arg);
 -static void second_elapsed_callback(int fd, short event, void *args);
 +static void second_elapsed_callback(periodic_timer_t *timer, void *args);
  static int conn_close_if_marked(int i);
  static void connection_start_reading_from_linked_conn(connection_t *conn);
  static int connection_should_read_from_linked_conn(connection_t *conn);
@@@ -119,12 -81,8 +119,12 @@@ static smartlist_t *active_linked_conne
  static int called_loop_once = 0;
  
  /** We set this to 1 when we've opened a circuit, so we can print a log
 - * entry to inform the user that Tor is working. */
 -int has_completed_circuit=0;
 + * entry to inform the user that Tor is working.  We set it to 0 when
 + * we think the fact that we once opened a circuit doesn't mean we can do so
 + * any longer (a big time jump happened, when we notice our directory is
 + * heinously out-of-date, etc.
 + */
 +int can_complete_circuit=0;
  
  /** How often do we check for router descriptors that we should download
   * when we have too little directory info? */
@@@ -169,10 -127,12 +169,10 @@@ connection_add(connection_t *conn
    smartlist_add(connection_array, conn);
  
    if (conn->s >= 0 || conn->linked) {
 -    conn->read_event = tor_malloc_zero(sizeof(struct event));
 -    conn->write_event = tor_malloc_zero(sizeof(struct event));
 -    event_set(conn->read_event, conn->s, EV_READ|EV_PERSIST,
 -              conn_read_callback, conn);
 -    event_set(conn->write_event, conn->s, EV_WRITE|EV_PERSIST,
 -              conn_write_callback, conn);
 +    conn->read_event = tor_event_new(tor_libevent_get_base(),
 +         conn->s, EV_READ|EV_PERSIST, conn_read_callback, conn);
 +    conn->write_event = tor_event_new(tor_libevent_get_base(),
 +         conn->s, EV_WRITE|EV_PERSIST, conn_write_callback, conn);
    }
  
    log_debug(LD_NET,"new conn type %s, socket %d, address %s, n_conns %d.",
@@@ -182,25 -142,6 +182,25 @@@
    return 0;
  }
  
 +/** Tell libevent that we don't care about <b>conn</b> any more. */
 +void
 +connection_unregister_events(connection_t *conn)
 +{
 +  if (conn->read_event) {
 +    if (event_del(conn->read_event))
 +      log_warn(LD_BUG, "Error removing read event for %d", conn->s);
 +    tor_free(conn->read_event);
 +  }
 +  if (conn->write_event) {
 +    if (event_del(conn->write_event))
 +      log_warn(LD_BUG, "Error removing write event for %d", conn->s);
 +    tor_free(conn->write_event);
 +  }
 +  if (conn->dns_server_port) {
 +    dnsserv_close_listener(conn);
 +  }
 +}
 +
  /** Remove the connection from the global list, and remove the
   * corresponding poll entry.  Calling this function will shift the last
   * connection (if any) into the position occupied by conn.
@@@ -305,17 -246,17 +305,17 @@@ get_connection_array(void
  }
  
  /** Set the event mask on <b>conn</b> to <b>events</b>.  (The event
 - * mask is a bitmask whose bits are EV_READ and EV_WRITE.)
 + * mask is a bitmask whose bits are READ_EVENT and WRITE_EVENT)
   */
  void
 -connection_watch_events(connection_t *conn, short events)
 +connection_watch_events(connection_t *conn, watchable_events_t events)
  {
 -  if (events & EV_READ)
 +  if (events & READ_EVENT)
      connection_start_reading(conn);
    else
      connection_stop_reading(conn);
  
 -  if (events & EV_WRITE)
 +  if (events & WRITE_EVENT)
      connection_start_writing(conn);
    else
      connection_stop_writing(conn);
@@@ -452,11 -393,11 +452,11 @@@ connection_start_reading_from_linked_co
      smartlist_add(active_linked_connection_lst, conn);
      if (!called_loop_once) {
        /* This is the first event on the list; we won't be in LOOP_ONCE mode,
 -       * so we need to make sure that the event_loop() actually exits at the
 -       * end of its run through the current connections and
 -       * lets us activate read events for linked connections. */
 +       * so we need to make sure that the event_base_loop() actually exits at
 +       * the end of its run through the current connections and lets us
 +       * activate read events for linked connections. */
        struct timeval tv = { 0, 0 };
 -      event_loopexit(&tv);
 +      tor_event_base_loopexit(tor_libevent_get_base(), &tv);
      }
    } else {
      tor_assert(smartlist_isin(active_linked_connection_lst, conn));
@@@ -599,7 -540,7 +599,7 @@@ conn_close_if_marked(int i
        log_info(LD_NET,
                 "Conn (addr %s, fd %d, type %s, state %d) marked, but wants "
                 "to flush %d bytes. (Marked at %s:%d)",
 -               escaped_safe_str(conn->address),
 +               escaped_safe_str_client(conn->address),
                 conn->s, conn_type_to_string(conn->type), conn->state,
                 (int)conn->outbuf_flushlen,
                  conn->marked_for_close_file, conn->marked_for_close);
@@@ -652,8 -593,8 +652,8 @@@
               "something is wrong with theirs. "
               "(fd %d, type %s, state %d, marked at %s:%d).",
               (int)buf_datalen(conn->outbuf),
 -             escaped_safe_str(conn->address), conn->s,
 -             conn_type_to_string(conn->type), conn->state,
 +             escaped_safe_str_client(conn->address),
 +             conn->s, conn_type_to_string(conn->type), conn->state,
               conn->marked_for_close_file,
               conn->marked_for_close);
      }
@@@ -682,7 -623,7 +682,7 @@@ directory_all_unreachable(time_t now
      log_notice(LD_NET,
                 "Is your network connection down? "
                 "Failing connection to '%s:%d'.",
 -               safe_str(edge_conn->socks_request->address),
 +               safe_str_client(edge_conn->socks_request->address),
                 edge_conn->socks_request->port);
      connection_mark_unattached_ap(edge_conn,
                                    END_STREAM_REASON_NET_UNREACHABLE);
@@@ -710,7 -651,7 +710,7 @@@ directory_info_has_arrived(time_t now, 
  
      /* if we have enough dir info, then update our guard status with
       * whatever we just learned. */
 -    entry_guards_compute_status();
 +    entry_guards_compute_status(options, now);
      /* Don't even bother trying to get extrainfo until the rest of our
       * directory info is up-to-date */
      if (options->DownloadExtraInfo)
@@@ -718,7 -659,7 +718,7 @@@
    }
  
    if (server_mode(options) && !we_are_hibernating() && !from_cache &&
 -      (has_completed_circuit || !any_predicted_circuits(now)))
 +      (can_complete_circuit || !any_predicted_circuits(now)))
      consider_testing_reachability(1, 1);
  }
  
@@@ -781,7 -722,6 +781,7 @@@ run_connection_housekeeping(int i, time
       the connection or send a keepalive, depending. */
  
    or_conn = TO_OR_CONN(conn);
 +  tor_assert(conn->outbuf);
  
    if (or_conn->is_bad_for_new_circs && !or_conn->n_circuits) {
      /* It's bad for new circuits, and has no unmarked circuits on it:
@@@ -838,7 -778,7 +838,7 @@@
    }
  }
  
 -/** Honor a NEWNYM request: make future requests unlinkability to past
 +/** Honor a NEWNYM request: make future requests unlinkable to past
   * requests. */
  static void
  signewnym_impl(time_t now)
@@@ -864,18 -804,16 +864,18 @@@ run_scheduled_events(time_t now
    static time_t time_to_try_getting_descriptors = 0;
    static time_t time_to_reset_descriptor_failures = 0;
    static time_t time_to_add_entropy = 0;
 -  static time_t time_to_write_hs_statistics = 0;
    static time_t time_to_write_bridge_status_file = 0;
    static time_t time_to_downrate_stability = 0;
    static time_t time_to_save_stability = 0;
    static time_t time_to_clean_caches = 0;
    static time_t time_to_recheck_bandwidth = 0;
    static time_t time_to_check_for_expired_networkstatus = 0;
 -  static time_t time_to_dump_geoip_stats = 0;
 +  static time_t time_to_write_stats_files = 0;
 +  static time_t time_to_write_bridge_stats = 0;
 +  static int should_init_bridge_stats = 1;
    static time_t time_to_retry_dns_init = 0;
    or_options_t *options = get_options();
 +  int is_server = server_mode(options);
    int i;
    int have_dir_info;
  
@@@ -893,14 -831,11 +893,14 @@@
      signewnym_impl(now);
    }
  
 +  /* 0c. If we've deferred log messages for the controller, handle them now */
 +  flush_pending_log_callbacks();
 +
    /** 1a. Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion keys,
     *  shut down and restart all cpuworkers, and update the directory if
     *  necessary.
     */
 -  if (server_mode(options) &&
 +  if (is_server &&
        get_onion_key_set_at()+MIN_ONION_KEY_LIFETIME < now) {
      log_info(LD_GENERAL,"Rotating onion key.");
      rotate_onion_key();
@@@ -916,7 -851,7 +916,7 @@@
      update_router_descriptor_downloads(now);
      update_extrainfo_downloads(now);
      if (options->UseBridges)
 -      fetch_bridge_descriptors(now);
 +      fetch_bridge_descriptors(options, now);
      if (router_have_minimum_dir_info())
        time_to_try_getting_descriptors = now + LAZY_DESCRIPTOR_RETRY_INTERVAL;
      else
@@@ -934,10 -869,7 +934,10 @@@
      last_rotated_x509_certificate = now;
    if (last_rotated_x509_certificate+MAX_SSL_KEY_LIFETIME < now) {
      log_info(LD_GENERAL,"Rotating tls context.");
 -    if (tor_tls_context_new(get_identity_key(), MAX_SSL_KEY_LIFETIME) < 0) {
 +    if (tor_tls_context_init(public_server_mode(options),
 +                             get_tlsclient_identity_key(),
 +                             is_server ? get_server_identity_key() : NULL,
 +                             MAX_SSL_KEY_LIFETIME) < 0) {
        log_warn(LD_BUG, "Error reinitializing TLS context");
        /* XXX is it a bug here, that we just keep going? -RD */
      }
@@@ -965,7 -897,7 +965,7 @@@
    if (now % 10 == 0 && (authdir_mode_tests_reachability(options)) &&
        !we_are_hibernating()) {
      /* try to determine reachability of the other Tor relays */
 -    dirserv_test_reachability(now, 0);
 +    dirserv_test_reachability(now);
    }
  
    /** 1d. Periodically, we discount older stability information so that new
@@@ -1007,56 -939,11 +1007,56 @@@
      time_to_check_for_expired_networkstatus = now + CHECK_EXPIRED_NS_INTERVAL;
    }
  
 -  if (time_to_dump_geoip_stats < now) {
 -#define DUMP_GEOIP_STATS_INTERVAL (60*60);
 -    if (time_to_dump_geoip_stats)
 -      dump_geoip_stats();
 -    time_to_dump_geoip_stats = now + DUMP_GEOIP_STATS_INTERVAL;
 +  /* 1g. Check whether we should write statistics to disk.
 +   */
 +  if (time_to_write_stats_files < now) {
 +#define CHECK_WRITE_STATS_INTERVAL (60*60)
 +    time_t next_time_to_write_stats_files = (time_to_write_stats_files > 0 ?
 +           time_to_write_stats_files : now) + CHECK_WRITE_STATS_INTERVAL;
 +    if (options->CellStatistics) {
 +      time_t next_write =
 +          rep_hist_buffer_stats_write(time_to_write_stats_files);
 +      if (next_write && next_write < next_time_to_write_stats_files)
 +        next_time_to_write_stats_files = next_write;
 +    }
 +    if (options->DirReqStatistics) {
 +      time_t next_write = geoip_dirreq_stats_write(time_to_write_stats_files);
 +      if (next_write && next_write < next_time_to_write_stats_files)
 +        next_time_to_write_stats_files = next_write;
 +    }
 +    if (options->EntryStatistics) {
 +      time_t next_write = geoip_entry_stats_write(time_to_write_stats_files);
 +      if (next_write && next_write < next_time_to_write_stats_files)
 +        next_time_to_write_stats_files = next_write;
 +    }
 +    if (options->ExitPortStatistics) {
 +      time_t next_write = rep_hist_exit_stats_write(time_to_write_stats_files);
 +      if (next_write && next_write < next_time_to_write_stats_files)
 +        next_time_to_write_stats_files = next_write;
 +    }
 +    time_to_write_stats_files = next_time_to_write_stats_files;
 +  }
 +
 +  /* 1h. Check whether we should write bridge statistics to disk.
 +   */
 +  if (should_record_bridge_info(options)) {
 +    if (time_to_write_bridge_stats < now) {
 +      if (should_init_bridge_stats) {
 +        /* (Re-)initialize bridge statistics. */
 +        geoip_bridge_stats_init(now);
 +        time_to_write_bridge_stats = now + WRITE_STATS_INTERVAL;
 +        should_init_bridge_stats = 0;
 +      } else {
 +        /* Possibly write bridge statistics to disk and ask when to write
 +         * them next time. */
 +        time_to_write_bridge_stats = geoip_bridge_stats_write(
 +                                           time_to_write_bridge_stats);
 +      }
 +    }
 +  } else if (!should_init_bridge_stats) {
 +    /* Bridge mode was turned off. Ensure that stats are re-initialized
 +     * next time bridge mode is turned on. */
 +    should_init_bridge_stats = 1;
    }
  
    /* Remove old information from rephist and the rend cache. */
@@@ -1104,7 -991,7 +1104,7 @@@
      /* also, check religiously for reachability, if it's within the first
       * 20 minutes of our uptime. */
      if (server_mode(options) &&
 -        (has_completed_circuit || !any_predicted_circuits(now)) &&
 +        (can_complete_circuit || !any_predicted_circuits(now)) &&
          !we_are_hibernating()) {
        if (stats_n_seconds_working < TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) {
          consider_testing_reachability(1, dirport_reachability_count==0);
@@@ -1180,7 -1067,7 +1180,7 @@@
      circuit_expire_old_circuits_serverside(now);
  
    /** 5. We do housekeeping for each connection... */
 -  connection_or_set_bad_connections();
 +  connection_or_set_bad_connections(NULL, 0);
    for (i=0;i<smartlist_len(connection_array);i++) {
      run_connection_housekeeping(i, now);
    }
@@@ -1203,7 -1090,7 +1203,7 @@@
    circuit_close_all_marked();
  
    /** 7. And upload service descriptors if necessary. */
 -  if (has_completed_circuit && !we_are_hibernating()) {
 +  if (can_complete_circuit && !we_are_hibernating()) {
      rend_consider_services_upload(now);
      rend_consider_descriptor_republication();
    }
@@@ -1220,7 -1107,7 +1220,7 @@@
  
    /** 9. and if we're a server, check whether our DNS is telling stories to
     * us. */
 -  if (server_mode(options) && time_to_check_for_correct_dns < now) {
 +  if (is_server && time_to_check_for_correct_dns < now) {
      if (!time_to_check_for_correct_dns) {
        time_to_check_for_correct_dns = now + 60 + crypto_rand_int(120);
      } else {
@@@ -1230,6 -1117,12 +1230,6 @@@
      }
    }
  
 -  /** 10. write hidden service usage statistic to disk */
 -  if (options->HSAuthorityRecordStats && time_to_write_hs_statistics < now) {
 -    hs_usage_write_statistics_to_file(now);
 -#define WRITE_HSUSAGE_INTERVAL (30*60)
 -    time_to_write_hs_statistics = now+WRITE_HSUSAGE_INTERVAL;
 -  }
    /** 10b. write bridge networkstatus file to disk */
    if (options->BridgeAuthoritativeDir &&
        time_to_write_bridge_status_file < now) {
@@@ -1239,30 -1132,39 +1239,30 @@@
    }
  }
  
 -/** Libevent timer: used to invoke second_elapsed_callback() once per
 - * second. */
 -static struct event *timeout_event = NULL;
 +/** Timer: used to invoke second_elapsed_callback() once per second. */
 +static periodic_timer_t *second_timer = NULL;
  /** Number of libevent errors in the last second: we die if we get too many. */
  static int n_libevent_errors = 0;
  
  /** Libevent callback: invoked once every second. */
  static void
 -second_elapsed_callback(int fd, short event, void *args)
 +second_elapsed_callback(periodic_timer_t *timer, void *arg)
  {
    /* XXXX This could be sensibly refactored into multiple callbacks, and we
     * could use Libevent's timers for this rather than checking the current
     * time against a bunch of timeouts every second. */
 -  static struct timeval one_second;
    static time_t current_second = 0;
    time_t now;
    size_t bytes_written;
    size_t bytes_read;
    int seconds_elapsed;
    or_options_t *options = get_options();
 -  (void)fd;
 -  (void)event;
 -  (void)args;
 -  if (!timeout_event) {
 -    timeout_event = tor_malloc_zero(sizeof(struct event));
 -    evtimer_set(timeout_event, second_elapsed_callback, NULL);
 -    one_second.tv_sec = 1;
 -    one_second.tv_usec = 0;
 -  }
 +  (void)timer;
 +  (void)arg;
  
    n_libevent_errors = 0;
  
 -  /* log_fn(LOG_NOTICE, "Tick."); */
 +  /* log_notice(LD_GENERAL, "Tick."); */
    now = time(NULL);
    update_approx_time(now);
  
@@@ -1285,7 -1187,7 +1285,7 @@@
    if (server_mode(options) &&
        !we_are_hibernating() &&
        seconds_elapsed > 0 &&
 -      has_completed_circuit &&
 +      can_complete_circuit &&
        stats_n_seconds_working / TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT !=
        (stats_n_seconds_working+seconds_elapsed) /
          TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) {
@@@ -1327,6 -1229,18 +1327,6 @@@
    run_scheduled_events(now);
  
    current_second = now; /* remember which second it is, for next time */
 -
 -#if 0
 -  if (current_second % 300 == 0) {
 -    rep_history_clean(current_second - options->RephistTrackTime);
 -    dumpmemusage(get_min_log_level()<LOG_INFO ?
 -                 get_min_log_level() : LOG_INFO);
 -  }
 -#endif
 -
 -  if (evtimer_add(timeout_event, &one_second))
 -    log_err(LD_NET,
 -            "Error from libevent when setting one-second timeout event");
  }
  
  #ifndef MS_WINDOWS
@@@ -1469,7 -1383,7 +1469,7 @@@ do_main_loop(void
  
    /* load the private keys, if we're supposed to have them, and set up the
     * TLS context. */
 -  if (! identity_key_is_set()) {
 +  if (! client_identity_key_is_set()) {
      if (init_keys() < 0) {
        log_err(LD_BUG,"Error initializing keys; exiting");
        return -1;
@@@ -1487,10 -1401,8 +1487,10 @@@
    /* initialize the bootstrap status events to know we're starting up */
    control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0);
  
 -  if (trusted_dirs_reload_certs())
 -    return -1;
 +  if (trusted_dirs_reload_certs()) {
 +    log_warn(LD_DIR,
 +             "Couldn't load all cached v3 certificates. Starting anyway.");
 +  }
    if (router_reload_v2_networkstatus()) {
      return -1;
    }
@@@ -1507,23 -1419,18 +1507,23 @@@
    now = time(NULL);
    directory_info_has_arrived(now, 1);
  
 -  if (authdir_mode_tests_reachability(get_options())) {
 -    /* the directory is already here, run startup things */
 -    dirserv_test_reachability(now, 1);
 -  }
 -
    if (server_mode(get_options())) {
      /* launch cpuworkers. Need to do this *after* we've read the onion key. */
      cpu_init();
    }
  
    /* set up once-a-second callback. */
 -  second_elapsed_callback(0,0,NULL);
 +  if (! second_timer) {
 +    struct timeval one_second;
 +    one_second.tv_sec = 1;
 +    one_second.tv_usec = 0;
 +
 +    second_timer = periodic_timer_new(tor_libevent_get_base(),
 +                                      &one_second,
 +                                      second_elapsed_callback,
 +                                      NULL);
 +    tor_assert(second_timer);
 +  }
  
    for (;;) {
      if (nt_service_is_stopping())
@@@ -1542,16 -1449,20 +1542,16 @@@
  
      /* poll until we have an event, or the second ends, or until we have
       * some active linked connections to trigger events for. */
 -    loop_result = event_loop(called_loop_once ? EVLOOP_ONCE : 0);
 +    loop_result = event_base_loop(tor_libevent_get_base(),
 +                                  called_loop_once ? EVLOOP_ONCE : 0);
  
      /* let catch() handle things like ^c, and otherwise don't worry about it */
      if (loop_result < 0) {
        int e = tor_socket_errno(-1);
        /* let the program survive things like ^z */
        if (e != EINTR && !ERRNO_IS_EINPROGRESS(e)) {
 -#ifdef HAVE_EVENT_GET_METHOD
          log_err(LD_NET,"libevent call with %s failed: %s [%d]",
 -                event_get_method(), tor_socket_strerror(e), e);
 -#else
 -        log_err(LD_NET,"libevent call failed: %s [%d]",
 -                tor_socket_strerror(e), e);
 -#endif
 +                tor_libevent_get_method(), tor_socket_strerror(e), e);
          return -1;
  #ifndef MS_WINDOWS
        } else if (e == EINVAL) {
@@@ -1694,7 -1605,6 +1694,7 @@@ dumpmemusage(int severity
        U64_PRINTF_ARG(rephist_total_alloc), rephist_total_num);
    dump_routerlist_mem_usage(severity);
    dump_cell_pool_usage(severity);
 +  dump_dns_mem_usage(severity);
    buf_dump_freelist_sizes(severity);
    tor_log_mallinfo(severity);
  }
@@@ -1721,8 -1631,7 +1721,8 @@@ dumpstats(int severity
      if (!connection_is_listener(conn)) {
        log(severity,LD_GENERAL,
            "Conn %d is to %s:%d.", i,
 -          safe_str(conn->address), conn->port);
 +          safe_str_client(conn->address),
 +          conn->port);
        log(severity,LD_GENERAL,
            "Conn %d: %d bytes waiting on inbuf (len %d, last read %d secs ago)",
            i,
@@@ -1819,7 -1728,7 +1819,7 @@@ handle_signals(int is_parent
  {
  #ifndef MS_WINDOWS /* do signal stuff only on Unix */
    int i;
 -  static int signals[] = {
 +  static const int signals[] = {
      SIGINT,  /* do a controlled slow shutdown */
      SIGTERM, /* to terminate now */
      SIGPIPE, /* otherwise SIGPIPE kills us */
@@@ -1831,13 -1740,12 +1831,13 @@@
  #endif
      SIGCHLD, /* handle dns/cpu workers that exit */
      -1 };
 -  static struct event signal_events[16]; /* bigger than it has to be. */
 +  static struct event *signal_events[16]; /* bigger than it has to be. */
    if (is_parent) {
      for (i = 0; signals[i] >= 0; ++i) {
 -      signal_set(&signal_events[i], signals[i], signal_callback,
 -                 (void*)(uintptr_t)signals[i]);
 -      if (signal_add(&signal_events[i], NULL))
 +      signal_events[i] = tor_evsignal_new(
 +                       tor_libevent_get_base(), signals[i], signal_callback,
 +                       (void*)(uintptr_t)signals[i]);
 +      if (event_add(signal_events[i], NULL))
          log_warn(LD_BUG, "Error from libevent when adding event for signal %d",
                   signals[i]);
      }
@@@ -1926,9 -1834,7 +1926,9 @@@ tor_init(int argc, char *argv[]
               "and you probably shouldn't.");
  #endif
  
 -  if (crypto_global_init(get_options()->HardwareAccel)) {
 +  if (crypto_global_init(get_options()->HardwareAccel,
 +                         get_options()->AccelName,
 +                         get_options()->AccelDir)) {
      log_err(LD_BUG, "Unable to initialize OpenSSL. Exiting.");
      return -1;
    }
@@@ -2022,6 -1928,7 +2022,6 @@@ tor_free_all(int postfork
    rend_cache_free_all();
    rend_service_authorization_free_all();
    rep_hist_free_all();
 -  hs_usage_free_all();
    dns_free_all();
    clear_pending_onions();
    circuit_free_all();
@@@ -2029,7 -1936,6 +2029,7 @@@
    connection_free_all();
    buf_shrink_freelists(1);
    memarea_clear_freelist();
 +  microdesc_free_all();
    if (!postfork) {
      config_free_all();
      router_free_all();
@@@ -2040,11 -1946,13 +2040,11 @@@
      tor_tls_free_all();
    }
    /* stuff in main.c */
 -  if (connection_array)
 -    smartlist_free(connection_array);
 -  if (closeable_connection_lst)
 -    smartlist_free(closeable_connection_lst);
 -  if (active_linked_connection_lst)
 -    smartlist_free(active_linked_connection_lst);
 -  tor_free(timeout_event);
 +
 +  smartlist_free(connection_array);
 +  smartlist_free(closeable_connection_lst);
 +  smartlist_free(active_linked_connection_lst);
 +  periodic_timer_free(second_timer);
    if (!postfork) {
      release_lockfile();
    }
@@@ -2105,7 -2013,7 +2105,7 @@@ do_list_fingerprint(void
      log_err(LD_BUG,"Error initializing keys; can't display fingerprint");
      return -1;
    }
 -  if (!(k = get_identity_key())) {
 +  if (!(k = get_server_identity_key())) {
      log_err(LD_GENERAL,"Error: missing identity key.");
      return -1;
    }
@@@ -2135,31 -2043,6 +2135,31 @@@ do_hash_password(void
    printf("16:%s\n",output);
  }
  
 +#if defined (WINCE)
 +int
 +find_flashcard_path(PWCHAR path, size_t size)
 +{
 +  WIN32_FIND_DATA d = {0};
 +  HANDLE h = NULL;
 +
 +  if (!path)
 +    return -1;
 +
 +  h = FindFirstFlashCard(&d);
 +  if (h == INVALID_HANDLE_VALUE)
 +    return -1;
 +
 +  if (wcslen(d.cFileName) == 0) {
 +    FindClose(h);
 +    return -1;
 +  }
 +
 +  wcsncpy(path,d.cFileName,size);
 +  FindClose(h);
 +  return 0;
 +}
 +#endif
 +
  /** Main entry point for the Tor process.  Called from main(). */
  /* This function is distinct from main() only so we can link main.c into
   * the unittest binary without conflicting with the unittests' main. */
@@@ -2167,33 -2050,6 +2167,33 @@@ in
  tor_main(int argc, char *argv[])
  {
    int result = 0;
 +#if defined (WINCE)
 +  WCHAR path [MAX_PATH] = {0};
 +  WCHAR fullpath [MAX_PATH] = {0};
 +  PWCHAR p = NULL;
 +  FILE* redir = NULL;
 +  FILE* redirdbg = NULL;
 +
 +  // this is to facilitate debugging by opening
 +  // a file on a folder shared by the wm emulator.
 +  // if no flashcard (real or emulated) is present,
 +  // log files will be written in the root folder
 +  if (find_flashcard_path(path,MAX_PATH) == -1)
 +  {
 +    redir = _wfreopen( L"\\stdout.log", L"w", stdout );
 +    redirdbg = _wfreopen( L"\\stderr.log", L"w", stderr );
 +  } else {
 +    swprintf(fullpath,L"\\%s\\tor",path);
 +    CreateDirectory(fullpath,NULL);
 +
 +    swprintf(fullpath,L"\\%s\\tor\\stdout.log",path);
 +    redir = _wfreopen( fullpath, L"w", stdout );
 +
 +    swprintf(fullpath,L"\\%s\\tor\\stderr.log",path);
 +    redirdbg = _wfreopen( fullpath, L"w", stderr );
 +  }
 +#endif
 +
    update_approx_time(time(NULL));
    tor_threads_init();
    init_logging();
diff --combined src/or/networkstatus.c
index d645ee2,53e8a63..94bcb41
--- a/src/or/networkstatus.c
+++ b/src/or/networkstatus.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -11,20 -11,6 +11,20 @@@
   */
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "relay.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  /* For tracking v2 networkstatus documents.  Only caches do this now. */
  
@@@ -49,22 -35,16 +49,22 @@@ static networkstatus_t *current_consens
  
  /** A v3 consensus networkstatus that we've received, but which we don't
   * have enough certificates to be happy about. */
 -static networkstatus_t *consensus_waiting_for_certs = NULL;
 -/** The encoded version of consensus_waiting_for_certs. */
 -static char *consensus_waiting_for_certs_body = NULL;
 -/** When did we set the current value of consensus_waiting_for_certs?  If this
 - * is too recent, we shouldn't try to fetch a new consensus for a little while,
 - * to give ourselves time to get certificates for this one. */
 -static time_t consensus_waiting_for_certs_set_at = 0;
 -/** Set to 1 if we've been holding on to consensus_waiting_for_certs so long
 - * that we should treat it as maybe being bad. */
 -static int consensus_waiting_for_certs_dl_failed = 0;
 +typedef struct consensus_waiting_for_certs_t {
 +  /** The consensus itself. */
 +  networkstatus_t *consensus;
 +  /** The encoded version of the consensus, nul-terminated. */
 +  char *body;
 +  /** When did we set the current value of consensus_waiting_for_certs?  If
 +   * this is too recent, we shouldn't try to fetch a new consensus for a
 +   * little while, to give ourselves time to get certificates for this one. */
 +  time_t set_at;
 +  /** Set to 1 if we've been holding on to it for so long we should maybe
 +   * treat it as being bad. */
 +  int dl_failed;
 +} consensus_waiting_for_certs_t;
 +
 +static consensus_waiting_for_certs_t
 +       consensus_waiting_for_certs[N_CONSENSUS_FLAVORS];
  
  /** The last time we tried to download a networkstatus, or 0 for "never".  We
   * use this to rate-limit download attempts for directory caches (including
@@@ -76,7 -56,7 +76,7 @@@ static time_t last_networkstatus_downlo
   * before the current consensus becomes invalid. */
  static time_t time_to_download_next_consensus = 0;
  /** Download status for the current consensus networkstatus. */
 -static download_status_t consensus_dl_status = { 0, 0, DL_SCHED_CONSENSUS };
 +static download_status_t consensus_dl_status[N_CONSENSUS_FLAVORS];
  
  /** True iff we have logged a warning about this OR's version being older than
   * listed by the authorities. */
@@@ -109,7 -89,6 +109,7 @@@ networkstatus_reset_warnings(void
  void
  networkstatus_reset_download_failures(void)
  {
 +  int i;
    const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list();
    SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
       SMARTLIST_FOREACH(ns->entries, routerstatus_t *, rs,
@@@ -118,8 -97,7 +118,8 @@@
             rs->need_to_mirror = 1;
         }));;
  
 -  download_status_reset(&consensus_dl_status);
 +  for (i=0; i < N_CONSENSUS_FLAVORS; ++i)
 +    download_status_reset(&consensus_dl_status[i]);
    if (v2_download_status_map) {
      digestmap_iter_t *iter;
      digestmap_t *map = v2_download_status_map;
@@@ -192,7 -170,7 +192,7 @@@ router_reload_v2_networkstatus(void
    return 0;
  }
  
 -/** Read the cached v3 consensus networkstatus from the disk. */
 +/** Read every cached v3 consensus networkstatus from the disk. */
  int
  router_reload_consensus_networkstatus(void)
  {
@@@ -201,46 -179,31 +201,46 @@@
    struct stat st;
    or_options_t *options = get_options();
    const unsigned int flags = NSSET_FROM_CACHE | NSSET_DONT_DOWNLOAD_CERTS;
 +  int flav;
  
    /* FFFF Suppress warnings if cached consensus is bad? */
 +  for (flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
 +    char buf[128];
 +    const char *flavor = networkstatus_get_flavor_name(flav);
 +    if (flav == FLAV_NS) {
 +      filename = get_datadir_fname("cached-consensus");
 +    } else {
 +      tor_snprintf(buf, sizeof(buf), "cached-%s-consensus", flavor);
 +      filename = get_datadir_fname(buf);
 +    }
 +    s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
 +    if (s) {
 +      if (networkstatus_set_current_consensus(s, flavor, flags) < -1) {
 +        log_warn(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"",
 +                 flavor, filename);
 +      }
 +      tor_free(s);
 +    }
 +    tor_free(filename);
  
 -  filename = get_datadir_fname("cached-consensus");
 -  s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
 -  if (s) {
 -    if (networkstatus_set_current_consensus(s, flags) < -1) {
 -      log_warn(LD_FS, "Couldn't load consensus networkstatus from \"%s\"",
 -               filename);
 +    if (flav == FLAV_NS) {
 +      filename = get_datadir_fname("unverified-consensus");
 +    } else {
 +      tor_snprintf(buf, sizeof(buf), "unverified-%s-consensus", flavor);
 +      filename = get_datadir_fname(buf);
      }
 -    tor_free(s);
 -  }
 -  tor_free(filename);
  
 -  filename = get_datadir_fname("unverified-consensus");
 -  s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
 -  if (s) {
 -    if (networkstatus_set_current_consensus(s,
 +    s = read_file_to_str(filename, RFTS_IGNORE_MISSING, NULL);
 +    if (s) {
 +      if (networkstatus_set_current_consensus(s, flavor,
                                       flags|NSSET_WAS_WAITING_FOR_CERTS)) {
 -      log_info(LD_FS, "Couldn't load consensus networkstatus from \"%s\"",
 -               filename);
 +      log_info(LD_FS, "Couldn't load consensus %s networkstatus from \"%s\"",
 +               flavor, filename);
      }
 -    tor_free(s);
 +      tor_free(s);
 +    }
 +    tor_free(filename);
    }
 -  tor_free(filename);
  
    if (!current_consensus ||
        (stat(options->FallbackNetworkstatusFile, &st)==0 &&
@@@ -248,7 -211,7 +248,7 @@@
      s = read_file_to_str(options->FallbackNetworkstatusFile,
                           RFTS_IGNORE_MISSING, NULL);
      if (s) {
 -      if (networkstatus_set_current_consensus(s,
 +      if (networkstatus_set_current_consensus(s, "ns",
                                                flags|NSSET_ACCEPT_OBSOLETE)) {
          log_info(LD_FS, "Couldn't load consensus networkstatus from \"%s\"",
                   options->FallbackNetworkstatusFile);
@@@ -279,16 -242,8 +279,16 @@@
  static void
  vote_routerstatus_free(vote_routerstatus_t *rs)
  {
 +  vote_microdesc_hash_t *h, *next;
 +  if (!rs)
 +    return;
    tor_free(rs->version);
    tor_free(rs->status.exitsummary);
 +  for (h = rs->microdesc; h; h = next) {
 +    tor_free(h->microdesc_hash_line);
 +    next = h->next;
 +    tor_free(h);
 +  }
    tor_free(rs);
  }
  
@@@ -296,8 -251,6 +296,8 @@@
  void
  routerstatus_free(routerstatus_t *rs)
  {
 +  if (!rs)
 +    return;
    tor_free(rs->exitsummary);
    tor_free(rs);
  }
@@@ -306,8 -259,6 +306,8 @@@
  void
  networkstatus_v2_free(networkstatus_v2_t *ns)
  {
 +  if (!ns)
 +    return;
    tor_free(ns->source_address);
    tor_free(ns->contact);
    if (ns->signing_key)
@@@ -322,25 -273,7 +322,25 @@@
    tor_free(ns);
  }
  
 -/** Clear all storage held in <b>ns</b>. */
 +/** Free all storage held in <b>sig</b> */
 +void
 +document_signature_free(document_signature_t *sig)
 +{
 +  tor_free(sig->signature);
 +  tor_free(sig);
 +}
 +
 +/** Return a newly allocated copy of <b>sig</b> */
 +document_signature_t *
 +document_signature_dup(const document_signature_t *sig)
 +{
 +  document_signature_t *r = tor_memdup(sig, sizeof(document_signature_t));
 +  if (r->signature)
 +    r->signature = tor_memdup(sig->signature, sig->signature_len);
 +  return r;
 +}
 +
 +/** Free all storage held in <b>ns</b>. */
  void
  networkstatus_vote_free(networkstatus_t *ns)
  {
@@@ -353,10 -286,6 +353,10 @@@
      SMARTLIST_FOREACH(ns->known_flags, char *, c, tor_free(c));
      smartlist_free(ns->known_flags);
    }
 +  if (ns->weight_params) {
 +    SMARTLIST_FOREACH(ns->weight_params, char *, c, tor_free(c));
 +    smartlist_free(ns->weight_params);
 +  }
    if (ns->net_params) {
      SMARTLIST_FOREACH(ns->net_params, char *, c, tor_free(c));
      smartlist_free(ns->net_params);
@@@ -366,20 -295,18 +366,20 @@@
      smartlist_free(ns->supported_methods);
    }
    if (ns->voters) {
 -    SMARTLIST_FOREACH(ns->voters, networkstatus_voter_info_t *, voter,
 -    {
 +    SMARTLIST_FOREACH_BEGIN(ns->voters, networkstatus_voter_info_t *, voter) {
        tor_free(voter->nickname);
        tor_free(voter->address);
        tor_free(voter->contact);
 -      tor_free(voter->signature);
 +      if (voter->sigs) {
 +        SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig,
 +                          document_signature_free(sig));
 +        smartlist_free(voter->sigs);
 +      }
        tor_free(voter);
 -    });
 +    } SMARTLIST_FOREACH_END(voter);
      smartlist_free(ns->voters);
    }
 -  if (ns->cert)
 -    authority_cert_free(ns->cert);
 +  authority_cert_free(ns->cert);
  
    if (ns->routerstatus_list) {
      if (ns->type == NS_TYPE_VOTE || ns->type == NS_TYPE_OPINION) {
@@@ -392,8 -319,8 +392,8 @@@
  
      smartlist_free(ns->routerstatus_list);
    }
 -  if (ns->desc_digest_map)
 -    digestmap_free(ns->desc_digest_map, NULL);
 +
 +  digestmap_free(ns->desc_digest_map, NULL);
  
    memset(ns, 11, sizeof(*ns));
    tor_free(ns);
@@@ -414,38 -341,34 +414,38 @@@ networkstatus_get_voter_by_id(networkst
    return NULL;
  }
  
 -/** Check whether the signature on <b>voter</b> is correctly signed by
 - * the signing key of <b>cert</b>. Return -1 if <b>cert</b> doesn't match the
 +/** Check whether the signature <b>sig</b> is correctly signed with the
 + * signing key in <b>cert</b>.  Return -1 if <b>cert</b> doesn't match the
   * signing key; otherwise set the good_signature or bad_signature flag on
   * <b>voter</b>, and return 0. */
 -/* (private; exposed for testing.) */
  int
 -networkstatus_check_voter_signature(networkstatus_t *consensus,
 -                                    networkstatus_voter_info_t *voter,
 -                                    authority_cert_t *cert)
 +networkstatus_check_document_signature(const networkstatus_t *consensus,
 +                                       document_signature_t *sig,
 +                                       const authority_cert_t *cert)
  {
 -  char d[DIGEST_LEN];
 +  char key_digest[DIGEST_LEN];
 +  const int dlen = sig->alg == DIGEST_SHA1 ? DIGEST_LEN : DIGEST256_LEN;
    char *signed_digest;
    size_t signed_digest_len;
 -  if (crypto_pk_get_digest(cert->signing_key, d)<0)
 +
 +  if (crypto_pk_get_digest(cert->signing_key, key_digest)<0)
      return -1;
 -  if (memcmp(voter->signing_key_digest, d, DIGEST_LEN))
 +  if (memcmp(sig->signing_key_digest, key_digest, DIGEST_LEN) ||
 +      memcmp(sig->identity_digest, cert->cache_info.identity_digest,
 +             DIGEST_LEN))
      return -1;
 +
    signed_digest_len = crypto_pk_keysize(cert->signing_key);
    signed_digest = tor_malloc(signed_digest_len);
    if (crypto_pk_public_checksig(cert->signing_key,
                                  signed_digest,
 -                                voter->signature,
 -                                voter->signature_len) != DIGEST_LEN ||
 -      memcmp(signed_digest, consensus->networkstatus_digest, DIGEST_LEN)) {
 +                                sig->signature,
 +                                sig->signature_len) < dlen ||
 +      memcmp(signed_digest, consensus->digests.d[sig->alg], dlen)) {
      log_warn(LD_DIR, "Got a bad signature on a networkstatus vote");
 -    voter->bad_signature = 1;
 +    sig->bad_signature = 1;
    } else {
 -    voter->good_signature = 1;
 +    sig->good_signature = 1;
    }
    tor_free(signed_digest);
    return 0;
@@@ -464,7 -387,7 +464,7 @@@ networkstatus_check_consensus_signature
                                          int warn)
  {
    int n_good = 0;
 -  int n_missing_key = 0;
 +  int n_missing_key = 0, n_dl_failed_key = 0;
    int n_bad = 0;
    int n_unknown = 0;
    int n_no_signature = 0;
@@@ -478,62 -401,37 +478,62 @@@
  
    tor_assert(consensus->type == NS_TYPE_CONSENSUS);
  
 -  SMARTLIST_FOREACH(consensus->voters, networkstatus_voter_info_t *, voter,
 -  {
 -    if (!voter->good_signature && !voter->bad_signature && voter->signature) {
 -      /* we can try to check the signature. */
 -      int is_v3_auth = trusteddirserver_get_by_v3_auth_digest(
 -                                          voter->identity_digest) != NULL;
 -      authority_cert_t *cert =
 -        authority_cert_get_by_digests(voter->identity_digest,
 -                                      voter->signing_key_digest);
 -      if (!is_v3_auth) {
 -        smartlist_add(unrecognized, voter);
 -        ++n_unknown;
 -        continue;
 -      } else if (!cert || cert->expires < now) {
 -        smartlist_add(need_certs_from, voter);
 -        ++n_missing_key;
 -        continue;
 -      }
 -      if (networkstatus_check_voter_signature(consensus, voter, cert) < 0) {
 -        smartlist_add(need_certs_from, voter);
 -        ++n_missing_key;
 -        continue;
 +  SMARTLIST_FOREACH_BEGIN(consensus->voters, networkstatus_voter_info_t *,
 +                          voter) {
 +    int good_here = 0;
 +    int bad_here = 0;
 +    int unknown_here = 0;
 +    int missing_key_here = 0, dl_failed_key_here = 0;
 +    SMARTLIST_FOREACH_BEGIN(voter->sigs, document_signature_t *, sig) {
 +      if (!sig->good_signature && !sig->bad_signature &&
 +          sig->signature) {
 +        /* we can try to check the signature. */
 +        int is_v3_auth = trusteddirserver_get_by_v3_auth_digest(
 +                                              sig->identity_digest) != NULL;
 +        authority_cert_t *cert =
 +          authority_cert_get_by_digests(sig->identity_digest,
 +                                        sig->signing_key_digest);
 +        tor_assert(!memcmp(sig->identity_digest, voter->identity_digest,
 +                           DIGEST_LEN));
 +
 +        if (!is_v3_auth) {
 +          smartlist_add(unrecognized, voter);
 +          ++unknown_here;
 +          continue;
 +        } else if (!cert || cert->expires < now) {
 +          smartlist_add(need_certs_from, voter);
 +          ++missing_key_here;
 +          if (authority_cert_dl_looks_uncertain(sig->identity_digest))
 +            ++dl_failed_key_here;
 +          continue;
 +        }
 +        if (networkstatus_check_document_signature(consensus, sig, cert) < 0) {
 +          smartlist_add(need_certs_from, voter);
 +          ++missing_key_here;
 +          if (authority_cert_dl_looks_uncertain(sig->identity_digest))
 +            ++dl_failed_key_here;
 +          continue;
 +        }
        }
 -    }
 -    if (voter->good_signature)
 +      if (sig->good_signature)
 +        ++good_here;
 +      else if (sig->bad_signature)
 +        ++bad_here;
 +    } SMARTLIST_FOREACH_END(sig);
 +    if (good_here)
        ++n_good;
 -    else if (voter->bad_signature)
 +    else if (bad_here)
        ++n_bad;
 -    else
 +    else if (missing_key_here) {
 +      ++n_missing_key;
 +      if (dl_failed_key_here)
 +        ++n_dl_failed_key;
 +    } else if (unknown_here) {
 +      ++n_unknown;
 +    } else {
        ++n_no_signature;
 -  });
 +    }
 +  } SMARTLIST_FOREACH_END(voter);
  
    /* Now see whether we're missing any voters entirely. */
    SMARTLIST_FOREACH(router_get_trusted_dir_servers(),
@@@ -544,71 -442,39 +544,71 @@@
          smartlist_add(missing_authorities, ds);
      });
  
 -  if (warn > 1 || (warn >= 0 && n_good < n_required))
 +  if (warn > 1 || (warn >= 0 &&
 +                   (n_good + n_missing_key - n_dl_failed_key < n_required))) {
      severity = LOG_WARN;
 -  else
 +  } else {
      severity = LOG_INFO;
 +  }
  
    if (warn >= 0) {
      SMARTLIST_FOREACH(unrecognized, networkstatus_voter_info_t *, voter,
        {
 -        log_info(LD_DIR, "Consensus includes unrecognized authority '%s' "
 -                 "at %s:%d (contact %s; identity %s)",
 +        log(severity, LD_DIR, "Consensus includes unrecognized authority "
 +                 "'%s' at %s:%d (contact %s; identity %s)",
                   voter->nickname, voter->address, (int)voter->dir_port,
                   voter->contact?voter->contact:"n/a",
                   hex_str(voter->identity_digest, DIGEST_LEN));
        });
      SMARTLIST_FOREACH(need_certs_from, networkstatus_voter_info_t *, voter,
        {
 -        log_info(LD_DIR, "Looks like we need to download a new certificate "
 -                 "from authority '%s' at %s:%d (contact %s; identity %s)",
 +        log(severity, LD_DIR, "Looks like we need to download a new "
 +                 "certificate from authority '%s' at %s:%d (contact %s; "
 +                 "identity %s)",
                   voter->nickname, voter->address, (int)voter->dir_port,
                   voter->contact?voter->contact:"n/a",
                   hex_str(voter->identity_digest, DIGEST_LEN));
        });
      SMARTLIST_FOREACH(missing_authorities, trusted_dir_server_t *, ds,
        {
 -        log_info(LD_DIR, "Consensus does not include configured "
 +        log(severity, LD_DIR, "Consensus does not include configured "
                   "authority '%s' at %s:%d (identity %s)",
                   ds->nickname, ds->address, (int)ds->dir_port,
                   hex_str(ds->v3_identity_digest, DIGEST_LEN));
        });
 -    log(severity, LD_DIR,
 -        "%d unknown, %d missing key, %d good, %d bad, %d no signature, "
 -        "%d required", n_unknown, n_missing_key, n_good, n_bad,
 -        n_no_signature, n_required);
 +    {
 +      smartlist_t *sl = smartlist_create();
 +      char *cp;
 +      tor_asprintf(&cp, "A consensus needs %d good signatures from recognized "
 +                   "authorities for us to accept it. This one has %d.",
 +                   n_required, n_good);
 +      smartlist_add(sl,cp);
 +      if (n_no_signature) {
 +        tor_asprintf(&cp, "%d of the authorities we know didn't sign it.",
 +                     n_no_signature);
 +        smartlist_add(sl,cp);
 +      }
 +      if (n_unknown) {
 +        tor_asprintf(&cp, "It has %d signatures from authorities we don't "
 +                      "recognize.", n_unknown);
 +        smartlist_add(sl,cp);
 +      }
 +      if (n_bad) {
 +        tor_asprintf(&cp, "%d of the signatures on it didn't verify "
 +                      "correctly.", n_bad);
 +        smartlist_add(sl,cp);
 +      }
 +      if (n_missing_key) {
 +        tor_asprintf(&cp, "We were unable to check %d of the signatures, "
 +                      "because we were missing the keys.", n_missing_key);
 +        smartlist_add(sl,cp);
 +      }
 +      cp = smartlist_join_strings(sl, " ", 0, NULL);
 +      log(severity, LD_DIR, "%s", cp);
 +      tor_free(cp);
 +      SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
 +      smartlist_free(sl);
 +    }
    }
  
    smartlist_free(unrecognized);
@@@ -918,8 -784,8 +918,8 @@@ networkstatus_v2_list_clean(time_t now
  
  /** Helper for bsearching a list of routerstatus_t pointers: compare a
   * digest in the key to the identity digest of a routerstatus_t. */
 -static int
 -_compare_digest_to_routerstatus_entry(const void *_key, const void **_member)
 +int
 +compare_digest_to_routerstatus_entry(const void *_key, const void **_member)
  {
    const char *key = _key;
    const routerstatus_t *rs = *_member;
@@@ -932,7 -798,7 +932,7 @@@ routerstatus_t 
  networkstatus_v2_find_entry(networkstatus_v2_t *ns, const char *digest)
  {
    return smartlist_bsearch(ns->entries, digest,
 -                           _compare_digest_to_routerstatus_entry);
 +                           compare_digest_to_routerstatus_entry);
  }
  
  /** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or
@@@ -941,7 -807,7 +941,7 @@@ routerstatus_t 
  networkstatus_vote_find_entry(networkstatus_t *ns, const char *digest)
  {
    return smartlist_bsearch(ns->routerstatus_list, digest,
 -                           _compare_digest_to_routerstatus_entry);
 +                           compare_digest_to_routerstatus_entry);
  }
  
  /*XXXX make this static once functions are moved into this file. */
@@@ -953,7 -819,7 +953,7 @@@ networkstatus_vote_find_entry_idx(netwo
                                    const char *digest, int *found_out)
  {
    return smartlist_bsearch_idx(ns->routerstatus_list, digest,
 -                               _compare_digest_to_routerstatus_entry,
 +                               compare_digest_to_routerstatus_entry,
                                 found_out);
  }
  
@@@ -1006,7 -872,7 +1006,7 @@@ router_get_consensus_status_by_id(cons
    if (!current_consensus)
      return NULL;
    return smartlist_bsearch(current_consensus->routerstatus_list, digest,
 -                           _compare_digest_to_routerstatus_entry);
 +                           compare_digest_to_routerstatus_entry);
  }
  
  /** Given a nickname (possibly verbose, possibly a hexadecimal digest), return
@@@ -1210,30 -1076,28 +1210,30 @@@ update_v2_networkstatus_cache_downloads
  static void
  update_consensus_networkstatus_downloads(time_t now)
  {
 -  or_options_t *options = get_options();
 +  int i;
    if (!networkstatus_get_live_consensus(now))
      time_to_download_next_consensus = now; /* No live consensus? Get one now!*/
    if (time_to_download_next_consensus > now)
      return; /* Wait until the current consensus is older. */
 -  if (authdir_mode_v3(options))
 -    return; /* Authorities never fetch a consensus */
 -  if (!download_status_is_ready(&consensus_dl_status, now,
 +  /* XXXXNM Microdescs: may need to download more types. */
 +  if (!download_status_is_ready(&consensus_dl_status[FLAV_NS], now,
                                  CONSENSUS_NETWORKSTATUS_MAX_DL_TRIES))
      return; /* We failed downloading a consensus too recently. */
    if (connection_get_by_type_purpose(CONN_TYPE_DIR,
                                       DIR_PURPOSE_FETCH_CONSENSUS))
      return; /* There's an in-progress download.*/
  
 -  if (consensus_waiting_for_certs) {
 -    /* XXXX make sure this doesn't delay sane downloads. */
 -    if (consensus_waiting_for_certs_set_at + DELAY_WHILE_FETCHING_CERTS > now)
 -      return; /* We're still getting certs for this one. */
 -    else {
 -      if (!consensus_waiting_for_certs_dl_failed) {
 -        download_status_failed(&consensus_dl_status, 0);
 -        consensus_waiting_for_certs_dl_failed=1;
 +  for (i=0; i < N_CONSENSUS_FLAVORS; ++i) {
 +    consensus_waiting_for_certs_t *waiting = &consensus_waiting_for_certs[i];
 +    if (waiting->consensus) {
 +      /* XXXX make sure this doesn't delay sane downloads. */
 +      if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now)
 +        return; /* We're still getting certs for this one. */
 +      else {
 +        if (!waiting->dl_failed) {
 +          download_status_failed(&consensus_dl_status[FLAV_NS], 0);
 +          waiting->dl_failed=1;
 +        }
        }
      }
    }
@@@ -1249,8 -1113,7 +1249,8 @@@
  void
  networkstatus_consensus_download_failed(int status_code)
  {
 -  download_status_failed(&consensus_dl_status, status_code);
 +  /* XXXXNM Microdescs: may need to handle more types. */
 +  download_status_failed(&consensus_dl_status[FLAV_NS], status_code);
    /* Retry immediately, if appropriate. */
    update_consensus_networkstatus_downloads(time(NULL));
  }
@@@ -1284,15 -1147,8 +1284,15 @@@ update_consensus_networkstatus_fetch_ti
        /* We want to cache the next one at some point after this one
         * is no longer fresh... */
        start = c->fresh_until + min_sec_before_caching;
 -      /* But only in the first half-interval after that. */
 -      dl_interval = interval/2;
 +      /* Some clients may need the consensus sooner than others. */
 +      if (options->FetchDirInfoExtraEarly || authdir_mode_v3(options)) {
 +        dl_interval = 60;
 +        if (min_sec_before_caching + dl_interval > interval)
 +          dl_interval = interval/2;
 +      } else {
 +        /* But only in the first half-interval after that. */
 +        dl_interval = interval/2;
 +      }
      } else {
        /* We're an ordinary client or a bridge. Give all the caches enough
         * time to download the consensus. */
@@@ -1311,7 -1167,7 +1311,7 @@@
      }
      if (dl_interval < 1)
        dl_interval = 1;
 -    /* We must not try to replace c while it's still the most valid: */
 +    /* We must not try to replace c while it's still fresh: */
      tor_assert(c->fresh_until < start);
      /* We must download the next one before c is invalid: */
      tor_assert(start+dl_interval < c->valid_until);
@@@ -1332,6 -1188,7 +1332,6 @@@
      time_to_download_next_consensus = now;
      log_info(LD_DIR, "No live consensus; we should fetch one immediately.");
    }
 -
  }
  
  /** Return 1 if there's a reason we shouldn't try any directory
@@@ -1366,14 -1223,10 +1366,14 @@@ update_networkstatus_downloads(time_t n
  void
  update_certificate_downloads(time_t now)
  {
 -  if (consensus_waiting_for_certs)
 -    authority_certs_fetch_missing(consensus_waiting_for_certs, now);
 -  else
 -    authority_certs_fetch_missing(current_consensus, now);
 +  int i;
 +  for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
 +    if (consensus_waiting_for_certs[i].consensus)
 +      authority_certs_fetch_missing(consensus_waiting_for_certs[i].consensus,
 +                                    now);
 +  }
 +
 +  authority_certs_fetch_missing(current_consensus, now);
  }
  
  /** Return 1 if we have a consensus but we don't have enough certificates
@@@ -1381,8 -1234,7 +1381,8 @@@
  int
  consensus_is_waiting_for_certs(void)
  {
 -  return consensus_waiting_for_certs ? 1 : 0;
 +  return consensus_waiting_for_certs[USABLE_CONSENSUS_FLAVOR].consensus
 +    ? 1 : 0;
  }
  
  /** Return the network status with a given identity digest. */
@@@ -1551,31 -1403,16 +1551,31 @@@ networkstatus_copy_old_consensus_info(n
   * user, and -2 for more serious problems.
   */
  int
 -networkstatus_set_current_consensus(const char *consensus, unsigned flags)
 +networkstatus_set_current_consensus(const char *consensus,
 +                                    const char *flavor,
 +                                    unsigned flags)
  {
 -  networkstatus_t *c;
 +  networkstatus_t *c=NULL;
    int r, result = -1;
    time_t now = time(NULL);
 +  or_options_t *options = get_options();
    char *unverified_fname = NULL, *consensus_fname = NULL;
 +  int flav = networkstatus_parse_flavor_name(flavor);
    const unsigned from_cache = flags & NSSET_FROM_CACHE;
    const unsigned was_waiting_for_certs = flags & NSSET_WAS_WAITING_FOR_CERTS;
    const unsigned dl_certs = !(flags & NSSET_DONT_DOWNLOAD_CERTS);
    const unsigned accept_obsolete = flags & NSSET_ACCEPT_OBSOLETE;
 +  const unsigned require_flavor = flags & NSSET_REQUIRE_FLAVOR;
 +  const digests_t *current_digests = NULL;
 +  consensus_waiting_for_certs_t *waiting = NULL;
 +  time_t current_valid_after = 0;
 +  int free_consensus = 1; /* Free 'c' at the end of the function */
 +
 +  if (flav < 0) {
 +    /* XXXX we don't handle unrecognized flavors yet. */
 +    log_warn(LD_BUG, "Unrecognized consensus flavor %s", flavor);
 +    return -2;
 +  }
  
    /* Make sure it's parseable. */
    c = networkstatus_parse_vote_from_string(consensus, NULL, NS_TYPE_CONSENSUS);
@@@ -1585,69 -1422,33 +1585,69 @@@
      goto done;
    }
  
 +  if ((int)c->flavor != flav) {
 +    /* This wasn't the flavor we thought we were getting. */
 +    if (require_flavor) {
 +      log_warn(LD_DIR, "Got consensus with unexpected flavor %s (wanted %s)",
 +               networkstatus_get_flavor_name(c->flavor), flavor);
 +      goto done;
 +    }
 +    flav = c->flavor;
 +    flavor = networkstatus_get_flavor_name(flav);
 +  }
 +
 +  if (flav != USABLE_CONSENSUS_FLAVOR &&
 +      !directory_caches_dir_info(options)) {
 +    /* This consensus is totally boring to us: we won't use it, and we won't
 +     * serve it.  Drop it. */
 +    goto done;
 +  }
 +
    if (from_cache && !accept_obsolete &&
        c->valid_until < now-OLD_ROUTER_DESC_MAX_AGE) {
      /* XXX022 when we try to make fallbackconsensus work again, we should
       * consider taking this out. Until then, believing obsolete consensuses
       * is causing more harm than good. See also bug 887. */
 -    log_info(LD_DIR, "Loaded an obsolete consensus. Discarding.");
 +    log_info(LD_DIR, "Loaded an expired consensus. Discarding.");
      goto done;
    }
  
 -  if (current_consensus &&
 -      !memcmp(c->networkstatus_digest, current_consensus->networkstatus_digest,
 -              DIGEST_LEN)) {
 +  if (!strcmp(flavor, "ns")) {
 +    consensus_fname = get_datadir_fname("cached-consensus");
 +    unverified_fname = get_datadir_fname("unverified-consensus");
 +    if (current_consensus) {
 +      current_digests = &current_consensus->digests;
 +      current_valid_after = current_consensus->valid_after;
 +    }
 +  } else {
 +    cached_dir_t *cur;
 +    char buf[128];
 +    tor_snprintf(buf, sizeof(buf), "cached-%s-consensus", flavor);
 +    consensus_fname = get_datadir_fname(buf);
 +    tor_snprintf(buf, sizeof(buf), "unverified-%s-consensus", flavor);
 +    unverified_fname = get_datadir_fname(buf);
 +    cur = dirserv_get_consensus(flavor);
 +    if (cur) {
 +      current_digests = &cur->digests;
 +      current_valid_after = cur->published;
 +    }
 +  }
 +
 +  if (current_digests &&
 +      !memcmp(&c->digests, current_digests, sizeof(c->digests))) {
      /* We already have this one. That's a failure. */
 -    log_info(LD_DIR, "Got a consensus we already have");
 +    log_info(LD_DIR, "Got a %s consensus we already have", flavor);
      goto done;
    }
  
 -  if (current_consensus && c->valid_after <= current_consensus->valid_after) {
 +  if (current_valid_after && c->valid_after <= current_valid_after) {
      /* We have a newer one.  There's no point in accepting this one,
       * even if it's great. */
 -    log_info(LD_DIR, "Got a consensus at least as old as the one we have");
 +    log_info(LD_DIR, "Got a %s consensus at least as old as the one we have",
 +             flavor);
      goto done;
    }
  
 -  consensus_fname = get_datadir_fname("cached-consensus");
 -  unverified_fname = get_datadir_fname("unverified-consensus");
 -
    /* Make sure it's signed enough. */
    if ((r=networkstatus_check_consensus_signature(c, 1))<0) {
      if (r == -1) {
@@@ -1656,16 -1457,16 +1656,16 @@@
          log_info(LD_DIR,
                   "Not enough certificates to check networkstatus consensus");
        }
 -      if (!current_consensus ||
 -          c->valid_after > current_consensus->valid_after) {
 -        if (consensus_waiting_for_certs)
 -          networkstatus_vote_free(consensus_waiting_for_certs);
 -        tor_free(consensus_waiting_for_certs_body);
 -        consensus_waiting_for_certs = c;
 -        c = NULL; /* Prevent free. */
 -        consensus_waiting_for_certs_body = tor_strdup(consensus);
 -        consensus_waiting_for_certs_set_at = now;
 -        consensus_waiting_for_certs_dl_failed = 0;
 +      if (!current_valid_after ||
 +          c->valid_after > current_valid_after) {
 +        waiting = &consensus_waiting_for_certs[flav];
 +        networkstatus_vote_free(waiting->consensus);
 +        tor_free(waiting->body);
 +        waiting->consensus = c;
 +        free_consensus = 0;
 +        waiting->body = tor_strdup(consensus);
 +        waiting->set_at = now;
 +        waiting->dl_failed = 0;
          if (!from_cache) {
            write_str_to_file(unverified_fname, consensus, 0);
          }
@@@ -1694,75 -1495,56 +1694,75 @@@
      }
    }
  
 -  if (!from_cache)
 +  if (!from_cache && flav == USABLE_CONSENSUS_FLAVOR)
      control_event_client_status(LOG_NOTICE, "CONSENSUS_ARRIVED");
  
    /* Are we missing any certificates at all? */
    if (r != 1 && dl_certs)
      authority_certs_fetch_missing(c, now);
  
 -  notify_control_networkstatus_changed(current_consensus, c);
 +  if (flav == USABLE_CONSENSUS_FLAVOR) {
 +    notify_control_networkstatus_changed(current_consensus, c);
  
 -  if (current_consensus) {
 -    networkstatus_copy_old_consensus_info(c, current_consensus);
 -    networkstatus_vote_free(current_consensus);
 +    if (current_consensus) {
 +      networkstatus_copy_old_consensus_info(c, current_consensus);
 +      networkstatus_vote_free(current_consensus);
 +      /* Defensive programming : we should set current_consensus very soon,
 +       * but we're about to call some stuff in the meantime, and leaving this
 +       * dangling pointer around has proven to be trouble. */
 +       current_consensus = NULL;
 +    }
    }
  
 -  if (consensus_waiting_for_certs &&
 -      consensus_waiting_for_certs->valid_after <= c->valid_after) {
 -    networkstatus_vote_free(consensus_waiting_for_certs);
 -    consensus_waiting_for_certs = NULL;
 -    if (consensus != consensus_waiting_for_certs_body)
 -      tor_free(consensus_waiting_for_certs_body);
 +  waiting = &consensus_waiting_for_certs[flav];
 +  if (waiting->consensus &&
 +      waiting->consensus->valid_after <= c->valid_after) {
 +    networkstatus_vote_free(waiting->consensus);
 +    waiting->consensus = NULL;
 +    if (consensus != waiting->body)
 +      tor_free(waiting->body);
      else
 -      consensus_waiting_for_certs_body = NULL;
 -    consensus_waiting_for_certs_set_at = 0;
 -    consensus_waiting_for_certs_dl_failed = 0;
 +      waiting->body = NULL;
 +    waiting->set_at = 0;
 +    waiting->dl_failed = 0;
      unlink(unverified_fname);
    }
  
    /* Reset the failure count only if this consensus is actually valid. */
    if (c->valid_after <= now && now <= c->valid_until) {
 -    download_status_reset(&consensus_dl_status);
 +    download_status_reset(&consensus_dl_status[flav]);
    } else {
      if (!from_cache)
 -      download_status_failed(&consensus_dl_status, 0);
 +      download_status_failed(&consensus_dl_status[flav], 0);
    }
  
 -  current_consensus = c;
 -  c = NULL; /* Prevent free. */
 +  if (flav == USABLE_CONSENSUS_FLAVOR) {
 +    current_consensus = c;
 +    free_consensus = 0; /* Prevent free. */
 +
 +    /* XXXXNM Microdescs: needs a non-ns variant. */
 +    update_consensus_networkstatus_fetch_time(now);
 +    dirvote_recalculate_timing(options, now);
 +    routerstatus_list_update_named_server_map();
 +    cell_ewma_set_scale_factor(options, current_consensus);
 +
 +    /* XXX022 where is the right place to put this call? */
 +    connection_or_update_token_buckets(get_connection_array(), options);
  
 -  update_consensus_networkstatus_fetch_time(now);
 -  dirvote_recalculate_timing(get_options(), now);
 -  routerstatus_list_update_named_server_map();
 +    circuit_build_times_new_consensus_params(&circ_times, current_consensus);
 +  }
 +
 +  if (directory_caches_dir_info(options)) {
 +    dirserv_set_cached_consensus_networkstatus(consensus,
 +                                               flavor,
 +                                               &c->digests,
 +                                               c->valid_after);
 +  }
  
    if (!from_cache) {
      write_str_to_file(consensus_fname, consensus, 0);
    }
  
 -  if (directory_caches_dir_info(get_options()))
 -    dirserv_set_cached_networkstatus_v3(consensus,
 -                                        current_consensus->valid_after);
 -
    if (ftime_definitely_before(now, current_consensus->valid_after)) {
      char tbuf[ISO_TIME_LEN+1];
      char dbuf[64];
@@@ -1781,7 -1563,7 +1781,7 @@@
  
    result = 0;
   done:
 -  if (c)
 +  if (free_consensus)
      networkstatus_vote_free(c);
    tor_free(consensus_fname);
    tor_free(unverified_fname);
@@@ -1793,17 -1575,13 +1793,17 @@@
  void
  networkstatus_note_certs_arrived(void)
  {
 -  if (consensus_waiting_for_certs) {
 -    if (networkstatus_check_consensus_signature(
 -                                    consensus_waiting_for_certs, 0)>=0) {
 +  int i;
 +  for (i=0; i<N_CONSENSUS_FLAVORS; ++i) {
 +    consensus_waiting_for_certs_t *waiting = &consensus_waiting_for_certs[i];
 +    if (!waiting->consensus)
 +      continue;
 +    if (networkstatus_check_consensus_signature(waiting->consensus, 0)>=0) {
        if (!networkstatus_set_current_consensus(
 -                                 consensus_waiting_for_certs_body,
 +                                 waiting->body,
 +                                 networkstatus_get_flavor_name(i),
                                   NSSET_WAS_WAITING_FOR_CERTS)) {
 -        tor_free(consensus_waiting_for_certs_body);
 +        tor_free(waiting->body);
        }
      }
    }
@@@ -1889,8 -1667,10 +1889,8 @@@ download_status_map_update_from_v2_netw
      v2_download_status_map = digestmap_new();
  
    dl_status = digestmap_new();
 -  SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
 -  {
 -    SMARTLIST_FOREACH(ns->entries, routerstatus_t *, rs,
 -    {
 +  SMARTLIST_FOREACH_BEGIN(networkstatus_v2_list, networkstatus_v2_t *, ns) {
 +    SMARTLIST_FOREACH_BEGIN(ns->entries, routerstatus_t *, rs) {
        const char *d = rs->descriptor_digest;
        download_status_t *s;
        if (digestmap_get(dl_status, d))
@@@ -1899,8 -1679,8 +1899,8 @@@
          s = tor_malloc_zero(sizeof(download_status_t));
        }
        digestmap_set(dl_status, d, s);
 -    });
 -  });
 +    } SMARTLIST_FOREACH_END(rs);
 +  } SMARTLIST_FOREACH_END(ns);
    digestmap_free(v2_download_status_map, _tor_free);
    v2_download_status_map = dl_status;
    networkstatus_v2_list_has_changed = 0;
@@@ -1914,9 -1694,11 +1914,9 @@@ routerstatus_list_update_named_server_m
    if (!current_consensus)
      return;
  
 -  if (named_server_map)
 -    strmap_free(named_server_map, _tor_free);
 +  strmap_free(named_server_map, _tor_free);
    named_server_map = strmap_new();
 -  if (unnamed_server_map)
 -    strmap_free(unnamed_server_map, NULL);
 +  strmap_free(unnamed_server_map, NULL);
    unnamed_server_map = strmap_new();
    SMARTLIST_FOREACH(current_consensus->routerstatus_list, routerstatus_t *, rs,
      {
@@@ -1991,15 -1773,6 +1991,15 @@@ routers_update_status_from_consensus_ne
        router->is_bad_directory = rs->is_bad_directory;
        router->is_bad_exit = rs->is_bad_exit;
        router->is_hs_dir = rs->is_hs_dir;
 +    } else {
 +      /* If we _are_ an authority, we should check whether this router
 +       * is one that will cause us to need a reachability test. */
 +      routerinfo_t *old_router =
 +        router_get_by_digest(router->cache_info.identity_digest);
 +      if (old_router != router) {
 +        router->needs_retest_if_added =
 +          dirserv_should_launch_reachability_test(router, old_router);
 +      }
      }
      if (router->is_running && ds) {
        download_status_reset(&ds->v2_ns_dl_status);
@@@ -2062,7 -1835,7 +2062,7 @@@ char 
  networkstatus_getinfo_helper_single(routerstatus_t *rs)
  {
    char buf[RS_ENTRY_LEN+1];
 -  routerstatus_format_entry(buf, sizeof(buf), rs, NULL, 0, 1);
 +  routerstatus_format_entry(buf, sizeof(buf), rs, NULL, NS_CONTROL_PORT);
    return tor_strdup(buf);
  }
  
@@@ -2099,7 -1872,7 +2099,7 @@@ networkstatus_getinfo_by_purpose(const 
      if (bridge_auth && ri->purpose == ROUTER_PURPOSE_BRIDGE)
        dirserv_set_router_is_running(ri, now);
      /* then generate and write out status lines for each of them */
 -    set_routerstatus_from_routerinfo(&rs, ri, now, 0, 0, 0, 0);
 +    set_routerstatus_from_routerinfo(&rs, ri, now, 0, 0, 0);
      smartlist_add(statuses, networkstatus_getinfo_helper_single(&rs));
    });
  
@@@ -2124,25 -1897,6 +2124,25 @@@ networkstatus_dump_bridge_status_to_fil
    tor_free(status);
  }
  
 +int32_t
 +get_net_param_from_list(smartlist_t *net_params, const char *param_name,
 +                        int default_val)
 +{
 +  size_t name_len = strlen(param_name);
 +
 +  SMARTLIST_FOREACH_BEGIN(net_params, const char *, p) {
 +    if (!strcmpstart(p, param_name) && p[name_len] == '=') {
 +      int ok=0;
 +      long v = tor_parse_long(p+name_len+1, 10, INT32_MIN,
 +                              INT32_MAX, &ok, NULL);
 +      if (ok)
 +        return (int32_t) v;
 +    }
 +  } SMARTLIST_FOREACH_END(p);
 +
 +  return default_val;
 +}
 +
  /** Return the value of a integer parameter from the networkstatus <b>ns</b>
   * whose name is <b>param_name</b>.  If <b>ns</b> is NULL, try loading the
   * latest consensus ourselves. Return <b>default_val</b> if no latest
@@@ -2151,59 -1905,27 +2151,59 @@@ int32_
  networkstatus_get_param(networkstatus_t *ns, const char *param_name,
                          int32_t default_val)
  {
 -  size_t name_len;
 -
    if (!ns) /* if they pass in null, go find it ourselves */
      ns = networkstatus_get_latest_consensus();
  
    if (!ns || !ns->net_params)
      return default_val;
  
 -  name_len = strlen(param_name);
 +  return get_net_param_from_list(ns->net_params, param_name, default_val);
 +}
  
 -  SMARTLIST_FOREACH_BEGIN(ns->net_params, const char *, p) {
 -    if (!strcmpstart(p, param_name) && p[name_len] == '=') {
 -      int ok=0;
 -      long v = tor_parse_long(p+name_len+1, 10, INT32_MIN, INT32_MAX, &ok,
 -                              NULL);
 -      if (ok)
 -        return (int32_t) v;
 -    }
 -  } SMARTLIST_FOREACH_END(p);
 +/** Return the value of a integer bw weight parameter from the networkstatus
 + * <b>ns</b> whose name is <b>weight_name</b>.  If <b>ns</b> is NULL, try
 + * loading the latest consensus ourselves. Return <b>default_val</b> if no
 + * latest consensus, or if it has no parameter called <b>param_name</b>. */
 +int32_t
 +networkstatus_get_bw_weight(networkstatus_t *ns, const char *weight_name,
 +                        int32_t default_val)
 +{
 +  if (!ns) /* if they pass in null, go find it ourselves */
 +    ns = networkstatus_get_latest_consensus();
  
 -  return default_val;
 +  if (!ns || !ns->weight_params)
 +    return default_val;
 +
 +  return get_net_param_from_list(ns->weight_params, weight_name, default_val);
 +}
 +
 +/** Return the name of the consensus flavor <b>flav</b> as used to identify
 + * the flavor in directory documents. */
 +const char *
 +networkstatus_get_flavor_name(consensus_flavor_t flav)
 +{
 +  switch (flav) {
 +    case FLAV_NS:
 +      return "ns";
 +    case FLAV_MICRODESC:
 +      return "microdesc";
 +    default:
 +      tor_fragile_assert();
 +      return "??";
 +  }
 +}
 +
 +/** Return the consensus_flavor_t value for the flavor called <b>flavname</b>,
 + * or -1 if the flavor is not recognized. */
 +int
 +networkstatus_parse_flavor_name(const char *flavname)
 +{
 +  if (!strcmp(flavname, "ns"))
 +    return FLAV_NS;
 +  else if (!strcmp(flavname, "microdesc"))
 +    return FLAV_MICRODESC;
 +  else
 +    return -1;
  }
  
  /** If <b>question</b> is a string beginning with "ns/" in a format the
@@@ -2212,8 -1934,7 +2212,8 @@@
   * ORs.  Return 0 on success, -1 on unrecognized question format. */
  int
  getinfo_helper_networkstatus(control_connection_t *conn,
 -                             const char *question, char **answer)
 +                             const char *question, char **answer,
 +                             const char **errmsg)
  {
    routerstatus_t *status;
    (void) conn;
@@@ -2237,10 -1958,8 +2237,10 @@@
    } else if (!strcmpstart(question, "ns/id/")) {
      char d[DIGEST_LEN];
  
 -    if (base16_decode(d, DIGEST_LEN, question+6, strlen(question+6)))
 +    if (base16_decode(d, DIGEST_LEN, question+6, strlen(question+6))) {
 +      *errmsg = "Data not decodeable as hex";
        return -1;
 +    }
      status = router_get_consensus_status_by_id(d);
    } else if (!strcmpstart(question, "ns/name/")) {
      status = router_get_consensus_status_by_nickname(question+8, 0);
@@@ -2248,7 -1967,7 +2248,7 @@@
      *answer = networkstatus_getinfo_by_purpose(question+11, time(NULL));
      return *answer ? 0 : -1;
    } else {
 -    return -1;
 +    return 0;
    }
  
    if (status)
@@@ -2260,29 -1979,30 +2260,29 @@@
  void
  networkstatus_free_all(void)
  {
 +  int i;
    if (networkstatus_v2_list) {
      SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
                        networkstatus_v2_free(ns));
      smartlist_free(networkstatus_v2_list);
      networkstatus_v2_list = NULL;
    }
 -  if (v2_download_status_map) {
 -    digestmap_free(v2_download_status_map, _tor_free);
 -    v2_download_status_map = NULL;
 -  }
 -  if (current_consensus) {
 -    networkstatus_vote_free(current_consensus);
 -    current_consensus = NULL;
 -  }
 -  if (consensus_waiting_for_certs) {
 -    networkstatus_vote_free(consensus_waiting_for_certs);
 -    consensus_waiting_for_certs = NULL;
 -  }
 -  tor_free(consensus_waiting_for_certs_body);
 -  if (named_server_map) {
 -    strmap_free(named_server_map, _tor_free);
 -  }
 -  if (unnamed_server_map) {
 -    strmap_free(unnamed_server_map, NULL);
 +
 +  digestmap_free(v2_download_status_map, _tor_free);
 +  v2_download_status_map = NULL;
 +  networkstatus_vote_free(current_consensus);
 +  current_consensus = NULL;
 +
 +  for (i=0; i < N_CONSENSUS_FLAVORS; ++i) {
 +    consensus_waiting_for_certs_t *waiting = &consensus_waiting_for_certs[i];
 +    if (waiting->consensus) {
 +      networkstatus_vote_free(waiting->consensus);
 +      waiting->consensus = NULL;
 +    }
 +    tor_free(waiting->body);
    }
 +
 +  strmap_free(named_server_map, _tor_free);
 +  strmap_free(unnamed_server_map, NULL);
  }
  
diff --combined src/or/ntmain.c
index 46e7afb,4dcf9b3..b01d4ca
--- a/src/or/ntmain.c
+++ b/src/or/ntmain.c
@@@ -1,26 -1,17 +1,26 @@@
  /* Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  #define MAIN_PRIVATE
  #include "or.h"
 -
 -#include <tchar.h>
 -#define GENSRV_SERVICENAME  TEXT("tor")
 -#define GENSRV_DISPLAYNAME  TEXT("Tor Win32 Service")
 +#include "config.h"
 +#include "main.h"
 +#include "ntmain.h"
 +
 +#ifdef HAVE_EVENT2_EVENT_H
 +#include <event2/event.h>
 +#else
 +#include <event.h>
 +#endif
 +
 +#include <windows.h>
 +#define GENSRV_SERVICENAME  "tor"
 +#define GENSRV_DISPLAYNAME  "Tor Win32 Service"
  #define GENSRV_DESCRIPTION  \
 -  TEXT("Provides an anonymous Internet communication system")
 -#define GENSRV_USERACCT TEXT("NT AUTHORITY\\LocalService")
 +  "Provides an anonymous Internet communication system"
 +#define GENSRV_USERACCT "NT AUTHORITY\\LocalService"
  
  // Cheating: using the pre-defined error codes, tricks Windows into displaying
  //           a semi-related human-readable error message if startup fails as
@@@ -36,6 -27,7 +36,6 @@@ static SERVICE_STATUS_HANDLE hStatus
   * to the NT service functions. */
  static char **backup_argv;
  static int backup_argc;
 -static char* nt_strerror(uint32_t errnum);
  
  static void nt_service_control(DWORD request);
  static void nt_service_body(int argc, char **argv);
@@@ -69,30 -61,30 +69,30 @@@ struct service_fns 
  
    SC_HANDLE (WINAPI *CreateServiceA_fn)(
                               SC_HANDLE hSCManager,
 -                             LPCTSTR lpServiceName,
 -                             LPCTSTR lpDisplayName,
 +                             LPCSTR lpServiceName,
 +                             LPCSTR lpDisplayName,
                               DWORD dwDesiredAccess,
                               DWORD dwServiceType,
                               DWORD dwStartType,
                               DWORD dwErrorControl,
 -                             LPCTSTR lpBinaryPathName,
 -                             LPCTSTR lpLoadOrderGroup,
 +                             LPCSTR lpBinaryPathName,
 +                             LPCSTR lpLoadOrderGroup,
                               LPDWORD lpdwTagId,
 -                             LPCTSTR lpDependencies,
 -                             LPCTSTR lpServiceStartName,
 -                             LPCTSTR lpPassword);
 +                             LPCSTR lpDependencies,
 +                             LPCSTR lpServiceStartName,
 +                             LPCSTR lpPassword);
  
    BOOL (WINAPI *DeleteService_fn)(
                               SC_HANDLE hService);
  
    SC_HANDLE (WINAPI *OpenSCManagerA_fn)(
 -                             LPCTSTR lpMachineName,
 -                             LPCTSTR lpDatabaseName,
 +                             LPCSTR lpMachineName,
 +                             LPCSTR lpDatabaseName,
                               DWORD dwDesiredAccess);
  
    SC_HANDLE (WINAPI *OpenServiceA_fn)(
                               SC_HANDLE hSCManager,
 -                             LPCTSTR lpServiceName,
 +                             LPCSTR lpServiceName,
                               DWORD dwDesiredAccess);
  
    BOOL (WINAPI *QueryServiceStatus_fn)(
@@@ -100,23 -92,23 +100,23 @@@
                               LPSERVICE_STATUS lpServiceStatus);
  
    SERVICE_STATUS_HANDLE (WINAPI *RegisterServiceCtrlHandlerA_fn)(
 -                             LPCTSTR lpServiceName,
 +                             LPCSTR lpServiceName,
                               LPHANDLER_FUNCTION lpHandlerProc);
  
    BOOL (WINAPI *SetServiceStatus_fn)(SERVICE_STATUS_HANDLE,
                               LPSERVICE_STATUS);
  
    BOOL (WINAPI *StartServiceCtrlDispatcherA_fn)(
 -                             const SERVICE_TABLE_ENTRY* lpServiceTable);
 +                             const SERVICE_TABLE_ENTRYA* lpServiceTable);
  
    BOOL (WINAPI *StartServiceA_fn)(
                               SC_HANDLE hService,
                               DWORD dwNumServiceArgs,
 -                             LPCTSTR* lpServiceArgVectors);
 +                             LPCSTR* lpServiceArgVectors);
  
    BOOL (WINAPI *LookupAccountNameA_fn)(
 -                             LPCTSTR lpSystemName,
 -                             LPCTSTR lpAccountName,
 +                             LPCSTR lpSystemName,
 +                             LPCSTR lpAccountName,
                               PSID Sid,
                               LPDWORD cbSid,
                               LPTSTR ReferencedDomainName,
@@@ -138,7 -130,8 +138,7 @@@ nt_service_loadlibrary(void
    if (service_fns.loaded)
      return;
  
 -  /* XXXX Possibly, we should hardcode the location of this DLL. */
 -  if (!(library = LoadLibrary("advapi32.dll"))) {
 +  if (!(library = load_windows_system_library(TEXT("advapi32.dll")))) {
      log_err(LD_GENERAL, "Couldn't open advapi32.dll.  Are you trying to use "
              "NT services on Windows 98? That doesn't work.");
      goto err;
@@@ -225,7 -218,7 +225,7 @@@ nt_service_control(DWORD request
            log_notice(LD_GENERAL,
                       "Got stop/shutdown request; shutting down cleanly.");
            service_status.dwCurrentState = SERVICE_STOP_PENDING;
 -          event_loopexit(&exit_now);
 +          event_base_loopexit(tor_libevent_get_base(), &exit_now);
            return;
    }
    service_fns.SetServiceStatus_fn(hStatus, &service_status);
@@@ -282,20 -275,20 +282,20 @@@ nt_service_body(int argc, char **argv
  static void
  nt_service_main(void)
  {
 -  SERVICE_TABLE_ENTRY table[2];
 +  SERVICE_TABLE_ENTRYA table[2];
    DWORD result = 0;
    char *errmsg;
    nt_service_loadlibrary();
    table[0].lpServiceName = (char*)GENSRV_SERVICENAME;
 -  table[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)nt_service_body;
 +  table[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTIONA)nt_service_body;
    table[1].lpServiceName = NULL;
    table[1].lpServiceProc = NULL;
  
    if (!service_fns.StartServiceCtrlDispatcherA_fn(table)) {
      result = GetLastError();
 -    errmsg = nt_strerror(result);
 +    errmsg = format_win32_error(result);
      printf("Service error %d : %s\n", (int) result, errmsg);
 -    LocalFree(errmsg);
 +    tor_free(errmsg);
      if (result == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
        if (tor_init(backup_argc, backup_argv) < 0)
          return;
@@@ -330,9 -323,9 +330,9 @@@ nt_service_open_scm(void
    nt_service_loadlibrary();
    if ((hSCManager = service_fns.OpenSCManagerA_fn(
                              NULL, NULL, SC_MANAGER_CREATE_SERVICE)) == NULL) {
 -    errmsg = nt_strerror(GetLastError());
 +    errmsg = format_win32_error(GetLastError());
      printf("OpenSCManager() failed : %s\n", errmsg);
 -    LocalFree(errmsg);
 +    tor_free(errmsg);
    }
    return hSCManager;
  }
@@@ -347,9 -340,9 +347,9 @@@ nt_service_open(SC_HANDLE hSCManager
    nt_service_loadlibrary();
    if ((hService = service_fns.OpenServiceA_fn(hSCManager, GENSRV_SERVICENAME,
                                SERVICE_ALL_ACCESS)) == NULL) {
 -    errmsg = nt_strerror(GetLastError());
 +    errmsg = format_win32_error(GetLastError());
      printf("OpenService() failed : %s\n", errmsg);
 -    LocalFree(errmsg);
 +    tor_free(errmsg);
    }
    return hService;
  }
@@@ -381,14 -374,14 +381,14 @@@ nt_service_start(SC_HANDLE hService
        printf("Service started successfully\n");
        return 0;
      } else {
 -      errmsg = nt_strerror(service_status.dwWin32ExitCode);
 +      errmsg = format_win32_error(service_status.dwWin32ExitCode);
        printf("Service failed to start : %s\n", errmsg);
 -      LocalFree(errmsg);
 +      tor_free(errmsg);
      }
    } else {
 -    errmsg = nt_strerror(GetLastError());
 +    errmsg = format_win32_error(GetLastError());
      printf("StartService() failed : %s\n", errmsg);
 -    LocalFree(errmsg);
 +    tor_free(errmsg);
    }
    return -1;
  }
@@@ -425,14 -418,14 +425,14 @@@ nt_service_stop(SC_HANDLE hService
      } else if (wait_time == MAX_SERVICE_WAIT_TIME) {
        printf("Service did not stop within %d seconds.\n", wait_time);
      } else {
 -      errmsg = nt_strerror(GetLastError());
 +      errmsg = format_win32_error(GetLastError());
        printf("QueryServiceStatus() failed : %s\n",errmsg);
 -      LocalFree(errmsg);
 +      tor_free(errmsg);
      }
    } else {
 -    errmsg = nt_strerror(GetLastError());
 +    errmsg = format_win32_error(GetLastError());
      printf("ControlService() failed : %s\n", errmsg);
 -    LocalFree(errmsg);
 +    tor_free(errmsg);
    }
    return -1;
  }
@@@ -446,7 -439,6 +446,7 @@@ static char 
  nt_service_command_line(int *using_default_torrc)
  {
    TCHAR tor_exe[MAX_PATH+1];
 +  char tor_exe_ascii[MAX_PATH+1];
    char *command, *options=NULL;
    smartlist_t *sl;
    int i, cmdlen;
@@@ -472,25 -464,18 +472,25 @@@
      options = smartlist_join_strings(sl,"\" \"",0,NULL);
    smartlist_free(sl);
  
 +#ifdef UNICODE
 +  wcstombs(tor_exe_ascii, tor_exe, sizeof(tor_exe_ascii));
 +#else
 +  strlcpy(tor_exe_ascii, tor_exe, sizeof(tor_exe_ascii));
 +#endif
 +
    /* Allocate a string for the NT service command line */
 -  cmdlen = strlen(tor_exe) + (options?strlen(options):0) + 32;
 +  cmdlen = strlen(tor_exe_ascii) + (options?strlen(options):0) + 32;
    command = tor_malloc(cmdlen);
  
    /* Format the service command */
    if (options) {
      if (tor_snprintf(command, cmdlen, "\"%s\" --nt-service \"%s\"",
 -                     tor_exe, options)<0) {
 +                     tor_exe_ascii, options)<0) {
        tor_free(command); /* sets command to NULL. */
      }
    } else { /* ! options */
 -    if (tor_snprintf(command, cmdlen, "\"%s\" --nt-service", tor_exe)<0) {
 +    if (tor_snprintf(command, cmdlen, "\"%s\" --nt-service",
 +                     tor_exe_ascii)<0) {
        tor_free(command); /* sets command to NULL. */
      }
    }
@@@ -515,7 -500,7 +515,7 @@@ nt_service_install(int argc, char **arg
  
    SC_HANDLE hSCManager = NULL;
    SC_HANDLE hService = NULL;
 -  SERVICE_DESCRIPTION sdBuff;
 +  SERVICE_DESCRIPTIONA sdBuff;
    char *command;
    char *errmsg;
    const char *user_acct = GENSRV_USERACCT;
@@@ -605,10 -590,10 +605,10 @@@
                                  SERVICE_AUTO_START, SERVICE_ERROR_IGNORE,
                                  command, NULL, NULL, NULL,
                                  user_acct, password)) == NULL) {
 -    errmsg = nt_strerror(GetLastError());
 +    errmsg = format_win32_error(GetLastError());
      printf("CreateService() failed : %s\n", errmsg);
      service_fns.CloseServiceHandle_fn(hSCManager);
 -    LocalFree(errmsg);
 +    tor_free(errmsg);
      tor_free(command);
      return -1;
    }
@@@ -649,9 -634,9 +649,9 @@@ nt_service_remove(void
  
    nt_service_stop(hService);
    if (service_fns.DeleteService_fn(hService) == FALSE) {
 -    errmsg = nt_strerror(GetLastError());
 +    errmsg = format_win32_error(GetLastError());
      printf("DeleteService() failed : %s\n", errmsg);
 -    LocalFree(errmsg);
 +    tor_free(errmsg);
      service_fns.CloseServiceHandle_fn(hService);
      service_fns.CloseServiceHandle_fn(hSCManager);
      return -1;
@@@ -708,6 -693,20 +708,6 @@@ nt_service_cmd_stop(void
    return stop;
  }
  
 -/** Given a Win32 error code, this attempts to make Windows
 - * return a human-readable error message. The char* returned
 - * is allocated by Windows, but should be freed with LocalFree()
 - * when finished with it. */
 -static char*
 -nt_strerror(uint32_t errnum)
 -{
 -   char *msgbuf;
 -   FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
 -                 NULL, errnum, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
 -                 (LPSTR)&msgbuf, 0, NULL);
 -   return msgbuf;
 -}
 -
  int
  nt_service_parse_options(int argc, char **argv, int *should_exit)
  {
diff --combined src/or/onion.c
index 4d14f90,9c826a5..b8f3019
--- a/src/or/onion.c
+++ b/src/or/onion.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -11,10 -11,6 +11,10 @@@
   **/
  
  #include "or.h"
 +#include "circuitlist.h"
 +#include "config.h"
 +#include "onion.h"
 +#include "rephist.h"
  
  /** Type for a linked list of circuits that are waiting for a free CPU worker
   * to process a waiting onion handshake. */
@@@ -62,18 -58,11 +62,18 @@@ onion_pending_add(or_circuit_t *circ, c
    tor_assert(!ol_tail->next);
  
    if (ol_length >= get_options()->MaxOnionsPending) {
 -    log_warn(LD_GENERAL,
 -             "Your computer is too slow to handle this many circuit "
 -             "creation requests! Please consider using the "
 -             "MaxAdvertisedBandwidth config option or choosing a more "
 -             "restricted exit policy.");
 +#define WARN_TOO_MANY_CIRC_CREATIONS_INTERVAL (60)
 +    static ratelim_t last_warned =
 +      RATELIM_INIT(WARN_TOO_MANY_CIRC_CREATIONS_INTERVAL);
 +    char *m;
 +    if ((m = rate_limit_log(&last_warned, approx_time()))) {
 +      log_warn(LD_GENERAL,
 +               "Your computer is too slow to handle this many circuit "
 +               "creation requests! Please consider using the "
 +               "MaxAdvertisedBandwidth config option or choosing a more "
 +               "restricted exit policy.%s",m);
 +      tor_free(m);
 +    }
      tor_free(tmp);
      return -1;
    }
@@@ -264,9 -253,8 +264,9 @@@ onion_skin_server_handshake(const char 
  
    key_material_len = DIGEST_LEN+key_out_len;
    key_material = tor_malloc(key_material_len);
 -  len = crypto_dh_compute_secret(dh, challenge, DH_KEY_LEN,
 -                                 key_material, key_material_len);
 +  len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, challenge,
 +                                 DH_KEY_LEN, key_material,
 +                                 key_material_len);
    if (len < 0) {
      log_info(LD_GENERAL, "crypto_dh_compute_secret failed.");
      goto err;
@@@ -316,9 -304,8 +316,9 @@@ onion_skin_client_handshake(crypto_dh_e
  
    key_material_len = DIGEST_LEN + key_out_len;
    key_material = tor_malloc(key_material_len);
 -  len = crypto_dh_compute_secret(handshake_state, handshake_reply, DH_KEY_LEN,
 -                                 key_material, key_material_len);
 +  len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, handshake_state,
 +                                 handshake_reply, DH_KEY_LEN, key_material,
 +                                 key_material_len);
    if (len < 0)
      goto err;
  
diff --combined src/or/or.h
index 07b73ec,57e091e..cb36126
--- a/src/or/or.h
+++ b/src/or/or.h
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -20,6 -20,9 +20,6 @@@
  #ifndef INSTRUMENT_DOWNLOADS
  #define INSTRUMENT_DOWNLOADS 1
  #endif
 -#ifndef ENABLE_GEOIP_STATS
 -#define ENABLE_GEOIP_STATS 1
 -#endif
  #endif
  
  #ifdef MS_WINDOWS
@@@ -59,9 -62,6 +59,9 @@@
  #ifdef HAVE_SYS_STAT_H
  #include <sys/stat.h>
  #endif
 +#ifdef HAVE_NETINET_IN_H
 +#include <netinet/in.h>
 +#endif
  #ifdef HAVE_ARPA_INET_H
  #include <arpa/inet.h>
  #endif
@@@ -83,13 -83,16 +83,13 @@@
  #define snprintf _snprintf
  #endif
  
 -#include "crypto.h"
  #include "tortls.h"
 -#include "log.h"
 -#include "compat.h"
 +#include "../common/torlog.h"
  #include "container.h"
 -#include "util.h"
  #include "torgzip.h"
  #include "address.h"
 -
 -#include <event.h>
 +#include "compat_libevent.h"
 +#include "ht.h"
  
  /* These signals are defined to help control_signal_act work.
   */
@@@ -158,7 -161,7 +158,7 @@@
  #define MAX_DNS_TTL (3*60*60)
  /** How small can a TTL be before we stop believing it?  Provides rudimentary
   * pinning. */
 -#define MIN_DNS_TTL (60)
 +#define MIN_DNS_TTL 60
  
  /** How often do we rotate onion keys? */
  #define MIN_ONION_KEY_LIFETIME (7*24*60*60)
@@@ -218,21 -221,6 +218,21 @@@ typedef enum 
  /* !!!! If _CONN_TYPE_MAX is ever over 15, we must grow the type field in
   * connection_t. */
  
 +/* Proxy client types */
 +#define PROXY_NONE 0
 +#define PROXY_CONNECT 1
 +#define PROXY_SOCKS4 2
 +#define PROXY_SOCKS5 3
 +
 +/* Proxy client handshake states */
 +#define PROXY_HTTPS_WANT_CONNECT_OK 1
 +#define PROXY_SOCKS4_WANT_CONNECT_OK 2
 +#define PROXY_SOCKS5_WANT_AUTH_METHOD_NONE 3
 +#define PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929 4
 +#define PROXY_SOCKS5_WANT_AUTH_RFC1929_OK 5
 +#define PROXY_SOCKS5_WANT_CONNECT_OK 6
 +#define PROXY_CONNECTED 7
 +
  /** True iff <b>x</b> is an edge connection. */
  #define CONN_IS_EDGE(x) \
    ((x)->type == CONN_TYPE_EXIT || (x)->type == CONN_TYPE_AP)
@@@ -253,24 -241,26 +253,24 @@@
  #define _OR_CONN_STATE_MIN 1
  /** State for a connection to an OR: waiting for connect() to finish. */
  #define OR_CONN_STATE_CONNECTING 1
 -/** State for a connection to an OR: waiting for proxy command to flush. */
 -#define OR_CONN_STATE_PROXY_FLUSHING 2
 -/** State for a connection to an OR: waiting for proxy response. */
 -#define OR_CONN_STATE_PROXY_READING 3
 +/** State for a connection to an OR: waiting for proxy handshake to complete */
 +#define OR_CONN_STATE_PROXY_HANDSHAKING 2
  /** State for a connection to an OR or client: SSL is handshaking, not done
   * yet. */
 -#define OR_CONN_STATE_TLS_HANDSHAKING 4
 +#define OR_CONN_STATE_TLS_HANDSHAKING 3
  /** State for a connection to an OR: We're doing a second SSL handshake for
   * renegotiation purposes. */
 -#define OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING 5
 +#define OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING 4
  /** State for a connection at an OR: We're waiting for the client to
   * renegotiate. */
 -#define OR_CONN_STATE_TLS_SERVER_RENEGOTIATING 6
 +#define OR_CONN_STATE_TLS_SERVER_RENEGOTIATING 5
  /** State for a connection to an OR: We're done with our SSL handshake, but we
   * haven't yet negotiated link protocol versions and sent a netinfo cell.
   */
 -#define OR_CONN_STATE_OR_HANDSHAKING 7
 +#define OR_CONN_STATE_OR_HANDSHAKING 6
  /** State for a connection to an OR: Ready to send/receive cells. */
 -#define OR_CONN_STATE_OPEN 8
 -#define _OR_CONN_STATE_MAX 8
 +#define OR_CONN_STATE_OPEN 7
 +#define _OR_CONN_STATE_MAX 7
  
  #define _EXIT_CONN_STATE_MIN 1
  /** State for an exit connection: waiting for response from DNS farm. */
@@@ -467,23 -457,23 +467,23 @@@
  #define CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED 11
  /** Client-side circuit purpose: at Alice, rendezvous established. */
  #define CIRCUIT_PURPOSE_C_REND_JOINED 12
 -
 -#define _CIRCUIT_PURPOSE_C_MAX 12
 -
 +/** This circuit is used for build time measurement only */
 +#define CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT 13
 +#define _CIRCUIT_PURPOSE_C_MAX 13
  /** Hidden-service-side circuit purpose: at Bob, waiting for introductions. */
 -#define CIRCUIT_PURPOSE_S_ESTABLISH_INTRO 13
 +#define CIRCUIT_PURPOSE_S_ESTABLISH_INTRO 14
  /** Hidden-service-side circuit purpose: at Bob, successfully established
   * intro. */
 -#define CIRCUIT_PURPOSE_S_INTRO 14
 +#define CIRCUIT_PURPOSE_S_INTRO 15
  /** Hidden-service-side circuit purpose: at Bob, connecting to rend point. */
 -#define CIRCUIT_PURPOSE_S_CONNECT_REND 15
 +#define CIRCUIT_PURPOSE_S_CONNECT_REND 16
  /** Hidden-service-side circuit purpose: at Bob, rendezvous established. */
 -#define CIRCUIT_PURPOSE_S_REND_JOINED 16
 +#define CIRCUIT_PURPOSE_S_REND_JOINED 17
  /** A testing circuit; not meant to be used for actual traffic. */
 -#define CIRCUIT_PURPOSE_TESTING 17
 +#define CIRCUIT_PURPOSE_TESTING 18
  /** A controller made this circuit and Tor should not use it. */
 -#define CIRCUIT_PURPOSE_CONTROLLER 18
 -#define _CIRCUIT_PURPOSE_MAX 18
 +#define CIRCUIT_PURPOSE_CONTROLLER 19
 +#define _CIRCUIT_PURPOSE_MAX 19
  /** A catch-all for unrecognized purposes. Currently we don't expect
   * to make or see any circuits with this purpose. */
  #define CIRCUIT_PURPOSE_UNKNOWN 255
@@@ -493,7 -483,7 +493,7 @@@
  #define CIRCUIT_PURPOSE_IS_ORIGIN(p) ((p)>_CIRCUIT_PURPOSE_OR_MAX)
  /** True iff the circuit purpose <b>p</b> is for a circuit that originated
   * here to serve as a client.  (Hidden services don't count here.) */
 -#define CIRCUIT_PURPOSE_IS_CLIENT(p) \
 +#define CIRCUIT_PURPOSE_IS_CLIENT(p)  \
    ((p)> _CIRCUIT_PURPOSE_OR_MAX &&    \
     (p)<=_CIRCUIT_PURPOSE_C_MAX)
  /** True iff the circuit_t <b>c</b> is actually an origin_circuit_t. */
@@@ -614,10 -604,6 +614,10 @@@
  
  /* Negative reasons are internal: we never send them in a DESTROY or TRUNCATE
   * call; they only go to the controller for tracking  */
 +/** Our post-timeout circuit time measurement period expired.
 + * We must give up now */
 +#define END_CIRC_REASON_MEASUREMENT_EXPIRED -3
 +
  /** We couldn't build a path for this circuit. */
  #define END_CIRC_REASON_NOPATH          -2
  /** Catch-all "other" reason for closing origin circuits. */
@@@ -655,6 -641,10 +655,6 @@@
  /** Length of a binary-encoded rendezvous service ID. */
  #define REND_SERVICE_ID_LEN 10
  
 -/** How long after we receive a hidden service descriptor do we consider
 - * it fresh? */
 -#define NUM_SECONDS_BEFORE_HS_REFETCH (60*15)
 -
  /** Time period for which a v2 descriptor will be valid. */
  #define REND_TIME_PERIOD_V2_DESC_VALIDITY (24*60*60)
  
@@@ -749,6 -739,12 +749,6 @@@ typedef struct rend_data_t 
  
    /** Rendezvous cookie used by both, client and service. */
    char rend_cookie[REND_COOKIE_LEN];
 -
 -  /** Rendezvous descriptor version that is used by a service. Used to
 -   * distinguish introduction and rendezvous points belonging to the same
 -   * rendezvous service ID, but different descriptor versions.
 -   */
 -  uint8_t rend_desc_version;
  } rend_data_t;
  
  /** Time interval for tracking possible replays of INTRODUCE2 cells.
@@@ -855,28 -851,12 +855,28 @@@ typedef struct packed_cell_t 
    char body[CELL_NETWORK_SIZE]; /**< Cell as packed for network. */
  } packed_cell_t;
  
 +/** Number of cells added to a circuit queue including their insertion
 + * time on 10 millisecond detail; used for buffer statistics. */
 +typedef struct insertion_time_elem_t {
 +  struct insertion_time_elem_t *next; /**< Next element in queue. */
 +  uint32_t insertion_time; /**< When were cells inserted (in 10 ms steps
 +                             * starting at 0:00 of the current day)? */
 +  unsigned counter; /**< How many cells were inserted? */
 +} insertion_time_elem_t;
 +
 +/** Queue of insertion times. */
 +typedef struct insertion_time_queue_t {
 +  struct insertion_time_elem_t *first; /**< First element in queue. */
 +  struct insertion_time_elem_t *last; /**< Last element in queue. */
 +} insertion_time_queue_t;
 +
  /** A queue of cells on a circuit, waiting to be added to the
   * or_connection_t's outbuf. */
  typedef struct cell_queue_t {
    packed_cell_t *head; /**< The first cell, or NULL if the queue is empty. */
    packed_cell_t *tail; /**< The last cell, or NULL if the queue is empty. */
    int n; /**< The number of cells in the queue. */
 +  insertion_time_queue_t *insertion_times; /**< Insertion times of cells. */
  } cell_queue_t;
  
  /** Beginning of a RELAY cell payload. */
@@@ -932,7 -912,7 +932,7 @@@ typedef struct connection_t 
                              * again once the bandwidth throttler allows it? */
    unsigned int write_blocked_on_bw:1; /**< Boolean: should we start writing
                               * again once the bandwidth throttler allows
 -                             * reads? */
 +                             * writes? */
    unsigned int hold_open_until_flushed:1; /**< Despite this connection's being
                                        * marked for close, do we flush it
                                        * before closing it? */
@@@ -957,11 -937,8 +957,11 @@@
     * connection. */
    unsigned int linked_conn_is_closed:1;
  
 -  int s; /**< Our socket; -1 if this connection is closed, or has no
 -          * socket. */
 +  /** CONNECT/SOCKS proxy client handshake state (for outgoing connections). */
 +  unsigned int proxy_state:4;
 +
 +  /** Our socket; -1 if this connection is closed, or has no socket. */
 +  evutil_socket_t s;
    int conn_array_index; /**< Index into the global connection array. */
    struct event *read_event; /**< Libevent event structure. */
    struct event *write_event; /**< Libevent event structure. */
@@@ -1003,8 -980,6 +1003,8 @@@
     * to the evdns_server_port is uses to listen to and answer connections. */
    struct evdns_server_port *dns_server_port;
  
 +  /** Unique ID for measuring tunneled network status requests. */
 +  uint64_t dirreq_id;
  } connection_t;
  
  /** Stores flags and information related to the portion of a v2 Tor OR
@@@ -1051,10 -1026,7 +1051,10 @@@ typedef struct or_connection_t 
     * NETINFO cell listed the address we're connected to as recognized. */
    unsigned int is_canonical:1;
    /** True iff this connection shouldn't get any new circs attached to it,
 -   * because the connection is too old, or because there's a better one, etc.
 +   * because the connection is too old, or because there's a better one.
 +   * More generally, this flag is used to note an unhealthy connection;
 +   * for example, if a bad connection fails we shouldn't assume that the
 +   * router itself has a problem.
     */
    unsigned int is_bad_for_new_circs:1;
    uint8_t link_proto; /**< What protocol version are we using? 0 for
@@@ -1069,13 -1041,12 +1069,13 @@@
    time_t timestamp_last_added_nonpadding; /** When did we last add a
                                             * non-padding cell to the outbuf? */
  
 -  /* bandwidth* and read_bucket only used by ORs in OPEN state: */
 +  /* bandwidth* and *_bucket only used by ORs in OPEN state: */
    int bandwidthrate; /**< Bytes/s added to the bucket. (OPEN ORs only.) */
    int bandwidthburst; /**< Max bucket size for this conn. (OPEN ORs only.) */
    int read_bucket; /**< When this hits 0, stop receiving. Every second we
                      * add 'bandwidthrate' to this, capping it at
                      * bandwidthburst. (OPEN ORs only) */
 +  int write_bucket; /**< When this hits 0, stop writing. Like read_bucket. */
    int n_circuits; /**< How many circuits use this connection as p_conn or
                     * n_conn ? */
  
@@@ -1083,17 -1054,6 +1083,17 @@@
     * free up on this connection's outbuf.  Every time we pull cells from a
     * circuit, we advance this pointer to the next circuit in the ring. */
    struct circuit_t *active_circuits;
 +  /** Priority queue of cell_ewma_t for circuits with queued cells waiting for
 +   * room to free up on this connection's outbuf.  Kept in heap order
 +   * according to EWMA.
 +   *
 +   * This is redundant with active_circuits; if we ever decide only to use the
 +   * cell_ewma algorithm for choosing circuits, we can remove active_circuits.
 +   */
 +  smartlist_t *active_circuit_pqueue;
 +  /** The tick on which the cell_ewma_ts in active_circuit_pqueue last had
 +   * their ewma values rescaled. */
 +  unsigned active_circuit_pqueue_last_recalibrated;
    struct or_connection_t *next_with_same_id; /**< Next connection with same
                                                * identity digest as this one. */
  } or_connection_t;
@@@ -1190,8 -1150,7 +1190,8 @@@ typedef struct dir_connection_t 
    enum {
      DIR_SPOOL_NONE=0, DIR_SPOOL_SERVER_BY_DIGEST, DIR_SPOOL_SERVER_BY_FP,
      DIR_SPOOL_EXTRA_BY_DIGEST, DIR_SPOOL_EXTRA_BY_FP,
 -    DIR_SPOOL_CACHED_DIR, DIR_SPOOL_NETWORKSTATUS
 +    DIR_SPOOL_CACHED_DIR, DIR_SPOOL_NETWORKSTATUS,
 +    DIR_SPOOL_MICRODESC, /* NOTE: if we add another entry, add another bit. */
    } dir_spool_src : 3;
    /** If we're fetching descriptors, what router purpose shall we assign
     * to them? */
@@@ -1219,6 -1178,12 +1219,6 @@@ typedef struct control_connection_t 
  
    uint32_t event_mask; /**< Bitfield: which events does this controller
                          * care about? */
 -  unsigned int use_long_names:1; /**< True if we should use long nicknames
 -                                  * on this (v1) connection. Only settable
 -                                  * via v1 controllers. */
 -  /** For control connections only. If set, we send extended info with control
 -   * events as appropriate. */
 -  unsigned int use_extended_events:1;
  
    /** True if we have sent a protocolinfo reply on this connection. */
    unsigned int have_sent_protocolinfo:1;
@@@ -1302,7 -1267,6 +1302,7 @@@ typedef struct cached_dir_t 
    size_t dir_len; /**< Length of <b>dir</b> (not counting its NUL). */
    size_t dir_z_len; /**< Length of <b>dir_z</b>. */
    time_t published; /**< When was this object published. */
 +  digests_t digests; /**< Digests of this object (networkstatus only) */
    int refcnt; /**< Reference count for this cached_dir_t. */
  } cached_dir_t;
  
@@@ -1463,9 -1427,6 +1463,9 @@@ typedef struct 
                               * directory according to the authorities. */
    unsigned int policy_is_reject_star:1; /**< True iff the exit policy for this
                                           * router rejects everything. */
 +  /** True if, after we have added this router, we should re-launch
 +   * tests for it. */
 +  unsigned int needs_retest_if_added:1;
  
  /** Tor can use this router for general positions in circuits. */
  #define ROUTER_PURPOSE_GENERAL 0
@@@ -1555,9 -1516,6 +1555,9 @@@ typedef struct routerstatus_t 
  
    unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */
    unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */
 +  unsigned int has_measured_bw:1; /**< The vote/consensus had a measured bw */
 +
 +  uint32_t measured_bw; /**< Measured bandwidth (capacity) of the router */
  
    uint32_t bandwidth; /**< Bandwidth (capacity) of the router as reported in
                         * the vote/consensus, in kilobytes/sec. */
@@@ -1582,52 -1540,6 +1582,52 @@@
  
  } routerstatus_t;
  
 +/** A microdescriptor is the smallest amount of information needed to build a
 + * circuit through a router.  They are generated by the directory authorities,
 + * using information from the uploaded routerinfo documents.  They are not
 + * self-signed, but are rather authenticated by having their hash in a signed
 + * networkstatus document. */
 +typedef struct microdesc_t {
 +  /** Hashtable node, used to look up the microdesc by its digest. */
 +  HT_ENTRY(microdesc_t) node;
 +
 +  /* Cache information */
 +
 +  /**  When was this microdescriptor last listed in a consensus document?
 +   * Once a microdesc has been unlisted long enough, we can drop it.
 +   */
 +  time_t last_listed;
 +  /** Where is this microdescriptor currently stored? */
 +  saved_location_t saved_location : 3;
 +  /** If true, do not attempt to cache this microdescriptor on disk. */
 +  unsigned int no_save : 1;
 +  /** If saved_location == SAVED_IN_CACHE, this field holds the offset of the
 +   * microdescriptor in the cache. */
 +  off_t off;
 +
 +  /* The string containing the microdesc. */
 +
 +  /** A pointer to the encoded body of the microdescriptor.  If the
 +   * saved_location is SAVED_IN_CACHE, then the body is a pointer into an
 +   * mmap'd region.  Otherwise, it is a malloc'd string.  The string might not
 +   * be NUL-terminated; take the length from <b>bodylen</b>. */
 +  char *body;
 +  /** The length of the microdescriptor in <b>body</b>. */
 +  size_t bodylen;
 +  /** A SHA256-digest of the microdescriptor. */
 +  char digest[DIGEST256_LEN];
 +
 +  /* Fields in the microdescriptor. */
 +
 +  /** As routerinfo_t.onion_pkey */
 +  crypto_pk_env_t *onion_pkey;
 +  /** As routerinfo_t.family */
 +  smartlist_t *family;
 +  /** Encoded exit policy summary */
 +  char *exitsummary; /**< exit policy summary -
 +                      * XXX this probably should not stay a string. */
 +} microdesc_t;
 +
  /** How many times will we try to download a router's descriptor before giving
   * up? */
  #define MAX_ROUTERDESC_DOWNLOAD_FAILURES 8
@@@ -1670,11 -1582,6 +1670,11 @@@ typedef struct networkstatus_v2_t 
                           * sorted by identity_digest. */
  } networkstatus_v2_t;
  
 +typedef struct vote_microdesc_hash_t {
 +  struct vote_microdesc_hash_t *next;
 +  char *microdesc_hash_line;
 +} vote_microdesc_hash_t;
 +
  /** The claim about a single router, made in a vote. */
  typedef struct vote_routerstatus_t {
    routerstatus_t status; /**< Underlying 'status' object for this router.
@@@ -1683,45 -1590,31 +1683,45 @@@
                     * networkstatus_t.known_flags. */
    char *version; /**< The version that the authority says this router is
                    * running. */
 +  vote_microdesc_hash_t *microdesc;
  } vote_routerstatus_t;
  
 +/** A signature of some document by an authority. */
 +typedef struct document_signature_t {
 +  /** Declared SHA-1 digest of this voter's identity key */
 +  char identity_digest[DIGEST_LEN];
 +  /** Declared SHA-1 digest of signing key used by this voter. */
 +  char signing_key_digest[DIGEST_LEN];
 +  /** Algorithm used to compute the digest of the document. */
 +  digest_algorithm_t alg;
 +  /** Signature of the signed thing. */
 +  char *signature;
 +  /** Length of <b>signature</b> */
 +  int signature_len;
 +  unsigned int bad_signature : 1; /**< Set to true if we've tried to verify
 +                                   * the sig, and we know it's bad. */
 +  unsigned int good_signature : 1; /**< Set to true if we've verified the sig
 +                                     * as good. */
 +} document_signature_t;
 +
  /** Information about a single voter in a vote or a consensus. */
  typedef struct networkstatus_voter_info_t {
 +  /** Declared SHA-1 digest of this voter's identity key */
 +  char identity_digest[DIGEST_LEN];
    char *nickname; /**< Nickname of this voter */
 -  char identity_digest[DIGEST_LEN]; /**< Digest of this voter's identity key */
 +  /** Digest of this voter's "legacy" identity key, if any.  In vote only; for
 +   * consensuses, we treat legacy keys as additional signers. */
 +  char legacy_id_digest[DIGEST_LEN];
    char *address; /**< Address of this voter, in string format. */
    uint32_t addr; /**< Address of this voter, in IPv4, in host order. */
    uint16_t dir_port; /**< Directory port of this voter */
    uint16_t or_port; /**< OR port of this voter */
    char *contact; /**< Contact information for this voter. */
    char vote_digest[DIGEST_LEN]; /**< Digest of this voter's vote, as signed. */
 -  /** Digest of this voter's "legacy" identity key, if any.  In vote only; for
 -   * consensuses, we treat legacy keys as additional signers. */
 -  char legacy_id_digest[DIGEST_LEN];
  
    /* Nothing from here on is signed. */
 -  char signing_key_digest[DIGEST_LEN]; /**< Declared digest of signing key
 -                                        * used by this voter. */
 -  char *signature; /**< Signature from this voter. */
 -  int signature_len; /**< Length of <b>signature</b> */
 -  unsigned int bad_signature : 1; /**< Set to true if we've tried to verify
 -                                   * the sig, and we know it's bad. */
 -  unsigned int good_signature : 1; /**< Set to true if we've verified the sig
 -                                     * as good. */
 +  /** The signature of the document and the signature's status. */
 +  smartlist_t *sigs;
  } networkstatus_voter_info_t;
  
  /** Enumerates the possible seriousness values of a networkstatus document. */
@@@ -1731,25 -1624,10 +1731,25 @@@ typedef enum 
    NS_TYPE_OPINION,
  } networkstatus_type_t;
  
 +/** Enumerates recognized flavors of a consensus networkstatus document.  All
 + * flavors of a consensus are generated from the same set of votes, but they
 + * present different types information to different versions of Tor. */
 +typedef enum {
 +  FLAV_NS = 0,
 +  FLAV_MICRODESC = 1,
 +} consensus_flavor_t;
 +
 +/** Which consensus flavor do we actually want to use to build circuits? */
 +#define USABLE_CONSENSUS_FLAVOR FLAV_NS
 +
 +/** How many different consensus flavors are there? */
 +#define N_CONSENSUS_FLAVORS ((int)(FLAV_MICRODESC)+1)
 +
  /** A common structure to hold a v3 network status vote, or a v3 network
   * status consensus. */
  typedef struct networkstatus_t {
 -  networkstatus_type_t type; /**< Vote, consensus, or opinion? */
 +  networkstatus_type_t type : 8; /**< Vote, consensus, or opinion? */
 +  consensus_flavor_t flavor : 8; /**< If a consensus, what kind? */
    time_t published; /**< Vote only: Time when vote was written. */
    time_t valid_after; /**< Time after which this vote or consensus applies. */
    time_t fresh_until; /**< Time before which this is the most recent vote or
@@@ -1781,10 -1659,6 +1781,10 @@@
     * consensus, sorted by key. */
    smartlist_t *net_params;
  
 +  /** List of key=value strings for the bw weight parameters in the
 +   * consensus. */
 +  smartlist_t *weight_params;
 +
    /** List of networkstatus_voter_info_t.  For a vote, only one element
     * is included.  For a consensus, one element is included for every voter
     * whose vote contributed to the consensus. */
@@@ -1792,8 -1666,8 +1792,8 @@@
  
    struct authority_cert_t *cert; /**< Vote only: the voter's certificate. */
  
 -  /** Digest of this document, as signed. */
 -  char networkstatus_digest[DIGEST_LEN];
 +  /** Digests of this document, as signed. */
 +  digests_t digests;
  
    /** List of router statuses, sorted by identity digest.  For a vote,
     * the elements are vote_routerstatus_t; for a consensus, the elements
@@@ -1805,15 -1679,14 +1805,15 @@@
    digestmap_t *desc_digest_map;
  } networkstatus_t;
  
 -/** A set of signatures for a networkstatus consensus.  All fields are as for
 - * networkstatus_t. */
 +/** A set of signatures for a networkstatus consensus.  Unless otherwise
 + * noted, all fields are as for networkstatus_t. */
  typedef struct ns_detached_signatures_t {
    time_t valid_after;
    time_t fresh_until;
    time_t valid_until;
 -  char networkstatus_digest[DIGEST_LEN];
 -  smartlist_t *signatures; /* list of networkstatus_voter_info_t */
 +  strmap_t *digests; /**< Map from flavor name to digestset_t */
 +  strmap_t *signatures; /**< Map from flavor name to list of
 +                         * document_signature_t */
  } ns_detached_signatures_t;
  
  /** Allowable types of desc_store_t. */
@@@ -2018,29 -1891,6 +2018,29 @@@ typedef struct 
    time_t expiry_time;
  } cpath_build_state_t;
  
 +/**
 + * The cell_ewma_t structure keeps track of how many cells a circuit has
 + * transferred recently.  It keeps an EWMA (exponentially weighted moving
 + * average) of the number of cells flushed from the circuit queue onto a
 + * connection in connection_or_flush_from_first_active_circuit().
 + */
 +typedef struct {
 +  /** The last 'tick' at which we recalibrated cell_count.
 +   *
 +   * A cell sent at exactly the start of this tick has weight 1.0. Cells sent
 +   * since the start of this tick have weight greater than 1.0; ones sent
 +   * earlier have less weight. */
 +  unsigned last_adjusted_tick;
 +  /** The EWMA of the cell count. */
 +  double cell_count;
 +  /** True iff this is the cell count for a circuit's previous
 +   * connection. */
 +  unsigned int is_for_p_conn : 1;
 +  /** The position of the circuit within the OR connection's priority
 +   * queue. */
 +  int heap_index;
 +} cell_ewma_t;
 +
  #define ORIGIN_CIRCUIT_MAGIC 0x35315243u
  #define OR_CIRCUIT_MAGIC 0x98ABC04Fu
  
@@@ -2110,7 -1960,6 +2110,7 @@@ typedef struct circuit_t 
    time_t timestamp_created; /**< When was this circuit created? */
    time_t timestamp_dirty; /**< When the circuit was first used, or 0 if the
                             * circuit is clean. */
 +  struct timeval highres_created; /**< When exactly was the circuit created? */
  
    uint16_t marked_for_close; /**< Should we close this circuit at the end of
                                * the main loop? (If true, holds the line number
@@@ -2127,14 -1976,6 +2127,14 @@@
     * linked to an OR connection. */
    struct circuit_t *prev_active_on_n_conn;
    struct circuit_t *next; /**< Next circuit in linked list of all circuits. */
 +
 +  /** Unique ID for measuring tunneled network status requests. */
 +  uint64_t dirreq_id;
 +
 +  /** The EWMA count for the number of cells flushed from the
 +   * n_conn_cells queue.  Used to determine which circuit to flush from next.
 +   */
 +  cell_ewma_t n_cell_ewma;
  } circuit_t;
  
  /** Largest number of relay_early cells that we can send on a given
@@@ -2167,13 -2008,6 +2167,13 @@@ typedef struct origin_circuit_t 
     * to the specification? */
    unsigned int remaining_relay_early_cells : 4;
  
 +  /** Set if this circuit is insanely old and we already informed the user */
 +  unsigned int is_ancient : 1;
 +
 +  /** Set if this circuit has already been opened. Used to detect
 +   * cannibalized circuits. */
 +  unsigned int has_opened : 1;
 +
    /** What commands were sent over this circuit that decremented the
     * RELAY_EARLY counter? This is for debugging task 878. */
    uint8_t relay_early_commands[MAX_RELAY_EARLY_CELLS_PER_CIRCUIT];
@@@ -2264,19 -2098,6 +2264,19 @@@ typedef struct or_circuit_t 
  
    /** True iff this circuit was made with a CREATE_FAST cell. */
    unsigned int is_first_hop : 1;
 +
 +  /** Number of cells that were removed from circuit queue; reset every
 +   * time when writing buffer stats to disk. */
 +  uint32_t processed_cells;
 +
 +  /** Total time in milliseconds that cells spent in both app-ward and
 +   * exit-ward queues of this circuit; reset every time when writing
 +   * buffer stats to disk. */
 +  uint64_t total_cell_waiting_time;
 +
 +  /** The EWMA count for the number of cells flushed from the
 +   * p_conn_cells queue. */
 +  cell_ewma_t p_cell_ewma;
  } or_circuit_t;
  
  /** Convert a circuit subtype to a circuit_t.*/
@@@ -2348,13 -2169,13 +2348,13 @@@ typedef struct 
    routerset_t *EntryNodes;/**< Structure containing nicknames, digests,
                             * country codes and IP address patterns of ORs to
                             * consider as entry points. */
 -  int StrictExitNodes; /**< Boolean: When none of our ExitNodes are up, do we
 -                        * stop building circuits? */
 -  int StrictEntryNodes; /**< Boolean: When none of our EntryNodes are up, do we
 -                         * stop building circuits? */
 +  int StrictNodes; /**< Boolean: When none of our EntryNodes or ExitNodes
 +                    * are up, or we need to access a node in ExcludeNodes,
 +                    * do we just fail instead? */
    routerset_t *ExcludeNodes;/**< Structure containing nicknames, digests,
                               * country codes and IP address patterns of ORs
 -                             * not to use in circuits. */
 +                             * not to use in circuits. But see StrictNodes
 +                             * above. */
    routerset_t *ExcludeExitNodes;/**< Structure containing nicknames, digests,
                                   * country codes and IP address patterns of
                                   * ORs not to consider as exits. */
@@@ -2362,9 -2183,6 +2362,9 @@@
    /** Union of ExcludeNodes and ExcludeExitNodes */
    struct routerset_t *_ExcludeExitNodesUnion;
  
 +  int DisableAllSwap; /**< Boolean: Attempt to call mlockall() on our
 +                       * process for all current and future memory. */
 +
    /** List of "entry", "middle", "exit", "introduction", "rendezvous". */
    smartlist_t *AllowInvalidNodes;
    /** Bitmask; derived from AllowInvalidNodes. */
@@@ -2379,7 -2197,7 +2379,7 @@@
     * connections. */
    config_line_t *TransListenAddress;
    /** Addresses to bind for listening for transparent natd connections */
 -  config_line_t *NatdListenAddress;
 +  config_line_t *NATDListenAddress;
    /** Addresses to bind for listening for SOCKS connections. */
    config_line_t *DNSListenAddress;
    /** Addresses to bind for listening for OR connections. */
@@@ -2403,7 -2221,7 +2403,7 @@@
    int SocksPort; /**< Port to listen on for SOCKS connections. */
    /** Port to listen on for transparent pf/netfilter connections. */
    int TransPort;
 -  int NatdPort; /**< Port to listen on for transparent natd connections. */
 +  int NATDPort; /**< Port to listen on for transparent natd connections. */
    int ControlPort; /**< Port to listen on for control connections. */
    config_line_t *ControlSocket; /**< List of Unix Domain Sockets to listen on
                                   * for control connections. */
@@@ -2419,6 -2237,8 +2419,6 @@@
                             * for version 3 directories? */
    int HSAuthoritativeDir; /**< Boolean: does this an authoritative directory
                             * handle hidden service requests? */
 -  int HSAuthorityRecordStats; /**< Boolean: does this HS authoritative
 -                               * directory record statistics? */
    int NamingAuthoritativeDir; /**< Boolean: is this an authoritative directory
                                 * that's willing to bind names? */
    int VersioningAuthoritativeDir; /**< Boolean: is this an authoritative
@@@ -2447,6 -2267,8 +2447,6 @@@
    int AvoidDiskWrites; /**< Boolean: should we never cache things to disk?
                          * Not used yet. */
    int ClientOnly; /**< Boolean: should we never evolve into a server role? */
 -  /** Boolean: should we never publish a descriptor? Deprecated. */
 -  int NoPublish;
    /** To what authority types do we publish our descriptor? Choices are
     * "v1", "v2", "v3", "bridge", or "". */
    smartlist_t *PublishServerDescriptor;
@@@ -2477,14 -2299,6 +2477,14 @@@
    int ConstrainedSockets; /**< Shrink xmit and recv socket buffers. */
    uint64_t ConstrainedSockSize; /**< Size of constrained buffers. */
  
 +  /** Whether we should drop exit streams from Tors that we don't know are
 +   * relays.  One of "0" (never refuse), "1" (always refuse), or "auto" (do
 +   * what the consensus says, defaulting to 'refuse' if the consensus says
 +   * nothing). */
 +  char *RefuseUnknownExits;
 +  /** Parsed version of RefuseUnknownExits. -1 for auto. */
 +  int RefuseUnknownExits_;
 +
    /** Application ports that require all nodes in circ to have sufficient
     * uptime. */
    smartlist_t *LongLivedPorts;
@@@ -2514,18 -2328,10 +2514,18 @@@
                          * connections alive? */
    int SocksTimeout; /**< How long do we let a socks connection wait
                       * unattached before we fail it? */
 -  int CircuitBuildTimeout; /**< Cull non-open circuits that were born
 -                            * at least this many seconds ago. */
 +  int LearnCircuitBuildTimeout; /**< If non-zero, we attempt to learn a value
 +                                 * for CircuitBuildTimeout based on timeout
 +                                 * history */
 +  int CircuitBuildTimeout; /**< Cull non-open circuits that were born at
 +                            * least this many seconds ago. Used until
 +                            * adaptive algorithm learns a new value. */
    int CircuitIdleTimeout; /**< Cull open clean circuits that were born
                             * at least this many seconds ago. */
 +  int CircuitStreamTimeout; /**< If non-zero, detach streams from circuits
 +                             * and try a new circuit if the stream has been
 +                             * waiting for this many seconds. If zero, use
 +                             * our default internal timeout schedule. */
    int MaxOnionsPending; /**< How many circuit CREATE requests do we allow
                           * to wait simultaneously before we start dropping
                           * them? */
@@@ -2543,36 -2349,24 +2543,36 @@@
                                   * willing to use for all relayed conns? */
    uint64_t RelayBandwidthBurst; /**< How much bandwidth, at maximum, will we
                                   * use in a second for all relayed conns? */
 -  int NumCpus; /**< How many CPUs should we try to use? */
 -  int RunTesting; /**< If true, create testing circuits to measure how well the
 -                   * other ORs are running. */
 +  uint64_t PerConnBWRate; /**< Long-term bw on a single TLS conn, if set. */
 +  uint64_t PerConnBWBurst; /**< Allowed burst on a single TLS conn, if set. */
 +  int NumCPUs; /**< How many CPUs should we try to use? */
 +//int RunTesting; /**< If true, create testing circuits to measure how well the
 +//                 * other ORs are running. */
    config_line_t *RendConfigLines; /**< List of configuration lines
                                            * for rendezvous services. */
    config_line_t *HidServAuth; /**< List of configuration lines for client-side
                                 * authorizations for hidden services */
    char *ContactInfo; /**< Contact info to be published in the directory. */
  
 -  char *HttpProxy; /**< hostname[:port] to use as http proxy, if any. */
 -  uint32_t HttpProxyAddr; /**< Parsed IPv4 addr for http proxy, if any. */
 -  uint16_t HttpProxyPort; /**< Parsed port for http proxy, if any. */
 -  char *HttpProxyAuthenticator; /**< username:password string, if any. */
 +  char *HTTPProxy; /**< hostname[:port] to use as http proxy, if any. */
 +  tor_addr_t HTTPProxyAddr; /**< Parsed IPv4 addr for http proxy, if any. */
 +  uint16_t HTTPProxyPort; /**< Parsed port for http proxy, if any. */
 +  char *HTTPProxyAuthenticator; /**< username:password string, if any. */
 +
 +  char *HTTPSProxy; /**< hostname[:port] to use as https proxy, if any. */
 +  tor_addr_t HTTPSProxyAddr; /**< Parsed addr for https proxy, if any. */
 +  uint16_t HTTPSProxyPort; /**< Parsed port for https proxy, if any. */
 +  char *HTTPSProxyAuthenticator; /**< username:password string, if any. */
  
 -  char *HttpsProxy; /**< hostname[:port] to use as https proxy, if any. */
 -  uint32_t HttpsProxyAddr; /**< Parsed IPv4 addr for https proxy, if any. */
 -  uint16_t HttpsProxyPort; /**< Parsed port for https proxy, if any. */
 -  char *HttpsProxyAuthenticator; /**< username:password string, if any. */
 +  char *Socks4Proxy; /**< hostname:port to use as a SOCKS4 proxy, if any. */
 +  tor_addr_t Socks4ProxyAddr; /**< Derived from Socks4Proxy. */
 +  uint16_t Socks4ProxyPort; /**< Derived from Socks4Proxy. */
 +
 +  char *Socks5Proxy; /**< hostname:port to use as a SOCKS5 proxy, if any. */
 +  tor_addr_t Socks5ProxyAddr; /**< Derived from Sock5Proxy. */
 +  uint16_t Socks5ProxyPort; /**< Derived from Socks5Proxy. */
 +  char *Socks5ProxyUsername; /**< Username for SOCKS5 authentication, if any */
 +  char *Socks5ProxyPassword; /**< Password for SOCKS5 authentication, if any */
  
    /** List of configuration lines for replacement directory authorities.
     * If you just want to replace one class of authority at a time,
@@@ -2635,13 -2429,8 +2635,13 @@@
                                   * or not (1)? */
    int ShutdownWaitLength; /**< When we get a SIGINT and we're a server, how
                             * long do we wait before exiting? */
 -  int SafeLogging; /**< Boolean: are we allowed to log sensitive strings
 -                    * such as addresses (0), or do we scrub them first (1)? */
 +  char *SafeLogging; /**< Contains "relay", "1", "0" (meaning no scrubbing). */
 +
 +  /* Derived from SafeLogging */
 +  enum {
 +    SAFELOG_SCRUB_ALL, SAFELOG_SCRUB_RELAY, SAFELOG_SCRUB_NONE
 +  } _SafeLogging;
 +
    int SafeSocks; /**< Boolean: should we outright refuse application
                    * connections that use socks4 or socks5-with-local-dns? */
  #define LOG_PROTOCOL_WARN (get_options()->ProtocolWarnings ? \
@@@ -2652,8 -2441,6 +2652,8 @@@
                    * log whether it was DNS-leaking or not? */
    int HardwareAccel; /**< Boolean: Should we enable OpenSSL hardware
                        * acceleration where available? */
 +  char *AccelName; /**< Optional hardware acceleration engine name. */
 +  char *AccelDir; /**< Optional hardware acceleration engine search dir. */
    int UseEntryGuards; /**< Boolean: Do we try to enter from a smallish number
                         * of fixed nodes? */
    int NumEntryGuards; /**< How many entry guards do we try to establish? */
@@@ -2664,9 -2451,6 +2664,9 @@@
     * means directly from the authorities) no matter our other config? */
    int FetchDirInfoEarly;
  
 +  /** Should we fetch our dir info at the start of the consensus period? */
 +  int FetchDirInfoExtraEarly;
 +
    char *VirtualAddrNetwork; /**< Address and mask to hand out for virtual
                               * MAPADDRESS requests. */
    int ServerDNSSearchDomains; /**< Boolean: If set, we don't force exit
@@@ -2715,33 -2499,6 +2715,33 @@@
     * exit allows it, we use it. */
    int AllowSingleHopCircuits;
  
 +  /** If true, we convert "www.google.com.foo.exit" addresses on the
 +   * socks/trans/natd ports into "www.google.com" addresses that
 +   * exit from the node "foo". Disabled by default since attacking
 +   * websites and exit relays can use it to manipulate your path
 +   * selection. */
 +  int AllowDotExit;
 +
 +  /** If true, we will warn if a user gives us only an IP address
 +   * instead of a hostname. */
 +  int WarnUnsafeSocks;
 +
 +  /** If true, the user wants us to collect statistics on clients
 +   * requesting network statuses from us as directory. */
 +  int DirReqStatistics;
 +
 +  /** If true, the user wants us to collect statistics on port usage. */
 +  int ExitPortStatistics;
 +
 +  /** If true, the user wants us to collect cell statistics. */
 +  int CellStatistics;
 +
 +  /** If true, the user wants us to collect statistics as entry node. */
 +  int EntryStatistics;
 +
 +  /** If true, include statistics file contents in extra-info documents. */
 +  int ExtraInfoStatistics;
 +
    /** If true, do not believe anybody who tells us that a domain resolves
     * to an internal address, or that an internal address has a PTR mapping.
     * Helps avoid some cross-site attacks. */
@@@ -2760,13 -2517,6 +2760,13 @@@
     * migration purposes? */
    int V3AuthUseLegacyKey;
  
 +  /** Location of bandwidth measurement file */
 +  char *V3BandwidthsFile;
 +
 +  /** Authority only: key=value pairs that we add to our networkstatus
 +   * consensus vote on the 'params' line. */
 +  char *ConsensusParams;
 +
    /** The length of time that we think an initial consensus should be fresh.
     * Only altered on testing networks. */
    int TestingV3AuthInitialVotingInterval;
@@@ -2803,6 -2553,19 +2803,6 @@@
     * the bridge authority guess which countries have blocked access to us. */
    int BridgeRecordUsageByCountry;
  
 -#ifdef ENABLE_GEOIP_STATS
 -  /** If true, and Tor is built with GEOIP_STATS support, and we're a
 -   * directory, record how many directory requests we get from each country. */
 -  int DirRecordUsageByCountry;
 -  /** Round all GeoIP results to the next multiple of this value, to avoid
 -   * leaking information. */
 -  int DirRecordUsageGranularity;
 -  /** Time interval: purge geoip stats after this long. */
 -  int DirRecordUsageRetainIPs;
 -  /** Time interval: Flush geoip data to disk this often. */
 -  int DirRecordUsageSaveInterval;
 -#endif
 -
    /** Optionally, a file with GeoIP data. */
    char *GeoIPFile;
  
@@@ -2810,21 -2573,6 +2810,21 @@@
     * to make this false. */
    int ReloadTorrcOnSIGHUP;
  
 +  /* The main parameter for picking circuits within a connection.
 +   *
 +   * If this value is positive, when picking a cell to relay on a connection,
 +   * we always relay from the circuit whose weighted cell count is lowest.
 +   * Cells are weighted exponentially such that if one cell is sent
 +   * 'CircuitPriorityHalflife' seconds before another, it counts for half as
 +   * much.
 +   *
 +   * If this value is zero, we're disabling the cell-EWMA algorithm.
 +   *
 +   * If this value is negative, we're using the default approach
 +   * according to either Tor or a parameter set in the consensus.
 +   */
 +  double CircuitPriorityHalflife;
 +
  } or_options_t;
  
  /** Persistent state for an onion router, as saved to disk. */
@@@ -2843,9 -2591,6 +2843,9 @@@ typedef struct 
    uint64_t AccountingBytesReadInInterval;
    uint64_t AccountingBytesWrittenInInterval;
    int AccountingSecondsActive;
 +  int AccountingSecondsToReachSoftLimit;
 +  time_t AccountingSoftLimitHitAt;
 +  uint64_t AccountingBytesAtSoftLimit;
    uint64_t AccountingExpectedUsage;
  
    /** A list of Entry Guard-related configuration lines. */
@@@ -2863,17 -2608,6 +2863,17 @@@
    time_t      BWHistoryWriteEnds;
    int         BWHistoryWriteInterval;
    smartlist_t *BWHistoryWriteValues;
 +  time_t      BWHistoryDirReadEnds;
 +  int         BWHistoryDirReadInterval;
 +  smartlist_t *BWHistoryDirReadValues;
 +  time_t      BWHistoryDirWriteEnds;
 +  int         BWHistoryDirWriteInterval;
 +  smartlist_t *BWHistoryDirWriteValues;
 +
 +  /** Build time histogram */
 +  config_line_t * BuildtimeHistogram;
 +  unsigned int TotalBuildTimes;
 +  unsigned int CircuitBuildAbandonedCount;
  
    /** What version of Tor wrote this state file? */
    char *TorVersion;
@@@ -2935,120 -2669,209 +2935,120 @@@ struct socks_request_t 
  
  /* all the function prototypes go here */
  
 -/********************************* buffers.c ***************************/
 -
 -buf_t *buf_new(void);
 -buf_t *buf_new_with_capacity(size_t size);
 -void buf_free(buf_t *buf);
 -void buf_clear(buf_t *buf);
 -void buf_shrink(buf_t *buf);
 -void buf_shrink_freelists(int free_all);
 -void buf_dump_freelist_sizes(int severity);
 -
 -size_t buf_datalen(const buf_t *buf);
 -size_t buf_allocation(const buf_t *buf);
 -size_t buf_slack(const buf_t *buf);
 -const char *_buf_peek_raw_buffer(const buf_t *buf);
 -
 -int read_to_buf(int s, size_t at_most, buf_t *buf, int *reached_eof,
 -                int *socket_error);
 -int read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf);
 -
 -int flush_buf(int s, buf_t *buf, size_t sz, size_t *buf_flushlen);
 -int flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t sz, size_t *buf_flushlen);
 -
 -int write_to_buf(const char *string, size_t string_len, buf_t *buf);
 -int write_to_buf_zlib(buf_t *buf, tor_zlib_state_t *state,
 -                      const char *data, size_t data_len, int done);
 -int move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen);
 -int fetch_from_buf(char *string, size_t string_len, buf_t *buf);
 -int fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto);
 -int fetch_from_buf_http(buf_t *buf,
 -                        char **headers_out, size_t max_headerlen,
 -                        char **body_out, size_t *body_used, size_t max_bodylen,
 -                        int force_complete);
 -int fetch_from_buf_socks(buf_t *buf, socks_request_t *req,
 -                         int log_sockstype, int safe_socks);
 -int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len);
 -
 -int peek_buf_has_control0_command(buf_t *buf);
 -
 -void assert_buf_ok(buf_t *buf);
 -
 -#ifdef BUFFERS_PRIVATE
 -int buf_find_string_offset(const buf_t *buf, const char *s, size_t n);
 -#endif
 -
  /********************************* circuitbuild.c **********************/
  
 -char *circuit_list_path(origin_circuit_t *circ, int verbose);
 -char *circuit_list_path_for_controller(origin_circuit_t *circ);
 -void circuit_log_path(int severity, unsigned int domain,
 -                      origin_circuit_t *circ);
 -void circuit_rep_hist_note_result(origin_circuit_t *circ);
 -origin_circuit_t *origin_circuit_init(uint8_t purpose, int flags);
 -origin_circuit_t *circuit_establish_circuit(uint8_t purpose,
 -                                            extend_info_t *exit,
 -                                            int flags);
 -int circuit_handle_first_hop(origin_circuit_t *circ);
 -void circuit_n_conn_done(or_connection_t *or_conn, int status);
 -int inform_testing_reachability(void);
 -int circuit_send_next_onion_skin(origin_circuit_t *circ);
 -void circuit_note_clock_jumped(int seconds_elapsed);
 -int circuit_extend(cell_t *cell, circuit_t *circ);
 -int circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
 -                              int reverse);
 -int circuit_finish_handshake(origin_circuit_t *circ, uint8_t cell_type,
 -                             const uint8_t *reply);
 -int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer);
 -int onionskin_answer(or_circuit_t *circ, uint8_t cell_type,
 -                     const char *payload, const char *keys);
 -int circuit_all_predicted_ports_handled(time_t now, int *need_uptime,
 -                                        int *need_capacity);
 -
 -int circuit_append_new_exit(origin_circuit_t *circ, extend_info_t *info);
 -int circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *info);
 -void onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop);
 -extend_info_t *extend_info_alloc(const char *nickname, const char *digest,
 -                                 crypto_pk_env_t *onion_key,
 -                                 const tor_addr_t *addr, uint16_t port);
 -extend_info_t *extend_info_from_router(routerinfo_t *r);
 -extend_info_t *extend_info_dup(extend_info_t *info);
 -void extend_info_free(extend_info_t *info);
 -routerinfo_t *build_state_get_exit_router(cpath_build_state_t *state);
 -const char *build_state_get_exit_nickname(cpath_build_state_t *state);
 -
 -void entry_guards_compute_status(void);
 -int entry_guard_register_connect_status(const char *digest, int succeeded,
 -                                        int mark_relay_status, time_t now);
 -void entry_nodes_should_be_added(void);
 -int entry_list_can_grow(or_options_t *options);
 -routerinfo_t *choose_random_entry(cpath_build_state_t *state);
 -int entry_guards_parse_state(or_state_t *state, int set, char **msg);
 -void entry_guards_update_state(or_state_t *state);
 -int getinfo_helper_entry_guards(control_connection_t *conn,
 -                                const char *question, char **answer);
 -
 -void clear_bridge_list(void);
 -int routerinfo_is_a_configured_bridge(routerinfo_t *ri);
 -void bridge_add_from_config(const tor_addr_t *addr, uint16_t port,
 -                            char *digest);
 -void retry_bridge_descriptor_fetch_directly(const char *digest);
 -void fetch_bridge_descriptors(time_t now);
 -void learned_bridge_descriptor(routerinfo_t *ri, int from_cache);
 -int any_bridge_descriptors_known(void);
 -int any_pending_bridge_descriptor_fetches(void);
 -int bridges_known_but_down(void);
 -void bridges_retry_all(void);
 -
 -void entry_guards_free_all(void);
 -
 -/********************************* circuitlist.c ***********************/
 -
 -circuit_t * _circuit_get_global_list(void);
 -const char *circuit_state_to_string(int state);
 -const char *circuit_purpose_to_controller_string(uint8_t purpose);
 -void circuit_dump_by_conn(connection_t *conn, int severity);
 -void circuit_set_p_circid_orconn(or_circuit_t *circ, circid_t id,
 -                                 or_connection_t *conn);
 -void circuit_set_n_circid_orconn(circuit_t *circ, circid_t id,
 -                                 or_connection_t *conn);
 -void circuit_set_state(circuit_t *circ, uint8_t state);
 -void circuit_close_all_marked(void);
 -int32_t circuit_initial_package_window(void);
 -origin_circuit_t *origin_circuit_new(void);
 -or_circuit_t *or_circuit_new(circid_t p_circ_id, or_connection_t *p_conn);
 -circuit_t *circuit_get_by_circid_orconn(circid_t circ_id,
 -                                        or_connection_t *conn);
 -int circuit_id_in_use_on_orconn(circid_t circ_id, or_connection_t *conn);
 -circuit_t *circuit_get_by_edge_conn(edge_connection_t *conn);
 -void circuit_unlink_all_from_or_conn(or_connection_t *conn, int reason);
 -origin_circuit_t *circuit_get_by_global_id(uint32_t id);
 -origin_circuit_t *circuit_get_by_rend_query_and_purpose(const char *rend_query,
 -                                                        uint8_t purpose);
 -origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
 -                                         const char *digest, uint8_t purpose);
 -or_circuit_t *circuit_get_rendezvous(const char *cookie);
 -or_circuit_t *circuit_get_intro_point(const char *digest);
 -origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose,
 -                                              extend_info_t *info, int flags);
 -void circuit_mark_all_unused_circs(void);
 -void circuit_expire_all_dirty_circs(void);
 -void _circuit_mark_for_close(circuit_t *circ, int reason,
 -                             int line, const char *file);
 -int circuit_get_cpath_len(origin_circuit_t *circ);
 -crypt_path_t *circuit_get_cpath_hop(origin_circuit_t *circ, int hopnum);
 -void circuit_get_all_pending_on_or_conn(smartlist_t *out,
 -                                        or_connection_t *or_conn);
 -int circuit_count_pending_on_or_conn(or_connection_t *or_conn);
 -
 -#define circuit_mark_for_close(c, reason)                               \
 -  _circuit_mark_for_close((c), (reason), __LINE__, _SHORT_FILE_)
 -
 -void assert_cpath_layer_ok(const crypt_path_t *cp);
 -void assert_circuit_ok(const circuit_t *c);
 -void circuit_free_all(void);
 -
 -/********************************* circuituse.c ************************/
 -
 -void circuit_expire_building(time_t now);
 -void circuit_remove_handled_ports(smartlist_t *needed_ports);
 -int circuit_stream_is_being_handled(edge_connection_t *conn, uint16_t port,
 -                                    int min);
 -int circuit_conforms_to_options(const origin_circuit_t *circ,
 -                                const or_options_t *options);
 -void circuit_build_needed_circs(time_t now);
 -void circuit_detach_stream(circuit_t *circ, edge_connection_t *conn);
 -
 -void circuit_expire_old_circuits_serverside(time_t now);
 -
 -void reset_bandwidth_test(void);
 -int circuit_enough_testing_circs(void);
 -
 -void circuit_has_opened(origin_circuit_t *circ);
 -void circuit_build_failed(origin_circuit_t *circ);
 -
 -/** Flag to set when a circuit should have only a single hop. */
 -#define CIRCLAUNCH_ONEHOP_TUNNEL  (1<<0)
 -/** Flag to set when a circuit needs to be built of high-uptime nodes */
 -#define CIRCLAUNCH_NEED_UPTIME    (1<<1)
 -/** Flag to set when a circuit needs to be built of high-capacity nodes */
 -#define CIRCLAUNCH_NEED_CAPACITY  (1<<2)
 -/** Flag to set when the last hop of a circuit doesn't need to be an
 - * exit node. */
 -#define CIRCLAUNCH_IS_INTERNAL    (1<<3)
 -origin_circuit_t *circuit_launch_by_extend_info(uint8_t purpose,
 -                                                extend_info_t *info,
 -                                                int flags);
 -origin_circuit_t *circuit_launch_by_router(uint8_t purpose,
 -                                           routerinfo_t *exit, int flags);
 -void circuit_reset_failure_count(int timeout);
 -int connection_ap_handshake_attach_chosen_circuit(edge_connection_t *conn,
 -                                                  origin_circuit_t *circ,
 -                                                  crypt_path_t *cpath);
 -int connection_ap_handshake_attach_circuit(edge_connection_t *conn);
 -
 -/********************************* command.c ***************************/
 -
 -void command_process_cell(cell_t *cell, or_connection_t *conn);
 -void command_process_var_cell(var_cell_t *cell, or_connection_t *conn);
 -
 -extern uint64_t stats_n_padding_cells_processed;
 -extern uint64_t stats_n_create_cells_processed;
 -extern uint64_t stats_n_created_cells_processed;
 -extern uint64_t stats_n_relay_cells_processed;
 -extern uint64_t stats_n_destroy_cells_processed;
 +/** How many hops does a general-purpose circuit have by default? */
 +#define DEFAULT_ROUTE_LEN 3
 +
 +/* Circuit Build Timeout "public" structures. */
 +
 +/** Total size of the circuit timeout history to accumulate.
 + * 1000 is approx 2.5 days worth of continual-use circuits. */
 +#define CBT_NCIRCUITS_TO_OBSERVE 1000
 +
 +/** Width of the histogram bins in milliseconds */
 +#define CBT_BIN_WIDTH ((build_time_t)50)
 +
 +/** Number of modes to use in the weighted-avg computation of Xm */
 +#define CBT_DEFAULT_NUM_XM_MODES 3
 +
 +/** A build_time_t is milliseconds */
 +typedef uint32_t build_time_t;
 +
 +/**
 + * CBT_BUILD_ABANDONED is our flag value to represent a force-closed
 + * circuit (Aka a 'right-censored' pareto value).
 + */
 +#define CBT_BUILD_ABANDONED ((build_time_t)(INT32_MAX-1))
 +#define CBT_BUILD_TIME_MAX ((build_time_t)(INT32_MAX))
 +
 +/** Save state every 10 circuits */
 +#define CBT_SAVE_STATE_EVERY 10
 +
 +/* Circuit build times consensus parameters */
 +
 +/**
 + * How long to wait before actually closing circuits that take too long to
 + * build in terms of CDF quantile.
 + */
 +#define CBT_DEFAULT_CLOSE_QUANTILE 95
 +
 +/**
 + * How many circuits count as recent when considering if the
 + * connection has gone gimpy or changed.
 + */
 +#define CBT_DEFAULT_RECENT_CIRCUITS 20
 +
 +/**
 + * Maximum count of timeouts that finish the first hop in the past
 + * RECENT_CIRCUITS before calculating a new timeout.
 + *
 + * This tells us whether to abandon timeout history and set
 + * the timeout back to whatever circuit_build_times_get_initial_timeout()
 + * gives us.
 + */
 +#define CBT_DEFAULT_MAX_RECENT_TIMEOUT_COUNT (CBT_DEFAULT_RECENT_CIRCUITS*9/10)
 +
 +/** Minimum circuits before estimating a timeout */
 +#define CBT_DEFAULT_MIN_CIRCUITS_TO_OBSERVE 100
 +
 +/** Cutoff percentile on the CDF for our timeout estimation. */
 +#define CBT_DEFAULT_QUANTILE_CUTOFF 80
 +double circuit_build_times_quantile_cutoff(void);
 +
 +/** How often in seconds should we build a test circuit */
 +#define CBT_DEFAULT_TEST_FREQUENCY 60
 +
 +/** Lowest allowable value for CircuitBuildTimeout in milliseconds */
 +#define CBT_DEFAULT_TIMEOUT_MIN_VALUE (1500)
 +
 +/** Initial circuit build timeout in milliseconds */
 +#define CBT_DEFAULT_TIMEOUT_INITIAL_VALUE (60*1000)
 +int32_t circuit_build_times_initial_timeout(void);
 +
 +#if CBT_DEFAULT_MAX_RECENT_TIMEOUT_COUNT < 1
 +#error "RECENT_CIRCUITS is set too low."
 +#endif
 +
 +/** Information about the state of our local network connection */
 +typedef struct {
 +  /** The timestamp we last completed a TLS handshake or received a cell */
 +  time_t network_last_live;
 +  /** If the network is not live, how many timeouts has this caused? */
 +  int nonlive_timeouts;
 +  /** Circular array of circuits that have made it to the first hop. Slot is
 +   * 1 if circuit timed out, 0 if circuit succeeded */
 +  int8_t *timeouts_after_firsthop;
 +  /** Number of elements allocated for the above array */
 +  int num_recent_circs;
 +  /** Index into circular array. */
 +  int after_firsthop_idx;
 +} network_liveness_t;
 +
 +/** Structure for circuit build times history */
 +typedef struct {
 +  /** The circular array of recorded build times in milliseconds */
 +  build_time_t circuit_build_times[CBT_NCIRCUITS_TO_OBSERVE];
 +  /** Current index in the circuit_build_times circular array */
 +  int build_times_idx;
 +  /** Total number of build times accumulated. Max CBT_NCIRCUITS_TO_OBSERVE */
 +  int total_build_times;
 +  /** Information about the state of our local network connection */
 +  network_liveness_t liveness;
 +  /** Last time we built a circuit. Used to decide to build new test circs */
 +  time_t last_circ_at;
 +  /** "Minimum" value of our pareto distribution (actually mode) */
 +  build_time_t Xm;
 +  /** alpha exponent for pareto dist. */
 +  double alpha;
 +  /** Have we computed a timeout? */
 +  int have_computed_timeout;
 +  /** The exact value for that timeout in milliseconds. Stored as a double
 +   * to maintain precision from calculations to and from quantile value. */
 +  double timeout_ms;
 +  /** How long we wait before actually closing the circuit. */
 +  double close_ms;
 +} circuit_build_times_t;
  
  /********************************* config.c ***************************/
  
@@@ -3061,8 -2884,203 +3061,8 @@@ typedef enum setopt_err_t 
    SETOPT_ERR_SETTING = -4,
  } setopt_err_t;
  
 -const char *get_dirportfrontpage(void);
 -or_options_t *get_options(void);
 -int set_options(or_options_t *new_val, char **msg);
 -void config_free_all(void);
 -const char *safe_str(const char *address);
 -const char *escaped_safe_str(const char *address);
 -const char *get_version(void);
 -
 -int config_get_lines(const char *string, config_line_t **result);
 -void config_free_lines(config_line_t *front);
 -setopt_err_t options_trial_assign(config_line_t *list, int use_defaults,
 -                                  int clear_first, char **msg);
 -int resolve_my_address(int warn_severity, or_options_t *options,
 -                       uint32_t *addr, char **hostname_out);
 -int is_local_addr(const tor_addr_t *addr) ATTR_PURE;
 -void options_init(or_options_t *options);
 -int options_init_from_torrc(int argc, char **argv);
 -setopt_err_t options_init_from_string(const char *cf,
 -                            int command, const char *command_arg, char **msg);
 -int option_is_recognized(const char *key);
 -const char *option_get_canonical_name(const char *key);
 -config_line_t *option_get_assignment(or_options_t *options,
 -                                     const char *key);
 -int options_save_current(void);
 -const char *get_torrc_fname(void);
 -char *options_get_datadir_fname2_suffix(or_options_t *options,
 -                                        const char *sub1, const char *sub2,
 -                                        const char *suffix);
 -#define get_datadir_fname2_suffix(sub1, sub2, suffix) \
 -  options_get_datadir_fname2_suffix(get_options(), (sub1), (sub2), (suffix))
 -/** Return a newly allocated string containing datadir/sub1.  See
 - * get_datadir_fname2_suffix.  */
 -#define get_datadir_fname(sub1) get_datadir_fname2_suffix((sub1), NULL, NULL)
 -/** Return a newly allocated string containing datadir/sub1/sub2.  See
 - * get_datadir_fname2_suffix.  */
 -#define get_datadir_fname2(sub1,sub2) \
 -  get_datadir_fname2_suffix((sub1), (sub2), NULL)
 -/** Return a newly allocated string containing datadir/sub1suffix.  See
 - * get_datadir_fname2_suffix. */
 -#define get_datadir_fname_suffix(sub1, suffix) \
 -  get_datadir_fname2_suffix((sub1), NULL, (suffix))
 -
 -or_state_t *get_or_state(void);
 -int or_state_save(time_t now);
 -
 -int options_need_geoip_info(or_options_t *options, const char **reason_out);
 -int getinfo_helper_config(control_connection_t *conn,
 -                          const char *question, char **answer);
 -
 -uint32_t get_effective_bwrate(or_options_t *options);
 -uint32_t get_effective_bwburst(or_options_t *options);
 -
 -#ifdef CONFIG_PRIVATE
 -/* Used only by config.c and test.c */
 -or_options_t *options_new(void);
 -#endif
 -
 -/********************************* connection.c ***************************/
 -
 -const char *conn_type_to_string(int type);
 -const char *conn_state_to_string(int type, int state);
 -
 -dir_connection_t *dir_connection_new(int socket_family);
 -or_connection_t *or_connection_new(int socket_family);
 -edge_connection_t *edge_connection_new(int type, int socket_family);
 -control_connection_t *control_connection_new(int socket_family);
 -connection_t *connection_new(int type, int socket_family);
 -
 -void connection_link_connections(connection_t *conn_a, connection_t *conn_b);
 -void connection_unregister_events(connection_t *conn);
 -void connection_free(connection_t *conn);
 -void connection_free_all(void);
 -void connection_about_to_close_connection(connection_t *conn);
 -void connection_close_immediate(connection_t *conn);
 -void _connection_mark_for_close(connection_t *conn,int line, const char *file);
 -
 -#define connection_mark_for_close(c) \
 -  _connection_mark_for_close((c), __LINE__, _SHORT_FILE_)
 -
 -void connection_expire_held_open(void);
 -
 -int connection_connect(connection_t *conn, const char *address,
 -                       const tor_addr_t *addr,
 -                       uint16_t port, int *socket_error);
 -int retry_all_listeners(smartlist_t *replaced_conns,
 -                        smartlist_t *new_conns);
 -
 -ssize_t connection_bucket_write_limit(connection_t *conn, time_t now);
 -int global_write_bucket_low(connection_t *conn, size_t attempt, int priority);
 -void connection_bucket_init(void);
 -void connection_bucket_refill(int seconds_elapsed, time_t now);
 -
 -int connection_handle_read(connection_t *conn);
 -
 -int connection_fetch_from_buf(char *string, size_t len, connection_t *conn);
 -
 -int connection_wants_to_flush(connection_t *conn);
 -int connection_outbuf_too_full(connection_t *conn);
 -int connection_handle_write(connection_t *conn, int force);
 -void _connection_write_to_buf_impl(const char *string, size_t len,
 -                                   connection_t *conn, int zlib);
 -static void connection_write_to_buf(const char *string, size_t len,
 -                                    connection_t *conn);
 -static void connection_write_to_buf_zlib(const char *string, size_t len,
 -                                         dir_connection_t *conn, int done);
 -static INLINE void
 -connection_write_to_buf(const char *string, size_t len, connection_t *conn)
 -{
 -  _connection_write_to_buf_impl(string, len, conn, 0);
 -}
 -static INLINE void
 -connection_write_to_buf_zlib(const char *string, size_t len,
 -                             dir_connection_t *conn, int done)
 -{
 -  _connection_write_to_buf_impl(string, len, TO_CONN(conn), done ? -1 : 1);
 -}
 -
 -connection_t *connection_get_by_global_id(uint64_t id);
 -
 -connection_t *connection_get_by_type(int type);
 -connection_t *connection_get_by_type_purpose(int type, int purpose);
 -connection_t *connection_get_by_type_addr_port_purpose(int type,
 -                                                   const tor_addr_t *addr,
 -                                                   uint16_t port, int purpose);
 -connection_t *connection_get_by_type_state(int type, int state);
 -connection_t *connection_get_by_type_state_rendquery(int type, int state,
 -                                                     const char *rendquery,
 -                                                     int rendversion);
 -
 -#define connection_speaks_cells(conn) ((conn)->type == CONN_TYPE_OR)
 -int connection_is_listener(connection_t *conn);
 -int connection_state_is_open(connection_t *conn);
 -int connection_state_is_connecting(connection_t *conn);
 -
 -char *alloc_http_authenticator(const char *authenticator);
 -
 -void assert_connection_ok(connection_t *conn, time_t now);
 -int connection_or_nonopen_was_started_here(or_connection_t *conn);
 -void connection_dump_buffer_mem_stats(int severity);
 -void remove_file_if_very_old(const char *fname, time_t now);
 -
  /********************************* connection_edge.c *************************/
  
 -#define connection_mark_unattached_ap(conn, endreason) \
 -  _connection_mark_unattached_ap((conn), (endreason), __LINE__, _SHORT_FILE_)
 -
 -void _connection_mark_unattached_ap(edge_connection_t *conn, int endreason,
 -                                    int line, const char *file);
 -int connection_edge_reached_eof(edge_connection_t *conn);
 -int connection_edge_process_inbuf(edge_connection_t *conn,
 -                                  int package_partial);
 -int connection_edge_destroy(circid_t circ_id, edge_connection_t *conn);
 -int connection_edge_end(edge_connection_t *conn, uint8_t reason);
 -int connection_edge_end_errno(edge_connection_t *conn);
 -int connection_edge_finished_flushing(edge_connection_t *conn);
 -int connection_edge_finished_connecting(edge_connection_t *conn);
 -
 -int connection_ap_handshake_send_begin(edge_connection_t *ap_conn);
 -int connection_ap_handshake_send_resolve(edge_connection_t *ap_conn);
 -
 -edge_connection_t  *connection_ap_make_link(char *address, uint16_t port,
 -                                            const char *digest,
 -                                            int use_begindir, int want_onehop);
 -void connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply,
 -                                         size_t replylen,
 -                                         int endreason);
 -void connection_ap_handshake_socks_resolved(edge_connection_t *conn,
 -                                            int answer_type,
 -                                            size_t answer_len,
 -                                            const uint8_t *answer,
 -                                            int ttl,
 -                                            time_t expires);
 -
 -int connection_exit_begin_conn(cell_t *cell, circuit_t *circ);
 -int connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ);
 -void connection_exit_connect(edge_connection_t *conn);
 -int connection_edge_is_rendezvous_stream(edge_connection_t *conn);
 -int connection_ap_can_use_exit(edge_connection_t *conn, routerinfo_t *exit);
 -void connection_ap_expire_beginning(void);
 -void connection_ap_attach_pending(void);
 -void connection_ap_fail_onehop(const char *failed_digest,
 -                               cpath_build_state_t *build_state);
 -void circuit_discard_optional_exit_enclaves(extend_info_t *info);
 -int connection_ap_detach_retriable(edge_connection_t *conn,
 -                                   origin_circuit_t *circ,
 -                                   int reason);
 -int connection_ap_process_transparent(edge_connection_t *conn);
 -
 -int address_is_invalid_destination(const char *address, int client);
 -
 -void addressmap_init(void);
 -void addressmap_clean(time_t now);
 -void addressmap_clear_configured(void);
 -void addressmap_clear_transient(void);
 -void addressmap_free_all(void);
 -int addressmap_rewrite(char *address, size_t maxlen, time_t *expires_out);
 -int addressmap_have_mapping(const char *address, int update_timeout);
  /** Enumerates possible origins of a client-side address mapping. */
  typedef enum {
    /** We're remapping this address because the controller told us to. */
@@@ -3077,6 -3095,75 +3077,6 @@@
     * Tor server that told us what its value was. */
    ADDRMAPSRC_DNS,
  } addressmap_entry_source_t;
 -void addressmap_register(const char *address, char *new_address,
 -                         time_t expires, addressmap_entry_source_t source);
 -int parse_virtual_addr_network(const char *val, int validate_only,
 -                               char **msg);
 -int client_dns_incr_failures(const char *address);
 -void client_dns_clear_failures(const char *address);
 -void client_dns_set_addressmap(const char *address, uint32_t val,
 -                               const char *exitname, int ttl);
 -const char *addressmap_register_virtual_address(int type, char *new_address);
 -void addressmap_get_mappings(smartlist_t *sl, time_t min_expires,
 -                             time_t max_expires, int want_expiry);
 -int connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
 -                                               origin_circuit_t *circ,
 -                                               crypt_path_t *cpath);
 -int hostname_is_noconnect_address(const char *address);
 -
 -/** Possible return values for parse_extended_hostname. */
 -typedef enum hostname_type_t {
 -  NORMAL_HOSTNAME, ONION_HOSTNAME, EXIT_HOSTNAME, BAD_HOSTNAME
 -} hostname_type_t;
 -hostname_type_t parse_extended_hostname(char *address);
 -
 -#if defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H)
 -int get_pf_socket(void);
 -#endif
 -
 -/********************************* connection_or.c ***************************/
 -
 -void connection_or_remove_from_identity_map(or_connection_t *conn);
 -void connection_or_clear_identity_map(void);
 -or_connection_t *connection_or_get_for_extend(const char *digest,
 -                                              const tor_addr_t *target_addr,
 -                                              const char **msg_out,
 -                                              int *launch_out);
 -void connection_or_set_bad_connections(void);
 -
 -int connection_or_reached_eof(or_connection_t *conn);
 -int connection_or_process_inbuf(or_connection_t *conn);
 -int connection_or_flushed_some(or_connection_t *conn);
 -int connection_or_finished_flushing(or_connection_t *conn);
 -int connection_or_finished_connecting(or_connection_t *conn);
 -
 -void connection_or_connect_failed(or_connection_t *conn,
 -                                  int reason, const char *msg);
 -or_connection_t *connection_or_connect(const tor_addr_t *addr, uint16_t port,
 -                                       const char *id_digest);
 -
 -int connection_tls_start_handshake(or_connection_t *conn, int receiving);
 -int connection_tls_continue_handshake(or_connection_t *conn);
 -
 -void or_handshake_state_free(or_handshake_state_t *state);
 -int connection_or_set_state_open(or_connection_t *conn);
 -void connection_or_write_cell_to_buf(const cell_t *cell,
 -                                     or_connection_t *conn);
 -void connection_or_write_var_cell_to_buf(const var_cell_t *cell,
 -                                         or_connection_t *conn);
 -int connection_or_send_destroy(circid_t circ_id, or_connection_t *conn,
 -                               int reason);
 -int connection_or_send_netinfo(or_connection_t *conn);
 -int connection_or_send_cert(or_connection_t *conn);
 -int connection_or_send_link_auth(or_connection_t *conn);
 -int connection_or_compute_link_auth_hmac(or_connection_t *conn,
 -                                         char *hmac_out);
 -int is_or_protocol_version_known(uint16_t version);
 -
 -void cell_pack(packed_cell_t *dest, const cell_t *src);
 -void var_cell_pack_header(const var_cell_t *cell, char *hdr_out);
 -var_cell_t *var_cell_new(uint16_t payload_len);
 -void var_cell_free(var_cell_t *cell);
  
  /********************************* control.c ***************************/
  
@@@ -3114,14 -3201,8 +3114,14 @@@ typedef enum or_conn_status_event_t 
    OR_CONN_EVENT_NEW          = 4,
  } or_conn_status_event_t;
  
 -void control_update_global_event_mask(void);
 -void control_adjust_event_log_severity(void);
 +/** Used to indicate the type of a buildtime event */
 +typedef enum buildtimeout_set_event_t {
 +  BUILDTIMEOUT_SET_EVENT_COMPUTED  = 0,
 +  BUILDTIMEOUT_SET_EVENT_RESET     = 1,
 +  BUILDTIMEOUT_SET_EVENT_SUSPENDED = 2,
 +  BUILDTIMEOUT_SET_EVENT_DISCARD = 3,
 +  BUILDTIMEOUT_SET_EVENT_RESUME = 4
 +} buildtimeout_set_event_t;
  
  /** Execute the statement <b>stmt</b>, which may log events concerning the
   * connection <b>conn</b>.  To prevent infinite loops, disable log messages
@@@ -3139,6 -3220,58 +3139,6 @@@
        enable_control_logging();                                         \
    STMT_END
  
 -/** Log information about the connection <b>conn</b>, protecting it as with
 - * CONN_LOG_PROTECT. Example:
 - *
 - * LOG_FN_CONN(conn, (LOG_DEBUG, "Socket %d wants to write", conn->s));
 - **/
 -#define LOG_FN_CONN(conn, args)                 \
 -  CONN_LOG_PROTECT(conn, log_fn args)
 -
 -int connection_control_finished_flushing(control_connection_t *conn);
 -int connection_control_reached_eof(control_connection_t *conn);
 -int connection_control_process_inbuf(control_connection_t *conn);
 -
 -#define EVENT_AUTHDIR_NEWDESCS 0x000D
 -#define EVENT_NS 0x000F
 -int control_event_is_interesting(int event);
 -
 -int control_event_circuit_status(origin_circuit_t *circ,
 -                                 circuit_status_event_t e, int reason);
 -int control_event_stream_status(edge_connection_t *conn,
 -                                stream_status_event_t e,
 -                                int reason);
 -int control_event_or_conn_status(or_connection_t *conn,
 -                                 or_conn_status_event_t e, int reason);
 -int control_event_bandwidth_used(uint32_t n_read, uint32_t n_written);
 -int control_event_stream_bandwidth(edge_connection_t *edge_conn);
 -int control_event_stream_bandwidth_used(void);
 -void control_event_logmsg(int severity, unsigned int domain, const char *msg);
 -int control_event_descriptors_changed(smartlist_t *routers);
 -int control_event_address_mapped(const char *from, const char *to,
 -                                 time_t expires, const char *error);
 -int control_event_or_authdir_new_descriptor(const char *action,
 -                                            const char *desc,
 -                                            size_t desclen,
 -                                            const char *msg);
 -int control_event_my_descriptor_changed(void);
 -int control_event_networkstatus_changed(smartlist_t *statuses);
 -int control_event_newconsensus(const networkstatus_t *consensus);
 -int control_event_networkstatus_changed_single(routerstatus_t *rs);
 -int control_event_general_status(int severity, const char *format, ...)
 -  CHECK_PRINTF(2,3);
 -int control_event_client_status(int severity, const char *format, ...)
 -  CHECK_PRINTF(2,3);
 -int control_event_server_status(int severity, const char *format, ...)
 -  CHECK_PRINTF(2,3);
 -int control_event_guard(const char *nickname, const char *digest,
 -                        const char *status);
 -
 -int init_cookie_authentication(int enabled);
 -smartlist_t *decode_hashed_passwords(config_line_t *passwords);
 -void disable_control_logging(void);
 -void enable_control_logging(void);
 -
  /** Enum describing various stages of bootstrapping, for use with controller
   * bootstrap status events. The values range from 0 to 100. */
  typedef enum {
@@@ -3159,30 -3292,252 +3159,30 @@@
    BOOTSTRAP_STATUS_DONE=100
  } bootstrap_status_t;
  
 -void control_event_bootstrap(bootstrap_status_t status, int progress);
 -void control_event_bootstrap_problem(const char *warn, int reason);
 -
 -void control_event_clients_seen(const char *timestarted,
 -                                const char *countries);
 -
 -#ifdef CONTROL_PRIVATE
 -/* Used only by control.c and test.c */
 -size_t write_escaped_data(const char *data, size_t len, char **out);
 -size_t read_escaped_data(const char *data, size_t len, char **out);
 -#endif
 -
 -/********************************* cpuworker.c *****************************/
 -
 -void cpu_init(void);
 -void cpuworkers_rotate(void);
 -int connection_cpu_finished_flushing(connection_t *conn);
 -int connection_cpu_reached_eof(connection_t *conn);
 -int connection_cpu_process_inbuf(connection_t *conn);
 -int assign_onionskin_to_cpuworker(connection_t *cpuworker,
 -                                  or_circuit_t *circ,
 -                                  char *onionskin);
 -
  /********************************* directory.c ***************************/
  
 -int directories_have_accepted_server_descriptor(void);
 -char *authority_type_to_string(authority_type_t auth);
 -void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
 -                                  authority_type_t type, const char *payload,
 -                                  size_t payload_len, size_t extrainfo_len);
 -void directory_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose,
 -                                  const char *resource,
 -                                  int pds_flags);
 -void directory_get_from_all_authorities(uint8_t dir_purpose,
 -                                        uint8_t router_purpose,
 -                                        const char *resource);
 -void directory_initiate_command_routerstatus(routerstatus_t *status,
 -                                             uint8_t dir_purpose,
 -                                             uint8_t router_purpose,
 -                                             int anonymized_connection,
 -                                             const char *resource,
 -                                             const char *payload,
 -                                             size_t payload_len,
 -                                             time_t if_modified_since);
 -void directory_initiate_command_routerstatus_rend(routerstatus_t *status,
 -                                                  uint8_t dir_purpose,
 -                                                  uint8_t router_purpose,
 -                                                  int anonymized_connection,
 -                                                  const char *resource,
 -                                                  const char *payload,
 -                                                  size_t payload_len,
 -                                                  time_t if_modified_since,
 -                                                const rend_data_t *rend_query);
 -
 -int parse_http_response(const char *headers, int *code, time_t *date,
 -                        compress_method_t *compression, char **response);
 -
 -int connection_dir_is_encrypted(dir_connection_t *conn);
 -int connection_dir_reached_eof(dir_connection_t *conn);
 -int connection_dir_process_inbuf(dir_connection_t *conn);
 -int connection_dir_finished_flushing(dir_connection_t *conn);
 -int connection_dir_finished_connecting(dir_connection_t *conn);
 -void connection_dir_request_failed(dir_connection_t *conn);
 -void directory_initiate_command(const char *address, const tor_addr_t *addr,
 -                                uint16_t or_port, uint16_t dir_port,
 -                                int supports_conditional_consensus,
 -                                int supports_begindir, const char *digest,
 -                                uint8_t dir_purpose, uint8_t router_purpose,
 -                                int anonymized_connection,
 -                                const char *resource,
 -                                const char *payload, size_t payload_len,
 -                                time_t if_modified_since);
 -
 -int dir_split_resource_into_fingerprints(const char *resource,
 -                                    smartlist_t *fp_out, int *compresseed_out,
 -                                    int decode_hex, int sort_uniq);
  /** A pair of digests created by dir_split_resource_info_fingerprint_pairs() */
  typedef struct {
    char first[DIGEST_LEN];
    char second[DIGEST_LEN];
  } fp_pair_t;
 -int dir_split_resource_into_fingerprint_pairs(const char *res,
 -                                              smartlist_t *pairs_out);
 -char *directory_dump_request_log(void);
 -void note_request(const char *key, size_t bytes);
 -int router_supports_extrainfo(const char *identity_digest, int is_authority);
 -
 -time_t download_status_increment_failure(download_status_t *dls,
 -                                         int status_code, const char *item,
 -                                         int server, time_t now);
 -/** Increment the failure count of the download_status_t <b>dls</b>, with
 - * the optional status code <b>sc</b>. */
 -#define download_status_failed(dls, sc)                                 \
 -  download_status_increment_failure((dls), (sc), NULL,                  \
 -                                    get_options()->DirPort, time(NULL))
 -
 -void download_status_reset(download_status_t *dls);
 -static int download_status_is_ready(download_status_t *dls, time_t now,
 -                                    int max_failures);
 -/** Return true iff, as of <b>now</b>, the resource tracked by <b>dls</b> is
 - * ready to get its download reattempted. */
 -static INLINE int
 -download_status_is_ready(download_status_t *dls, time_t now,
 -                         int max_failures)
 -{
 -  return (dls->n_download_failures <= max_failures
 -          && dls->next_attempt_at <= now);
 -}
 -
 -static void download_status_mark_impossible(download_status_t *dl);
 -/** Mark <b>dl</b> as never downloadable. */
 -static INLINE void
 -download_status_mark_impossible(download_status_t *dl)
 -{
 -  dl->n_download_failures = IMPOSSIBLE_TO_DOWNLOAD;
 -}
  
  /********************************* dirserv.c ***************************/
 -/** Maximum length of an exit policy summary. */
 -#define MAX_EXITPOLICY_SUMMARY_LEN (1000)
 -
 -/** Maximum allowable length of a version line in a networkstatus. */
 -#define MAX_V_LINE_LEN 128
 -/** Length of "r Authority BadDirectory BadExit Exit Fast Guard HSDir Named
 - * Running Stable Unnamed V2Dir Valid\n". */
 -#define MAX_FLAG_LINE_LEN 96
 -/** Length of "w" line for weighting.  Currently at most
 - * "w Bandwidth=<uint32t>\n" */
 -#define MAX_WEIGHT_LINE_LEN (13+10)
 -/** Maximum length of an exit policy summary line. */
 -#define MAX_POLICY_LINE_LEN (3+MAX_EXITPOLICY_SUMMARY_LEN)
 -/** Amount of space to allocate for each entry: r, s, and v lines. */
 -#define RS_ENTRY_LEN                                                    \
 -  ( /* first line */                                                    \
 -   MAX_NICKNAME_LEN+BASE64_DIGEST_LEN*2+ISO_TIME_LEN+INET_NTOA_BUF_LEN+ \
 -   5*2 /* ports */ + 10 /* punctuation */ +                             \
 -   /* second line */                                                    \
 -   MAX_FLAG_LINE_LEN +                                                  \
 -   /* weight line */                                                    \
 -   MAX_WEIGHT_LINE_LEN +                                                \
 -   /* p line. */                                                        \
 -   MAX_POLICY_LINE_LEN +                                                \
 -   /* v line. */                                                        \
 -   MAX_V_LINE_LEN                                                       \
 -   )
 -#define UNNAMED_ROUTER_NICKNAME "Unnamed"
 -
 -int connection_dirserv_flushed_some(dir_connection_t *conn);
 -
 -int dirserv_add_own_fingerprint(const char *nickname, crypto_pk_env_t *pk);
 -int dirserv_load_fingerprint_file(void);
 -void dirserv_free_fingerprint_list(void);
 -const char *dirserv_get_nickname_by_digest(const char *digest);
 -enum was_router_added_t dirserv_add_multiple_descriptors(
 -                                     const char *desc, uint8_t purpose,
 -                                     const char *source,
 -                                     const char **msg);
 -enum was_router_added_t dirserv_add_descriptor(routerinfo_t *ri,
 -                                               const char **msg,
 -                                               const char *source);
 -int getinfo_helper_dirserv_unregistered(control_connection_t *conn,
 -                                        const char *question, char **answer);
 -void dirserv_free_descriptors(void);
 -void dirserv_set_router_is_running(routerinfo_t *router, time_t now);
 -int list_server_status_v1(smartlist_t *routers, char **router_status_out,
 -                          int for_controller);
 -int dirserv_dump_directory_to_string(char **dir_out,
 -                                     crypto_pk_env_t *private_key);
 -
 -int directory_fetches_from_authorities(or_options_t *options);
 -int directory_fetches_dir_info_early(or_options_t *options);
 -int directory_fetches_dir_info_later(or_options_t *options);
 -int directory_caches_v2_dir_info(or_options_t *options);
 -#define directory_caches_v1_dir_info(o) directory_caches_v2_dir_info(o)
 -int directory_caches_dir_info(or_options_t *options);
 -int directory_permits_begindir_requests(or_options_t *options);
 -int directory_permits_controller_requests(or_options_t *options);
 -int directory_too_idle_to_fetch_descriptors(or_options_t *options, time_t now);
 -
 -void directory_set_dirty(void);
 -cached_dir_t *dirserv_get_directory(void);
 -cached_dir_t *dirserv_get_runningrouters(void);
 -cached_dir_t *dirserv_get_consensus(void);
 -void dirserv_set_cached_directory(const char *directory, time_t when,
 -                                  int is_running_routers);
 -void dirserv_set_cached_networkstatus_v2(const char *directory,
 -                                         const char *identity,
 -                                         time_t published);
 -void dirserv_set_cached_networkstatus_v3(const char *consensus,
 -                                         time_t published);
 -void dirserv_clear_old_networkstatuses(time_t cutoff);
 -void dirserv_clear_old_v1_info(time_t now);
 -void dirserv_get_networkstatus_v2(smartlist_t *result, const char *key);
 -void dirserv_get_networkstatus_v2_fingerprints(smartlist_t *result,
 -                                               const char *key);
 -int dirserv_get_routerdesc_fingerprints(smartlist_t *fps_out, const char *key,
 -                                        const char **msg,
 -                                        int for_unencrypted_conn,
 -                                        int is_extrainfo);
 -int dirserv_get_routerdescs(smartlist_t *descs_out, const char *key,
 -                            const char **msg);
 -void dirserv_orconn_tls_done(const char *address,
 -                             uint16_t or_port,
 -                             const char *digest_rcvd,
 -                             int as_advertised);
 -void dirserv_test_reachability(time_t now, int try_all);
 -int authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg,
 -                                   int complain);
 -int dirserv_would_reject_router(routerstatus_t *rs);
 -int dirserv_remove_old_statuses(smartlist_t *fps, time_t cutoff);
 -int dirserv_have_any_serverdesc(smartlist_t *fps, int spool_src);
 -size_t dirserv_estimate_data_size(smartlist_t *fps, int is_serverdescs,
 -                                  int compressed);
 -int routerstatus_format_entry(char *buf, size_t buf_len,
 -                              routerstatus_t *rs, const char *platform,
 -                              int first_line_only, int v2_format);
 -void dirserv_free_all(void);
 -void cached_dir_decref(cached_dir_t *d);
 -cached_dir_t *new_cached_dir(char *s, time_t published);
 +typedef enum {
 +  NS_V2, NS_V3_CONSENSUS, NS_V3_VOTE, NS_CONTROL_PORT,
 +  NS_V3_CONSENSUS_MICRODESC
 +} routerstatus_format_type_t;
  
 -/********************************* dirvote.c ************************/
 +#ifdef DIRSERV_PRIVATE
 +typedef struct measured_bw_line_t {
 +  char node_id[DIGEST_LEN];
 +  char node_hex[MAX_HEX_NICKNAME_LEN+1];
 +  long int bw;
 +} measured_bw_line_t;
 +
 +#endif
  
 -/** Lowest allowable value for VoteSeconds. */
 -#define MIN_VOTE_SECONDS 20
 -/** Lowest allowable value for DistSeconds. */
 -#define MIN_DIST_SECONDS 20
 -/** Smallest allowable voting interval. */
 -#define MIN_VOTE_INTERVAL 300
 -
 -void dirvote_free_all(void);
 -
 -/* vote manipulation */
 -char *networkstatus_compute_consensus(smartlist_t *votes,
 -                                      int total_authorities,
 -                                      crypto_pk_env_t *identity_key,
 -                                      crypto_pk_env_t *signing_key,
 -                                      const char *legacy_identity_key_digest,
 -                                      crypto_pk_env_t *legacy_signing_key);
 -int networkstatus_add_detached_signatures(networkstatus_t *target,
 -                                          ns_detached_signatures_t *sigs,
 -                                          const char **msg_out);
 -char *networkstatus_get_detached_signatures(networkstatus_t *consensus);
 -void ns_detached_signatures_free(ns_detached_signatures_t *s);
 -
 -/* cert manipulation */
 -authority_cert_t *authority_cert_dup(authority_cert_t *cert);
 +/********************************* dirvote.c ************************/
  
  /** Describes the schedule by which votes should be generated. */
  typedef struct vote_timing_t {
@@@ -3191,85 -3546,173 +3191,85 @@@
    int vote_delay;
    int dist_delay;
  } vote_timing_t;
  
  /********************************* geoip.c **************************/
  
 -#ifdef GEOIP_PRIVATE
 -int geoip_parse_entry(const char *line);
 -#endif
 -int should_record_bridge_info(or_options_t *options);
 -int geoip_load_file(const char *filename, or_options_t *options);
 -int geoip_get_country_by_ip(uint32_t ipaddr);
 -int geoip_get_n_countries(void);
 -const char *geoip_get_country_name(country_t num);
 -int geoip_is_loaded(void);
 -country_t geoip_get_country(const char *countrycode);
 +/** Round all GeoIP results to the next multiple of this value, to avoid
 + * leaking information. */
 +#define DIR_RECORD_USAGE_GRANULARITY 8
 +/** Time interval: Flush geoip data to disk this often. */
 +#define DIR_ENTRY_RECORD_USAGE_RETAIN_IPS (24*60*60)
 +/** How long do we have to have observed per-country request history before
 + * we are willing to talk about it? */
 +#define DIR_RECORD_USAGE_MIN_OBSERVATION_TIME (12*60*60)
 +
  /** Indicates an action that we might be noting geoip statistics on.
   * Note that if we're noticing CONNECT, we're a bridge, and if we're noticing
   * the others, we're not.
   */
  typedef enum {
 -  /** We've noticed a connection as a bridge relay. */
 +  /** We've noticed a connection as a bridge relay or entry guard. */
    GEOIP_CLIENT_CONNECT = 0,
    /** We've served a networkstatus consensus as a directory server. */
    GEOIP_CLIENT_NETWORKSTATUS = 1,
    /** We've served a v2 networkstatus consensus as a directory server. */
    GEOIP_CLIENT_NETWORKSTATUS_V2 = 2,
  } geoip_client_action_t;
 -void geoip_note_client_seen(geoip_client_action_t action,
 -                            uint32_t addr, time_t now);
 -void geoip_remove_old_clients(time_t cutoff);
 -time_t geoip_get_history_start(void);
 -char *geoip_get_client_history(time_t now, geoip_client_action_t action);
 -char *geoip_get_request_history(time_t now, geoip_client_action_t action);
 -int getinfo_helper_geoip(control_connection_t *control_conn,
 -                         const char *question, char **answer);
 -void geoip_free_all(void);
 -void dump_geoip_stats(void);
 -
 -/********************************* hibernate.c **********************/
 -
 -int accounting_parse_options(or_options_t *options, int validate_only);
 -int accounting_is_enabled(or_options_t *options);
 -void configure_accounting(time_t now);
 -void accounting_run_housekeeping(time_t now);
 -void accounting_add_bytes(size_t n_read, size_t n_written, int seconds);
 -int accounting_record_bandwidth_usage(time_t now, or_state_t *state);
 -void hibernate_begin_shutdown(void);
 -int we_are_hibernating(void);
 -void consider_hibernation(time_t now);
 -int getinfo_helper_accounting(control_connection_t *conn,
 -                              const char *question, char **answer);
 -void accounting_set_bandwidth_usage_from_state(or_state_t *state);
 -
 -/********************************* main.c ***************************/
 -
 -extern int has_completed_circuit;
 -
 -int connection_add(connection_t *conn);
 -int connection_remove(connection_t *conn);
 -int connection_in_array(connection_t *conn);
 -void add_connection_to_closeable_list(connection_t *conn);
 -int connection_is_on_closeable_list(connection_t *conn);
 -
 -smartlist_t *get_connection_array(void);
 -
 -void connection_watch_events(connection_t *conn, short events);
 -int connection_is_reading(connection_t *conn);
 -void connection_stop_reading(connection_t *conn);
 -void connection_start_reading(connection_t *conn);
 -
 -int connection_is_writing(connection_t *conn);
 -void connection_stop_writing(connection_t *conn);
 -void connection_start_writing(connection_t *conn);
 -
 -void connection_stop_reading_from_linked_conn(connection_t *conn);
 -
 -void directory_all_unreachable(time_t now);
 -void directory_info_has_arrived(time_t now, int from_cache);
 -
 -void ip_address_changed(int at_interface);
 -void dns_servers_relaunch_checks(void);
 -
 -void control_signal_act(int the_signal);
 -void handle_signals(int is_parent);
 -
 -int try_locking(or_options_t *options, int err_if_locked);
 -int have_lockfile(void);
 -void release_lockfile(void);
 -
 -void tor_cleanup(void);
 -void tor_free_all(int postfork);
 -
 -int tor_main(int argc, char *argv[]);
 -
 -#ifdef MAIN_PRIVATE
 -int do_main_loop(void);
 -int do_list_fingerprint(void);
 -void do_hash_password(void);
 -int tor_init(int argc, char **argv);
 -#endif
 +/** Indicates either a positive reply or a reason for rejectng a network
 + * status request that will be included in geoip statistics. */
 +typedef enum {
 +  /** Request is answered successfully. */
 +  GEOIP_SUCCESS = 0,
 +  /** V3 network status is not signed by a sufficient number of requested
 +   * authorities. */
 +  GEOIP_REJECT_NOT_ENOUGH_SIGS = 1,
 +  /** Requested network status object is unavailable. */
 +  GEOIP_REJECT_UNAVAILABLE = 2,
 +  /** Requested network status not found. */
 +  GEOIP_REJECT_NOT_FOUND = 3,
 +  /** Network status has not been modified since If-Modified-Since time. */
 +  GEOIP_REJECT_NOT_MODIFIED = 4,
 +  /** Directory is busy. */
 +  GEOIP_REJECT_BUSY = 5,
 +} geoip_ns_response_t;
 +#define GEOIP_NS_RESPONSE_NUM 6
 +
 +/** Directory requests that we are measuring can be either direct or
 + * tunneled. */
 +typedef enum {
 +  DIRREQ_DIRECT = 0,
 +  DIRREQ_TUNNELED = 1,
 +} dirreq_type_t;
  
 -/********************************* networkstatus.c *********************/
 +/** Possible states for either direct or tunneled directory requests that
 + * are relevant for determining network status download times. */
 +typedef enum {
 +  /** Found that the client requests a network status; applies to both
 +   * direct and tunneled requests; initial state of a request that we are
 +   * measuring. */
 +  DIRREQ_IS_FOR_NETWORK_STATUS = 0,
 +  /** Finished writing a network status to the directory connection;
 +   * applies to both direct and tunneled requests; completes a direct
 +   * request. */
 +  DIRREQ_FLUSHING_DIR_CONN_FINISHED = 1,
 +  /** END cell sent to circuit that initiated a tunneled request. */
 +  DIRREQ_END_CELL_SENT = 2,
 +  /** Flushed last cell from queue of the circuit that initiated a
 +    * tunneled request to the outbuf of the OR connection. */
 +  DIRREQ_CIRC_QUEUE_FLUSHED = 3,
 +  /** Flushed last byte from buffer of the OR connection belonging to the
 +    * circuit that initiated a tunneled request; completes a tunneled
 +    * request. */
 +  DIRREQ_OR_CONN_BUFFER_FLUSHED = 4
 +} dirreq_state_t;
 +
 +#define WRITE_STATS_INTERVAL (24*60*60)
 +
 +/********************************* microdesc.c *************************/
 +
 +typedef struct microdesc_cache_t microdesc_cache_t;
  
 -/** How old do we allow a v2 network-status to get before removing it
 - * completely? */
 -#define MAX_NETWORKSTATUS_AGE (10*24*60*60)
 +/********************************* networkstatus.c *********************/
  
  /** Location where we found a v2 networkstatus. */
  typedef enum {
@@@ -3290,8 -3733,127 +3290,8 @@@ typedef enum version_status_t 
    VS_UNKNOWN, /**< We have no idea. */
  } version_status_t;
  
 -void networkstatus_reset_warnings(void);
 -void networkstatus_reset_download_failures(void);
 -int router_reload_v2_networkstatus(void);
 -int router_reload_consensus_networkstatus(void);
 -void routerstatus_free(routerstatus_t *rs);
 -void networkstatus_v2_free(networkstatus_v2_t *ns);
 -void networkstatus_vote_free(networkstatus_t *ns);
 -networkstatus_voter_info_t *networkstatus_get_voter_by_id(
 -                                       networkstatus_t *vote,
 -                                       const char *identity);
 -int networkstatus_check_consensus_signature(networkstatus_t *consensus,
 -                                            int warn);
 -int networkstatus_check_voter_signature(networkstatus_t *consensus,
 -                                        networkstatus_voter_info_t *voter,
 -                                        authority_cert_t *cert);
 -char *networkstatus_get_cache_filename(const char *identity_digest);
 -int router_set_networkstatus_v2(const char *s, time_t arrived_at,
 -                             v2_networkstatus_source_t source,
 -                             smartlist_t *requested_fingerprints);
 -void networkstatus_v2_list_clean(time_t now);
 -routerstatus_t *networkstatus_v2_find_entry(networkstatus_v2_t *ns,
 -                                         const char *digest);
 -routerstatus_t *networkstatus_vote_find_entry(networkstatus_t *ns,
 -                                              const char *digest);
 -int networkstatus_vote_find_entry_idx(networkstatus_t *ns,
 -                                      const char *digest, int *found_out);
 -const smartlist_t *networkstatus_get_v2_list(void);
 -download_status_t *router_get_dl_status_by_descriptor_digest(const char *d);
 -routerstatus_t *router_get_consensus_status_by_id(const char *digest);
 -routerstatus_t *router_get_consensus_status_by_descriptor_digest(
 -                                                        const char *digest);
 -routerstatus_t *router_get_consensus_status_by_nickname(const char *nickname,
 -                                                       int warn_if_unnamed);
 -const char *networkstatus_get_router_digest_by_nickname(const char *nickname);
 -int networkstatus_nickname_is_unnamed(const char *nickname);
 -void networkstatus_consensus_download_failed(int status_code);
 -void update_consensus_networkstatus_fetch_time(time_t now);
 -int should_delay_dir_fetches(or_options_t *options);
 -void update_networkstatus_downloads(time_t now);
 -void update_certificate_downloads(time_t now);
 -int consensus_is_waiting_for_certs(void);
 -networkstatus_v2_t *networkstatus_v2_get_by_digest(const char *digest);
 -networkstatus_t *networkstatus_get_latest_consensus(void);
 -networkstatus_t *networkstatus_get_live_consensus(time_t now);
 -networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now);
 -#define NSSET_FROM_CACHE 1
 -#define NSSET_WAS_WAITING_FOR_CERTS 2
 -#define NSSET_DONT_DOWNLOAD_CERTS 4
 -#define NSSET_ACCEPT_OBSOLETE 8
 -int networkstatus_set_current_consensus(const char *consensus, unsigned flags);
 -void networkstatus_note_certs_arrived(void);
 -void routers_update_all_from_networkstatus(time_t now, int dir_version);
 -void routerstatus_list_update_from_consensus_networkstatus(time_t now);
 -void routers_update_status_from_consensus_networkstatus(smartlist_t *routers,
 -                                                        int reset_failures);
 -void signed_descs_update_status_from_consensus_networkstatus(
 -                                                         smartlist_t *descs);
 -
 -char *networkstatus_getinfo_helper_single(routerstatus_t *rs);
 -char *networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now);
 -void networkstatus_dump_bridge_status_to_file(time_t now);
 -int32_t networkstatus_get_param(networkstatus_t *ns, const char *param_name,
 -                                int32_t default_val);
 -int getinfo_helper_networkstatus(control_connection_t *conn,
 -                                 const char *question, char **answer);
 -void networkstatus_free_all(void);
 -
 -/********************************* ntmain.c ***************************/
 -#ifdef MS_WINDOWS
 -#define NT_SERVICE
 -#endif
 -
 -#ifdef NT_SERVICE
 -int nt_service_parse_options(int argc, char **argv, int *should_exit);
 -int nt_service_is_stopping(void);
 -void nt_service_set_state(DWORD state);
 -#else
 -#define nt_service_is_stopping() (0)
 -#endif
 -
 -/********************************* onion.c ***************************/
 -
 -int onion_pending_add(or_circuit_t *circ, char *onionskin);
 -or_circuit_t *onion_next_task(char **onionskin_out);
 -void onion_pending_remove(or_circuit_t *circ);
 -
 -int onion_skin_create(crypto_pk_env_t *router_key,
 -                      crypto_dh_env_t **handshake_state_out,
 -                      char *onion_skin_out);
 -
 -int onion_skin_server_handshake(const char *onion_skin,
 -                                crypto_pk_env_t *private_key,
 -                                crypto_pk_env_t *prev_private_key,
 -                                char *handshake_reply_out,
 -                                char *key_out,
 -                                size_t key_out_len);
 -
 -int onion_skin_client_handshake(crypto_dh_env_t *handshake_state,
 -                                const char *handshake_reply,
 -                                char *key_out,
 -                                size_t key_out_len);
 -
 -int fast_server_handshake(const uint8_t *key_in,
 -                          uint8_t *handshake_reply_out,
 -                          uint8_t *key_out,
 -                          size_t key_out_len);
 -
 -int fast_client_handshake(const uint8_t *handshake_state,
 -                          const uint8_t *handshake_reply_out,
 -                          uint8_t *key_out,
 -                          size_t key_out_len);
 -
 -void clear_pending_onions(void);
 -
  /********************************* policies.c ************************/
  
 -/* (length of "accept 255.255.255.255/255.255.255.255:65535-65535\n" plus a
 - * NUL.)
 - */
 -#define POLICY_BUF_LEN 52
 -
  /** Outcome of applying an address policy to an address. */
  typedef enum {
    /** The address was accepted */
@@@ -3306,8 -3868,146 +3306,8 @@@
    ADDR_POLICY_PROBABLY_REJECTED=2
  } addr_policy_result_t;
  
 -int firewall_is_fascist_or(void);
 -int fascist_firewall_allows_address_or(const tor_addr_t *addr, uint16_t port);
 -int fascist_firewall_allows_or(routerinfo_t *ri);
 -int fascist_firewall_allows_address_dir(const tor_addr_t *addr, uint16_t port);
 -int dir_policy_permits_address(const tor_addr_t *addr);
 -int socks_policy_permits_address(const tor_addr_t *addr);
 -int authdir_policy_permits_address(uint32_t addr, uint16_t port);
 -int authdir_policy_valid_address(uint32_t addr, uint16_t port);
 -int authdir_policy_baddir_address(uint32_t addr, uint16_t port);
 -int authdir_policy_badexit_address(uint32_t addr, uint16_t port);
 -
 -int validate_addr_policies(or_options_t *options, char **msg);
 -void policy_expand_private(smartlist_t **policy);
 -int policies_parse_from_options(or_options_t *options);
 -
 -addr_policy_t *addr_policy_get_canonical_entry(addr_policy_t *ent);
 -int cmp_addr_policies(smartlist_t *a, smartlist_t *b);
 -addr_policy_result_t compare_tor_addr_to_addr_policy(const tor_addr_t *addr,
 -                              uint16_t port, const smartlist_t *policy);
 -addr_policy_result_t compare_addr_to_addr_policy(uint32_t addr,
 -                              uint16_t port, const smartlist_t *policy);
 -int policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest,
 -                               int rejectprivate, const char *local_address);
 -void policies_set_router_exitpolicy_to_reject_all(routerinfo_t *exitrouter);
 -int exit_policy_is_general_exit(smartlist_t *policy);
 -int policy_is_reject_star(const smartlist_t *policy);
 -int getinfo_helper_policies(control_connection_t *conn,
 -                            const char *question, char **answer);
 -int policy_write_item(char *buf, size_t buflen, addr_policy_t *item,
 -                      int format_for_desc);
 -
 -void addr_policy_list_free(smartlist_t *p);
 -void addr_policy_free(addr_policy_t *p);
 -void policies_free_all(void);
 -
 -char *policy_summarize(smartlist_t *policy);
 -
 -/********************************* reasons.c ***************************/
 -
 -const char *stream_end_reason_to_control_string(int reason);
 -const char *stream_end_reason_to_string(int reason);
 -socks5_reply_status_t stream_end_reason_to_socks5_response(int reason);
 -uint8_t errno_to_stream_end_reason(int e);
 -
 -const char *orconn_end_reason_to_control_string(int r);
 -int tls_error_to_orconn_end_reason(int e);
 -int errno_to_orconn_end_reason(int e);
 -
 -const char *circuit_end_reason_to_control_string(int reason);
 -
 -/********************************* relay.c ***************************/
 -
 -extern uint64_t stats_n_relay_cells_relayed;
 -extern uint64_t stats_n_relay_cells_delivered;
 -
 -int circuit_receive_relay_cell(cell_t *cell, circuit_t *circ,
 -                               cell_direction_t cell_direction);
 -
 -void relay_header_pack(uint8_t *dest, const relay_header_t *src);
 -void relay_header_unpack(relay_header_t *dest, const uint8_t *src);
 -int relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ,
 -                               uint8_t relay_command, const char *payload,
 -                               size_t payload_len, crypt_path_t *cpath_layer);
 -int connection_edge_send_command(edge_connection_t *fromconn,
 -                                 uint8_t relay_command, const char *payload,
 -                                 size_t payload_len);
 -int connection_edge_package_raw_inbuf(edge_connection_t *conn,
 -                                      int package_partial);
 -void connection_edge_consider_sending_sendme(edge_connection_t *conn);
 -
 -extern uint64_t stats_n_data_cells_packaged;
 -extern uint64_t stats_n_data_bytes_packaged;
 -extern uint64_t stats_n_data_cells_received;
 -extern uint64_t stats_n_data_bytes_received;
 -
 -void init_cell_pool(void);
 -void free_cell_pool(void);
 -void clean_cell_pool(void);
 -void dump_cell_pool_usage(int severity);
 -
 -void cell_queue_clear(cell_queue_t *queue);
 -void cell_queue_append(cell_queue_t *queue, packed_cell_t *cell);
 -void cell_queue_append_packed_copy(cell_queue_t *queue, const cell_t *cell);
 -
 -void append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn,
 -                                  cell_t *cell, cell_direction_t direction);
 -void connection_or_unlink_all_active_circs(or_connection_t *conn);
 -int connection_or_flush_from_first_active_circuit(or_connection_t *conn,
 -                                                  int max, time_t now);
 -void assert_active_circuits_ok(or_connection_t *orconn);
 -void make_circuit_inactive_on_conn(circuit_t *circ, or_connection_t *conn);
 -void make_circuit_active_on_conn(circuit_t *circ, or_connection_t *conn);
 -
 -int append_address_to_payload(uint8_t *payload_out, const tor_addr_t *addr);
 -const uint8_t *decode_address_from_payload(tor_addr_t *addr_out,
 -                                        const uint8_t *payload,
 -                                        int payload_len);
 -
  /********************************* rephist.c ***************************/
  
 -void rep_hist_init(void);
 -void rep_hist_note_connect_failed(const char* nickname, time_t when);
 -void rep_hist_note_connect_succeeded(const char* nickname, time_t when);
 -void rep_hist_note_disconnect(const char* nickname, time_t when);
 -void rep_hist_note_connection_died(const char* nickname, time_t when);
 -void rep_hist_note_extend_succeeded(const char *from_name,
 -                                    const char *to_name);
 -void rep_hist_note_extend_failed(const char *from_name, const char *to_name);
 -void rep_hist_dump_stats(time_t now, int severity);
 -void rep_hist_note_bytes_read(size_t num_bytes, time_t when);
 -void rep_hist_note_bytes_written(size_t num_bytes, time_t when);
 -int rep_hist_bandwidth_assess(void);
 -char *rep_hist_get_bandwidth_lines(int for_extrainfo);
 -void rep_hist_update_state(or_state_t *state);
 -int rep_hist_load_state(or_state_t *state, char **err);
 -void rep_history_clean(time_t before);
 -
 -void rep_hist_note_router_reachable(const char *id, time_t when);
 -void rep_hist_note_router_unreachable(const char *id, time_t when);
 -int rep_hist_record_mtbf_data(time_t now, int missing_means_down);
 -int rep_hist_load_mtbf_data(time_t now);
 -
 -time_t rep_hist_downrate_old_runs(time_t now);
 -double rep_hist_get_stability(const char *id, time_t when);
 -double rep_hist_get_weighted_fractional_uptime(const char *id, time_t when);
 -long rep_hist_get_weighted_time_known(const char *id, time_t when);
 -int rep_hist_have_measured_enough_stability(void);
 -const char *rep_hist_get_router_stability_doc(time_t now);
 -
 -void rep_hist_note_used_port(time_t now, uint16_t port);
 -smartlist_t *rep_hist_get_predicted_ports(time_t now);
 -void rep_hist_note_used_resolve(time_t now);
 -void rep_hist_note_used_internal(time_t now, int need_uptime,
 -                                 int need_capacity);
 -int rep_hist_get_predicted_internal(time_t now, int *need_uptime,
 -                                    int *need_capacity);
 -
 -int any_predicted_circuits(time_t now);
 -int rep_hist_circbuilding_dormant(time_t now);
 -
  /** Possible public/private key operations in Tor: used to keep track of where
   * we're spending our time. */
  typedef enum {
@@@ -3317,6 -4017,48 +3317,6 @@@
    TLS_HANDSHAKE_C, TLS_HANDSHAKE_S,
    REND_CLIENT, REND_MID, REND_SERVER,
  } pk_op_t;
 -void note_crypto_pk_op(pk_op_t operation);
 -void dump_pk_ops(int severity);
 -
 -void rep_hist_free_all(void);
 -
 -/* for hidden service usage statistics */
 -void hs_usage_note_publish_total(const char *service_id, time_t now);
 -void hs_usage_note_publish_novel(const char *service_id, time_t now);
 -void hs_usage_note_fetch_total(const char *service_id, time_t now);
 -void hs_usage_note_fetch_successful(const char *service_id, time_t now);
 -void hs_usage_write_statistics_to_file(time_t now);
 -void hs_usage_free_all(void);
 -
 -/********************************* rendclient.c ***************************/
 -
 -void rend_client_introcirc_has_opened(origin_circuit_t *circ);
 -void rend_client_rendcirc_has_opened(origin_circuit_t *circ);
 -int rend_client_introduction_acked(origin_circuit_t *circ,
 -                                   const uint8_t *request,
 -                                   size_t request_len);
 -void rend_client_refetch_renddesc(const char *query);
 -void rend_client_refetch_v2_renddesc(const rend_data_t *rend_query);
 -int rend_client_remove_intro_point(extend_info_t *failed_intro,
 -                                   const rend_data_t *rend_query);
 -int rend_client_rendezvous_acked(origin_circuit_t *circ,
 -                                 const uint8_t *request,
 -                                 size_t request_len);
 -int rend_client_receive_rendezvous(origin_circuit_t *circ,
 -                                   const uint8_t *request,
 -                                   size_t request_len);
 -void rend_client_desc_trynow(const char *query, int rend_version);
 -
 -extend_info_t *rend_client_get_random_intro(const rend_data_t *rend_query);
 -
 -int rend_client_send_introduction(origin_circuit_t *introcirc,
 -                                  origin_circuit_t *rendcirc);
 -int rend_parse_service_authorization(or_options_t *options,
 -                                     int validate_only);
 -rend_service_authorization_t *rend_client_lookup_service_authorization(
 -                                                const char *onion_address);
 -void rend_service_authorization_free_all(void);
 -rend_data_t *rend_data_dup(const rend_data_t *request);
  
  /********************************* rendcommon.c ***************************/
  
@@@ -3359,6 -4101,31 +3359,6 @@@ typedef struct rend_service_descriptor_
    smartlist_t *successful_uploads;
  } rend_service_descriptor_t;
  
 -/** Free all storage associated with <b>data</b> */
 -static INLINE void
 -rend_data_free(rend_data_t *data)
 -{
 -  tor_free(data);
 -}
 -
 -int rend_cmp_service_ids(const char *one, const char *two);
 -
 -void rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
 -                             int command, size_t length,
 -                             const uint8_t *payload);
 -
 -void rend_service_descriptor_free(rend_service_descriptor_t *desc);
 -int rend_encode_service_descriptor(rend_service_descriptor_t *desc,
 -                                   crypto_pk_env_t *key,
 -                                   char **str_out,
 -                                   size_t *len_out);
 -rend_service_descriptor_t *rend_parse_service_descriptor(const char *str,
 -                                                         size_t len);
 -int rend_get_service_id(crypto_pk_env_t *pk, char *out);
 -void rend_encoded_v2_service_descriptor_free(
 -                               rend_encoded_v2_service_descriptor_t *desc);
 -void rend_intro_point_free(rend_intro_point_t *intro);
 -
  /** A cached rendezvous descriptor. */
  typedef struct rend_cache_entry_t {
    size_t len; /**< Length of <b>desc</b> */
@@@ -3367,6 -4134,150 +3367,6 @@@
    rend_service_descriptor_t *parsed; /**< Parsed value of 'desc' */
  } rend_cache_entry_t;
  
 -void rend_cache_init(void);
 -void rend_cache_clean(void);
 -void rend_cache_clean_v2_descs_as_dir(void);
 -void rend_cache_free_all(void);
 -int rend_valid_service_id(const char *query);
 -int rend_cache_lookup_desc(const char *query, int version, const char **desc,
 -                           size_t *desc_len);
 -int rend_cache_lookup_entry(const char *query, int version,
 -                            rend_cache_entry_t **entry_out);
 -int rend_cache_lookup_v2_desc_as_dir(const char *query, const char **desc);
 -int rend_cache_store(const char *desc, size_t desc_len, int published);
 -int rend_cache_store_v2_desc_as_client(const char *desc,
 -                                       const rend_data_t *rend_query);
 -int rend_cache_store_v2_desc_as_dir(const char *desc);
 -int rend_cache_size(void);
 -int rend_encode_v2_descriptors(smartlist_t *descs_out,
 -                               rend_service_descriptor_t *desc, time_t now,
 -                               uint8_t period, rend_auth_type_t auth_type,
 -                               crypto_pk_env_t *client_key,
 -                               smartlist_t *client_cookies);
 -int rend_compute_v2_desc_id(char *desc_id_out, const char *service_id,
 -                            const char *descriptor_cookie,
 -                            time_t now, uint8_t replica);
 -int rend_id_is_in_interval(const char *a, const char *b, const char *c);
 -void rend_get_descriptor_id_bytes(char *descriptor_id_out,
 -                                  const char *service_id,
 -                                  const char *secret_id_part);
 -
 -/********************************* rendservice.c ***************************/
 -
 -int num_rend_services(void);
 -int rend_config_services(or_options_t *options, int validate_only);
 -int rend_service_load_keys(void);
 -void rend_services_init(void);
 -void rend_services_introduce(void);
 -void rend_consider_services_upload(time_t now);
 -void rend_hsdir_routers_changed(void);
 -void rend_consider_descriptor_republication(void);
 -
 -void rend_service_intro_has_opened(origin_circuit_t *circuit);
 -int rend_service_intro_established(origin_circuit_t *circuit,
 -                                   const uint8_t *request,
 -                                   size_t request_len);
 -void rend_service_rendezvous_has_opened(origin_circuit_t *circuit);
 -int rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
 -                           size_t request_len);
 -void rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc);
 -int rend_service_set_connection_addr_port(edge_connection_t *conn,
 -                                          origin_circuit_t *circ);
 -void rend_service_dump_stats(int severity);
 -void rend_service_free_all(void);
 -
 -/********************************* rendmid.c *******************************/
 -int rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
 -                             size_t request_len);
 -int rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
 -                       size_t request_len);
 -int rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
 -                                  size_t request_len);
 -int rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
 -                        size_t request_len);
 -
 -/********************************* router.c ***************************/
 -
 -crypto_pk_env_t *get_onion_key(void);
 -time_t get_onion_key_set_at(void);
 -void set_identity_key(crypto_pk_env_t *k);
 -crypto_pk_env_t *get_identity_key(void);
 -int identity_key_is_set(void);
 -authority_cert_t *get_my_v3_authority_cert(void);
 -crypto_pk_env_t *get_my_v3_authority_signing_key(void);
 -authority_cert_t *get_my_v3_legacy_cert(void);
 -crypto_pk_env_t *get_my_v3_legacy_signing_key(void);
 -void dup_onion_keys(crypto_pk_env_t **key, crypto_pk_env_t **last);
 -void rotate_onion_key(void);
 -crypto_pk_env_t *init_key_from_file(const char *fname, int generate,
 -                                    int severity);
 -void v3_authority_check_key_expiry(void);
 -
 -int init_keys(void);
 -
 -int check_whether_orport_reachable(void);
 -int check_whether_dirport_reachable(void);
 -void consider_testing_reachability(int test_or, int test_dir);
 -void router_orport_found_reachable(void);
 -void router_dirport_found_reachable(void);
 -void router_perform_bandwidth_test(int num_circs, time_t now);
 -
 -int authdir_mode(or_options_t *options);
 -int authdir_mode_v1(or_options_t *options);
 -int authdir_mode_v2(or_options_t *options);
 -int authdir_mode_v3(or_options_t *options);
 -int authdir_mode_any_main(or_options_t *options);
 -int authdir_mode_any_nonhidserv(or_options_t *options);
 -int authdir_mode_handles_descs(or_options_t *options, int purpose);
 -int authdir_mode_publishes_statuses(or_options_t *options);
 -int authdir_mode_tests_reachability(or_options_t *options);
 -int authdir_mode_bridge(or_options_t *options);
 -
 -int server_mode(or_options_t *options);
 -int advertised_server_mode(void);
 -int proxy_mode(or_options_t *options);
 -void consider_publishable_server(int force);
 -
 -void router_upload_dir_desc_to_dirservers(int force);
 -void mark_my_descriptor_dirty_if_older_than(time_t when);
 -void mark_my_descriptor_dirty(void);
 -void check_descriptor_bandwidth_changed(time_t now);
 -void check_descriptor_ipaddress_changed(time_t now);
 -void router_new_address_suggestion(const char *suggestion,
 -                                   const dir_connection_t *d_conn);
 -int router_compare_to_my_exit_policy(edge_connection_t *conn);
 -routerinfo_t *router_get_my_routerinfo(void);
 -extrainfo_t *router_get_my_extrainfo(void);
 -const char *router_get_my_descriptor(void);
 -int router_digest_is_me(const char *digest);
 -int router_extrainfo_digest_is_me(const char *digest);
 -int router_is_me(routerinfo_t *router);
 -int router_fingerprint_is_me(const char *fp);
 -int router_pick_published_address(or_options_t *options, uint32_t *addr);
 -int router_rebuild_descriptor(int force);
 -int router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router,
 -                                 crypto_pk_env_t *ident_key);
 -int extrainfo_dump_to_string(char *s, size_t maxlen, extrainfo_t *extrainfo,
 -                             crypto_pk_env_t *ident_key);
 -char *extrainfo_get_client_geoip_summary(time_t);
 -int is_legal_nickname(const char *s);
 -int is_legal_nickname_or_hexdigest(const char *s);
 -int is_legal_hexdigest(const char *s);
 -void router_get_verbose_nickname(char *buf, const routerinfo_t *router);
 -void routerstatus_get_verbose_nickname(char *buf,
 -                                       const routerstatus_t *router);
 -void router_reset_warnings(void);
 -void router_reset_reachability(void);
 -void router_free_all(void);
 -
 -const char *router_purpose_to_string(uint8_t p);
 -uint8_t router_purpose_from_string(const char *s);
 -
 -#ifdef ROUTER_PRIVATE
 -/* Used only by router.c and test.c */
 -void get_platform_str(char *platform, size_t len);
 -#endif
 -
  /********************************* routerlist.c ***************************/
  
  /** Represents information about a single trusted directory server. */
@@@ -3406,6 -4317,21 +3406,6 @@@ typedef struct trusted_dir_server_t 
  
  #define ROUTER_MAX_DECLARED_BANDWIDTH INT32_MAX
  
 -int get_n_authorities(authority_type_t type);
 -int trusted_dirs_reload_certs(void);
 -int trusted_dirs_load_certs_from_string(const char *contents, int from_store,
 -                                        int flush);
 -void trusted_dirs_flush_certs_to_disk(void);
 -authority_cert_t *authority_cert_get_newest_by_id(const char *id_digest);
 -authority_cert_t *authority_cert_get_by_sk_digest(const char *sk_digest);
 -authority_cert_t *authority_cert_get_by_digests(const char *id_digest,
 -                                                const char *sk_digest);
 -void authority_cert_get_all(smartlist_t *certs_out);
 -void authority_cert_dl_failed(const char *id_digest, int status);
 -void authority_certs_fetch_missing(networkstatus_t *status, time_t now);
 -int router_reload_router_list(void);
 -smartlist_t *router_get_trusted_dir_servers(void);
 -
  /* Flags for pick_directory_server and pick_trusteddirserver. */
  /** Flag to indicate that we should not automatically be willing to use
   * ourself to answer a directory request.
@@@ -3435,13 -4361,34 +3435,13 @@@
   */
  #define PDS_NO_EXISTING_SERVERDESC_FETCH (1<<3)
  #define _PDS_PREFER_TUNNELED_DIR_CONNS (1<<16)
  
  /** Possible ways to weight routers when choosing one randomly.  See
   * routerlist_sl_choose_by_bandwidth() for more information.*/
 -typedef enum {
 -  NO_WEIGHTING, WEIGHT_FOR_EXIT, WEIGHT_FOR_GUARD
 +typedef enum bandwidth_weight_rule_t {
 +  NO_WEIGHTING, WEIGHT_FOR_EXIT, WEIGHT_FOR_MID, WEIGHT_FOR_GUARD,
 +  WEIGHT_FOR_DIR
  } bandwidth_weight_rule_t;
 -routerinfo_t *routerlist_sl_choose_by_bandwidth(smartlist_t *sl,
 -                                                bandwidth_weight_rule_t rule);
 -routerstatus_t *routerstatus_sl_choose_by_bandwidth(smartlist_t *sl);
  
  /** Flags to be passed to control router_choose_random_node() to indicate what
   * kind of nodes to pick according to what algorithm. */
@@@ -3451,9 -4398,44 +3451,9 @@@ typedef enum 
    CRN_NEED_GUARD = 1<<2,
    CRN_ALLOW_INVALID = 1<<3,
    /* XXXX not used, apparently. */
    CRN_WEIGHT_AS_EXIT = 1<<5
  } router_crn_flags_t;
  
 -routerinfo_t *router_choose_random_node(const char *preferred,
 -                                        smartlist_t *excludedsmartlist,
 -                                        struct routerset_t *excludedset,
 -                                        router_crn_flags_t flags);
 -
 -routerinfo_t *router_get_by_nickname(const char *nickname,
 -                                     int warn_if_unnamed);
 -int router_digest_version_as_new_as(const char *digest, const char *cutoff);
 -int router_digest_is_trusted_dir_type(const char *digest,
 -                                      authority_type_t type);
 -#define router_digest_is_trusted_dir(d) \
 -  router_digest_is_trusted_dir_type((d), NO_AUTHORITY)
 -
 -int router_addr_is_trusted_dir(uint32_t addr);
 -int hexdigest_to_digest(const char *hexdigest, char *digest);
 -routerinfo_t *router_get_by_hexdigest(const char *hexdigest);
 -routerinfo_t *router_get_by_digest(const char *digest);
 -signed_descriptor_t *router_get_by_descriptor_digest(const char *digest);
 -signed_descriptor_t *router_get_by_extrainfo_digest(const char *digest);
 -signed_descriptor_t *extrainfo_get_by_descriptor_digest(const char *digest);
 -const char *signed_descriptor_get_body(signed_descriptor_t *desc);
 -const char *signed_descriptor_get_annotations(signed_descriptor_t *desc);
 -routerlist_t *router_get_routerlist(void);
 -void routerinfo_free(routerinfo_t *router);
 -void extrainfo_free(extrainfo_t *extrainfo);
 -void routerlist_free(routerlist_t *rl);
 -void dump_routerlist_mem_usage(int severity);
 -void routerlist_remove(routerlist_t *rl, routerinfo_t *ri, int make_old,
 -                       time_t now);
 -void routerlist_free_all(void);
 -void routerlist_reset_warnings(void);
 -void router_set_status(const char *digest, int up);
 -
  /** Return value for router_add_to_routerlist() and dirserv_add_descriptor() */
  typedef enum was_router_added_t {
    ROUTER_ADDED_SUCCESSFULLY = 1,
@@@ -3465,6 -4447,109 +3465,6 @@@
    ROUTER_AUTHDIR_REJECTS = -5,
  } was_router_added_t;
  
 -static int WRA_WAS_ADDED(was_router_added_t s);
 -static int WRA_WAS_OUTDATED(was_router_added_t s);
 -static int WRA_WAS_REJECTED(was_router_added_t s);
 -/** Return true iff the descriptor was added. It might still be necessary to
 - * check whether the descriptor generator should be notified.
 - */
 -static INLINE int
 -WRA_WAS_ADDED(was_router_added_t s) {
 -  return s == ROUTER_ADDED_SUCCESSFULLY || s == ROUTER_ADDED_NOTIFY_GENERATOR;
 -}
 -/** Return true iff the descriptor was not added because it was either:
 - * - not in the consensus
 - * - neither in the consensus nor in any networkstatus document
 - * - it was outdated.
 - */
 -static INLINE int WRA_WAS_OUTDATED(was_router_added_t s)
 -{
 -  return (s == ROUTER_WAS_NOT_NEW ||
 -          s == ROUTER_NOT_IN_CONSENSUS ||
 -          s == ROUTER_NOT_IN_CONSENSUS_OR_NETWORKSTATUS);
 -}
 -/** Return true iff the descriptor rejected because it was malformed. */
 -static INLINE int WRA_WAS_REJECTED(was_router_added_t s)
 -{
 -  return (s == ROUTER_AUTHDIR_REJECTS);
 -}
 -was_router_added_t router_add_to_routerlist(routerinfo_t *router,
 -                                            const char **msg,
 -                                            int from_cache,
 -                                            int from_fetch);
 -was_router_added_t router_add_extrainfo_to_routerlist(
 -                                        extrainfo_t *ei, const char **msg,
 -                                        int from_cache, int from_fetch);
 -void routerlist_remove_old_routers(void);
 -int router_load_single_router(const char *s, uint8_t purpose, int cache,
 -                              const char **msg);
 -int router_load_routers_from_string(const char *s, const char *eos,
 -                                     saved_location_t saved_location,
 -                                     smartlist_t *requested_fingerprints,
 -                                     int descriptor_digests,
 -                                     const char *prepend_annotations);
 -void router_load_extrainfo_from_string(const char *s, const char *eos,
 -                                       saved_location_t saved_location,
 -                                       smartlist_t *requested_fingerprints,
 -                                       int descriptor_digests);
 -void routerlist_retry_directory_downloads(time_t now);
 -int router_exit_policy_all_routers_reject(uint32_t addr, uint16_t port,
 -                                          int need_uptime);
 -int router_exit_policy_rejects_all(routerinfo_t *router);
 -trusted_dir_server_t *add_trusted_dir_server(const char *nickname,
 -                           const char *address,
 -                           uint16_t dir_port, uint16_t or_port,
 -                           const char *digest, const char *v3_auth_digest,
 -                           authority_type_t type);
 -void authority_cert_free(authority_cert_t *cert);
 -void clear_trusted_dir_servers(void);
 -int any_trusted_dir_is_v1_authority(void);
 -void update_router_descriptor_downloads(time_t now);
 -void update_extrainfo_downloads(time_t now);
 -int router_have_minimum_dir_info(void);
 -void router_dir_info_changed(void);
 -const char *get_dir_info_status_string(void);
 -int count_loading_descriptors_progress(void);
 -void router_reset_descriptor_download_failures(void);
 -int router_differences_are_cosmetic(routerinfo_t *r1, routerinfo_t *r2);
 -int routerinfo_incompatible_with_extrainfo(routerinfo_t *ri, extrainfo_t *ei,
 -                                           signed_descriptor_t *sd,
 -                                           const char **msg);
 -void routerlist_assert_ok(routerlist_t *rl);
 -const char *esc_router_info(routerinfo_t *router);
 -void routers_sort_by_identity(smartlist_t *routers);
 -
 -routerset_t *routerset_new(void);
 -int routerset_parse(routerset_t *target, const char *s,
 -                    const char *description);
 -void routerset_union(routerset_t *target, const routerset_t *source);
 -int routerset_is_list(const routerset_t *set);
 -int routerset_needs_geoip(const routerset_t *set);
 -int routerset_contains_router(const routerset_t *set, routerinfo_t *ri);
 -int routerset_contains_routerstatus(const routerset_t *set,
 -                                    routerstatus_t *rs);
 -int routerset_contains_extendinfo(const routerset_t *set,
 -                                  const extend_info_t *ei);
 -void routerset_get_all_routers(smartlist_t *out, const routerset_t *routerset,
 -                               int running_only);
 -void routersets_get_disjunction(smartlist_t *target, const smartlist_t *source,
 -                                const routerset_t *include,
 -                                const routerset_t *exclude, int running_only);
 -void routerset_subtract_routers(smartlist_t *out,
 -                                const routerset_t *routerset);
 -char *routerset_to_string(const routerset_t *routerset);
 -void routerset_refresh_countries(routerset_t *target);
 -int routerset_equal(const routerset_t *old, const routerset_t *new);
 -void routerset_free(routerset_t *routerset);
 -void routerinfo_set_country(routerinfo_t *ri);
 -void routerlist_refresh_countries(void);
 -void refresh_all_country_info(void);
 -
 -int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs,
 -                                         const char *id);
 -int hid_serv_acting_as_directory(void);
 -int hid_serv_responsible_for_desc_id(const char *id);
 -
  /********************************* routerparse.c ************************/
  
  #define MAX_STATUS_TAG_LEN 32
@@@ -3483,10 -4568,71 +3483,10 @@@ typedef struct tor_version_t 
    int patchlevel;
    char status_tag[MAX_STATUS_TAG_LEN];
    int svn_revision;
 -} tor_version_t;
  
 -int router_get_router_hash(const char *s, size_t s_len, char *digest);
 -int router_get_dir_hash(const char *s, char *digest);
 -int router_get_runningrouters_hash(const char *s, char *digest);
 -int router_get_networkstatus_v2_hash(const char *s, char *digest);
 -int router_get_networkstatus_v3_hash(const char *s, char *digest);
 -int router_get_extrainfo_hash(const char *s, char *digest);
 -int router_append_dirobj_signature(char *buf, size_t buf_len,
 -                                   const char *digest,
 -                                   crypto_pk_env_t *private_key);
 -int router_parse_list_from_string(const char **s, const char *eos,
 -                                  smartlist_t *dest,
 -                                  saved_location_t saved_location,
 -                                  int is_extrainfo,
 -                                  int allow_annotations,
 -                                  const char *prepend_annotations);
 -int router_parse_routerlist_from_directory(const char *s,
 -                                           routerlist_t **dest,
 -                                           crypto_pk_env_t *pkey,
 -                                           int check_version,
 -                                           int write_to_cache);
 -int router_parse_runningrouters(const char *str);
 -int router_parse_directory(const char *str);
 -routerinfo_t *router_parse_entry_from_string(const char *s, const char *end,
 -                                             int cache_copy,
 -                                             int allow_annotations,
 -                                             const char *prepend_annotations);
 -extrainfo_t *extrainfo_parse_entry_from_string(const char *s, const char *end,
 -                         int cache_copy, struct digest_ri_map_t *routermap);
 -addr_policy_t *router_parse_addr_policy_item_from_string(const char *s,
 -                                                  int assume_action);
 -version_status_t tor_version_is_obsolete(const char *myversion,
 -                                         const char *versionlist);
 -int tor_version_parse(const char *s, tor_version_t *out);
 -int tor_version_as_new_as(const char *platform, const char *cutoff);
 -int tor_version_compare(tor_version_t *a, tor_version_t *b);
 -void sort_version_list(smartlist_t *lst, int remove_duplicates);
 -void assert_addr_policy_ok(smartlist_t *t);
 -void dump_distinct_digest_count(int severity);
 -
 -networkstatus_v2_t *networkstatus_v2_parse_from_string(const char *s);
 -networkstatus_t *networkstatus_parse_vote_from_string(const char *s,
 -                                                 const char **eos_out,
 -                                                 networkstatus_type_t ns_type);
 -ns_detached_signatures_t *networkstatus_parse_detached_signatures(
 -                                          const char *s, const char *eos);
 -
 -authority_cert_t *authority_cert_parse_from_string(const char *s,
 -                                                   const char **end_of_string);
 -int rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out,
 -                                     char *desc_id_out,
 -                                     char **intro_points_encrypted_out,
 -                                     size_t *intro_points_encrypted_size_out,
 -                                     size_t *encoded_size_out,
 -                                     const char **next_out, const char *desc);
 -int rend_decrypt_introduction_points(char **ipos_decrypted,
 -                                     size_t *ipos_decrypted_size,
 -                                     const char *descriptor_cookie,
 -                                     const char *ipos_encrypted,
 -                                     size_t ipos_encrypted_size);
 -int rend_parse_introduction_points(rend_service_descriptor_t *parsed,
 -                                   const char *intro_points_encoded,
 -                                   size_t intro_points_encoded_size);
 -int rend_parse_client_keys(strmap_t *parsed_clients, const char *str);
 +  int git_tag_len;
 +  char git_tag[DIGEST_LEN];
 +} tor_version_t;
  
  #endif
  
diff --combined src/or/policies.c
index 4fd0904,d4b4a07..6947222
--- a/src/or/policies.c
+++ b/src/or/policies.c
@@@ -1,6 -1,6 +1,6 @@@
  /* Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -9,10 -9,6 +9,10 @@@
   **/
  
  #include "or.h"
 +#include "config.h"
 +#include "dirserv.h"
 +#include "policies.h"
 +#include "routerparse.h"
  #include "ht.h"
  
  /** Policy that addresses for incoming SOCKS connections must match. */
@@@ -348,8 -344,7 +348,8 @@@ validate_addr_policies(or_options_t *op
    *msg = NULL;
  
    if (policies_parse_exit_policy(options->ExitPolicy, &addr_policy,
 -                                 options->ExitPolicyRejectPrivate, NULL))
 +                                 options->ExitPolicyRejectPrivate, NULL,
 +                                 !options->BridgeRelay))
      REJECT("Error in ExitPolicy entry.");
  
    /* The rest of these calls *append* to addr_policy. So don't actually
@@@ -380,8 -375,14 +380,8 @@@
    if (parse_addr_policy(options->ReachableDirAddresses, &addr_policy,
                          ADDR_POLICY_ACCEPT))
      REJECT("Error in ReachableDirAddresses entry.");
 -  if (parse_addr_policy(options->AuthDirReject, &addr_policy,
 -                        ADDR_POLICY_REJECT))
 -    REJECT("Error in AuthDirReject entry.");
 -  if (parse_addr_policy(options->AuthDirInvalid, &addr_policy,
 -                        ADDR_POLICY_REJECT))
 -    REJECT("Error in AuthDirInvalid entry.");
  
 -err:
 + err:
    addr_policy_list_free(addr_policy);
    return *msg ? -1 : 0;
  #undef REJECT
@@@ -828,16 -829,14 +828,16 @@@ exit_policy_remove_redundancies(smartli
    "reject *:6346-6429,reject *:6699,reject *:6881-6999,accept *:*"
  
  /** Parse the exit policy <b>cfg</b> into the linked list *<b>dest</b>. If
 - * cfg doesn't end in an absolute accept or reject, add the default exit
 + * cfg doesn't end in an absolute accept or reject and if
 + * <b>add_default_policy</b> is true, add the default exit
   * policy afterwards. If <b>rejectprivate</b> is true, prepend
   * "reject private:*" to the policy. Return -1 if we can't parse cfg,
   * else return 0.
   */
  int
  policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest,
 -                           int rejectprivate, const char *local_address)
 +                           int rejectprivate, const char *local_address,
 +                           int add_default_policy)
  {
    if (rejectprivate) {
      append_exit_policy_string(dest, "reject private:*");
@@@ -849,10 -848,8 +849,10 @@@
    }
    if (parse_addr_policy(cfg, dest, -1))
      return -1;
 -  append_exit_policy_string(dest, DEFAULT_EXIT_POLICY);
 -
 +  if (add_default_policy)
 +    append_exit_policy_string(dest, DEFAULT_EXIT_POLICY);
 +  else
 +    append_exit_policy_string(dest, "reject *:*");
    exit_policy_remove_redundancies(*dest);
  
    return 0;
@@@ -869,49 -866,6 +869,49 @@@ policies_set_router_exitpolicy_to_rejec
    smartlist_add(r->exit_policy, item);
  }
  
 +/** Return 1 if there is at least one /8 subnet in <b>policy</b> that
 + * allows exiting to <b>port</b>.  Otherwise, return 0. */
 +static int
 +exit_policy_is_general_exit_helper(smartlist_t *policy, int port)
 +{
 +  uint32_t mask, ip, i;
 +  /* Is this /8 rejected (1), or undecided (0)? */
 +  char subnet_status[256];
 +
 +  memset(subnet_status, 0, sizeof(subnet_status));
 +  SMARTLIST_FOREACH(policy, addr_policy_t *, p, {
 +    if (p->prt_min > port || p->prt_max < port)
 +      continue; /* Doesn't cover our port. */
 +    mask = 0;
 +    tor_assert(p->maskbits <= 32);
 +
 +    if (p->maskbits)
 +      mask = UINT32_MAX<<(32-p->maskbits);
 +    ip = tor_addr_to_ipv4h(&p->addr);
 +
 +    /* Calculate the first and last subnet that this exit policy touches
 +     * and set it as loop boundaries. */
 +    for (i = ((mask & ip)>>24); i <= (~((mask & ip) ^ mask)>>24); ++i) {
 +      tor_addr_t addr;
 +      if (subnet_status[i] != 0)
 +        continue; /* We already reject some part of this /8 */
 +      tor_addr_from_ipv4h(&addr, i<<24);
 +      if (tor_addr_is_internal(&addr, 0))
 +        continue; /* Local or non-routable addresses */
 +      if (p->policy_type == ADDR_POLICY_ACCEPT) {
 +        if (p->maskbits > 8)
 +          continue; /* Narrower than a /8. */
 +        /* We found an allowed subnet of at least size /8. Done
 +         * for this port! */
 +        return 1;
 +      } else if (p->policy_type == ADDR_POLICY_REJECT) {
 +        subnet_status[i] = 1;
 +      }
 +    }
 +  });
 +  return 0;
 +}
 +
  /** Return true iff <b>ri</b> is "useful as an exit node", meaning
   * it allows exit to at least one /8 address space for at least
   * two of ports 80, 443, and 6667. */
@@@ -925,7 -879,19 +925,7 @@@ exit_policy_is_general_exit(smartlist_
      return 0;
  
    for (i = 0; i < 3; ++i) {
 -    SMARTLIST_FOREACH(policy, addr_policy_t *, p, {
 -      if (p->prt_min > ports[i] || p->prt_max < ports[i])
 -        continue; /* Doesn't cover our port. */
 -      if (p->maskbits > 8)
 -        continue; /* Narrower than a /8. */
 -      if (tor_addr_is_loopback(&p->addr))
 -        continue; /* 127.x or ::1. */
 -      /* We have a match that is at least a /8. */
 -      if (p->policy_type == ADDR_POLICY_ACCEPT) {
 -        ++n_allowed;
 -        break; /* stop considering this port */
 -      }
 -    });
 +    n_allowed += exit_policy_is_general_exit_helper(policy, ports[i]);
    }
    return n_allowed >= 2;
  }
@@@ -1272,7 -1238,7 +1272,7 @@@ policy_summarize(smartlist_t *policy
    result = tor_malloc(final_size);
    tor_snprintf(result, final_size, "%s %s", prefix, shorter_str);
  
 -cleanup:
 + cleanup:
    /* cleanup */
    SMARTLIST_FOREACH(summary, policy_summary_item_t *, s, tor_free(s));
    smartlist_free(summary);
@@@ -1292,11 -1258,9 +1292,11 @@@
   * about "exit-policy/..." */
  int
  getinfo_helper_policies(control_connection_t *conn,
 -                        const char *question, char **answer)
 +                        const char *question, char **answer,
 +                        const char **errmsg)
  {
    (void) conn;
 +  (void) errmsg;
    if (!strcmp(question, "exit-policy/default")) {
      *answer = tor_strdup(DEFAULT_EXIT_POLICY);
    }
@@@ -1307,8 -1271,7 +1307,8 @@@
  void
  addr_policy_list_free(smartlist_t *lst)
  {
 -  if (!lst) return;
 +  if (!lst)
 +    return;
    SMARTLIST_FOREACH(lst, addr_policy_t *, policy, addr_policy_free(policy));
    smartlist_free(lst);
  }
@@@ -1317,20 -1280,19 +1317,20 @@@
  void
  addr_policy_free(addr_policy_t *p)
  {
 -  if (p) {
 -    if (--p->refcnt <= 0) {
 -      if (p->is_canonical) {
 -        policy_map_ent_t search, *found;
 -        search.policy = p;
 -        found = HT_REMOVE(policy_map, &policy_root, &search);
 -        if (found) {
 -          tor_assert(p == found->policy);
 -          tor_free(found);
 -        }
 +  if (!p)
 +    return;
 +
 +  if (--p->refcnt <= 0) {
 +    if (p->is_canonical) {
 +      policy_map_ent_t search, *found;
 +      search.policy = p;
 +      found = HT_REMOVE(policy_map, &policy_root, &search);
 +      if (found) {
 +        tor_assert(p == found->policy);
 +        tor_free(found);
        }
 -      tor_free(p);
      }
 +    tor_free(p);
    }
  }
  
diff --combined src/or/reasons.c
index aa7972b,38eb407..1401552
--- a/src/or/reasons.c
+++ b/src/or/reasons.c
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -9,8 -9,6 +9,8 @@@
   **/
  
  #include "or.h"
 +#include "config.h"
 +#include "reasons.h"
  
  /***************************** Edge (stream) reasons **********************/
  
@@@ -334,78 -332,9 +334,78 @@@ circuit_end_reason_to_control_string(in
        return "NOPATH";
      case END_CIRC_REASON_NOSUCHSERVICE:
        return "NOSUCHSERVICE";
 +    case END_CIRC_REASON_MEASUREMENT_EXPIRED:
 +      return "MEASUREMENT_EXPIRED";
      default:
        log_warn(LD_BUG, "Unrecognized reason code %d", (int)reason);
        return NULL;
    }
  }
  
 +/** Return a string corresponding to a SOCKS4 reponse code. */
 +const char *
 +socks4_response_code_to_string(uint8_t code)
 +{
 +  switch (code) {
 +    case 0x5a:
 +      return "connection accepted";
 +    case 0x5b:
 +      return "server rejected connection";
 +    case 0x5c:
 +      return "server cannot connect to identd on this client";
 +    case 0x5d:
 +      return "user id does not match identd";
 +    default:
 +      return "invalid SOCKS 4 response code";
 +  }
 +}
 +
 +/** Return a string corresponding to a SOCKS5 reponse code. */
 +const char *
 +socks5_response_code_to_string(uint8_t code)
 +{
 +  switch (code) {
 +    case 0x00:
 +      return "connection accepted";
 +    case 0x01:
 +      return "general SOCKS server failure";
 +    case 0x02:
 +      return "connection not allowed by ruleset";
 +    case 0x03:
 +      return "Network unreachable";
 +    case 0x04:
 +      return "Host unreachable";
 +    case 0x05:
 +      return "Connection refused";
 +    case 0x06:
 +      return "TTL expired";
 +    case 0x07:
 +      return "Command not supported";
 +    case 0x08:
 +      return "Address type not supported";
 +    default:
 +      return "unknown reason";
 +  }
 +}
 +
 +/** Return a string corresponding to a bandwidht_weight_rule_t */
 +const char *
 +bandwidth_weight_rule_to_string(bandwidth_weight_rule_t rule)
 +{
 +  switch (rule)
 +    {
 +    case NO_WEIGHTING:
 +      return "no weighting";
 +    case WEIGHT_FOR_EXIT:
 +      return "weight as exit";
 +    case WEIGHT_FOR_MID:
 +      return "weight as middle node";
 +    case WEIGHT_FOR_GUARD:
 +      return "weight as guard";
 +    case WEIGHT_FOR_DIR:
 +      return "weight as directory";
 +    default:
 +      return "unknown rule";
 +  }
 +}
 +
diff --combined src/or/relay.c
index c951cab,b40d7ad..32ac96e
--- a/src/or/relay.c
+++ b/src/or/relay.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -10,26 -10,8 +10,26 @@@
   *    receiving from circuits, plus queuing on circuits.
   **/
  
 +#include <math.h>
  #include "or.h"
 +#include "buffers.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "connection_or.h"
 +#include "control.h"
 +#include "geoip.h"
 +#include "main.h"
  #include "mempool.h"
 +#include "networkstatus.h"
 +#include "policies.h"
 +#include "reasons.h"
 +#include "relay.h"
 +#include "rendcommon.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  static int relay_crypt(circuit_t *circ, cell_t *cell,
                         cell_direction_t cell_direction,
@@@ -38,46 -20,20 +38,46 @@@ static edge_connection_t *relay_lookup_
                                              cell_direction_t cell_direction,
                                              crypt_path_t *layer_hint);
  
 -static int
 -connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
 -                                   edge_connection_t *conn,
 -                                   crypt_path_t *layer_hint);
 -static void
 -circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint);
 +static int connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
 +                                              edge_connection_t *conn,
 +                                              crypt_path_t *layer_hint);
 +static void circuit_consider_sending_sendme(circuit_t *circ,
 +                                            crypt_path_t *layer_hint);
 +static void circuit_resume_edge_reading(circuit_t *circ,
 +                                        crypt_path_t *layer_hint);
 +static int circuit_resume_edge_reading_helper(edge_connection_t *conn,
 +                                              circuit_t *circ,
 +                                              crypt_path_t *layer_hint);
 +static int circuit_consider_stop_edge_reading(circuit_t *circ,
 +                                              crypt_path_t *layer_hint);
 +static int circuit_queue_streams_are_blocked(circuit_t *circ);
 +
 +/** Cache the current hi-res time; the cache gets reset when libevent
 + * calls us. */
 +
 +static struct timeval cached_time_hires = {0, 0};
 +
 +/** Stop reading on edge connections when we have this many cells
 + * waiting on the appropriate queue. */
 +#define CELL_QUEUE_HIGHWATER_SIZE 256
 +/** Start reading from edge connections again when we get down to this many
 + * cells. */
 +#define CELL_QUEUE_LOWWATER_SIZE 64
 +
  static void
 -circuit_resume_edge_reading(circuit_t *circ, crypt_path_t *layer_hint);
 -static int
 -circuit_resume_edge_reading_helper(edge_connection_t *conn,
 -                                   circuit_t *circ,
 -                                   crypt_path_t *layer_hint);
 -static int
 -circuit_consider_stop_edge_reading(circuit_t *circ, crypt_path_t *layer_hint);
 +tor_gettimeofday_cached(struct timeval *tv)
 +{
 +  if (cached_time_hires.tv_sec == 0) {
 +    tor_gettimeofday(&cached_time_hires);
 +  }
 +  *tv = cached_time_hires;
 +}
 +
 +void
 +tor_gettimeofday_cache_clear(void)
 +{
 +    cached_time_hires.tv_sec = 0;
 +}
  
  /** Stats: how many relay cells have originated at this hop, or have
   * been relayed onward (not recognized at this hop)?
@@@ -274,7 -230,7 +274,7 @@@ circuit_receive_relay_cell(cell_t *cell
                                    * we might kill the circ before we relay
                                    * the cells. */
  
 -  append_cell_to_circuit_queue(circ, or_conn, cell, cell_direction);
 +  append_cell_to_circuit_queue(circ, or_conn, cell, cell_direction, 0);
    return 0;
  }
  
@@@ -371,7 -327,7 +371,7 @@@ relay_crypt(circuit_t *circ, cell_t *ce
  static int
  circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
                             cell_direction_t cell_direction,
 -                           crypt_path_t *layer_hint)
 +                           crypt_path_t *layer_hint, streamid_t on_stream)
  {
    or_connection_t *conn; /* where to send the cell */
  
@@@ -415,7 -371,7 +415,7 @@@
    }
    ++stats_n_relay_cells_relayed;
  
 -  append_cell_to_circuit_queue(circ, conn, cell, cell_direction);
 +  append_cell_to_circuit_queue(circ, conn, cell, cell_direction, on_stream);
    return 0;
  }
  
@@@ -540,7 -496,7 +540,7 @@@ relay_command_to_string(uint8_t command
   * return 0.
   */
  int
 -relay_send_command_from_edge(uint16_t stream_id, circuit_t *circ,
 +relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ,
                               uint8_t relay_command, const char *payload,
                               size_t payload_len, crypt_path_t *cpath_layer)
  {
@@@ -575,12 -531,6 +575,12 @@@
    log_debug(LD_OR,"delivering %d cell %s.", relay_command,
              cell_direction == CELL_DIRECTION_OUT ? "forward" : "backward");
  
 +  /* If we are sending an END cell and this circuit is used for a tunneled
 +   * directory request, advance its state. */
 +  if (relay_command == RELAY_COMMAND_END && circ->dirreq_id)
 +    geoip_change_dirreq_state(circ->dirreq_id, DIRREQ_TUNNELED,
 +                              DIRREQ_END_CELL_SENT);
 +
    if (cell_direction == CELL_DIRECTION_OUT && circ->n_conn) {
      /* if we're using relaybandwidthrate, this conn wants priority */
      circ->n_conn->client_used = approx_time();
@@@ -590,11 -540,17 +590,11 @@@
      origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ);
      if (origin_circ->remaining_relay_early_cells > 0 &&
          (relay_command == RELAY_COMMAND_EXTEND ||
 -         (cpath_layer != origin_circ->cpath &&
 -          !CIRCUIT_PURPOSE_IS_ESTABLISHED_REND(circ->purpose)))) {
 -      /* If we've got any relay_early cells left, and we're sending
 -       * an extend cell or (we're not talking to the first hop and we're
 -       * not talking to a rendezvous circuit), use one of them.
 -       * Don't worry about the conn protocol version:
 +         cpath_layer != origin_circ->cpath)) {
 +      /* If we've got any relay_early cells left and (we're sending
 +       * an extend cell or we're not talking to the first hop), use
 +       * one of them.  Don't worry about the conn protocol version:
         * append_cell_to_circuit_queue will fix it up. */
 -      /* XXX For now, clients don't use RELAY_EARLY cells when sending
 -       * relay cells on rendezvous circuits. See bug 1038. Eventually,
 -       * we can take this behavior away in favor of having clients avoid
 -       * rendezvous points running 0.2.1.3-alpha through 0.2.1.18. -RD */
        cell.command = CELL_RELAY_EARLY;
        --origin_circ->remaining_relay_early_cells;
        log_debug(LD_OR, "Sending a RELAY_EARLY cell; %d remaining.",
@@@ -622,8 -578,8 +622,8 @@@
      }
    }
  
 -  if (circuit_package_relay_cell(&cell, circ, cell_direction, cpath_layer)
 -      < 0) {
 +  if (circuit_package_relay_cell(&cell, circ, cell_direction, cpath_layer,
 +                                 stream_id) < 0) {
      log_warn(LD_BUG,"circuit_package_relay_cell failed. Closing.");
      circuit_mark_for_close(circ, END_CIRC_REASON_INTERNAL);
      return -1;
@@@ -945,7 -901,7 +945,7 @@@ connection_edge_process_relay_cell_not_
      }
  
      /* handle anything that might have queued */
 -    if (connection_edge_package_raw_inbuf(conn, 1) < 0) {
 +    if (connection_edge_package_raw_inbuf(conn, 1, NULL) < 0) {
        /* (We already sent an end cell if possible) */
        connection_mark_for_close(TO_CONN(conn));
        return 0;
@@@ -1043,8 -999,7 +1043,8 @@@ connection_edge_process_relay_cell(cell
    relay_header_unpack(&rh, cell->payload);
  //  log_fn(LOG_DEBUG,"command %d stream %d", rh.command, rh.stream_id);
    num_seen++;
 -  log_debug(domain, "Now seen %d relay cells here.", num_seen);
 +  log_debug(domain, "Now seen %d relay cells here (command %d, stream %d).",
 +            num_seen, rh.command, rh.stream_id);
  
    if (rh.length > RELAY_PAYLOAD_SIZE) {
      log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
@@@ -1083,16 -1038,6 +1083,16 @@@
                 "Begin cell for known stream. Dropping.");
          return 0;
        }
 +      if (rh.command == RELAY_COMMAND_BEGIN_DIR) {
 +        /* Assign this circuit and its app-ward OR connection a unique ID,
 +         * so that we can measure download times. The local edge and dir
 +         * connection will be assigned the same ID when they are created
 +         * and linked. */
 +        static uint64_t next_id = 0;
 +        circ->dirreq_id = ++next_id;
 +        TO_CONN(TO_OR_CIRCUIT(circ)->p_conn)->dirreq_id = circ->dirreq_id;
 +      }
 +
        return connection_exit_begin_conn(cell, circ);
      case RELAY_COMMAND_DATA:
        ++stats_n_data_cells_received;
@@@ -1186,7 -1131,6 +1186,7 @@@
        }
        if (circ->n_conn) {
          uint8_t trunc_reason = *(uint8_t*)(cell->payload + RELAY_HEADER_SIZE);
 +        circuit_clear_cell_queue(circ, circ->n_conn);
          connection_or_send_destroy(circ->n_circ_id, circ->n_conn,
                                     trunc_reason);
          circuit_set_n_circid_orconn(circ, 0, NULL);
@@@ -1235,13 -1179,9 +1235,13 @@@
        conn->package_window += STREAMWINDOW_INCREMENT;
        log_debug(domain,"stream-level sendme, packagewindow now %d.",
                  conn->package_window);
 +      if (circuit_queue_streams_are_blocked(circ)) {
 +        /* Still waiting for queue to flush; don't touch conn */
 +        return 0;
 +      }
        connection_start_reading(TO_CONN(conn));
        /* handle whatever might still be on the inbuf */
 -      if (connection_edge_package_raw_inbuf(conn, 1) < 0) {
 +      if (connection_edge_package_raw_inbuf(conn, 1, NULL) < 0) {
          /* (We already sent an end cell if possible) */
          connection_mark_for_close(TO_CONN(conn));
          return 0;
@@@ -1307,19 -1247,15 +1307,19 @@@ uint64_t stats_n_data_cells_received = 
   * ever received were completely full of data. */
  uint64_t stats_n_data_bytes_received = 0;
  
 -/** While conn->inbuf has an entire relay payload of bytes on it,
 - * and the appropriate package windows aren't empty, grab a cell
 - * and send it down the circuit.
 +/** If <b>conn</b> has an entire relay payload of bytes on its inbuf (or
 + * <b>package_partial</b> is true), and the appropriate package windows aren't
 + * empty, grab a cell and send it down the circuit.
 + *
 + * If *<b>max_cells</b> is given, package no more than max_cells.  Decrement
 + * *<b>max_cells</b> by the number of cells packaged.
   *
   * Return -1 (and send a RELAY_COMMAND_END cell if necessary) if conn should
   * be marked for close, else return 0.
   */
  int
 -connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial)
 +connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial,
 +                                  int *max_cells)
  {
    size_t amount_to_process, length;
    char payload[CELL_PAYLOAD_SIZE];
@@@ -1335,10 -1271,7 +1335,10 @@@
      return 0;
    }
  
 -repeat_connection_edge_package_raw_inbuf:
 +  if (max_cells && *max_cells <= 0)
 +    return 0;
 +
 + repeat_connection_edge_package_raw_inbuf:
  
    circ = circuit_get_by_edge_conn(conn);
    if (!circ) {
@@@ -1399,12 -1332,6 +1399,12 @@@
    }
    log_debug(domain,"conn->package_window is now %d",conn->package_window);
  
 +  if (max_cells) {
 +    *max_cells -= 1;
 +    if (*max_cells <= 0)
 +      return 0;
 +  }
 +
    /* handle more if there's more, or return 0 if there isn't */
    goto repeat_connection_edge_package_raw_inbuf;
  }
@@@ -1452,10 -1379,7 +1452,10 @@@ connection_edge_consider_sending_sendme
  static void
  circuit_resume_edge_reading(circuit_t *circ, crypt_path_t *layer_hint)
  {
 -
 +  if (circuit_queue_streams_are_blocked(circ)) {
 +    log_debug(layer_hint?LD_APP:LD_EXIT,"Too big queue, no resuming");
 +    return;
 +  }
    log_debug(layer_hint?LD_APP:LD_EXIT,"resuming");
  
    if (CIRCUIT_IS_ORIGIN(circ))
@@@ -1471,136 -1395,31 +1471,136 @@@
   * of a linked list of edge streams that should each be considered.
   */
  static int
 -circuit_resume_edge_reading_helper(edge_connection_t *conn,
 +circuit_resume_edge_reading_helper(edge_connection_t *first_conn,
                                     circuit_t *circ,
                                     crypt_path_t *layer_hint)
  {
 -  for ( ; conn; conn=conn->next_stream) {
 -    if (conn->_base.marked_for_close)
 +  edge_connection_t *conn;
 +  int n_packaging_streams, n_streams_left;
 +  int packaged_this_round;
 +  int cells_on_queue;
 +  int cells_per_conn;
 +  edge_connection_t *chosen_stream = NULL;
 +
 +  /* How many cells do we have space for?  It will be the minimum of
 +   * the number needed to exhaust the package window, and the minimum
 +   * needed to fill the cell queue. */
 +  int max_to_package = circ->package_window;
 +  if (CIRCUIT_IS_ORIGIN(circ)) {
 +    cells_on_queue = circ->n_conn_cells.n;
 +  } else {
 +    or_circuit_t *or_circ = TO_OR_CIRCUIT(circ);
 +    cells_on_queue = or_circ->p_conn_cells.n;
 +  }
 +  if (CELL_QUEUE_HIGHWATER_SIZE - cells_on_queue < max_to_package)
 +    max_to_package = CELL_QUEUE_HIGHWATER_SIZE - cells_on_queue;
 +
 +  /* Once we used to start listening on the streams in the order they
 +   * appeared in the linked list.  That leads to starvation on the
 +   * streams that appeared later on the list, since the first streams
 +   * would always get to read first.  Instead, we just pick a random
 +   * stream on the list, and enable reading for streams starting at that
 +   * point (and wrapping around as if the list were circular).  It would
 +   * probably be better to actually remember which streams we've
 +   * serviced in the past, but this is simple and effective. */
 +
 +  /* Select a stream uniformly at random from the linked list.  We
 +   * don't need cryptographic randomness here. */
 +  {
 +    int num_streams = 0;
 +    for (conn = first_conn; conn; conn = conn->next_stream) {
 +      num_streams++;
 +      if ((tor_weak_random() % num_streams)==0)
 +        chosen_stream = conn;
 +      /* Invariant: chosen_stream has been chosen uniformly at random from
 +       * among the first num_streams streams on first_conn. */
 +    }
 +  }
 +
 +  /* Count how many non-marked streams there are that have anything on
 +   * their inbuf, and enable reading on all of the connections. */
 +  n_packaging_streams = 0;
 +  /* Activate reading starting from the chosen stream */
 +  for (conn=chosen_stream; conn; conn = conn->next_stream) {
 +    /* Start reading for the streams starting from here */
 +    if (conn->_base.marked_for_close || conn->package_window <= 0)
        continue;
 -    if ((!layer_hint && conn->package_window > 0) ||
 -        (layer_hint && conn->package_window > 0 &&
 -         conn->cpath_layer == layer_hint)) {
 +    if (!layer_hint || conn->cpath_layer == layer_hint) {
        connection_start_reading(TO_CONN(conn));
 +
 +      if (buf_datalen(conn->_base.inbuf) > 0)
 +        ++n_packaging_streams;
 +    }
 +  }
 +  /* Go back and do the ones we skipped, circular-style */
 +  for (conn = first_conn; conn != chosen_stream; conn = conn->next_stream) {
 +    if (conn->_base.marked_for_close || conn->package_window <= 0)
 +      continue;
 +    if (!layer_hint || conn->cpath_layer == layer_hint) {
 +      connection_start_reading(TO_CONN(conn));
 +
 +      if (buf_datalen(conn->_base.inbuf) > 0)
 +        ++n_packaging_streams;
 +    }
 +  }
 +
 +  if (n_packaging_streams == 0) /* avoid divide-by-zero */
 +    return 0;
 +
 + again:
 +
 +  cells_per_conn = CEIL_DIV(max_to_package, n_packaging_streams);
 +
 +  packaged_this_round = 0;
 +  n_streams_left = 0;
 +
 +  /* Iterate over all connections.  Package up to cells_per_conn cells on
 +   * each.  Update packaged_this_round with the total number of cells
 +   * packaged, and n_streams_left with the number that still have data to
 +   * package.
 +   */
 +  for (conn=first_conn; conn; conn=conn->next_stream) {
 +    if (conn->_base.marked_for_close || conn->package_window <= 0)
 +      continue;
 +    if (!layer_hint || conn->cpath_layer == layer_hint) {
 +      int n = cells_per_conn, r;
        /* handle whatever might still be on the inbuf */
 -      if (connection_edge_package_raw_inbuf(conn, 1)<0) {
 -        /* (We already sent an end cell if possible) */
 +      r = connection_edge_package_raw_inbuf(conn, 1, &n);
 +
 +      /* Note how many we packaged */
 +      packaged_this_round += (cells_per_conn-n);
 +
 +      if (r<0) {
 +        /* Problem while packaging. (We already sent an end cell if
 +         * possible) */
          connection_mark_for_close(TO_CONN(conn));
          continue;
        }
  
 +      /* If there's still data to read, we'll be coming back to this stream. */
 +      if (buf_datalen(conn->_base.inbuf))
 +          ++n_streams_left;
 +
        /* If the circuit won't accept any more data, return without looking
         * at any more of the streams. Any connections that should be stopped
         * have already been stopped by connection_edge_package_raw_inbuf. */
        if (circuit_consider_stop_edge_reading(circ, layer_hint))
          return -1;
 +      /* XXXX should we also stop immediately if we fill up the cell queue?
 +       * Probably. */
      }
    }
 +
 +  /* If we made progress, and we are willing to package more, and there are
 +   * any streams left that want to package stuff... try again!
 +   */
 +  if (packaged_this_round && packaged_this_round < max_to_package &&
 +      n_streams_left) {
 +    max_to_package -= packaged_this_round;
 +    n_packaging_streams = n_streams_left;
 +    goto again;
 +  }
 +
    return 0;
  }
  
@@@ -1669,6 -1488,13 +1669,6 @@@ circuit_consider_sending_sendme(circuit
    }
  }
  
 -/** Stop reading on edge connections when we have this many cells
 - * waiting on the appropriate queue. */
 -#define CELL_QUEUE_HIGHWATER_SIZE 256
 -/** Start reading from edge connections again when we get down to this many
 - * cells. */
 -#define CELL_QUEUE_LOWWATER_SIZE 64
 -
  #ifdef ACTIVE_CIRCUITS_PARANOIA
  #define assert_active_circuits_ok_paranoid(conn) \
       assert_active_circuits_ok(conn)
@@@ -1682,10 -1508,6 +1682,10 @@@ static int total_cells_allocated = 0
  /** A memory pool to allocate packed_cell_t objects. */
  static mp_pool_t *cell_pool = NULL;
  
 +/** Memory pool to allocate insertion_time_elem_t objects used for cell
 + * statistics. */
 +static mp_pool_t *it_pool = NULL;
 +
  /** Allocate structures to hold cells. */
  void
  init_cell_pool(void)
@@@ -1694,8 -1516,7 +1694,8 @@@
    cell_pool = mp_pool_new(sizeof(packed_cell_t), 128*1024);
  }
  
 -/** Free all storage used to hold cells. */
 +/** Free all storage used to hold cells (and insertion times if we measure
 + * cell statistics). */
  void
  free_cell_pool(void)
  {
@@@ -1704,10 -1525,6 +1704,10 @@@
      mp_pool_destroy(cell_pool);
      cell_pool = NULL;
    }
 +  if (it_pool) {
 +    mp_pool_destroy(it_pool);
 +    it_pool = NULL;
 +  }
  }
  
  /** Free excess storage in cell pool. */
@@@ -1720,7 -1537,7 +1720,7 @@@ clean_cell_pool(void
  
  /** Release storage held by <b>cell</b>. */
  static INLINE void
 -packed_cell_free(packed_cell_t *cell)
 +packed_cell_free_unchecked(packed_cell_t *cell)
  {
    --total_cells_allocated;
    mp_pool_release(cell);
@@@ -1782,38 -1599,7 +1782,38 @@@ cell_queue_append(cell_queue_t *queue, 
  void
  cell_queue_append_packed_copy(cell_queue_t *queue, const cell_t *cell)
  {
 -  cell_queue_append(queue, packed_cell_copy(cell));
 +  packed_cell_t *copy = packed_cell_copy(cell);
 +  /* Remember the time when this cell was put in the queue. */
 +  if (get_options()->CellStatistics) {
 +    struct timeval now;
 +    uint32_t added;
 +    insertion_time_queue_t *it_queue = queue->insertion_times;
 +    if (!it_pool)
 +      it_pool = mp_pool_new(sizeof(insertion_time_elem_t), 1024);
 +    tor_gettimeofday_cached(&now);
 +#define SECONDS_IN_A_DAY 86400L
 +    added = (uint32_t)(((now.tv_sec % SECONDS_IN_A_DAY) * 100L)
 +            + ((uint32_t)now.tv_usec / (uint32_t)10000L));
 +    if (!it_queue) {
 +      it_queue = tor_malloc_zero(sizeof(insertion_time_queue_t));
 +      queue->insertion_times = it_queue;
 +    }
 +    if (it_queue->last && it_queue->last->insertion_time == added) {
 +      it_queue->last->counter++;
 +    } else {
 +      insertion_time_elem_t *elem = mp_pool_get(it_pool);
 +      elem->next = NULL;
 +      elem->insertion_time = added;
 +      elem->counter = 1;
 +      if (it_queue->last) {
 +        it_queue->last->next = elem;
 +        it_queue->last = elem;
 +      } else {
 +        it_queue->first = it_queue->last = elem;
 +      }
 +    }
 +  }
 +  cell_queue_append(queue, copy);
  }
  
  /** Remove and free every cell in <b>queue</b>. */
@@@ -1824,19 -1610,11 +1824,19 @@@ cell_queue_clear(cell_queue_t *queue
    cell = queue->head;
    while (cell) {
      next = cell->next;
 -    packed_cell_free(cell);
 +    packed_cell_free_unchecked(cell);
      cell = next;
    }
    queue->head = queue->tail = NULL;
    queue->n = 0;
 +  if (queue->insertion_times) {
 +    while (queue->insertion_times->first) {
 +      insertion_time_elem_t *elem = queue->insertion_times->first;
 +      queue->insertion_times->first = elem->next;
 +      mp_pool_release(elem);
 +    }
 +    tor_free(queue->insertion_times);
 +  }
  }
  
  /** Extract and return the cell at the head of <b>queue</b>; return NULL if
@@@ -1888,226 -1666,8 +1888,226 @@@ prev_circ_on_conn_p(circuit_t *circ, or
    }
  }
  
 +/** Helper for sorting cell_ewma_t values in their priority queue. */
 +static int
 +compare_cell_ewma_counts(const void *p1, const void *p2)
 +{
 +  const cell_ewma_t *e1=p1, *e2=p2;
 +  if (e1->cell_count < e2->cell_count)
 +    return -1;
 +  else if (e1->cell_count > e2->cell_count)
 +    return 1;
 +  else
 +    return 0;
 +}
 +
 +/** Given a cell_ewma_t, return a pointer to the circuit containing it. */
 +static circuit_t *
 +cell_ewma_to_circuit(cell_ewma_t *ewma)
 +{
 +  if (ewma->is_for_p_conn) {
 +    /* This is an or_circuit_t's p_cell_ewma. */
 +    or_circuit_t *orcirc = SUBTYPE_P(ewma, or_circuit_t, p_cell_ewma);
 +    return TO_CIRCUIT(orcirc);
 +  } else {
 +    /* This is some circuit's n_cell_ewma. */
 +    return SUBTYPE_P(ewma, circuit_t, n_cell_ewma);
 +  }
 +}
 +
 +/* ==== Functions for scaling cell_ewma_t ====
 +
 +   When choosing which cells to relay first, we favor circuits that have been
 +   quiet recently.  This gives better latency on connections that aren't
 +   pushing lots of data, and makes the network feel more interactive.
 +
 +   Conceptually, we take an exponentially weighted mean average of the number
 +   of cells a circuit has sent, and allow active circuits (those with cells to
 +   relay) to send cells in reverse order of their exponentially-weighted mean
 +   average (EWMA) cell count.  [That is, a cell sent N seconds ago 'counts'
 +   F^N times as much as a cell sent now, for 0<F<1.0, and we favor the
 +   circuit that has sent the fewest cells]
 +
 +   If 'double' had infinite precision, we could do this simply by counting a
 +   cell sent at startup as having weight 1.0, and a cell sent N seconds later
 +   as having weight F^-N.  This way, we would never need to re-scale
 +   any already-sent cells.
 +
 +   To prevent double from overflowing, we could count a cell sent now as
 +   having weight 1.0 and a cell sent N seconds ago as having weight F^N.
 +   This, however, would mean we'd need to re-scale *ALL* old circuits every
 +   time we wanted to send a cell.
 +
 +   So as a compromise, we divide time into 'ticks' (currently, 10-second
 +   increments) and say that a cell sent at the start of a current tick is
 +   worth 1.0, a cell sent N seconds before the start of the current tick is
 +   worth F^N, and a cell sent N seconds after the start of the current tick is
 +   worth F^-N.  This way we don't overflow, and we don't need to constantly
 +   rescale.
 + */
 +
 +/** How long does a tick last (seconds)? */
 +#define EWMA_TICK_LEN 10
 +
 +/** The default per-tick scale factor, if it hasn't been overridden by a
 + * consensus or a configuration setting.  zero means "disabled". */
 +#define EWMA_DEFAULT_HALFLIFE 0.0
 +
 +/** Given a timeval <b>now</b>, compute the cell_ewma tick in which it occurs
 + * and the fraction of the tick that has elapsed between the start of the tick
 + * and <b>now</b>.  Return the former and store the latter in
 + * *<b>remainder_out</b>.
 + *
 + * These tick values are not meant to be shared between Tor instances, or used
 + * for other purposes. */
 +static unsigned
 +cell_ewma_tick_from_timeval(const struct timeval *now,
 +                            double *remainder_out)
 +{
 +  unsigned res = (unsigned) (now->tv_sec / EWMA_TICK_LEN);
 +  /* rem */
 +  double rem = (now->tv_sec % EWMA_TICK_LEN) +
 +    ((double)(now->tv_usec)) / 1.0e6;
 +  *remainder_out = rem / EWMA_TICK_LEN;
 +  return res;
 +}
 +
 +/** Compute and return the current cell_ewma tick. */
 +unsigned
 +cell_ewma_get_tick(void)
 +{
 +  return ((unsigned)approx_time() / EWMA_TICK_LEN);
 +}
 +
 +/** The per-tick scale factor to be used when computing cell-count EWMA
 + * values.  (A cell sent N ticks before the start of the current tick
 + * has value ewma_scale_factor ** N.)
 + */
 +static double ewma_scale_factor = 0.1;
 +static int ewma_enabled = 0;
 +
 +#define EPSILON 0.00001
 +#define LOG_ONEHALF -0.69314718055994529
 +
 +/** Adjust the global cell scale factor based on <b>options</b> */
 +void
 +cell_ewma_set_scale_factor(or_options_t *options, networkstatus_t *consensus)
 +{
 +  int32_t halflife_ms;
 +  double halflife;
 +  const char *source;
 +  if (options && options->CircuitPriorityHalflife >= -EPSILON) {
 +    halflife = options->CircuitPriorityHalflife;
 +    source = "CircuitPriorityHalflife in configuration";
 +  } else if (consensus &&
 +             (halflife_ms = networkstatus_get_param(
 +                   consensus, "CircuitPriorityHalflifeMsec", -1)) >= 0) {
 +    halflife = ((double)halflife_ms)/1000.0;
 +    source = "CircuitPriorityHalflifeMsec in consensus";
 +  } else {
 +    halflife = EWMA_DEFAULT_HALFLIFE;
 +    source = "Default value";
 +  }
 +
 +  if (halflife <= EPSILON) {
 +    /* The cell EWMA algorithm is disabled. */
 +    ewma_scale_factor = 0.1;
 +    ewma_enabled = 0;
 +    log_info(LD_OR,
 +             "Disabled cell_ewma algorithm because of value in %s",
 +             source);
 +  } else {
 +    /* convert halflife into halflife-per-tick. */
 +    halflife /= EWMA_TICK_LEN;
 +    /* compute per-tick scale factor. */
 +    ewma_scale_factor = exp( LOG_ONEHALF / halflife );
 +    ewma_enabled = 1;
 +    log_info(LD_OR,
 +             "Enabled cell_ewma algorithm because of value in %s; "
 +             "scale factor is %lf per %d seconds",
 +             source, ewma_scale_factor, EWMA_TICK_LEN);
 +  }
 +}
 +
 +/** Return the multiplier necessary to convert the value of a cell sent in
 + * 'from_tick' to one sent in 'to_tick'. */
 +static INLINE double
 +get_scale_factor(unsigned from_tick, unsigned to_tick)
 +{
 +  /* This math can wrap around, but that's okay: unsigned overflow is
 +     well-defined */
 +  int diff = (int)(to_tick - from_tick);
 +  return pow(ewma_scale_factor, diff);
 +}
 +
 +/** Adjust the cell count of <b>ewma</b> so that it is scaled with respect to
 + * <b>cur_tick</b> */
 +static void
 +scale_single_cell_ewma(cell_ewma_t *ewma, unsigned cur_tick)
 +{
 +  double factor = get_scale_factor(ewma->last_adjusted_tick, cur_tick);
 +  ewma->cell_count *= factor;
 +  ewma->last_adjusted_tick = cur_tick;
 +}
 +
 +/** Adjust the cell count of every active circuit on <b>conn</b> so
 + * that they are scaled with respect to <b>cur_tick</b> */
 +static void
 +scale_active_circuits(or_connection_t *conn, unsigned cur_tick)
 +{
 +
 +  double factor = get_scale_factor(
 +              conn->active_circuit_pqueue_last_recalibrated,
 +              cur_tick);
 +  /** Ordinarily it isn't okay to change the value of an element in a heap,
 +   * but it's okay here, since we are preserving the order. */
 +  SMARTLIST_FOREACH(conn->active_circuit_pqueue, cell_ewma_t *, e, {
 +      tor_assert(e->last_adjusted_tick ==
 +                 conn->active_circuit_pqueue_last_recalibrated);
 +      e->cell_count *= factor;
 +      e->last_adjusted_tick = cur_tick;
 +  });
 +  conn->active_circuit_pqueue_last_recalibrated = cur_tick;
 +}
 +
 +/** Rescale <b>ewma</b> to the same scale as <b>conn</b>, and add it to
 + * <b>conn</b>'s priority queue of active circuits */
 +static void
 +add_cell_ewma_to_conn(or_connection_t *conn, cell_ewma_t *ewma)
 +{
 +  tor_assert(ewma->heap_index == -1);
 +  scale_single_cell_ewma(ewma,
 +                         conn->active_circuit_pqueue_last_recalibrated);
 +
 +  smartlist_pqueue_add(conn->active_circuit_pqueue,
 +                       compare_cell_ewma_counts,
 +                       STRUCT_OFFSET(cell_ewma_t, heap_index),
 +                       ewma);
 +}
 +
 +/** Remove <b>ewma</b> from <b>conn</b>'s priority queue of active circuits */
 +static void
 +remove_cell_ewma_from_conn(or_connection_t *conn, cell_ewma_t *ewma)
 +{
 +  tor_assert(ewma->heap_index != -1);
 +  smartlist_pqueue_remove(conn->active_circuit_pqueue,
 +                          compare_cell_ewma_counts,
 +                          STRUCT_OFFSET(cell_ewma_t, heap_index),
 +                          ewma);
 +}
 +
 +/** Remove and return the first cell_ewma_t from conn's priority queue of
 + * active circuits.  Requires that the priority queue is nonempty. */
 +static cell_ewma_t *
 +pop_first_cell_ewma_from_conn(or_connection_t *conn)
 +{
 +  return smartlist_pqueue_pop(conn->active_circuit_pqueue,
 +                              compare_cell_ewma_counts,
 +                              STRUCT_OFFSET(cell_ewma_t, heap_index));
 +}
 +
  /** Add <b>circ</b> to the list of circuits with pending cells on
 - * <b>conn</b>.  No effect if <b>circ</b> is already unlinked. */
 + * <b>conn</b>.  No effect if <b>circ</b> is already linked. */
  void
  make_circuit_active_on_conn(circuit_t *circ, or_connection_t *conn)
  {
@@@ -2119,8 -1679,6 +2119,8 @@@
      return;
    }
  
 +  assert_active_circuits_ok_paranoid(conn);
 +
    if (! conn->active_circuits) {
      conn->active_circuits = circ;
      *prevp = *nextp = circ;
@@@ -2132,19 -1690,10 +2132,19 @@@
      *prev_circ_on_conn_p(head, conn) = circ;
      *prevp = old_tail;
    }
 +
 +  if (circ->n_conn == conn) {
 +    add_cell_ewma_to_conn(conn, &circ->n_cell_ewma);
 +  } else {
 +    or_circuit_t *orcirc = TO_OR_CIRCUIT(circ);
 +    tor_assert(conn == orcirc->p_conn);
 +    add_cell_ewma_to_conn(conn, &orcirc->p_cell_ewma);
 +  }
 +
    assert_active_circuits_ok_paranoid(conn);
  }
  
 -/** Remove <b>circ</b> to the list of circuits with pending cells on
 +/** Remove <b>circ</b> from the list of circuits with pending cells on
   * <b>conn</b>.  No effect if <b>circ</b> is already unlinked. */
  void
  make_circuit_inactive_on_conn(circuit_t *circ, or_connection_t *conn)
@@@ -2158,8 -1707,6 +2158,8 @@@
      return;
    }
  
 +  assert_active_circuits_ok_paranoid(conn);
 +
    tor_assert(next && prev);
    tor_assert(*prev_circ_on_conn_p(next, conn) == circ);
    tor_assert(*next_circ_on_conn_p(prev, conn) == circ);
@@@ -2173,15 -1720,6 +2173,15 @@@
        conn->active_circuits = next;
    }
    *prevp = *nextp = NULL;
 +
 +  if (circ->n_conn == conn) {
 +    remove_cell_ewma_from_conn(conn, &circ->n_cell_ewma);
 +  } else {
 +    or_circuit_t *orcirc = TO_OR_CIRCUIT(circ);
 +    tor_assert(conn == orcirc->p_conn);
 +    remove_cell_ewma_from_conn(conn, &orcirc->p_cell_ewma);
 +  }
 +
    assert_active_circuits_ok_paranoid(conn);
  }
  
@@@ -2201,27 -1739,16 +2201,27 @@@ connection_or_unlink_all_active_circs(o
      cur = next;
    } while (cur != head);
    orconn->active_circuits = NULL;
 +
 +  SMARTLIST_FOREACH(orconn->active_circuit_pqueue, cell_ewma_t *, e,
 +                    e->heap_index = -1);
 +  smartlist_clear(orconn->active_circuit_pqueue);
  }
  
  /** Block (if <b>block</b> is true) or unblock (if <b>block</b> is false)
   * every edge connection that is using <b>circ</b> to write to <b>orconn</b>,
 - * and start or stop reading as appropriate. */
 -static void
 + * and start or stop reading as appropriate.
 + *
 + * If <b>stream_id</b> is nonzero, block only the edge connection whose
 + * stream_id matches it.
 + *
 + * Returns the number of streams whose status we changed.
 + */
 +static int
  set_streams_blocked_on_circ(circuit_t *circ, or_connection_t *orconn,
 -                            int block)
 +                            int block, streamid_t stream_id)
  {
    edge_connection_t *edge = NULL;
 +  int n = 0;
    if (circ->n_conn == orconn) {
      circ->streams_blocked_on_n_conn = block;
      if (CIRCUIT_IS_ORIGIN(circ))
@@@ -2234,13 -1761,7 +2234,13 @@@
  
    for (; edge; edge = edge->next_stream) {
      connection_t *conn = TO_CONN(edge);
 -    edge->edge_blocked_on_circ = block;
 +    if (stream_id && edge->stream_id != stream_id)
 +      continue;
 +
 +    if (edge->edge_blocked_on_circ != block) {
 +      ++n;
 +      edge->edge_blocked_on_circ = block;
 +    }
  
      if (!conn->read_event) {
        /* This connection is a placeholder for something; probably a DNS
@@@ -2257,12 -1778,10 +2257,12 @@@
          connection_start_reading(conn);
      }
    }
 +
 +  return n;
  }
  
  /** Pull as many cells as possible (but no more than <b>max</b>) from the
 - * queue of the first active circuit on <b>conn</b>, and write then to
 + * queue of the first active circuit on <b>conn</b>, and write them to
   * <b>conn</b>-&gt;outbuf.  Return the number of cells written.  Advance
   * the active circuit pointer to the next active circuit in the ring. */
  int
@@@ -2273,35 -1792,9 +2273,35 @@@ connection_or_flush_from_first_active_c
    cell_queue_t *queue;
    circuit_t *circ;
    int streams_blocked;
 +
 +  /* The current (hi-res) time */
 +  struct timeval now_hires;
 +
 +  /* The EWMA cell counter for the circuit we're flushing. */
 +  cell_ewma_t *cell_ewma = NULL;
 +  double ewma_increment = -1;
 +
    circ = conn->active_circuits;
    if (!circ) return 0;
    assert_active_circuits_ok_paranoid(conn);
 +
 +  /* See if we're doing the ewma circuit selection algorithm. */
 +  if (ewma_enabled) {
 +    unsigned tick;
 +    double fractional_tick;
 +    tor_gettimeofday_cached(&now_hires);
 +    tick = cell_ewma_tick_from_timeval(&now_hires, &fractional_tick);
 +
 +    if (tick != conn->active_circuit_pqueue_last_recalibrated) {
 +      scale_active_circuits(conn, tick);
 +    }
 +
 +    ewma_increment = pow(ewma_scale_factor, -fractional_tick);
 +
 +    cell_ewma = smartlist_get(conn->active_circuit_pqueue, 0);
 +    circ = cell_ewma_to_circuit(cell_ewma);
 +  }
 +
    if (circ->n_conn == conn) {
      queue = &circ->n_conn_cells;
      streams_blocked = circ->streams_blocked_on_n_conn;
@@@ -2315,60 -1808,10 +2315,60 @@@
      packed_cell_t *cell = cell_queue_pop(queue);
      tor_assert(*next_circ_on_conn_p(circ,conn));
  
 +    /* Calculate the exact time that this cell has spent in the queue. */
 +    if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) {
 +      struct timeval now;
 +      uint32_t flushed;
 +      uint32_t cell_waiting_time;
 +      insertion_time_queue_t *it_queue = queue->insertion_times;
 +      tor_gettimeofday_cached(&now);
 +      flushed = (uint32_t)((now.tv_sec % SECONDS_IN_A_DAY) * 100L +
 +                 (uint32_t)now.tv_usec / (uint32_t)10000L);
 +      if (!it_queue || !it_queue->first) {
 +        log_info(LD_GENERAL, "Cannot determine insertion time of cell. "
 +                             "Looks like the CellStatistics option was "
 +                             "recently enabled.");
 +      } else {
 +        or_circuit_t *orcirc = TO_OR_CIRCUIT(circ);
 +        insertion_time_elem_t *elem = it_queue->first;
 +        cell_waiting_time =
 +            (uint32_t)((flushed * 10L + SECONDS_IN_A_DAY * 1000L -
 +                        elem->insertion_time * 10L) %
 +                       (SECONDS_IN_A_DAY * 1000L));
 +#undef SECONDS_IN_A_DAY
 +        elem->counter--;
 +        if (elem->counter < 1) {
 +          it_queue->first = elem->next;
 +          if (elem == it_queue->last)
 +            it_queue->last = NULL;
 +          mp_pool_release(elem);
 +        }
 +        orcirc->total_cell_waiting_time += cell_waiting_time;
 +        orcirc->processed_cells++;
 +      }
 +    }
 +
 +    /* If we just flushed our queue and this circuit is used for a
 +     * tunneled directory request, possibly advance its state. */
 +    if (queue->n == 0 && TO_CONN(conn)->dirreq_id)
 +      geoip_change_dirreq_state(TO_CONN(conn)->dirreq_id,
 +                                DIRREQ_TUNNELED,
 +                                DIRREQ_CIRC_QUEUE_FLUSHED);
 +
      connection_write_to_buf(cell->body, CELL_NETWORK_SIZE, TO_CONN(conn));
  
 -    packed_cell_free(cell);
 +    packed_cell_free_unchecked(cell);
      ++n_flushed;
 +    if (cell_ewma) {
 +      cell_ewma_t *tmp;
 +      cell_ewma->cell_count += ewma_increment;
 +      /* We pop and re-add the cell_ewma_t here, not above, since we need to
 +       * re-add it immediately to keep the priority queue consistent with
 +       * the linked-list implementation */
 +      tmp = pop_first_cell_ewma_from_conn(conn);
 +      tor_assert(tmp == cell_ewma);
 +      add_cell_ewma_to_conn(conn, cell_ewma);
 +    }
      if (circ != conn->active_circuits) {
        /* If this happens, the current circuit just got made inactive by
         * a call in connection_write_to_buf().  That's nothing to worry about:
@@@ -2386,9 -1829,9 +2386,9 @@@
    /* Is the cell queue low enough to unblock all the streams that are waiting
     * to write to this circuit? */
    if (streams_blocked && queue->n <= CELL_QUEUE_LOWWATER_SIZE)
 -    set_streams_blocked_on_circ(circ, conn, 0); /* unblock streams */
 +    set_streams_blocked_on_circ(circ, conn, 0, 0); /* unblock streams */
  
 -  /* Did we just ran out of cells on this queue? */
 +  /* Did we just run out of cells on this circuit's queue? */
    if (queue->n == 0) {
      log_debug(LD_GENERAL, "Made a circuit inactive.");
      make_circuit_inactive_on_conn(circ, conn);
@@@ -2403,14 -1846,10 +2403,14 @@@
   * transmitting in <b>direction</b>. */
  void
  append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn,
 -                             cell_t *cell, cell_direction_t direction)
 +                             cell_t *cell, cell_direction_t direction,
 +                             streamid_t fromstream)
  {
    cell_queue_t *queue;
    int streams_blocked;
 +  if (circ->marked_for_close)
 +    return;
 +
    if (direction == CELL_DIRECTION_OUT) {
      queue = &circ->n_conn_cells;
      streams_blocked = circ->streams_blocked_on_n_conn;
@@@ -2429,12 -1868,7 +2429,12 @@@
    /* If we have too many cells on the circuit, we should stop reading from
     * the edge streams for a while. */
    if (!streams_blocked && queue->n >= CELL_QUEUE_HIGHWATER_SIZE)
 -    set_streams_blocked_on_circ(circ, orconn, 1); /* block streams */
 +    set_streams_blocked_on_circ(circ, orconn, 1, 0); /* block streams */
 +
 +  if (streams_blocked && fromstream) {
 +    /* This edge connection is apparently not blocked; block it. */
 +    set_streams_blocked_on_circ(circ, orconn, 1, fromstream);
 +  }
  
    if (queue->n == 1) {
      /* This was the first cell added to the queue.  We need to make this
@@@ -2513,25 -1947,6 +2513,25 @@@ decode_address_from_payload(tor_addr_t 
    return payload + 2 + payload[1];
  }
  
 +/** Remove all the cells queued on <b>circ</b> for <b>orconn</b>. */
 +void
 +circuit_clear_cell_queue(circuit_t *circ, or_connection_t *orconn)
 +{
 +  cell_queue_t *queue;
 +  if (circ->n_conn == orconn) {
 +    queue = &circ->n_conn_cells;
 +  } else {
 +    or_circuit_t *orcirc = TO_OR_CIRCUIT(circ);
 +    tor_assert(orcirc->p_conn == orconn);
 +    queue = &orcirc->p_conn_cells;
 +  }
 +
 +  if (queue->n)
 +    make_circuit_inactive_on_conn(circ,orconn);
 +
 +  cell_queue_clear(queue);
 +}
 +
  /** Fail with an assert if the active circuits ring on <b>orconn</b> is
   * corrupt.  */
  void
@@@ -2539,44 -1954,16 +2539,44 @@@ assert_active_circuits_ok(or_connection
  {
    circuit_t *head = orconn->active_circuits;
    circuit_t *cur = head;
 +  int n = 0;
    if (! head)
      return;
    do {
      circuit_t *next = *next_circ_on_conn_p(cur, orconn);
      circuit_t *prev = *prev_circ_on_conn_p(cur, orconn);
 +    cell_ewma_t *ewma;
      tor_assert(next);
      tor_assert(prev);
      tor_assert(*next_circ_on_conn_p(prev, orconn) == cur);
      tor_assert(*prev_circ_on_conn_p(next, orconn) == cur);
 +    if (orconn == cur->n_conn) {
 +      ewma = &cur->n_cell_ewma;
 +      tor_assert(!ewma->is_for_p_conn);
 +    } else {
 +      ewma = &TO_OR_CIRCUIT(cur)->p_cell_ewma;
 +      tor_assert(ewma->is_for_p_conn);
 +    }
 +    tor_assert(ewma->heap_index != -1);
 +    tor_assert(ewma == smartlist_get(orconn->active_circuit_pqueue,
 +                                     ewma->heap_index));
 +    n++;
      cur = next;
    } while (cur != head);
 +
 +  tor_assert(n == smartlist_len(orconn->active_circuit_pqueue));
 +}
 +
 +/** Return 1 if we shouldn't restart reading on this circuit, even if
 + * we get a SENDME.  Else return 0.
 +*/
 +static int
 +circuit_queue_streams_are_blocked(circuit_t *circ)
 +{
 +  if (CIRCUIT_IS_ORIGIN(circ)) {
 +    return circ->streams_blocked_on_n_conn;
 +  } else {
 +    return circ->streams_blocked_on_p_conn;
 +  }
  }
  
diff --combined src/or/rendclient.c
index ab96807,edd24d8..7c626c6
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -8,19 -8,6 +8,19 @@@
   **/
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "connection_edge.h"
 +#include "directory.h"
 +#include "main.h"
 +#include "relay.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rephist.h"
 +#include "routerlist.h"
  
  /** Called when we've established a circuit to an introduction point:
   * send the introduction request. */
@@@ -76,7 -63,7 +76,7 @@@ rend_client_send_introduction(origin_ci
    rend_cache_entry_t *entry;
    crypt_path_t *cpath;
    off_t dh_offset;
 -  crypto_pk_env_t *intro_key; /* either Bob's public key or an intro key. */
 +  crypto_pk_env_t *intro_key = NULL;
  
    tor_assert(introcirc->_base.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
    tor_assert(rendcirc->_base.purpose == CIRCUIT_PURPOSE_C_REND_READY);
@@@ -89,26 -76,48 +89,26 @@@
                                &entry) < 1) {
      log_warn(LD_REND,
               "query %s didn't have valid rend desc in cache. Failing.",
 -             escaped_safe_str(introcirc->rend_data->onion_address));
 +             escaped_safe_str_client(introcirc->rend_data->onion_address));
      goto err;
    }
  
 -  /* first 20 bytes of payload are the hash of Bob's pk */
 -  if (entry->parsed->version == 0) { /* un-versioned descriptor */
 -    intro_key = entry->parsed->pk;
 -  } else { /* versioned descriptor */
 -    intro_key = NULL;
 -    SMARTLIST_FOREACH(entry->parsed->intro_nodes, rend_intro_point_t *,
 -                      intro, {
 -      if (!memcmp(introcirc->build_state->chosen_exit->identity_digest,
 -                  intro->extend_info->identity_digest, DIGEST_LEN)) {
 -        intro_key = intro->intro_key;
 -        break;
 -      }
 -    });
 -    if (!intro_key) {
 -      /** XXX This case probably means that the intro point vanished while
 -       * we were building a circuit to it. In the future, we should find
 -       * out how that happened and whether we should kill the circuits to
 -       * removed intro points immediately. See task 1073. */
 -      int num_intro_points = smartlist_len(entry->parsed->intro_nodes);
 -      if (rend_cache_lookup_entry(introcirc->rend_data->onion_address,
 -          0, &entry) > 0) {
 -        log_info(LD_REND, "We have both a v0 and a v2 rend desc for this "
 -                 "service. The v2 desc doesn't contain the introduction "
 -                 "point (and key) to send an INTRODUCE1/2 cell to this "
 -                 "introduction point. Assuming the introduction point "
 -                 "is for v0 rend clients and using the service key "
 -                 "from the v0 desc instead. (This is probably a bug, "
 -                 "because we shouldn't even have both a v0 and a v2 "
 -                 "descriptor for the same service.)");
 -        /* See flyspray task 1024. */
 -        intro_key = entry->parsed->pk;
 -      } else {
 -        log_info(LD_REND, "Internal error: could not find intro key; we "
 -                 "only have a v2 rend desc with %d intro points.",
 -                 num_intro_points);
 -        goto err;
 -      }
 +  /* first 20 bytes of payload are the hash of the intro key */
 +  intro_key = NULL;
 +  SMARTLIST_FOREACH(entry->parsed->intro_nodes, rend_intro_point_t *,
 +                    intro, {
 +    if (!memcmp(introcirc->build_state->chosen_exit->identity_digest,
 +                intro->extend_info->identity_digest, DIGEST_LEN)) {
 +      intro_key = intro->intro_key;
 +      break;
      }
 +  });
 +  if (!intro_key) {
 +    log_info(LD_REND, "Our introduction point knowledge changed in "
 +             "mid-connect! Could not find intro key; we only have a "
 +             "v2 rend desc with %d intro points. Giving up.",
 +             smartlist_len(entry->parsed->intro_nodes));
 +    goto err;
    }
    if (crypto_pk_get_digest(intro_key, payload)<0) {
      log_warn(LD_BUG, "Internal error: couldn't hash public key.");
@@@ -209,7 -218,7 +209,7 @@@
    introcirc->_base.purpose = CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT;
  
    return 0;
 -err:
 + err:
    circuit_mark_for_close(TO_CIRCUIT(introcirc), END_CIRC_REASON_INTERNAL);
    circuit_mark_for_close(TO_CIRCUIT(rendcirc), END_CIRC_REASON_INTERNAL);
    return -1;
@@@ -282,7 -291,7 +282,7 @@@ rend_client_introduction_acked(origin_c
        extend_info = rend_client_get_random_intro(circ->rend_data);
        if (!extend_info) {
          log_warn(LD_REND, "No introduction points left for %s. Closing.",
 -                 escaped_safe_str(circ->rend_data->onion_address));
 +                 escaped_safe_str_client(circ->rend_data->onion_address));
          circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
          return -1;
        }
@@@ -290,7 -299,7 +290,7 @@@
          log_info(LD_REND,
                   "Got nack for %s from %s. Re-extending circ %d, "
                   "this time to %s.",
 -                 escaped_safe_str(circ->rend_data->onion_address),
 +                 escaped_safe_str_client(circ->rend_data->onion_address),
                   circ->build_state->chosen_exit->nickname,
                   circ->_base.n_circ_id, extend_info->nickname);
          result = circuit_extend_to_new_exit(circ, extend_info);
@@@ -298,7 -307,7 +298,7 @@@
          log_info(LD_REND,
                   "Got nack for %s from %s. Building a new introduction "
                   "circuit, this time to %s.",
 -                 escaped_safe_str(circ->rend_data->onion_address),
 +                 escaped_safe_str_client(circ->rend_data->onion_address),
                   circ->build_state->chosen_exit->nickname,
                   extend_info->nickname);
          circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
@@@ -461,21 -470,45 +461,21 @@@ directory_get_from_hs_dir(const char *d
             rend_query->onion_address, desc_id_base32,
             rend_query->auth_type,
             (rend_query->auth_type == REND_NO_AUTH ? "[none]" :
 -           escaped_safe_str(descriptor_cookie_base64)),
 +           escaped_safe_str_client(descriptor_cookie_base64)),
             hs_dir->nickname, hs_dir->dir_port);
    return 1;
  }
  
 -/** If we are not currently fetching a rendezvous service descriptor
 - * for the service ID <b>query</b>, start a directory connection to fetch a
 - * new one.
 - */
 -void
 -rend_client_refetch_renddesc(const char *query)
 -{
 -  if (!get_options()->FetchHidServDescriptors)
 -    return;
 -  log_info(LD_REND, "Fetching rendezvous descriptor for service %s",
 -           escaped_safe_str(query));
 -  if (connection_get_by_type_state_rendquery(CONN_TYPE_DIR, 0, query, 0)) {
 -    log_info(LD_REND,"Would fetch a new renddesc here (for %s), but one is "
 -             "already in progress.", escaped_safe_str(query));
 -  } else {
 -    /* not one already; initiate a dir rend desc lookup */
 -    directory_get_from_dirserver(DIR_PURPOSE_FETCH_RENDDESC,
 -                                 ROUTER_PURPOSE_GENERAL, query,
 -                                 PDS_RETRY_IF_NO_SERVERS);
 -  }
 -}
 -
 -/** Start a connection to a hidden service directory to fetch a v2
 - * rendezvous service descriptor for the base32-encoded service ID
 - * <b>query</b>.
 - */
 +/** Unless we already have a descriptor for <b>rend_query</b> with at least
 + * one (possibly) working introduction point in it, start a connection to a
 + * hidden service directory to fetch a v2 rendezvous service descriptor. */
  void
  rend_client_refetch_v2_renddesc(const rend_data_t *rend_query)
  {
    char descriptor_id[DIGEST_LEN];
    int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
 -  int i, tries_left, r;
 +  int i, tries_left;
    rend_cache_entry_t *e = NULL;
 -  time_t now = time(NULL);
    tor_assert(rend_query);
    /* Are we configured to fetch descriptors? */
    if (!get_options()->FetchHidServDescriptors) {
@@@ -484,13 -517,15 +484,13 @@@
      return;
    }
    /* Before fetching, check if we already have the descriptor here. */
 -  r = rend_cache_lookup_entry(rend_query->onion_address, -1, &e);
 -  if (r > 0 && now - e->received < NUM_SECONDS_BEFORE_HS_REFETCH) {
 +  if (rend_cache_lookup_entry(rend_query->onion_address, -1, &e) > 0) {
      log_info(LD_REND, "We would fetch a v2 rendezvous descriptor, but we "
 -                      "already have a fresh copy of that descriptor here. "
 -                      "Not fetching.");
 +                      "already have that descriptor here. Not fetching.");
      return;
    }
    log_debug(LD_REND, "Fetching v2 rendezvous descriptor for service %s",
 -            safe_str(rend_query->onion_address));
 +            safe_str_client(rend_query->onion_address));
    /* Randomly iterate over the replicas until a descriptor can be fetched
     * from one of the consecutive nodes, or no options are left. */
    tries_left = REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
@@@ -516,8 -551,8 +516,8 @@@
    log_info(LD_REND, "Could not pick one of the responsible hidden "
                      "service directories to fetch descriptors, because "
                      "we already tried them all unsuccessfully.");
 -  /* Close pending connections (unless a v0 request is still going on). */
 -  rend_client_desc_trynow(rend_query->onion_address, 2);
 +  /* Close pending connections. */
 +  rend_client_desc_trynow(rend_query->onion_address);
    return;
  }
  
@@@ -538,13 -573,18 +538,13 @@@ rend_client_remove_intro_point(extend_i
    r = rend_cache_lookup_entry(rend_query->onion_address, -1, &ent);
    if (r<0) {
      log_warn(LD_BUG, "Malformed service ID %s.",
 -             escaped_safe_str(rend_query->onion_address));
 +             escaped_safe_str_client(rend_query->onion_address));
      return -1;
    }
    if (r==0) {
      log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.",
 -             escaped_safe_str(rend_query->onion_address));
 -    /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
 -     * arrives first. Exception: When using client authorization, only
 -     * fetch v2 descriptors.*/
 +             escaped_safe_str_client(rend_query->onion_address));
      rend_client_refetch_v2_renddesc(rend_query);
 -    if (rend_query->auth_type == REND_NO_AUTH)
 -      rend_client_refetch_renddesc(rend_query->onion_address);
      return 0;
    }
  
@@@ -561,13 -601,18 +561,13 @@@
    if (smartlist_len(ent->parsed->intro_nodes) == 0) {
      log_info(LD_REND,
               "No more intro points remain for %s. Re-fetching descriptor.",
 -             escaped_safe_str(rend_query->onion_address));
 -    /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
 -     * arrives first. Exception: When using client authorization, only
 -     * fetch v2 descriptors.*/
 +             escaped_safe_str_client(rend_query->onion_address));
      rend_client_refetch_v2_renddesc(rend_query);
 -    if (rend_query->auth_type == REND_NO_AUTH)
 -      rend_client_refetch_renddesc(rend_query->onion_address);
  
      /* move all pending streams back to renddesc_wait */
      while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
                                     AP_CONN_STATE_CIRCUIT_WAIT,
 -                                   rend_query->onion_address, -1))) {
 +                                   rend_query->onion_address))) {
        conn->state = AP_CONN_STATE_RENDDESC_WAIT;
      }
  
@@@ -575,7 -620,7 +575,7 @@@
    }
    log_info(LD_REND,"%d options left for %s.",
             smartlist_len(ent->parsed->intro_nodes),
 -           escaped_safe_str(rend_query->onion_address));
 +           escaped_safe_str_client(rend_query->onion_address));
    return 1;
  }
  
@@@ -637,8 -682,7 +637,8 @@@ rend_client_receive_rendezvous(origin_c
    tor_assert(circ->build_state->pending_final_cpath);
    hop = circ->build_state->pending_final_cpath;
    tor_assert(hop->dh_handshake_state);
 -  if (crypto_dh_compute_secret(hop->dh_handshake_state, (char*)request,
 +  if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN,
 +                               hop->dh_handshake_state, (char*)request,
                                 DH_KEY_LEN,
                                 keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
      log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
@@@ -678,18 -722,24 +678,18 @@@
    return -1;
  }
  
 -/** Find all the apconns in state AP_CONN_STATE_RENDDESC_WAIT that
 - * are waiting on query. If there's a working cache entry here
 - * with at least one intro point, move them to the next state. If
 - * <b>rend_version</b> is non-negative, fail connections that have
 - * requested <b>query</b> unless there are still descriptor fetch
 - * requests in progress for other descriptor versions than
 - * <b>rend_version</b>.
 - */
 +/** Find all the apconns in state AP_CONN_STATE_RENDDESC_WAIT that are
 + * waiting on <b>query</b>. If there's a working cache entry here with at
 + * least one intro point, move them to the next state. */
  void
 -rend_client_desc_trynow(const char *query, int rend_version)
 +rend_client_desc_trynow(const char *query)
  {
    edge_connection_t *conn;
    rend_cache_entry_t *entry;
    time_t now = time(NULL);
  
    smartlist_t *conns = get_connection_array();
 -  SMARTLIST_FOREACH(conns, connection_t *, _conn,
 -  {
 +  SMARTLIST_FOREACH_BEGIN(conns, connection_t *, _conn) {
      if (_conn->type != CONN_TYPE_AP ||
          _conn->state != AP_CONN_STATE_RENDDESC_WAIT ||
          _conn->marked_for_close)
@@@ -721,12 -771,17 +721,12 @@@
            connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH);
        }
      } else { /* 404, or fetch didn't get that far */
 -      /* Unless there are requests for another descriptor version pending,
 -       * close the connection. */
 -      if (rend_version >= 0 &&
 -          !connection_get_by_type_state_rendquery(CONN_TYPE_DIR, 0, query,
 -                                                  rend_version == 0 ? 2 : 0)) {
 -        log_notice(LD_REND,"Closing stream for '%s.onion': hidden service is "
 -                   "unavailable (try again later).", safe_str(query));
 -        connection_mark_unattached_ap(conn, END_STREAM_REASON_RESOLVEFAILED);
 -      }
 +      log_notice(LD_REND,"Closing stream for '%s.onion': hidden service is "
 +                 "unavailable (try again later).",
 +                 safe_str_client(query));
 +      connection_mark_unattached_ap(conn, END_STREAM_REASON_RESOLVEFAILED);
      }
 -  });
 +  } SMARTLIST_FOREACH_END(_conn);
  }
  
  /** Return a newly allocated extend_info_t* for a randomly chosen introduction
@@@ -744,7 -799,7 +744,7 @@@ rend_client_get_random_intro(const rend
    if (rend_cache_lookup_entry(rend_query->onion_address, -1, &entry) < 1) {
      log_warn(LD_REND,
               "Query '%s' didn't have valid rend desc in cache. Failing.",
 -             safe_str(rend_query->onion_address));
 +             safe_str_client(rend_query->onion_address));
      return NULL;
    }
  
@@@ -756,10 -811,7 +756,10 @@@
    intro = smartlist_get(entry->parsed->intro_nodes, i);
    /* Do we need to look up the router or is the extend info complete? */
    if (!intro->extend_info->onion_key) {
 -    router = router_get_by_nickname(intro->extend_info->nickname, 0);
 +    if (tor_digest_is_zero(intro->extend_info->identity_digest))
 +      router = router_get_by_hexdigest(intro->extend_info->nickname);
 +    else
 +      router = router_get_by_digest(intro->extend_info->identity_digest);
      if (!router) {
        log_info(LD_REND, "Unknown router with nickname '%s'; trying another.",
                 intro->extend_info->nickname);
@@@ -897,7 -949,8 +897,7 @@@ rend_parse_service_authorization(or_opt
   err:
    res = -1;
   done:
 -  if (auth)
 -    rend_service_authorization_free(auth);
 +  rend_service_authorization_free(auth);
    SMARTLIST_FOREACH(sl, char *, c, tor_free(c););
    smartlist_free(sl);
    if (!validate_only && res == 0) {
diff --combined src/or/rendcommon.c
index 85d97e0,1d96f3d..290e8f8
--- a/src/or/rendcommon.c
+++ b/src/or/rendcommon.c
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -9,15 -9,6 +9,15 @@@
   **/
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "config.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rendmid.h"
 +#include "rendservice.h"
 +#include "rephist.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  /** Return 0 if one and two are the same service ids, else -1 or 1 */
  int
@@@ -31,8 -22,6 +31,8 @@@ rend_cmp_service_ids(const char *one, c
  void
  rend_service_descriptor_free(rend_service_descriptor_t *desc)
  {
 +  if (!desc)
 +    return;
    if (desc->pk)
      crypto_free_pk_env(desc->pk);
    if (desc->intro_nodes) {
@@@ -136,8 -125,7 +136,8 @@@ rend_compute_v2_desc_id(char *desc_id_o
    if (!service_id ||
        strlen(service_id) != REND_SERVICE_ID_LEN_BASE32) {
      log_warn(LD_REND, "Could not compute v2 descriptor ID: "
 -                      "Illegal service ID: %s", safe_str(service_id));
 +                      "Illegal service ID: %s",
 +             safe_str(service_id));
      return -1;
    }
    if (replica >= REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS) {
@@@ -150,7 -138,7 +150,7 @@@
                      service_id, REND_SERVICE_ID_LEN_BASE32) < 0) {
      log_warn(LD_REND, "Could not compute v2 descriptor ID: "
                        "Illegal characters in service ID: %s",
 -             safe_str(service_id));
 +             safe_str_client(service_id));
      return -1;
    }
    /* Calculate current time-period. */
@@@ -415,7 -403,8 +415,7 @@@ rend_desc_v2_is_parsable(rend_encoded_v
                                           &test_intro_size,
                                           &test_encoded_size,
                                           &test_next, desc->desc_str);
 -  if (test_parsed)
 -    rend_service_descriptor_free(test_parsed);
 +  rend_service_descriptor_free(test_parsed);
    tor_free(test_intro_content);
    return (res >= 0);
  }
@@@ -425,8 -414,6 +425,8 @@@ voi
  rend_encoded_v2_service_descriptor_free(
    rend_encoded_v2_service_descriptor_t *desc)
  {
 +  if (!desc)
 +    return;
    tor_free(desc->desc_str);
    tor_free(desc);
  }
@@@ -435,11 -422,10 +435,11 @@@
  void
  rend_intro_point_free(rend_intro_point_t *intro)
  {
 -  if (intro->extend_info)
 -    extend_info_free(intro->extend_info);
 -  if (intro->intro_key)
 -    crypto_free_pk_env(intro->intro_key);
 +  if (!intro)
 +    return;
 +
 +  extend_info_free(intro->extend_info);
 +  crypto_free_pk_env(intro->intro_key);
    tor_free(intro);
  }
  
@@@ -632,8 -618,7 +632,8 @@@ rend_encode_v2_descriptors(smartlist_t 
      }
      if (router_append_dirobj_signature(desc_str + written,
                                         desc_len - written,
 -                                       desc_digest, service_key) < 0) {
 +                                       desc_digest, DIGEST_LEN,
 +                                       service_key) < 0) {
        log_warn(LD_BUG, "Couldn't sign desc.");
        rend_encoded_v2_service_descriptor_free(enc);
        goto err;
@@@ -670,6 -655,61 +670,6 @@@
    return seconds_valid;
  }
  
 -/** Encode a service descriptor for <b>desc</b>, and sign it with
 - * <b>key</b>. Store the descriptor in *<b>str_out</b>, and set
 - * *<b>len_out</b> to its length.
 - */
 -int
 -rend_encode_service_descriptor(rend_service_descriptor_t *desc,
 -                               crypto_pk_env_t *key,
 -                               char **str_out, size_t *len_out)
 -{
 -  char *cp;
 -  char *end;
 -  int i, r;
 -  size_t asn1len;
 -  size_t buflen =
 -         PK_BYTES*2*(smartlist_len(desc->intro_nodes)+2);/*Too long, but ok*/
 -  cp = *str_out = tor_malloc(buflen);
 -  end = cp + PK_BYTES*2*(smartlist_len(desc->intro_nodes)+1);
 -  r = crypto_pk_asn1_encode(desc->pk, cp+2, end-(cp+2));
 -  if (r < 0) {
 -    tor_free(*str_out);
 -    return -1;
 -  }
 -  asn1len = r;
 -  set_uint16(cp, htons((uint16_t)asn1len));
 -  cp += 2+asn1len;
 -  set_uint32(cp, htonl((uint32_t)desc->timestamp));
 -  cp += 4;
 -  set_uint16(cp, htons((uint16_t)smartlist_len(desc->intro_nodes)));
 -  cp += 2;
 -  for (i=0; i < smartlist_len(desc->intro_nodes); ++i) {
 -    rend_intro_point_t *intro = smartlist_get(desc->intro_nodes, i);
 -    char ipoint[HEX_DIGEST_LEN+2];
 -    const size_t ipoint_len = HEX_DIGEST_LEN+1;
 -    ipoint[0] = '$';
 -    base16_encode(ipoint+1, HEX_DIGEST_LEN+1,
 -                  intro->extend_info->identity_digest,
 -                  DIGEST_LEN);
 -    tor_assert(strlen(ipoint) == ipoint_len);
 -    /* Assert that appending ipoint and its NUL won't over overrun the
 -     * buffer. */
 -    tor_assert(cp + ipoint_len+1 < *str_out + buflen);
 -    memcpy(cp, ipoint, ipoint_len+1);
 -    cp += ipoint_len+1;
 -  }
 -  note_crypto_pk_op(REND_SERVER);
 -  r = crypto_pk_private_sign_digest(key, cp, *str_out, cp-*str_out);
 -  if (r<0) {
 -    tor_free(*str_out);
 -    return -1;
 -  }
 -  cp += r;
 -  *len_out = (size_t)(cp-*str_out);
 -  return 0;
 -}
 -
  /** Parse a service descriptor at <b>str</b> (<b>len</b> bytes).  On
   * success, return a newly alloced service_descriptor_t.  On failure,
   * return NULL.
@@@ -786,27 -826,22 +786,27 @@@ rend_cache_init(void
  
  /** Helper: free storage held by a single service descriptor cache entry. */
  static void
 -_rend_cache_entry_free(void *p)
 +rend_cache_entry_free(rend_cache_entry_t *e)
  {
 -  rend_cache_entry_t *e = p;
 +  if (!e)
 +    return;
    rend_service_descriptor_free(e->parsed);
    tor_free(e->desc);
    tor_free(e);
  }
  
 +static void
 +_rend_cache_entry_free(void *p)
 +{
 +  rend_cache_entry_free(p);
 +}
 +
  /** Free all storage held by the service descriptor cache. */
  void
  rend_cache_free_all(void)
  {
 -  if (rend_cache)
 -    strmap_free(rend_cache, _rend_cache_entry_free);
 -  if (rend_cache_v2_dir)
 -    digestmap_free(rend_cache_v2_dir, _rend_cache_entry_free);
 +  strmap_free(rend_cache, _rend_cache_entry_free);
 +  digestmap_free(rend_cache_v2_dir, _rend_cache_entry_free);
    rend_cache = NULL;
    rend_cache_v2_dir = NULL;
  }
@@@ -827,7 -862,7 +827,7 @@@ rend_cache_clean(void
      ent = (rend_cache_entry_t*)val;
      if (ent->parsed->timestamp < cutoff) {
        iter = strmap_iter_next_rmv(rend_cache, iter);
 -      _rend_cache_entry_free(ent);
 +      rend_cache_entry_free(ent);
      } else {
        iter = strmap_iter_next(rend_cache, iter);
      }
@@@ -853,9 -888,9 +853,9 @@@ rend_cache_clean_v2_descs_as_dir(void
        char key_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
        base32_encode(key_base32, sizeof(key_base32), key, DIGEST_LEN);
        log_info(LD_REND, "Removing descriptor with ID '%s' from cache",
 -               safe_str(key_base32));
 +               safe_str_client(key_base32));
        iter = digestmap_iter_next_rmv(rend_cache_v2_dir, iter);
 -      _rend_cache_entry_free(ent);
 +      rend_cache_entry_free(ent);
      } else {
        iter = digestmap_iter_next(rend_cache_v2_dir, iter);
      }
@@@ -931,11 -966,6 +931,11 @@@ rend_cache_lookup_entry(const char *que
    }
    if (!*e)
      return 0;
 +  tor_assert((*e)->parsed && (*e)->parsed->intro_nodes);
 +  /* XXX022 hack for now, to return "not found" if there are no intro
 +   * points remaining. See bug 997. */
 +  if (smartlist_len((*e)->parsed->intro_nodes) == 0)
 +    return 0;
    return 1;
  }
  
@@@ -1014,6 -1044,7 +1014,6 @@@ rend_cache_store(const char *desc, size
    char query[REND_SERVICE_ID_LEN_BASE32+1];
    char key[REND_SERVICE_ID_LEN_BASE32+2]; /* 0<query>\0 */
    time_t now;
 -  or_options_t *options = get_options();
    tor_assert(rend_cache);
    parsed = rend_parse_service_descriptor(desc,desc_len);
    if (!parsed) {
@@@ -1028,15 -1059,13 +1028,15 @@@
    now = time(NULL);
    if (parsed->timestamp < now-REND_CACHE_MAX_AGE-REND_CACHE_MAX_SKEW) {
      log_fn(LOG_PROTOCOL_WARN, LD_REND,
 -           "Service descriptor %s is too old.", safe_str(query));
 +           "Service descriptor %s is too old.",
 +           safe_str_client(query));
      rend_service_descriptor_free(parsed);
      return -2;
    }
    if (parsed->timestamp > now+REND_CACHE_MAX_SKEW) {
      log_fn(LOG_PROTOCOL_WARN, LD_REND,
 -           "Service descriptor %s is too far in the future.", safe_str(query));
 +           "Service descriptor %s is too far in the future.",
 +           safe_str_client(query));
      rend_service_descriptor_free(parsed);
      return -2;
    }
@@@ -1044,22 -1073,25 +1044,22 @@@
    tor_snprintf(key, sizeof(key), "2%s", query);
    if (!published && strmap_get_lc(rend_cache, key)) {
      log_info(LD_REND, "We already have a v2 descriptor for service %s.",
 -             safe_str(query));
 +             safe_str_client(query));
      rend_service_descriptor_free(parsed);
      return -1;
    }
 -  /* report novel publication to statistics */
 -  if (published && options->HSAuthorityRecordStats) {
 -    hs_usage_note_publish_total(query, now);
 -  }
    tor_snprintf(key, sizeof(key), "0%s", query);
    e = (rend_cache_entry_t*) strmap_get_lc(rend_cache, key);
    if (e && e->parsed->timestamp > parsed->timestamp) {
      log_info(LD_REND,"We already have a newer service descriptor %s with the "
 -             "same ID and version.", safe_str(query));
 +             "same ID and version.",
 +             safe_str_client(query));
      rend_service_descriptor_free(parsed);
      return 0;
    }
    if (e && e->len == desc_len && !memcmp(desc,e->desc,desc_len)) {
      log_info(LD_REND,"We already have this service descriptor %s.",
 -             safe_str(query));
 +             safe_str_client(query));
      e->received = time(NULL);
      rend_service_descriptor_free(parsed);
      return 0;
@@@ -1067,6 -1099,10 +1067,6 @@@
    if (!e) {
      e = tor_malloc_zero(sizeof(rend_cache_entry_t));
      strmap_set_lc(rend_cache, key, e);
 -    /* report novel publication to statistics */
 -    if (published && options->HSAuthorityRecordStats) {
 -      hs_usage_note_publish_novel(query, now);
 -    }
    } else {
      rend_service_descriptor_free(e->parsed);
      tor_free(e->desc);
@@@ -1078,7 -1114,7 +1078,7 @@@
    memcpy(e->desc, desc, desc_len);
  
    log_debug(LD_REND,"Successfully stored rend desc '%s', len %d.",
 -            safe_str(query), (int)desc_len);
 +            safe_str_client(query), (int)desc_len);
    return 1;
  }
  
@@@ -1129,7 -1165,7 +1129,7 @@@ rend_cache_store_v2_desc_as_dir(const c
      if (!hid_serv_responsible_for_desc_id(desc_id)) {
        log_info(LD_REND, "Service descriptor with desc ID %s is not in "
                          "interval that we are responsible for.",
 -               safe_str(desc_id_base32));
 +               safe_str_client(desc_id_base32));
        goto skip;
      }
      /* Is descriptor too old? */
@@@ -1258,8 -1294,7 +1258,8 @@@ rend_cache_store_v2_desc_as_client(cons
    /* Decode/decrypt introduction points. */
    if (intro_content) {
      if (rend_query->auth_type != REND_NO_AUTH &&
 -        rend_query->descriptor_cookie) {
 +        !tor_mem_is_zero(rend_query->descriptor_cookie,
 +                         sizeof(rend_query->descriptor_cookie))) {
        char *ipos_decrypted = NULL;
        size_t ipos_decrypted_size;
        if (rend_decrypt_introduction_points(&ipos_decrypted,
@@@ -1294,14 -1329,14 +1294,14 @@@
    /* Is descriptor too old? */
    if (parsed->timestamp < now - REND_CACHE_MAX_AGE-REND_CACHE_MAX_SKEW) {
      log_warn(LD_REND, "Service descriptor with service ID %s is too old.",
 -             safe_str(service_id));
 +             safe_str_client(service_id));
      retval = -2;
      goto err;
    }
    /* Is descriptor too far in the future? */
    if (parsed->timestamp > now + REND_CACHE_MAX_SKEW) {
      log_warn(LD_REND, "Service descriptor with service ID %s is too far in "
 -                      "the future.", safe_str(service_id));
 +                      "the future.", safe_str_client(service_id));
      retval = -2;
      goto err;
    }
@@@ -1309,7 -1344,7 +1309,7 @@@
    tor_snprintf(key, sizeof(key), "0%s", service_id);
    if (strmap_get_lc(rend_cache, key)) {
      log_info(LD_REND, "We already have a v0 descriptor for service ID %s.",
 -             safe_str(service_id));
 +             safe_str_client(service_id));
      retval = -1;
      goto err;
    }
@@@ -1319,14 -1354,14 +1319,14 @@@
    if (e && e->parsed->timestamp > parsed->timestamp) {
      log_info(LD_REND, "We already have a newer service descriptor for "
                        "service ID %s with the same desc ID and version.",
 -             safe_str(service_id));
 +             safe_str_client(service_id));
      retval = 0;
      goto err;
    }
    /* Do we already have this descriptor? */
    if (e && !strcmp(desc, e->desc)) {
      log_info(LD_REND,"We already have this service descriptor %s.",
 -             safe_str(service_id));
 +             safe_str_client(service_id));
      e->received = time(NULL);
      retval = 0;
      goto err;
@@@ -1344,11 -1379,12 +1344,11 @@@
    strlcpy(e->desc, desc, encoded_size + 1);
    e->len = encoded_size;
    log_debug(LD_REND,"Successfully stored rend desc '%s', len %d.",
 -            safe_str(service_id), (int)encoded_size);
 +            safe_str_client(service_id), (int)encoded_size);
    return 1;
  
   err:
 -  if (parsed)
 -    rend_service_descriptor_free(parsed);
 +  rend_service_descriptor_free(parsed);
    tor_free(intro_content);
    return retval;
  }
diff --combined src/or/rendmid.c
index bc36a4e,84b1214..baa8329
--- a/src/or/rendmid.c
+++ b/src/or/rendmid.c
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -8,11 -8,6 +8,11 @@@
   **/
  
  #include "or.h"
 +#include "circuitlist.h"
 +#include "config.h"
 +#include "relay.h"
 +#include "rendmid.h"
 +#include "rephist.h"
  
  /** Respond to an ESTABLISH_INTRO cell by checking the signed data and
   * setting the circuit's purpose and service pk digest.
diff --combined src/or/rendservice.c
index f06c983,9035ea4..44b5a4b
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@@ -1,5 -1,5 +1,5 @@@
  /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -8,23 -8,10 +8,23 @@@
   **/
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "directory.h"
 +#include "networkstatus.h"
 +#include "rendclient.h"
 +#include "rendcommon.h"
 +#include "rendservice.h"
 +#include "router.h"
 +#include "relay.h"
 +#include "rephist.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  static origin_circuit_t *find_intro_circuit(rend_intro_point_t *intro,
 -                                            const char *pk_digest,
 -                                            int desc_version);
 +                                            const char *pk_digest);
  
  /** Represents the mapping from a virtual port of a rendezvous service to
   * a real port on some IP.
@@@ -55,6 -42,8 +55,6 @@@ typedef struct rend_service_t 
    /* Fields specified in config file */
    char *directory; /**< where in the filesystem it stores it */
    smartlist_t *ports; /**< List of rend_service_port_config_t */
 -  int descriptor_version; /**< Rendezvous descriptor version that will be
 -                           * published. */
    rend_auth_type_t auth_type; /**< Client authorization type or 0 if no client
                                 * authorization is performed. */
    smartlist_t *clients; /**< List of rend_authorized_client_t's of
@@@ -69,7 -58,7 +69,7 @@@
                               * or are trying to establish. */
    time_t intro_period_started; /**< Start of the current period to build
                                  * introduction points. */
 -  int n_intro_circuits_launched; /**< count of intro circuits we have
 +  int n_intro_circuits_launched; /**< Count of intro circuits we have
                                    * established in this period. */
    rend_service_descriptor_t *desc; /**< Current hidden service descriptor. */
    time_t desc_is_dirty; /**< Time at which changes to the hidden service
@@@ -101,8 -90,7 +101,8 @@@ num_rend_services(void
  static void
  rend_authorized_client_free(rend_authorized_client_t *client)
  {
 -  if (!client) return;
 +  if (!client)
 +    return;
    if (client->client_key)
      crypto_free_pk_env(client->client_key);
    tor_free(client->client_name);
@@@ -121,9 -109,7 +121,9 @@@ rend_authorized_client_strmap_item_free
  static void
  rend_service_free(rend_service_t *service)
  {
 -  if (!service) return;
 +  if (!service)
 +    return;
 +
    tor_free(service->directory);
    SMARTLIST_FOREACH(service->ports, void*, p, tor_free(p));
    smartlist_free(service->ports);
@@@ -134,14 -120,15 +134,14 @@@
        rend_intro_point_free(intro););
      smartlist_free(service->intro_nodes);
    }
 -  if (service->desc)
 -    rend_service_descriptor_free(service->desc);
 +
 +  rend_service_descriptor_free(service->desc);
    if (service->clients) {
      SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, c,
        rend_authorized_client_free(c););
      smartlist_free(service->clients);
    }
 -  if (service->accepted_intros)
 -    digestmap_free(service->accepted_intros, _tor_free);
 +  digestmap_free(service->accepted_intros, _tor_free);
    tor_free(service);
  }
  
@@@ -150,9 -137,9 +150,9 @@@
  void
  rend_service_free_all(void)
  {
 -  if (!rend_service_list) {
 +  if (!rend_service_list)
      return;
 -  }
 +
    SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
                      rend_service_free(ptr));
    smartlist_free(rend_service_list);
@@@ -169,6 -156,36 +169,6 @@@ rend_add_service(rend_service_t *servic
  
    service->intro_nodes = smartlist_create();
  
 -  /* If the service is configured to publish unversioned (v0) and versioned
 -   * descriptors (v2 or higher), split it up into two separate services
 -   * (unless it is configured to perform client authorization). */
 -  if (service->descriptor_version == -1) {
 -    if (service->auth_type == REND_NO_AUTH) {
 -      rend_service_t *v0_service = tor_malloc_zero(sizeof(rend_service_t));
 -      v0_service->directory = tor_strdup(service->directory);
 -      v0_service->ports = smartlist_create();
 -      SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, {
 -        rend_service_port_config_t *copy =
 -          tor_malloc_zero(sizeof(rend_service_port_config_t));
 -        memcpy(copy, p, sizeof(rend_service_port_config_t));
 -        smartlist_add(v0_service->ports, copy);
 -      });
 -      v0_service->intro_period_started = service->intro_period_started;
 -      v0_service->descriptor_version = 0; /* Unversioned descriptor. */
 -      v0_service->auth_type = REND_NO_AUTH;
 -      rend_add_service(v0_service);
 -    }
 -
 -    service->descriptor_version = 2; /* Versioned descriptor. */
 -  }
 -
 -  if (service->auth_type != REND_NO_AUTH && !service->descriptor_version) {
 -    log_warn(LD_CONFIG, "Hidden service with client authorization and "
 -                        "version 0 descriptors configured; ignoring.");
 -    rend_service_free(service);
 -    return;
 -  }
 -
    if (service->auth_type != REND_NO_AUTH &&
        smartlist_len(service->clients) == 0) {
      log_warn(LD_CONFIG, "Hidden service with client authorization but no "
@@@ -280,7 -297,7 +280,7 @@@ rend_config_services(or_options_t *opti
  
    for (line = options->RendConfigLines; line; line = line->next) {
      if (!strcasecmp(line->key, "HiddenServiceDir")) {
 -      if (service) {
 +      if (service) { /* register the one we just finished parsing */
          if (validate_only)
            rend_service_free(service);
          else
@@@ -290,6 -307,7 +290,6 @@@
        service->directory = tor_strdup(line->value);
        service->ports = smartlist_create();
        service->intro_period_started = time(NULL);
 -      service->descriptor_version = -1; /**< All descriptor versions. */
        continue;
      }
      if (!service) {
@@@ -415,13 -433,35 +415,13 @@@
          return -1;
        }
      } else {
 -      smartlist_t *versions;
 -      char *version_str;
 -      int i, version, ver_ok=1, versions_bitmask = 0;
        tor_assert(!strcasecmp(line->key, "HiddenServiceVersion"));
 -      versions = smartlist_create();
 -      smartlist_split_string(versions, line->value, ",",
 -                             SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
 -      for (i = 0; i < smartlist_len(versions); i++) {
 -        version_str = smartlist_get(versions, i);
 -        if (strlen(version_str) != 1 || strspn(version_str, "02") != 1) {
 -          log_warn(LD_CONFIG,
 -                   "HiddenServiceVersion can only be 0 and/or 2.");
 -          SMARTLIST_FOREACH(versions, char *, cp, tor_free(cp));
 -          smartlist_free(versions);
 -          rend_service_free(service);
 -          return -1;
 -        }
 -        version = (int)tor_parse_long(version_str, 10, 0, INT_MAX, &ver_ok,
 -                                      NULL);
 -        if (!ver_ok)
 -          continue;
 -        versions_bitmask |= 1 << version;
 +      if (strcmp(line->value, "2")) {
 +        log_warn(LD_CONFIG,
 +                 "The only supported HiddenServiceVersion is 2.");
 +        rend_service_free(service);
 +        return -1;
        }
 -      /* If exactly one version is set, change descriptor_version to that
 -       * value; otherwise leave it at -1. */
 -      if (versions_bitmask == 1 << 0) service->descriptor_version = 0;
 -      if (versions_bitmask == 1 << 2) service->descriptor_version = 2;
 -      SMARTLIST_FOREACH(versions, char *, cp, tor_free(cp));
 -      smartlist_free(versions);
      }
    }
    if (service) {
@@@ -443,7 -483,8 +443,7 @@@
       * probably ok? */
      SMARTLIST_FOREACH(rend_service_list, rend_service_t *, new, {
        SMARTLIST_FOREACH(old_service_list, rend_service_t *, old, {
 -        if (!strcmp(old->directory, new->directory) &&
 -            old->descriptor_version == new->descriptor_version) {
 +        if (!strcmp(old->directory, new->directory)) {
            smartlist_add_all(new->intro_nodes, old->intro_nodes);
            smartlist_clear(old->intro_nodes);
            smartlist_add(surviving_services, old);
@@@ -466,16 -507,18 +466,16 @@@
          tor_assert(oc->rend_data);
          SMARTLIST_FOREACH(surviving_services, rend_service_t *, ptr, {
            if (!memcmp(ptr->pk_digest, oc->rend_data->rend_pk_digest,
 -                      DIGEST_LEN) &&
 -              ptr->descriptor_version == oc->rend_data->rend_desc_version) {
 +                      DIGEST_LEN)) {
              keep_it = 1;
              break;
            }
          });
          if (keep_it)
            continue;
 -        log_info(LD_REND, "Closing intro point %s for service %s version %d.",
 -                 safe_str(oc->build_state->chosen_exit->nickname),
 -                 oc->rend_data->onion_address,
 -                 oc->rend_data->rend_desc_version);
 +        log_info(LD_REND, "Closing intro point %s for service %s.",
 +                 safe_str_client(oc->build_state->chosen_exit->nickname),
 +                 oc->rend_data->onion_address);
          circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
          /* XXXX Is there another reason we should use here? */
        }
@@@ -498,13 -541,14 +498,13 @@@ rend_service_update_descriptor(rend_ser
    rend_service_descriptor_t *d;
    origin_circuit_t *circ;
    int i;
 -  if (service->desc) {
 -    rend_service_descriptor_free(service->desc);
 -    service->desc = NULL;
 -  }
 +
 +  rend_service_descriptor_free(service->desc);
 +  service->desc = NULL;
 +
    d = service->desc = tor_malloc_zero(sizeof(rend_service_descriptor_t));
    d->pk = crypto_pk_dup_key(service->private_key);
    d->timestamp = time(NULL);
 -  d->version = service->descriptor_version;
    d->intro_nodes = smartlist_create();
    /* Support intro protocols 2 and 3. */
    d->protocols = (1 << 2) + (1 << 3);
@@@ -512,7 -556,7 +512,7 @@@
    for (i = 0; i < smartlist_len(service->intro_nodes); ++i) {
      rend_intro_point_t *intro_svc = smartlist_get(service->intro_nodes, i);
      rend_intro_point_t *intro_desc;
 -    circ = find_intro_circuit(intro_svc, service->pk_digest, d->version);
 +    circ = find_intro_circuit(intro_svc, service->pk_digest);
      if (!circ || circ->_base.purpose != CIRCUIT_PURPOSE_S_INTRO)
        continue;
  
@@@ -753,15 -797,17 +753,15 @@@ rend_service_load_keys(void
    return r;
  }
  
 -/** Return the service whose public key has a digest of <b>digest</b> and
 - * which publishes the given descriptor <b>version</b>.  Return NULL if no
 - * such service exists.
 +/** Return the service whose public key has a digest of <b>digest</b>, or
 + * NULL if no such service exists.
   */
  static rend_service_t *
 -rend_service_get_by_pk_digest_and_version(const char* digest,
 -                                          uint8_t version)
 +rend_service_get_by_pk_digest(const char* digest)
  {
    SMARTLIST_FOREACH(rend_service_list, rend_service_t*, s,
 -                    if (!memcmp(s->pk_digest,digest,DIGEST_LEN) &&
 -                        s->descriptor_version == version) return s);
 +                    if (!memcmp(s->pk_digest,digest,DIGEST_LEN))
 +                        return s);
    return NULL;
  }
  
@@@ -898,16 -944,21 +898,16 @@@ rend_service_introduce(origin_circuit_
    }
  
    /* look up service depending on circuit. */
 -  service = rend_service_get_by_pk_digest_and_version(
 -              circuit->rend_data->rend_pk_digest,
 -              circuit->rend_data->rend_desc_version);
 +  service = rend_service_get_by_pk_digest(
 +                circuit->rend_data->rend_pk_digest);
    if (!service) {
      log_warn(LD_REND, "Got an INTRODUCE2 cell for an unrecognized service %s.",
               escaped(serviceid));
      return -1;
    }
  
 -  /* if descriptor version is 2, use intro key instead of service key. */
 -  if (circuit->rend_data->rend_desc_version == 0) {
 -    intro_key = service->private_key;
 -  } else {
 -    intro_key = circuit->intro_key;
 -  }
 +  /* use intro key instead of service key. */
 +  intro_key = circuit->intro_key;
  
    /* first DIGEST_LEN bytes of request is intro or service pk digest */
    crypto_pk_get_digest(intro_key, intro_key_digest);
@@@ -937,7 -988,7 +937,7 @@@
    len = r;
    if (*buf == 3) {
      /* Version 3 INTRODUCE2 cell. */
 -    time_t ts = 0, now = time(NULL);
 +    time_t ts = 0;
      v3_shift = 1;
      auth_type = buf[1];
      switch (auth_type) {
@@@ -1031,7 -1082,7 +1031,7 @@@
      router = router_get_by_nickname(rp_nickname, 0);
      if (!router) {
        log_info(LD_REND, "Couldn't find router %s named in introduce2 cell.",
 -               escaped_safe_str(rp_nickname));
 +               escaped_safe_str_client(rp_nickname));
        /* XXXX Add a no-such-router reason? */
        reason = END_CIRC_REASON_TORPROTOCOL;
        goto err;
@@@ -1106,8 -1157,7 +1106,8 @@@
      reason = END_CIRC_REASON_INTERNAL;
      goto err;
    }
 -  if (crypto_dh_compute_secret(dh, ptr+REND_COOKIE_LEN, DH_KEY_LEN, keys,
 +  if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, ptr+REND_COOKIE_LEN,
 +                               DH_KEY_LEN, keys,
                                 DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
      log_warn(LD_BUG, "Internal error: couldn't complete DH handshake");
      reason = END_CIRC_REASON_INTERNAL;
@@@ -1117,7 -1167,7 +1117,7 @@@
    circ_needs_uptime = rend_service_requires_uptime(service);
  
    /* help predict this next time */
 -  rep_hist_note_used_internal(time(NULL), circ_needs_uptime, 1);
 +  rep_hist_note_used_internal(now, circ_needs_uptime, 1);
  
    /* Launch a circuit to alice's chosen rendezvous point.
     */
@@@ -1133,16 -1183,14 +1133,16 @@@
    if (!launched) { /* give up */
      log_warn(LD_REND, "Giving up launching first hop of circuit to rendezvous "
               "point %s for service %s.",
 -             escaped_safe_str(extend_info->nickname), serviceid);
 +             escaped_safe_str_client(extend_info->nickname),
 +             serviceid);
      reason = END_CIRC_REASON_CONNECTFAILED;
      goto err;
    }
    log_info(LD_REND,
             "Accepted intro; launching circuit to %s "
             "(cookie %s) for service %s.",
 -           escaped_safe_str(extend_info->nickname), hexcookie, serviceid);
 +           escaped_safe_str_client(extend_info->nickname),
 +           hexcookie, serviceid);
    tor_assert(launched->build_state);
    /* Fill in the circuit's state. */
    launched->rend_data = tor_malloc_zero(sizeof(rend_data_t));
@@@ -1152,10 -1200,11 +1152,10 @@@
    memcpy(launched->rend_data->rend_cookie, r_cookie, REND_COOKIE_LEN);
    strlcpy(launched->rend_data->onion_address, service->service_id,
            sizeof(launched->rend_data->onion_address));
    launched->build_state->pending_final_cpath = cpath =
      tor_malloc_zero(sizeof(crypt_path_t));
    cpath->magic = CRYPT_PATH_MAGIC;
 -  launched->build_state->expiry_time = time(NULL) + MAX_REND_TIMEOUT;
 +  launched->build_state->expiry_time = now + MAX_REND_TIMEOUT;
  
    cpath->dh_handshake_state = dh;
    dh = NULL;
@@@ -1237,7 -1286,7 +1237,7 @@@ rend_service_launch_establish_intro(ren
  
    log_info(LD_REND,
             "Launching circuit to introduction point %s for service %s",
 -           escaped_safe_str(intro->extend_info->nickname),
 +           escaped_safe_str_client(intro->extend_info->nickname),
             service->service_id);
  
    rep_hist_note_used_internal(time(NULL), 1, 0);
@@@ -1250,7 -1299,7 +1250,7 @@@
    if (!launched) {
      log_info(LD_REND,
               "Can't launch circuit to establish introduction at %s.",
 -             escaped_safe_str(intro->extend_info->nickname));
 +             escaped_safe_str_client(intro->extend_info->nickname));
      return -1;
    }
  
@@@ -1273,16 -1322,18 +1273,16 @@@
    strlcpy(launched->rend_data->onion_address, service->service_id,
            sizeof(launched->rend_data->onion_address));
    memcpy(launched->rend_data->rend_pk_digest, service->pk_digest, DIGEST_LEN);
 -  launched->rend_data->rend_desc_version = service->descriptor_version;
 -  if (service->descriptor_version == 2)
 -    launched->intro_key = crypto_pk_dup_key(intro->intro_key);
 +  launched->intro_key = crypto_pk_dup_key(intro->intro_key);
    if (launched->_base.state == CIRCUIT_STATE_OPEN)
      rend_service_intro_has_opened(launched);
    return 0;
  }
  
  /** Return the number of introduction points that are or have been
 - * established for the given service address and rendezvous version. */
 + * established for the given service address in <b>query</b>. */
  static int
 -count_established_intro_points(const char *query, int rend_version)
 +count_established_intro_points(const char *query)
  {
    int num_ipos = 0;
    circuit_t *circ;
@@@ -1293,6 -1344,7 +1293,6 @@@
           circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) {
        origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ);
        if (oc->rend_data &&
 -          oc->rend_data->rend_desc_version == rend_version &&
            !rend_cmp_service_ids(query, oc->rend_data->onion_address))
          num_ipos++;
      }
@@@ -1322,8 -1374,9 +1322,8 @@@ rend_service_intro_has_opened(origin_ci
    base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                  circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
  
 -  service = rend_service_get_by_pk_digest_and_version(
 -              circuit->rend_data->rend_pk_digest,
 -              circuit->rend_data->rend_desc_version);
 +  service = rend_service_get_by_pk_digest(
 +                circuit->rend_data->rend_pk_digest);
    if (!service) {
      log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %d.",
               serviceid, circuit->_base.n_circ_id);
@@@ -1333,7 -1386,8 +1333,7 @@@
  
    /* If we already have enough introduction circuits for this service,
     * redefine this one as a general circuit. */
 -  if (count_established_intro_points(serviceid,
 -          circuit->rend_data->rend_desc_version) > NUM_INTRO_POINTS) {
 +  if (count_established_intro_points(serviceid) > NUM_INTRO_POINTS) {
      log_info(LD_CIRC|LD_REND, "We have just finished an introduction "
               "circuit, but we already have enough. Redefining purpose to "
               "general.");
@@@ -1346,8 -1400,13 +1346,8 @@@
             "Established circuit %d as introduction point for service %s",
             circuit->_base.n_circ_id, serviceid);
  
 -  /* If the introduction point will not be used in an unversioned
 -   * descriptor, use the intro key instead of the service key in
 -   * ESTABLISH_INTRO. */
 -  if (service->descriptor_version == 0)
 -    intro_key = service->private_key;
 -  else
 -    intro_key = circuit->intro_key;
 +  /* Use the intro key instead of the service key in ESTABLISH_INTRO. */
 +  intro_key = circuit->intro_key;
    /* Build the payload for a RELAY_ESTABLISH_INTRO cell. */
    r = crypto_pk_asn1_encode(intro_key, buf+2,
                              RELAY_PAYLOAD_SIZE-2);
@@@ -1407,8 -1466,9 +1407,8 @@@ rend_service_intro_established(origin_c
      goto err;
    }
    tor_assert(circuit->rend_data);
 -  service = rend_service_get_by_pk_digest_and_version(
 -              circuit->rend_data->rend_pk_digest,
 -              circuit->rend_data->rend_desc_version);
 +  service = rend_service_get_by_pk_digest(
 +                circuit->rend_data->rend_pk_digest);
    if (!service) {
      log_warn(LD_REND, "Unknown service on introduction circuit %d.",
               circuit->_base.n_circ_id);
@@@ -1458,8 -1518,9 +1458,8 @@@ rend_service_rendezvous_has_opened(orig
             "cookie %s for service %s",
             circuit->_base.n_circ_id, hexcookie, serviceid);
  
 -  service = rend_service_get_by_pk_digest_and_version(
 -              circuit->rend_data->rend_pk_digest,
 -              circuit->rend_data->rend_desc_version);
 +  service = rend_service_get_by_pk_digest(
 +                circuit->rend_data->rend_pk_digest);
    if (!service) {
      log_warn(LD_GENERAL, "Internal error: unrecognized service ID on "
               "introduction circuit.");
@@@ -1515,12 -1576,13 +1515,12 @@@
   */
  
  /** Return the (possibly non-open) introduction circuit ending at
 - * <b>intro</b> for the service whose public key is <b>pk_digest</b> and
 - * which publishes descriptor of version <b>desc_version</b>.  Return
 - * NULL if no such service is found.
 + * <b>intro</b> for the service whose public key is <b>pk_digest</b>.
 + * (<b>desc_version</b> is ignored). Return NULL if no such service is
 + * found.
   */
  static origin_circuit_t *
 -find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest,
 -                   int desc_version)
 +find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest)
  {
    origin_circuit_t *circ = NULL;
  
@@@ -1529,7 -1591,8 +1529,7 @@@
                                                    CIRCUIT_PURPOSE_S_INTRO))) {
      if (!memcmp(circ->build_state->chosen_exit->identity_digest,
                  intro->extend_info->identity_digest, DIGEST_LEN) &&
 -        circ->rend_data &&
 -        circ->rend_data->rend_desc_version == desc_version) {
 +        circ->rend_data) {
        return circ;
      }
    }
@@@ -1539,7 -1602,8 +1539,7 @@@
                                          CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) {
      if (!memcmp(circ->build_state->chosen_exit->identity_digest,
                  intro->extend_info->identity_digest, DIGEST_LEN) &&
 -        circ->rend_data &&
 -        circ->rend_data->rend_desc_version == desc_version) {
 +        circ->rend_data) {
        return circ;
      }
    }
@@@ -1572,7 -1636,6 +1572,7 @@@ directory_post_to_hs_dir(rend_service_d
      }
      for (j = 0; j < smartlist_len(responsible_dirs); j++) {
        char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
 +      char *hs_dir_ip;
        hs_dir = smartlist_get(responsible_dirs, j);
        if (smartlist_digest_isin(renddesc->successful_uploads,
                                  hs_dir->identity_digest))
@@@ -1594,18 -1657,15 +1594,18 @@@
                                                strlen(desc->desc_str), 0);
        base32_encode(desc_id_base32, sizeof(desc_id_base32),
                      desc->desc_id, DIGEST_LEN);
 +      hs_dir_ip = tor_dup_ip(hs_dir->addr);
        log_info(LD_REND, "Sending publish request for v2 descriptor for "
                          "service '%s' with descriptor ID '%s' with validity "
                          "of %d seconds to hidden service directory '%s' on "
 -                        "port %d.",
 -               safe_str(service_id),
 -               safe_str(desc_id_base32),
 +                        "%s:%d.",
 +               safe_str_client(service_id),
 +               safe_str_client(desc_id_base32),
                 seconds_valid,
                 hs_dir->nickname,
 -               hs_dir->dir_port);
 +               hs_dir_ip,
 +               hs_dir->or_port);
 +      tor_free(hs_dir_ip);
        /* Remember successful upload to this router for next time. */
        if (!smartlist_digest_isin(successful_uploads, hs_dir->identity_digest))
          smartlist_add(successful_uploads, hs_dir->identity_digest);
@@@ -1635,8 -1695,9 +1635,8 @@@
    smartlist_free(successful_uploads);
  }
  
 -/** Encode and sign up-to-date v0 and/or v2 service descriptors for
 - * <b>service</b>, and upload it/them to all the dirservers/to the
 - * responsible hidden service directories.
 +/** Encode and sign an up-to-date service descriptor for <b>service</b>,
 + * and upload it/them to the responsible hidden service directories.
   */
  static void
  upload_service_descriptor(rend_service_t *service)
@@@ -1648,8 -1709,35 +1648,8 @@@
  
    rendpostperiod = get_options()->RendPostPeriod;
  
 -  /* Upload unversioned (v0) descriptor? */
 -  if (service->descriptor_version == 0 &&
 -      get_options()->PublishHidServDescriptors) {
 -    char *desc;
 -    size_t desc_len;
 -    /* Encode the descriptor. */
 -    if (rend_encode_service_descriptor(service->desc,
 -                                       service->private_key,
 -                                       &desc, &desc_len)<0) {
 -      log_warn(LD_BUG, "Internal error: couldn't encode service descriptor; "
 -               "not uploading.");
 -      return;
 -    }
 -
 -    /* Post it to the dirservers */
 -    rend_get_service_id(service->desc->pk, serviceid);
 -    log_info(LD_REND, "Sending publish request for hidden service %s",
 -             serviceid);
 -    directory_post_to_dirservers(DIR_PURPOSE_UPLOAD_RENDDESC,
 -                                 ROUTER_PURPOSE_GENERAL,
 -                                 HIDSERV_AUTHORITY, desc, desc_len, 0);
 -    tor_free(desc);
 -    service->next_upload_time = now + rendpostperiod;
 -    uploaded = 1;
 -  }
 -
 -  /* Upload v2 descriptor? */
 -  if (service->descriptor_version == 2 &&
 -      get_options()->PublishHidServDescriptors) {
 +  /* Upload descriptor? */
 +  if (get_options()->PublishHidServDescriptors) {
      networkstatus_t *c = networkstatus_get_latest_consensus();
      if (c && smartlist_len(c->routerstatus_list) > 0) {
        int seconds_valid, i, j, num_descs;
@@@ -1788,7 -1876,8 +1788,7 @@@ rend_services_introduce(void
      for (j=0; j < smartlist_len(service->intro_nodes); ++j) {
        intro = smartlist_get(service->intro_nodes, j);
        router = router_get_by_digest(intro->extend_info->identity_digest);
 -      if (!router || !find_intro_circuit(intro, service->pk_digest,
 -                                         service->descriptor_version)) {
 +      if (!router || !find_intro_circuit(intro, service->pk_digest)) {
          log_info(LD_REND,"Giving up on %s as intro point for %s.",
                   intro->extend_info->nickname, service->service_id);
          if (service->desc) {
@@@ -1840,7 -1929,7 +1840,7 @@@
        router_crn_flags_t flags = CRN_NEED_UPTIME;
        if (get_options()->_AllowInvalid & ALLOW_INVALID_INTRODUCTION)
          flags |= CRN_ALLOW_INVALID;
 -      router = router_choose_random_node(NULL, intro_routers,
 +      router = router_choose_random_node(intro_routers,
                                           options->ExcludeNodes, flags);
        if (!router) {
          log_warn(LD_REND,
@@@ -1852,8 -1941,10 +1852,8 @@@
        smartlist_add(intro_routers, router);
        intro = tor_malloc_zero(sizeof(rend_intro_point_t));
        intro->extend_info = extend_info_from_router(router);
 -      if (service->descriptor_version == 2) {
 -        intro->intro_key = crypto_new_pk_env();
 -        tor_assert(!crypto_pk_generate_key(intro->intro_key));
 -      }
 +      intro->intro_key = crypto_new_pk_env();
 +      tor_assert(!crypto_pk_generate_key(intro->intro_key));
        smartlist_add(service->intro_nodes, intro);
        log_info(LD_REND, "Picked router %s as an intro point for %s.",
                 router->nickname, service->service_id);
@@@ -1946,7 -2037,8 +1946,7 @@@ rend_consider_descriptor_republication(
  
    for (i=0; i < smartlist_len(rend_service_list); ++i) {
      service = smartlist_get(rend_service_list, i);
 -    if (service->descriptor_version && service->desc &&
 -        !service->desc->all_uploads_performed) {
 +    if (service->desc && !service->desc->all_uploads_performed) {
        /* If we failed in uploading a descriptor last time, try again *without*
         * updating the descriptor's contents. */
        upload_service_descriptor(service);
@@@ -1972,9 -2064,10 +1972,9 @@@ rend_service_dump_stats(int severity
          service->directory);
      for (j=0; j < smartlist_len(service->intro_nodes); ++j) {
        intro = smartlist_get(service->intro_nodes, j);
 -      safe_name = safe_str(intro->extend_info->nickname);
 +      safe_name = safe_str_client(intro->extend_info->nickname);
  
 -      circ = find_intro_circuit(intro, service->pk_digest,
 -                                service->descriptor_version);
 +      circ = find_intro_circuit(intro, service->pk_digest);
        if (!circ) {
          log(severity, LD_GENERAL, "  Intro point %d at %s: no circuit",
              j, safe_name);
@@@ -2005,8 -2098,9 +2005,8 @@@ rend_service_set_connection_addr_port(e
    log_debug(LD_REND,"beginning to hunt for addr/port");
    base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                  circ->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
 -  service = rend_service_get_by_pk_digest_and_version(
 -                circ->rend_data->rend_pk_digest,
 -                circ->rend_data->rend_desc_version);
 +  service = rend_service_get_by_pk_digest(
 +                circ->rend_data->rend_pk_digest);
    if (!service) {
      log_warn(LD_REND, "Couldn't find any service associated with pk %s on "
               "rendezvous circuit %d; closing.",
diff --combined src/or/rephist.c
index 5a57b95,dee74ed..baedc13
--- a/src/or/rephist.c
+++ b/src/or/rephist.c
@@@ -1,26 -1,20 +1,26 @@@
  /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
   * \file rephist.c
   * \brief Basic history and "reputation" functionality to remember
   *    which servers have worked in the past, how much bandwidth we've
 - *    been using, which ports we tend to want, and so on.
 + *    been using, which ports we tend to want, and so on; further,
 + *    exit port statistics and cell statistics.
   **/
  
  #include "or.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
  #include "ht.h"
  
  static void bw_arrays_init(void);
  static void predicted_ports_init(void);
 -static void hs_usage_init(void);
  
  /** Total number of bytes currently allocated in fields used by rephist.c. */
  uint64_t rephist_total_alloc=0;
@@@ -191,6 -185,7 +191,6 @@@ rep_hist_init(void
    history_map = digestmap_new();
    bw_arrays_init();
    predicted_ports_init();
 -  hs_usage_init();
  }
  
  /** Helper: note that we are no longer connected to the router with history
@@@ -800,12 -795,12 +800,12 @@@ rep_hist_record_mtbf_data(time_t now, i
  static char *
  rep_hist_format_router_status(or_history_t *hist, time_t now)
  {
 -  char buf[1024];
    char sor_buf[ISO_TIME_LEN+1];
    char sod_buf[ISO_TIME_LEN+1];
    double wfu;
    double mtbf;
    int up = 0, down = 0;
 +  char *cp = NULL;
  
    if (hist->start_of_run) {
      format_iso_time(sor_buf, hist->start_of_run);
@@@ -818,7 -813,7 +818,7 @@@
  
    wfu = get_weighted_fractional_uptime(hist, now);
    mtbf = get_stability(hist, now);
 -  tor_snprintf(buf, sizeof(buf),
 +  tor_asprintf(&cp,
                 "%s%s%s"
                 "%s%s%s"
                 "wfu %0.3lf\n"
@@@ -836,7 -831,8 +836,7 @@@
                 hist->weighted_run_length,
                 hist->total_run_weights
                 );
 -
 -  return tor_strdup(buf);
 +  return cp;
  }
  
  /** The last stability analysis document that we created, or NULL if we never
@@@ -1284,21 -1280,13 +1284,21 @@@ bw_array_new(void
  static bw_array_t *read_array = NULL;
  /** Recent history of bandwidth observations for write operations. */
  static bw_array_t *write_array = NULL;
 -
 -/** Set up read_array and write_array. */
 +/** Recent history of bandwidth observations for read operations for the
 +    directory protocol. */
 +static bw_array_t *dir_read_array = NULL;
 +/** Recent history of bandwidth observations for write operations for the
 +    directory protocol. */
 +static bw_array_t *dir_write_array = NULL;
 +
 +/** Set up [dir-]read_array and [dir-]write_array. */
  static void
  bw_arrays_init(void)
  {
    read_array = bw_array_new();
    write_array = bw_array_new();
 +  dir_read_array = bw_array_new();
 +  dir_write_array = bw_array_new();
  }
  
  /** We read <b>num_bytes</b> more bytes in second <b>when</b>.
@@@ -1332,24 -1320,6 +1332,24 @@@ rep_hist_note_bytes_read(size_t num_byt
    add_obs(read_array, when, num_bytes);
  }
  
 +/** We wrote <b>num_bytes</b> more directory bytes in second <b>when</b>.
 + * (like rep_hist_note_bytes_written() above)
 + */
 +void
 +rep_hist_note_dir_bytes_written(size_t num_bytes, time_t when)
 +{
 +  add_obs(dir_write_array, when, num_bytes);
 +}
 +
 +/** We read <b>num_bytes</b> more directory bytes in second <b>when</b>.
 + * (like rep_hist_note_bytes_written() above)
 + */
 +void
 +rep_hist_note_dir_bytes_read(size_t num_bytes, time_t when)
 +{
 +  add_obs(dir_read_array, when, num_bytes);
 +}
 +
  /** Helper: Return the largest value in b->maxima.  (This is equal to the
   * most bandwidth used in any NUM_SECS_ROLLING_MEASURE period for the last
   * NUM_SECS_BW_SUM_IS_VALID seconds.)
@@@ -1385,9 -1355,9 +1385,9 @@@ rep_hist_bandwidth_assess(void
      return (int)(U64_TO_DBL(r)/NUM_SECS_ROLLING_MEASURE);
  }
  
 -/** Print the bandwidth history of b (either read_array or write_array)
 - * into the buffer pointed to by buf.  The format is simply comma
 - * separated numbers, from oldest to newest.
 +/** Print the bandwidth history of b (either [dir-]read_array or
 + * [dir-]write_array) into the buffer pointed to by buf.  The format is
 + * simply comma separated numbers, from oldest to newest.
   *
   * It returns the number of bytes written.
   */
@@@ -1440,42 -1410,26 +1440,42 @@@ rep_hist_fill_bandwidth_history(char *b
   * history in its descriptor.
   */
  char *
 -rep_hist_get_bandwidth_lines(int for_extrainfo)
 +rep_hist_get_bandwidth_lines(void)
  {
    char *buf, *cp;
    char t[ISO_TIME_LEN+1];
    int r;
 -  bw_array_t *b;
 +  bw_array_t *b = NULL;
 +  const char *desc = NULL;
    size_t len;
  
 -  /* opt (read|write)-history yyyy-mm-dd HH:MM:SS (n s) n,n,n,n,n... */
 -  len = (60+20*NUM_TOTALS)*2;
 +  /* opt [dirreq-](read|write)-history yyyy-mm-dd HH:MM:SS (n s) n,n,n... */
 +  len = (67+21*NUM_TOTALS)*4;
    buf = tor_malloc_zero(len);
    cp = buf;
 -  for (r=0;r<2;++r) {
 -    b = r?read_array:write_array;
 +  for (r=0;r<4;++r) {
 +    switch (r) {
 +      case 0:
 +        b = write_array;
 +        desc = "write-history";
 +        break;
 +      case 1:
 +        b = read_array;
 +        desc = "read-history";
 +        break;
 +      case 2:
 +        b = dir_write_array;
 +        desc = "dirreq-write-history";
 +        break;
 +      case 3:
 +        b = dir_read_array;
 +        desc = "dirreq-read-history";
 +        break;
 +    }
      tor_assert(b);
      format_iso_time(t, b->next_period-NUM_SECS_BW_SUM_INTERVAL);
 -    tor_snprintf(cp, len-(cp-buf), "%s%s %s (%d s) ",
 -                 for_extrainfo ? "" : "opt ",
 -                 r ? "read-history" : "write-history", t,
 -                 NUM_SECS_BW_SUM_INTERVAL);
 +    tor_snprintf(cp, len-(cp-buf), "%s %s (%d s) ",
 +                 desc, t, NUM_SECS_BW_SUM_INTERVAL);
      cp += strlen(cp);
      cp += rep_hist_fill_bandwidth_history(cp, len-(cp-buf), b);
      strlcat(cp, "\n", len-(cp-buf));
@@@ -1490,41 -1444,20 +1490,41 @@@ rep_hist_update_state(or_state_t *state
  {
    int len, r;
    char *buf, *cp;
 -  smartlist_t **s_values;
 -  time_t *s_begins;
 -  int *s_interval;
 -  bw_array_t *b;
 +  smartlist_t **s_values = NULL;
 +  time_t *s_begins = NULL;
 +  int *s_interval = NULL;
 +  bw_array_t *b = NULL;
  
    len = 20*NUM_TOTALS+1;
    buf = tor_malloc_zero(len);
  
 -  for (r=0;r<2;++r) {
 -    b = r?read_array:write_array;
 -    s_begins  = r?&state->BWHistoryReadEnds    :&state->BWHistoryWriteEnds;
 -    s_interval= r?&state->BWHistoryReadInterval:&state->BWHistoryWriteInterval;
 -    s_values  = r?&state->BWHistoryReadValues  :&state->BWHistoryWriteValues;
 -
 +  for (r=0;r<4;++r) {
 +    switch (r) {
 +      case 0:
 +        b = write_array;
 +        s_begins = &state->BWHistoryWriteEnds;
 +        s_interval = &state->BWHistoryWriteInterval;
 +        s_values = &state->BWHistoryWriteValues;
 +        break;
 +      case 1:
 +        b = read_array;
 +        s_begins = &state->BWHistoryReadEnds;
 +        s_interval = &state->BWHistoryReadInterval;
 +        s_values = &state->BWHistoryReadValues;
 +        break;
 +      case 2:
 +        b = dir_write_array;
 +        s_begins = &state->BWHistoryDirWriteEnds;
 +        s_interval = &state->BWHistoryDirWriteInterval;
 +        s_values = &state->BWHistoryDirWriteValues;
 +        break;
 +      case 3:
 +        b = dir_read_array;
 +        s_begins = &state->BWHistoryDirReadEnds;
 +        s_interval = &state->BWHistoryDirReadInterval;
 +        s_values = &state->BWHistoryDirReadValues;
 +        break;
 +    }
      if (*s_values) {
        SMARTLIST_FOREACH(*s_values, char *, val, tor_free(val));
        smartlist_free(*s_values);
@@@ -1564,45 -1497,23 +1564,45 @@@
  int
  rep_hist_load_state(or_state_t *state, char **err)
  {
 -  time_t s_begins, start;
 +  time_t s_begins = 0, start;
    time_t now = time(NULL);
    uint64_t v;
    int r,i,ok;
    int all_ok = 1;
 -  int s_interval;
 -  smartlist_t *s_values;
 -  bw_array_t *b;
 +  int s_interval = 0;
 +  smartlist_t *s_values = NULL;
 +  bw_array_t *b = NULL;
  
    /* Assert they already have been malloced */
    tor_assert(read_array && write_array);
  
 -  for (r=0;r<2;++r) {
 -    b = r?read_array:write_array;
 -    s_begins = r?state->BWHistoryReadEnds:state->BWHistoryWriteEnds;
 -    s_interval =  r?state->BWHistoryReadInterval:state->BWHistoryWriteInterval;
 -    s_values =  r?state->BWHistoryReadValues:state->BWHistoryWriteValues;
 +  for (r=0;r<4;++r) {
 +    switch (r) {
 +      case 0:
 +        b = write_array;
 +        s_begins = state->BWHistoryWriteEnds;
 +        s_interval = state->BWHistoryWriteInterval;
 +        s_values = state->BWHistoryWriteValues;
 +        break;
 +      case 1:
 +        b = read_array;
 +        s_begins = state->BWHistoryReadEnds;
 +        s_interval = state->BWHistoryReadInterval;
 +        s_values = state->BWHistoryReadValues;
 +        break;
 +      case 2:
 +        b = dir_write_array;
 +        s_begins = state->BWHistoryDirWriteEnds;
 +        s_interval = state->BWHistoryDirWriteInterval;
 +        s_values = state->BWHistoryDirWriteValues;
 +        break;
 +      case 3:
 +        b = dir_read_array;
 +        s_begins = state->BWHistoryDirReadEnds;
 +        s_interval = state->BWHistoryDirReadInterval;
 +        s_values = state->BWHistoryDirReadValues;
 +        break;
 +    }
      if (s_values && s_begins >= now - NUM_SECS_BW_SUM_INTERVAL*NUM_TOTALS) {
        start = s_begins - s_interval*(smartlist_len(s_values));
        if (start > now)
@@@ -1804,8 -1715,8 +1804,8 @@@ rep_hist_get_predicted_internal(time_t 
      return 0; /* too long ago */
    if (predicted_internal_uptime_time + PREDICTED_CIRCS_RELEVANCE_TIME >= now)
      *need_uptime = 1;
 -  if (predicted_internal_capacity_time + PREDICTED_CIRCS_RELEVANCE_TIME >= now)
 -    *need_capacity = 1;
 +  // Always predict that we need capacity.
 +  *need_capacity = 1;
    return 1;
  }
  
@@@ -1934,517 -1845,564 +1934,517 @@@ dump_pk_ops(int severity
        pk_op_counts.n_rend_server_ops);
  }
  
 -/** Free all storage held by the OR/link history caches, by the
 - * bandwidth history arrays, or by the port history. */
 +/*** Exit port statistics ***/
 +
 +/* Some constants */
 +/** To what multiple should byte numbers be rounded up? */
 +#define EXIT_STATS_ROUND_UP_BYTES 1024
 +/** To what multiple should stream counts be rounded up? */
 +#define EXIT_STATS_ROUND_UP_STREAMS 4
 +/** Number of TCP ports */
 +#define EXIT_STATS_NUM_PORTS 65536
 +/** Top n ports that will be included in exit stats. */
 +#define EXIT_STATS_TOP_N_PORTS 10
 +
 +/* The following data structures are arrays and no fancy smartlists or maps,
 + * so that all write operations can be done in constant time. This comes at
 + * the price of some memory (1.25 MB) and linear complexity when writing
 + * stats for measuring relays. */
 +/** Number of bytes read in current period by exit port */
 +static uint64_t *exit_bytes_read = NULL;
 +/** Number of bytes written in current period by exit port */
 +static uint64_t *exit_bytes_written = NULL;
 +/** Number of streams opened in current period by exit port */
 +static uint32_t *exit_streams = NULL;
 +
 +/** Start time of exit stats or 0 if we're not collecting exit stats. */
 +static time_t start_of_exit_stats_interval;
 +
 +/** Initialize exit port stats. */
  void
 -rep_hist_free_all(void)
 +rep_hist_exit_stats_init(time_t now)
  {
 -  digestmap_free(history_map, free_or_history);
 -  tor_free(read_array);
 -  tor_free(write_array);
 -  tor_free(last_stability_doc);
 -  built_last_stability_doc_at = 0;
 -  predicted_ports_free();
 +  start_of_exit_stats_interval = now;
 +  exit_bytes_read = tor_malloc_zero(EXIT_STATS_NUM_PORTS *
 +                                    sizeof(uint64_t));
 +  exit_bytes_written = tor_malloc_zero(EXIT_STATS_NUM_PORTS *
 +                                       sizeof(uint64_t));
 +  exit_streams = tor_malloc_zero(EXIT_STATS_NUM_PORTS *
 +                                 sizeof(uint32_t));
  }
  
 -/****************** hidden service usage statistics ******************/
 -
 -/** How large are the intervals for which we track and report hidden service
 - * use? */
 -#define NUM_SECS_HS_USAGE_SUM_INTERVAL (15*60)
 -/** How far in the past do we remember and publish hidden service use? */
 -#define NUM_SECS_HS_USAGE_SUM_IS_VALID (24*60*60)
 -/** How many hidden service usage intervals do we remember? (derived) */
 -#define NUM_TOTALS_HS_USAGE (NUM_SECS_HS_USAGE_SUM_IS_VALID/ \
 -                             NUM_SECS_HS_USAGE_SUM_INTERVAL)
 -
 -/** List element containing a service id and the count. */
 -typedef struct hs_usage_list_elem_t {
 -   /** Service id of this elem. */
 -  char service_id[REND_SERVICE_ID_LEN_BASE32+1];
 -  /** Number of occurrences for the given service id. */
 -  uint32_t count;
 -  /* Pointer to next list elem */
 -  struct hs_usage_list_elem_t *next;
 -} hs_usage_list_elem_t;
 -
 -/** Ordered list that stores service ids and the number of observations. It is
 - * ordered by the number of occurrences in descending order. Its purpose is to
 - * calculate the frequency distribution when the period is over. */
 -typedef struct hs_usage_list_t {
 -  /* Pointer to the first element in the list. */
 -  hs_usage_list_elem_t *start;
 -  /* Number of total occurrences for all list elements. */
 -  uint32_t total_count;
 -  /* Number of service ids, i.e. number of list elements. */
 -  uint32_t total_service_ids;
 -} hs_usage_list_t;
 -
 -/** Tracks service-related observations in the current period and their
 - * history. */
 -typedef struct hs_usage_service_related_observation_t {
 -  /** Ordered list that stores service ids and the number of observations in
 -   * the current period. It is ordered by the number of occurrences in
 -   * descending order. Its purpose is to calculate the frequency distribution
 -   * when the period is over. */
 -  hs_usage_list_t *list;
 -  /** Circular arrays that store the history of observations. totals stores all
 -   * observations, twenty (ten, five) the number of observations related to a
 -   * service id being accounted for the top 20 (10, 5) percent of all
 -   * observations. */
 -  uint32_t totals[NUM_TOTALS_HS_USAGE];
 -  uint32_t five[NUM_TOTALS_HS_USAGE];
 -  uint32_t ten[NUM_TOTALS_HS_USAGE];
 -  uint32_t twenty[NUM_TOTALS_HS_USAGE];
 -} hs_usage_service_related_observation_t;
 -
 -/** Tracks the history of general period-related observations, i.e. those that
 - * cannot be related to a specific service id. */
 -typedef struct hs_usage_general_period_related_observations_t {
 -  /** Circular array that stores the history of observations. */
 -  uint32_t totals[NUM_TOTALS_HS_USAGE];
 -} hs_usage_general_period_related_observations_t;
 -
 -/** Keeps information about the current observation period and its relation to
 - * the histories of observations. */
 -typedef struct hs_usage_current_observation_period_t {
 -  /** Where do we write the next history entry? */
 -  int next_idx;
 -  /** How many values in history have been set ever? (upper bound!) */
 -  int num_set;
 -  /** When did this period begin? */
 -  time_t start_of_current_period;
 -  /** When does the next period begin? */
 -  time_t start_of_next_period;
 -} hs_usage_current_observation_period_t;
 -
 -/** Usage statistics for the current observation period. */
 -static hs_usage_current_observation_period_t *current_period = NULL;
 -
 -/** Total number of descriptor publish requests in the current observation
 - * period. */
 -static hs_usage_service_related_observation_t *publish_total = NULL;
 -
 -/** Number of descriptor publish requests for services that have not been
 - * seen before in the current observation period. */
 -static hs_usage_service_related_observation_t *publish_novel = NULL;
 -
 -/** Total number of descriptor fetch requests in the current observation
 - * period. */
 -static hs_usage_service_related_observation_t *fetch_total = NULL;
 -
 -/** Number of successful descriptor fetch requests in the current
 - * observation period. */
 -static hs_usage_service_related_observation_t *fetch_successful = NULL;
 -
 -/** Number of descriptors stored in the current observation period. */
 -static hs_usage_general_period_related_observations_t *descs = NULL;
 -
 -/** Creates an empty ordered list element. */
 -static hs_usage_list_elem_t *
 -hs_usage_list_elem_new(void)
 +/** Reset counters for exit port statistics. */
 +void
 +rep_hist_reset_exit_stats(time_t now)
  {
 -  hs_usage_list_elem_t *e;
 -  e = tor_malloc_zero(sizeof(hs_usage_list_elem_t));
 -  rephist_total_alloc += sizeof(hs_usage_list_elem_t);
 -  e->count = 1;
 -  e->next = NULL;
 -  return e;
 +  start_of_exit_stats_interval = now;
 +  memset(exit_bytes_read, 0, EXIT_STATS_NUM_PORTS * sizeof(uint64_t));
 +  memset(exit_bytes_written, 0, EXIT_STATS_NUM_PORTS * sizeof(uint64_t));
 +  memset(exit_streams, 0, EXIT_STATS_NUM_PORTS * sizeof(uint32_t));
  }
  
 -/** Creates an empty ordered list. */
 -static hs_usage_list_t *
 -hs_usage_list_new(void)
 +/** Stop collecting exit port stats in a way that we can re-start doing
 + * so in rep_hist_exit_stats_init(). */
 +void
 +rep_hist_exit_stats_term(void)
  {
 -  hs_usage_list_t *l;
 -  l = tor_malloc_zero(sizeof(hs_usage_list_t));
 -  rephist_total_alloc += sizeof(hs_usage_list_t);
 -  l->start = NULL;
 -  l->total_count = 0;
 -  l->total_service_ids = 0;
 -  return l;
 +  start_of_exit_stats_interval = 0;
 +  tor_free(exit_bytes_read);
 +  tor_free(exit_bytes_written);
 +  tor_free(exit_streams);
  }
  
 -/** Creates an empty structure for storing service-related observations. */
 -static hs_usage_service_related_observation_t *
 -hs_usage_service_related_observation_new(void)
 -{
 -  hs_usage_service_related_observation_t *h;
 -  h = tor_malloc_zero(sizeof(hs_usage_service_related_observation_t));
 -  rephist_total_alloc += sizeof(hs_usage_service_related_observation_t);
 -  h->list = hs_usage_list_new();
 -  return h;
 +/** Helper for qsort: compare two ints. */
 +static int
 +_compare_int(const void *x, const void *y) {
 +  return (*(int*)x - *(int*)y);
  }
  
 -/** Creates an empty structure for storing general period-related
 - * observations. */
 -static hs_usage_general_period_related_observations_t *
 -hs_usage_general_period_related_observations_new(void)
 -{
 -  hs_usage_general_period_related_observations_t *p;
 -  p = tor_malloc_zero(sizeof(hs_usage_general_period_related_observations_t));
 -  rephist_total_alloc+= sizeof(hs_usage_general_period_related_observations_t);
 -  return p;
 -}
 +/** Return a newly allocated string containing the exit port statistics
 + * until <b>now</b>, or NULL if we're not collecting exit stats. */
 +char *
 +rep_hist_format_exit_stats(time_t now)
 +{
 +  int i, j, top_elements = 0, cur_min_idx = 0, cur_port;
 +  uint64_t top_bytes[EXIT_STATS_TOP_N_PORTS];
 +  int top_ports[EXIT_STATS_TOP_N_PORTS];
 +  uint64_t cur_bytes = 0, other_read = 0, other_written = 0,
 +           total_read = 0, total_written = 0;
 +  uint32_t total_streams = 0, other_streams = 0;
 +  char *buf;
 +  smartlist_t *written_strings, *read_strings, *streams_strings;
 +  char *written_string, *read_string, *streams_string;
 +  char t[ISO_TIME_LEN+1];
 +  char *result;
  
 -/** Creates an empty structure for storing period-specific information. */
 -static hs_usage_current_observation_period_t *
 -hs_usage_current_observation_period_new(void)
 -{
 -  hs_usage_current_observation_period_t *c;
 -  time_t now;
 -  c = tor_malloc_zero(sizeof(hs_usage_current_observation_period_t));
 -  rephist_total_alloc += sizeof(hs_usage_current_observation_period_t);
 -  now = time(NULL);
 -  c->start_of_current_period = now;
 -  c->start_of_next_period = now + NUM_SECS_HS_USAGE_SUM_INTERVAL;
 -  return c;
 -}
 +  if (!start_of_exit_stats_interval)
 +    return NULL; /* Not initialized. */
  
 -/** Initializes the structures for collecting hidden service usage data. */
 -static void
 -hs_usage_init(void)
 -{
 -  current_period = hs_usage_current_observation_period_new();
 -  publish_total = hs_usage_service_related_observation_new();
 -  publish_novel = hs_usage_service_related_observation_new();
 -  fetch_total = hs_usage_service_related_observation_new();
 -  fetch_successful = hs_usage_service_related_observation_new();
 -  descs = hs_usage_general_period_related_observations_new();
 -}
 +  /* Go through all ports to find the n ports that saw most written and
 +   * read bytes.
 +   *
 +   * Invariant: at the end of the loop for iteration i,
 +   *    total_read is the sum of all exit_bytes_read[0..i]
 +   *    total_written is the sum of all exit_bytes_written[0..i]
 +   *    total_stream is the sum of all exit_streams[0..i]
 +   *
 +   *    top_elements = MAX(EXIT_STATS_TOP_N_PORTS,
 +   *                  #{j | 0 <= j <= i && volume(i) > 0})
 +   *
 +   *    For all 0 <= j < top_elements,
 +   *        top_bytes[j] > 0
 +   *        0 <= top_ports[j] <= 65535
 +   *        top_bytes[j] = volume(top_ports[j])
 +   *
 +   *    There is no j in 0..i and k in 0..top_elements such that:
 +   *        volume(j) > top_bytes[k] AND j is not in top_ports[0..top_elements]
 +   *
 +   *    There is no j!=cur_min_idx in 0..top_elements such that:
 +   *        top_bytes[j] < top_bytes[cur_min_idx]
 +   *
 +   * where volume(x) == exit_bytes_read[x]+exit_bytes_written[x]
 +   *
 +   * Worst case: O(EXIT_STATS_NUM_PORTS * EXIT_STATS_TOP_N_PORTS)
 +   */
 +  for (i = 1; i < EXIT_STATS_NUM_PORTS; i++) {
 +    total_read += exit_bytes_read[i];
 +    total_written += exit_bytes_written[i];
 +    total_streams += exit_streams[i];
 +    cur_bytes = exit_bytes_read[i] + exit_bytes_written[i];
 +    if (cur_bytes == 0) {
 +      continue;
 +    }
 +    if (top_elements < EXIT_STATS_TOP_N_PORTS) {
 +      top_bytes[top_elements] = cur_bytes;
 +      top_ports[top_elements++] = i;
 +    } else if (cur_bytes > top_bytes[cur_min_idx]) {
 +      top_bytes[cur_min_idx] = cur_bytes;
 +      top_ports[cur_min_idx] = i;
 +    } else {
 +      continue;
 +    }
 +    cur_min_idx = 0;
 +    for (j = 1; j < top_elements; j++) {
 +      if (top_bytes[j] < top_bytes[cur_min_idx]) {
 +        cur_min_idx = j;
 +      }
 +    }
 +  }
  
 -/** Clears the given ordered list by resetting its attributes and releasing
 - * the memory allocated by its elements. */
 -static void
 -hs_usage_list_clear(hs_usage_list_t *lst)
 -{
 -  /* walk through elements and free memory */
 -  hs_usage_list_elem_t *current = lst->start;
 -  hs_usage_list_elem_t *tmp;
 -  while (current != NULL) {
 -    tmp = current->next;
 -    rephist_total_alloc -= sizeof(hs_usage_list_elem_t);
 -    tor_free(current);
 -    current = tmp;
 +  /* Add observations of top ports to smartlists. */
 +  written_strings = smartlist_create();
 +  read_strings = smartlist_create();
 +  streams_strings = smartlist_create();
 +  other_read = total_read;
 +  other_written = total_written;
 +  other_streams = total_streams;
 +  /* Sort the ports; this puts them out of sync with top_bytes, but we
 +   * won't be using top_bytes again anyway */
 +  qsort(top_ports, top_elements, sizeof(int), _compare_int);
 +  for (j = 0; j < top_elements; j++) {
 +    cur_port = top_ports[j];
 +    if (exit_bytes_written[cur_port] > 0) {
 +      uint64_t num = round_uint64_to_next_multiple_of(
 +                     exit_bytes_written[cur_port],
 +                     EXIT_STATS_ROUND_UP_BYTES);
 +      num /= 1024;
 +      buf = NULL;
 +      tor_asprintf(&buf, "%d="U64_FORMAT, cur_port, U64_PRINTF_ARG(num));
 +      smartlist_add(written_strings, buf);
 +      other_written -= exit_bytes_written[cur_port];
 +    }
 +    if (exit_bytes_read[cur_port] > 0) {
 +      uint64_t num = round_uint64_to_next_multiple_of(
 +                     exit_bytes_read[cur_port],
 +                     EXIT_STATS_ROUND_UP_BYTES);
 +      num /= 1024;
 +      buf = NULL;
 +      tor_asprintf(&buf, "%d="U64_FORMAT, cur_port, U64_PRINTF_ARG(num));
 +      smartlist_add(read_strings, buf);
 +      other_read -= exit_bytes_read[cur_port];
 +    }
 +    if (exit_streams[cur_port] > 0) {
 +      uint32_t num = round_uint32_to_next_multiple_of(
 +                     exit_streams[cur_port],
 +                     EXIT_STATS_ROUND_UP_STREAMS);
 +      buf = NULL;
 +      tor_asprintf(&buf, "%d=%u", cur_port, num);
 +      smartlist_add(streams_strings, buf);
 +      other_streams -= exit_streams[cur_port];
 +    }
    }
 -  /* reset attributes */
 -  lst->start = NULL;
 -  lst->total_count = 0;
 -  lst->total_service_ids = 0;
 -  return;
 -}
  
 -/** Frees the memory used by the given list. */
 -static void
 -hs_usage_list_free(hs_usage_list_t *lst)
 -{
 -  if (!lst)
 -    return;
 -  hs_usage_list_clear(lst);
 -  rephist_total_alloc -= sizeof(hs_usage_list_t);
 -  tor_free(lst);
 +  /* Add observations of other ports in a single element. */
 +  other_written = round_uint64_to_next_multiple_of(other_written,
 +                  EXIT_STATS_ROUND_UP_BYTES);
 +  other_written /= 1024;
 +  buf = NULL;
 +  tor_asprintf(&buf, "other="U64_FORMAT, U64_PRINTF_ARG(other_written));
 +  smartlist_add(written_strings, buf);
 +  other_read = round_uint64_to_next_multiple_of(other_read,
 +               EXIT_STATS_ROUND_UP_BYTES);
 +  other_read /= 1024;
 +  buf = NULL;
 +  tor_asprintf(&buf, "other="U64_FORMAT, U64_PRINTF_ARG(other_read));
 +  smartlist_add(read_strings, buf);
 +  other_streams = round_uint32_to_next_multiple_of(other_streams,
 +                  EXIT_STATS_ROUND_UP_STREAMS);
 +  buf = NULL;
 +  tor_asprintf(&buf, "other=%u", other_streams);
 +  smartlist_add(streams_strings, buf);
 +
 +  /* Join all observations in single strings. */
 +  written_string = smartlist_join_strings(written_strings, ",", 0, NULL);
 +  read_string = smartlist_join_strings(read_strings, ",", 0, NULL);
 +  streams_string = smartlist_join_strings(streams_strings, ",", 0, NULL);
 +  SMARTLIST_FOREACH(written_strings, char *, cp, tor_free(cp));
 +  SMARTLIST_FOREACH(read_strings, char *, cp, tor_free(cp));
 +  SMARTLIST_FOREACH(streams_strings, char *, cp, tor_free(cp));
 +  smartlist_free(written_strings);
 +  smartlist_free(read_strings);
 +  smartlist_free(streams_strings);
 +
 +  /* Put everything together. */
 +  format_iso_time(t, now);
 +  tor_asprintf(&result, "exit-stats-end %s (%d s)\n"
 +               "exit-kibibytes-written %s\n"
 +               "exit-kibibytes-read %s\n"
 +               "exit-streams-opened %s\n",
 +               t, (unsigned) (now - start_of_exit_stats_interval),
 +               written_string,
 +               read_string,
 +               streams_string);
 +  tor_free(written_string);
 +  tor_free(read_string);
 +  tor_free(streams_string);
 +  return result;
  }
  
 -/** Frees the memory used by the given service-related observations. */
 -static void
 -hs_usage_service_related_observation_free(
 -                                    hs_usage_service_related_observation_t *s)
 +/** If 24 hours have passed since the beginning of the current exit port
 + * stats period, write exit stats to $DATADIR/stats/exit-stats (possibly
 + * overwriting an existing file) and reset counters.  Return when we would
 + * next want to write exit stats or 0 if we never want to write. */
 +time_t
 +rep_hist_exit_stats_write(time_t now)
  {
 -  if (!s)
 -    return;
 -  hs_usage_list_free(s->list);
 -  rephist_total_alloc -= sizeof(hs_usage_service_related_observation_t);
 -  tor_free(s);
 -}
 +  char *statsdir = NULL, *filename = NULL, *str = NULL;
  
 -/** Frees the memory used by the given period-specific observations. */
 -static void
 -hs_usage_general_period_related_observations_free(
 -                             hs_usage_general_period_related_observations_t *s)
 -{
 -  rephist_total_alloc-=sizeof(hs_usage_general_period_related_observations_t);
 -  tor_free(s);
 -}
 +  if (!start_of_exit_stats_interval)
 +    return 0; /* Not initialized. */
 +  if (start_of_exit_stats_interval + WRITE_STATS_INTERVAL > now)
 +    goto done; /* Not ready to write. */
  
 -/** Frees the memory used by period-specific information. */
 -static void
 -hs_usage_current_observation_period_free(
 -                                    hs_usage_current_observation_period_t *s)
 -{
 -  rephist_total_alloc -= sizeof(hs_usage_current_observation_period_t);
 -  tor_free(s);
 -}
 +  log_info(LD_HIST, "Writing exit port statistics to disk.");
  
 -/** Frees all memory that was used for collecting hidden service usage data. */
 -void
 -hs_usage_free_all(void)
 -{
 -  hs_usage_general_period_related_observations_free(descs);
 -  descs = NULL;
 -  hs_usage_service_related_observation_free(fetch_successful);
 -  hs_usage_service_related_observation_free(fetch_total);
 -  hs_usage_service_related_observation_free(publish_novel);
 -  hs_usage_service_related_observation_free(publish_total);
 -  fetch_successful = fetch_total = publish_novel = publish_total = NULL;
 -  hs_usage_current_observation_period_free(current_period);
 -  current_period = NULL;
 -}
 +  /* Generate history string. */
 +  str = rep_hist_format_exit_stats(now);
  
 -/** Inserts a new occurrence for the given service id to the given ordered
 - * list. */
 -static void
 -hs_usage_insert_value(hs_usage_list_t *lst, const char *service_id)
 -{
 -  /* search if there is already an elem with same service_id in list */
 -  hs_usage_list_elem_t *current = lst->start;
 -  hs_usage_list_elem_t *previous = NULL;
 -  while (current != NULL && strcasecmp(current->service_id,service_id)) {
 -    previous = current;
 -    current = current->next;
 -  }
 -  /* found an element with same service_id? */
 -  if (current == NULL) {
 -    /* not found! append to end (which could also be the end of a zero-length
 -     * list), don't need to sort (1 is smallest value). */
 -    /* create elem */
 -    hs_usage_list_elem_t *e = hs_usage_list_elem_new();
 -    /* update list attributes (one new elem, one new occurrence) */
 -    lst->total_count++;
 -    lst->total_service_ids++;
 -    /* copy service id to elem */
 -    strlcpy(e->service_id,service_id,sizeof(e->service_id));
 -    /* let either l->start or previously last elem point to new elem */
 -    if (lst->start == NULL) {
 -      /* this is the first elem */
 -      lst->start = e;
 -    } else {
 -      /* there were elems in the list before */
 -      previous->next = e;
 -    }
 -  } else {
 -    /* found! add occurrence to elem and consider resorting */
 -    /* update list attributes (no new elem, but one new occurrence) */
 -    lst->total_count++;
 -    /* add occurrence to elem */
 -    current->count++;
 -    /* is it another than the first list elem? and has previous elem fewer
 -     * count than current? then we need to resort */
 -    if (previous != NULL && previous->count < current->count) {
 -      /* yes! we need to resort */
 -      /* remove current elem first */
 -      previous->next = current->next;
 -      /* can we prepend elem to all other elements? */
 -      if (lst->start->count <= current->count) {
 -        /* yes! prepend elem */
 -        current->next = lst->start;
 -        lst->start = current;
 -      } else {
 -        /* no! walk through list a second time and insert at correct place */
 -        hs_usage_list_elem_t *insert_current = lst->start->next;
 -        hs_usage_list_elem_t *insert_previous = lst->start;
 -        while (insert_current != NULL &&
 -               insert_current->count > current->count) {
 -          insert_previous = insert_current;
 -          insert_current = insert_current->next;
 -        }
 -        /* insert here */
 -        current->next = insert_current;
 -        insert_previous->next = current;
 -      }
 -    }
 -  }
 -}
 +  /* Reset counters. */
 +  rep_hist_reset_exit_stats(now);
  
 -/** Writes the current service-related observations to the history array and
 - * clears the observations of the current period. */
 -static void
 -hs_usage_write_service_related_observations_to_history(
 -                                    hs_usage_current_observation_period_t *p,
 -                                    hs_usage_service_related_observation_t *h)
 -{
 -  /* walk through the first 20 % of list elements and calculate frequency
 -   * distributions */
 -  /* maximum indices for the three frequencies */
 -  int five_percent_idx = h->list->total_service_ids/20;
 -  int ten_percent_idx = h->list->total_service_ids/10;
 -  int twenty_percent_idx = h->list->total_service_ids/5;
 -  /* temp values */
 -  uint32_t five_percent = 0;
 -  uint32_t ten_percent = 0;
 -  uint32_t twenty_percent = 0;
 -  /* walk through list */
 -  hs_usage_list_elem_t *current = h->list->start;
 -  int i=0;
 -  while (current != NULL && i <= twenty_percent_idx) {
 -    twenty_percent += current->count;
 -    if (i <= ten_percent_idx)
 -      ten_percent += current->count;
 -    if (i <= five_percent_idx)
 -      five_percent += current->count;
 -    current = current->next;
 -    i++;
 +  /* Try to write to disk. */
 +  statsdir = get_datadir_fname("stats");
 +  if (check_private_dir(statsdir, CPD_CREATE) < 0) {
 +    log_warn(LD_HIST, "Unable to create stats/ directory!");
 +    goto done;
    }
 -  /* copy frequencies */
 -  h->twenty[p->next_idx] = twenty_percent;
 -  h->ten[p->next_idx] = ten_percent;
 -  h->five[p->next_idx] = five_percent;
 -  /* copy total number of observations */
 -  h->totals[p->next_idx] = h->list->total_count;
 -  /* free memory of old list */
 -  hs_usage_list_clear(h->list);
 -}
 +  filename = get_datadir_fname2("stats", "exit-stats");
 +  if (write_str_to_file(filename, str, 0) < 0)
 +    log_warn(LD_HIST, "Unable to write exit port statistics to disk!");
  
 -/** Advances to next observation period. */
 -static void
 -hs_usage_advance_current_observation_period(void)
 -{
 -  /* aggregate observations to history, including frequency distribution
 -   * arrays */
 -  hs_usage_write_service_related_observations_to_history(
 -                                    current_period, publish_total);
 -  hs_usage_write_service_related_observations_to_history(
 -                                    current_period, publish_novel);
 -  hs_usage_write_service_related_observations_to_history(
 -                                    current_period, fetch_total);
 -  hs_usage_write_service_related_observations_to_history(
 -                                    current_period, fetch_successful);
 -  /* write current number of descriptors to descs history */
 -  descs->totals[current_period->next_idx] = rend_cache_size();
 -  /* advance to next period */
 -  current_period->next_idx++;
 -  if (current_period->next_idx == NUM_TOTALS_HS_USAGE)
 -    current_period->next_idx = 0;
 -  if (current_period->num_set < NUM_TOTALS_HS_USAGE)
 -    ++current_period->num_set;
 -  current_period->start_of_current_period=current_period->start_of_next_period;
 -  current_period->start_of_next_period += NUM_SECS_HS_USAGE_SUM_INTERVAL;
 -}
 -
 -/** Checks if the current period is up to date, and if not, advances it. */
 -static void
 -hs_usage_check_if_current_period_is_up_to_date(time_t now)
 -{
 -  while (now > current_period->start_of_next_period) {
 -    hs_usage_advance_current_observation_period();
 -  }
 + done:
 +  tor_free(str);
 +  tor_free(statsdir);
 +  tor_free(filename);
 +  return start_of_exit_stats_interval + WRITE_STATS_INTERVAL;
  }
  
 -/** Adds a service-related observation, maybe after advancing to next
 - * observation period. */
 -static void
 -hs_usage_add_service_related_observation(
 -                                    hs_usage_service_related_observation_t *h,
 -                                    time_t now,
 -                                    const char *service_id)
 +/** Note that we wrote <b>num_written</b> bytes and read <b>num_read</b>
 + * bytes to/from an exit connection to <b>port</b>. */
 +void
 +rep_hist_note_exit_bytes(uint16_t port, size_t num_written,
 +                         size_t num_read)
  {
 -  if (now < current_period->start_of_current_period) {
 -    /* don't record old data */
 -    return;
 -  }
 -  /* check if we are up-to-date */
 -  hs_usage_check_if_current_period_is_up_to_date(now);
 -  /* add observation */
 -  hs_usage_insert_value(h->list, service_id);
 +  if (!start_of_exit_stats_interval)
 +    return; /* Not initialized. */
 +  exit_bytes_written[port] += num_written;
 +  exit_bytes_read[port] += num_read;
 +  log_debug(LD_HIST, "Written %lu bytes and read %lu bytes to/from an "
 +            "exit connection to port %d.",
 +            (unsigned long)num_written, (unsigned long)num_read, port);
  }
  
 -/** Adds the observation of storing a rendezvous service descriptor to our
 - * cache in our role as HS authoritative directory. */
 +/** Note that we opened an exit stream to <b>port</b>. */
  void
 -hs_usage_note_publish_total(const char *service_id, time_t now)
 +rep_hist_note_exit_stream_opened(uint16_t port)
  {
 -  hs_usage_add_service_related_observation(publish_total, now, service_id);
 +  if (!start_of_exit_stats_interval)
 +    return; /* Not initialized. */
 +  exit_streams[port]++;
 +  log_debug(LD_HIST, "Opened exit stream to port %d", port);
  }
  
 -/** Adds the observation of storing a novel rendezvous service descriptor to
 - * our cache in our role as HS authoritative directory. */
 +/*** cell statistics ***/
 +
 +/** Start of the current buffer stats interval or 0 if we're not
 + * collecting buffer statistics. */
 +static time_t start_of_buffer_stats_interval;
 +
 +/** Initialize buffer stats. */
  void
 -hs_usage_note_publish_novel(const char *service_id, time_t now)
 +rep_hist_buffer_stats_init(time_t now)
  {
 -  hs_usage_add_service_related_observation(publish_novel, now, service_id);
 +  start_of_buffer_stats_interval = now;
  }
  
 -/** Adds the observation of being requested for a rendezvous service descriptor
 - * in our role as HS authoritative directory. */
 +typedef struct circ_buffer_stats_t {
 +  uint32_t processed_cells;
 +  double mean_num_cells_in_queue;
 +  double mean_time_cells_in_queue;
 +  uint32_t local_circ_id;
 +} circ_buffer_stats_t;
 +
 +/** Holds stats. */
 +smartlist_t *circuits_for_buffer_stats = NULL;
 +
 +/** Remember cell statistics for circuit <b>circ</b> at time
 + * <b>end_of_interval</b> and reset cell counters in case the circuit
 + * remains open in the next measurement interval. */
  void
 -hs_usage_note_fetch_total(const char *service_id, time_t now)
 +rep_hist_buffer_stats_add_circ(circuit_t *circ, time_t end_of_interval)
 +{
 +  circ_buffer_stats_t *stat;
 +  time_t start_of_interval;
 +  int interval_length;
 +  or_circuit_t *orcirc;
 +  if (CIRCUIT_IS_ORIGIN(circ))
 +    return;
 +  orcirc = TO_OR_CIRCUIT(circ);
 +  if (!orcirc->processed_cells)
 +    return;
 +  if (!circuits_for_buffer_stats)
 +    circuits_for_buffer_stats = smartlist_create();
 +  start_of_interval = circ->timestamp_created >
 +      start_of_buffer_stats_interval ?
 +        circ->timestamp_created :
 +        start_of_buffer_stats_interval;
 +  interval_length = (int) (end_of_interval - start_of_interval);
 +  stat = tor_malloc_zero(sizeof(circ_buffer_stats_t));
 +  stat->processed_cells = orcirc->processed_cells;
 +  /* 1000.0 for s -> ms; 2.0 because of app-ward and exit-ward queues */
 +  stat->mean_num_cells_in_queue = interval_length == 0 ? 0.0 :
 +      (double) orcirc->total_cell_waiting_time /
 +      (double) interval_length / 1000.0 / 2.0;
 +  stat->mean_time_cells_in_queue =
 +      (double) orcirc->total_cell_waiting_time /
 +      (double) orcirc->processed_cells;
 +  smartlist_add(circuits_for_buffer_stats, stat);
 +  orcirc->total_cell_waiting_time = 0;
 +  orcirc->processed_cells = 0;
 +}
 +
 +/** Sorting helper: return -1, 1, or 0 based on comparison of two
 + * circ_buffer_stats_t */
 +static int
 +_buffer_stats_compare_entries(const void **_a, const void **_b)
  {
 -  hs_usage_add_service_related_observation(fetch_total, now, service_id);
 +  const circ_buffer_stats_t *a = *_a, *b = *_b;
 +  if (a->processed_cells < b->processed_cells)
 +    return 1;
 +  else if (a->processed_cells > b->processed_cells)
 +    return -1;
 +  else
 +    return 0;
  }
  
 -/** Adds the observation of being requested for a rendezvous service descriptor
 - * in our role as HS authoritative directory and being able to answer that
 - * request successfully. */
 +/** Stop collecting cell stats in a way that we can re-start doing so in
 + * rep_hist_buffer_stats_init(). */
  void
 -hs_usage_note_fetch_successful(const char *service_id, time_t now)
 +rep_hist_buffer_stats_term(void)
  {
 -  hs_usage_add_service_related_observation(fetch_successful, now, service_id);
 +  start_of_buffer_stats_interval = 0;
 +  if (!circuits_for_buffer_stats)
 +    circuits_for_buffer_stats = smartlist_create();
 +  SMARTLIST_FOREACH(circuits_for_buffer_stats, circ_buffer_stats_t *,
 +      stat, tor_free(stat));
 +  smartlist_clear(circuits_for_buffer_stats);
  }
  
 -/** Writes the given circular array to a string. */
 -static size_t
 -hs_usage_format_history(char *buf, size_t len, uint32_t *data)
 +/** Write buffer statistics to $DATADIR/stats/buffer-stats and return when
 + * we would next want to write exit stats. */
 +time_t
 +rep_hist_buffer_stats_write(time_t now)
  {
 -  char *cp = buf; /* pointer where we are in the buffer */
 -  int i, n;
 -  if (current_period->num_set <= current_period->next_idx) {
 -    i = 0; /* not been through circular array */
 -  } else {
 -    i = current_period->next_idx;
 +  char *statsdir = NULL, *filename = NULL;
 +  char written[ISO_TIME_LEN+1];
 +  open_file_t *open_file = NULL;
 +  FILE *out;
 +#define SHARES 10
 +  int processed_cells[SHARES], circs_in_share[SHARES],
 +      number_of_circuits, i;
 +  double queued_cells[SHARES], time_in_queue[SHARES];
 +  smartlist_t *str_build = smartlist_create();
 +  char *str = NULL, *buf=NULL;
 +  circuit_t *circ;
 +
 +  if (!start_of_buffer_stats_interval)
 +    return 0; /* Not initialized. */
 +  if (start_of_buffer_stats_interval + WRITE_STATS_INTERVAL > now)
 +    goto done; /* Not ready to write */
 +
 +  /* add current circuits to stats */
 +  for (circ = _circuit_get_global_list(); circ; circ = circ->next)
 +    rep_hist_buffer_stats_add_circ(circ, now);
 +  /* calculate deciles */
 +  memset(processed_cells, 0, SHARES * sizeof(int));
 +  memset(circs_in_share, 0, SHARES * sizeof(int));
 +  memset(queued_cells, 0, SHARES * sizeof(double));
 +  memset(time_in_queue, 0, SHARES * sizeof(double));
 +  if (!circuits_for_buffer_stats)
 +    circuits_for_buffer_stats = smartlist_create();
 +  smartlist_sort(circuits_for_buffer_stats,
 +                 _buffer_stats_compare_entries);
 +  number_of_circuits = smartlist_len(circuits_for_buffer_stats);
 +  if (number_of_circuits < 1) {
 +    log_info(LD_HIST, "Attempt to write cell statistics to disk failed. "
 +             "We haven't seen a single circuit to report about.");
 +    goto done;
    }
 -  for (n = 0; n < current_period->num_set; ++n,++i) {
 -    if (i >= NUM_TOTALS_HS_USAGE)
 -      i -= NUM_TOTALS_HS_USAGE;
 -    tor_assert(i < NUM_TOTALS_HS_USAGE);
 -    if (n == (current_period->num_set-1))
 -      tor_snprintf(cp, len-(cp-buf), "%d", data[i]);
 -    else
 -      tor_snprintf(cp, len-(cp-buf), "%d,", data[i]);
 -    cp += strlen(cp);
 +  i = 0;
 +  SMARTLIST_FOREACH_BEGIN(circuits_for_buffer_stats,
 +                          circ_buffer_stats_t *, stat)
 +  {
 +    int share = i++ * SHARES / number_of_circuits;
 +    processed_cells[share] += stat->processed_cells;
 +    queued_cells[share] += stat->mean_num_cells_in_queue;
 +    time_in_queue[share] += stat->mean_time_cells_in_queue;
 +    circs_in_share[share]++;
    }
 -  return cp-buf;
 -}
 -
 -/** Writes the complete usage history as hidden service authoritative directory
 - * to a string. */
 -static char *
 -hs_usage_format_statistics(void)
 -{
 -  char *buf, *cp, *s = NULL;
 -  char t[ISO_TIME_LEN+1];
 -  int r;
 -  uint32_t *data = NULL;
 -  size_t len;
 -  len = (70+20*NUM_TOTALS_HS_USAGE)*11;
 -  buf = tor_malloc_zero(len);
 -  cp = buf;
 -  for (r = 0; r < 11; ++r) {
 -    switch (r) {
 -    case 0:
 -      s = (char*) "publish-total-history";
 -      data = publish_total->totals;
 -      break;
 -    case 1:
 -      s = (char*) "publish-novel-history";
 -      data = publish_novel->totals;
 -      break;
 -    case 2:
 -      s = (char*) "publish-top-5-percent-history";
 -      data = publish_total->five;
 -      break;
 -    case 3:
 -      s = (char*) "publish-top-10-percent-history";
 -      data = publish_total->ten;
 -      break;
 -    case 4:
 -      s = (char*) "publish-top-20-percent-history";
 -      data = publish_total->twenty;
 -      break;
 -    case 5:
 -      s = (char*) "fetch-total-history";
 -      data = fetch_total->totals;
 -      break;
 -    case 6:
 -      s = (char*) "fetch-successful-history";
 -      data = fetch_successful->totals;
 -      break;
 -    case 7:
 -      s = (char*) "fetch-top-5-percent-history";
 -      data = fetch_total->five;
 -      break;
 -    case 8:
 -      s = (char*) "fetch-top-10-percent-history";
 -      data = fetch_total->ten;
 -      break;
 -    case 9:
 -      s = (char*) "fetch-top-20-percent-history";
 -      data = fetch_total->twenty;
 -      break;
 -    case 10:
 -      s = (char*) "desc-total-history";
 -      data = descs->totals;
 -      break;
 -    }
 -    format_iso_time(t, current_period->start_of_current_period);
 -    tor_snprintf(cp, len-(cp-buf), "%s %s (%d s) ", s, t,
 -                 NUM_SECS_HS_USAGE_SUM_INTERVAL);
 -    cp += strlen(cp);
 -    cp += hs_usage_format_history(cp, len-(cp-buf), data);
 -    strlcat(cp, "\n", len-(cp-buf));
 -    ++cp;
 +  SMARTLIST_FOREACH_END(stat);
 +  /* clear buffer stats history */
 +  SMARTLIST_FOREACH(circuits_for_buffer_stats, circ_buffer_stats_t *,
 +      stat, tor_free(stat));
 +  smartlist_clear(circuits_for_buffer_stats);
 +  /* write to file */
 +  statsdir = get_datadir_fname("stats");
 +  if (check_private_dir(statsdir, CPD_CREATE) < 0)
 +    goto done;
 +  filename = get_datadir_fname2("stats", "buffer-stats");
 +  out = start_writing_to_stdio_file(filename, OPEN_FLAGS_APPEND,
 +                                    0600, &open_file);
 +  if (!out)
 +    goto done;
 +  format_iso_time(written, now);
 +  if (fprintf(out, "cell-stats-end %s (%d s)\n", written,
 +              (unsigned) (now - start_of_buffer_stats_interval)) < 0)
 +    goto done;
 +  for (i = 0; i < SHARES; i++) {
 +    tor_asprintf(&buf,"%d", !circs_in_share[i] ? 0 :
 +                 processed_cells[i] / circs_in_share[i]);
 +    smartlist_add(str_build, buf);
    }
 -  return buf;
 +  str = smartlist_join_strings(str_build, ",", 0, NULL);
 +  if (fprintf(out, "cell-processed-cells %s\n", str) < 0)
 +    goto done;
 +  tor_free(str);
 +  SMARTLIST_FOREACH(str_build, char *, c, tor_free(c));
 +  smartlist_clear(str_build);
 +  for (i = 0; i < SHARES; i++) {
 +    tor_asprintf(&buf, "%.2f", circs_in_share[i] == 0 ? 0.0 :
 +                 queued_cells[i] / (double) circs_in_share[i]);
 +    smartlist_add(str_build, buf);
 +  }
 +  str = smartlist_join_strings(str_build, ",", 0, NULL);
 +  if (fprintf(out, "cell-queued-cells %s\n", str) < 0)
 +    goto done;
 +  tor_free(str);
 +  SMARTLIST_FOREACH(str_build, char *, c, tor_free(c));
 +  smartlist_clear(str_build);
 +  for (i = 0; i < SHARES; i++) {
 +    tor_asprintf(&buf, "%.0f", circs_in_share[i] == 0 ? 0.0 :
 +                 time_in_queue[i] / (double) circs_in_share[i]);
 +    smartlist_add(str_build, buf);
 +  }
 +  str = smartlist_join_strings(str_build, ",", 0, NULL);
 +  if (fprintf(out, "cell-time-in-queue %s\n", str) < 0)
 +    goto done;
 +  tor_free(str);
 +  SMARTLIST_FOREACH(str_build, char *, c, tor_free(c));
 +  smartlist_free(str_build);
 +  str_build = NULL;
 +  if (fprintf(out, "cell-circuits-per-decile %d\n",
 +              (number_of_circuits + SHARES - 1) / SHARES) < 0)
 +    goto done;
 +  finish_writing_to_file(open_file);
 +  open_file = NULL;
 +  start_of_buffer_stats_interval = now;
 + done:
 +  if (open_file)
 +    abort_writing_to_file(open_file);
 +  tor_free(filename);
 +  tor_free(statsdir);
 +  if (str_build) {
 +    SMARTLIST_FOREACH(str_build, char *, c, tor_free(c));
 +    smartlist_free(str_build);
 +  }
 +  tor_free(str);
 +#undef SHARES
 +  return start_of_buffer_stats_interval + WRITE_STATS_INTERVAL;
  }
  
 -/** Write current statistics about hidden service usage to file. */
 +/** Free all storage held by the OR/link history caches, by the
 + * bandwidth history arrays, by the port history, or by statistics . */
  void
 -hs_usage_write_statistics_to_file(time_t now)
 +rep_hist_free_all(void)
  {
 -  char *buf;
 -  size_t len;
 -  char *fname;
 -  or_options_t *options = get_options();
 -  /* check if we are up-to-date */
 -  hs_usage_check_if_current_period_is_up_to_date(now);
 -  buf = hs_usage_format_statistics();
 -  len = strlen(options->DataDirectory) + 16;
 -  fname = tor_malloc(len);
 -  tor_snprintf(fname, len, "%s"PATH_SEPARATOR"hsusage",
 -               options->DataDirectory);
 -  write_str_to_file(fname,buf,0);
 -  tor_free(buf);
 -  tor_free(fname);
 +  digestmap_free(history_map, free_or_history);
 +  tor_free(read_array);
 +  tor_free(write_array);
 +  tor_free(last_stability_doc);
 +  tor_free(exit_bytes_read);
 +  tor_free(exit_bytes_written);
 +  tor_free(exit_streams);
 +  built_last_stability_doc_at = 0;
 +  predicted_ports_free();
  }
  
diff --combined src/or/router.c
index 66ef8ec,75f592d..3bb37de
--- a/src/or/router.c
+++ b/src/or/router.c
@@@ -1,30 -1,12 +1,30 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  #define ROUTER_PRIVATE
  
  #include "or.h"
 +#include "circuitlist.h"
 +#include "circuituse.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "control.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dns.h"
 +#include "geoip.h"
 +#include "hibernate.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "policies.h"
 +#include "relay.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  /**
   * \file router.c
@@@ -49,15 -31,11 +49,15 @@@ static crypto_pk_env_t *onionkey=NULL
  /** Previous private onionskin decryption key: used to decode CREATE cells
   * generated by clients that have an older version of our descriptor. */
  static crypto_pk_env_t *lastonionkey=NULL;
 -/** Private "identity key": used to sign directory info and TLS
 +/** Private server "identity key": used to sign directory info and TLS
   * certificates. Never changes. */
 -static crypto_pk_env_t *identitykey=NULL;
 -/** Digest of identitykey. */
 -static char identitykey_digest[DIGEST_LEN];
 +static crypto_pk_env_t *server_identitykey=NULL;
 +/** Digest of server_identitykey. */
 +static char server_identitykey_digest[DIGEST_LEN];
 +/** Private client "identity key": used to sign bridges' and clients'
 + * outbound TLS certificates. Regenerated on startup and on IP address
 + * change. */
 +static crypto_pk_env_t *client_identitykey=NULL;
  /** Signing key used for v3 directory material; only set for authorities. */
  static crypto_pk_env_t *authority_signing_key = NULL;
  /** Key certificate to authenticate v3 directory material; only set for
@@@ -83,7 -61,8 +83,7 @@@ static voi
  set_onion_key(crypto_pk_env_t *k)
  {
    tor_mutex_acquire(key_lock);
 -  if (onionkey)
 -    crypto_free_pk_env(onionkey);
 +  crypto_free_pk_env(onionkey);
    onionkey = k;
    onionkey_set_at = time(NULL);
    tor_mutex_release(key_lock);
@@@ -127,78 -106,32 +127,78 @@@ get_onion_key_set_at(void
    return onionkey_set_at;
  }
  
 -/** Set the current identity key to k.
 +/** Set the current server identity key to <b>k</b>.
   */
  void
 -set_identity_key(crypto_pk_env_t *k)
 +set_server_identity_key(crypto_pk_env_t *k)
  {
 -  if (identitykey)
 -    crypto_free_pk_env(identitykey);
 -  identitykey = k;
 -  crypto_pk_get_digest(identitykey, identitykey_digest);
 +  crypto_free_pk_env(server_identitykey);
 +  server_identitykey = k;
 +  crypto_pk_get_digest(server_identitykey, server_identitykey_digest);
  }
  
 -/** Returns the current identity key; requires that the identity key has been
 - * set.
 +/** Make sure that we have set up our identity keys to match or not match as
 + * appropriate, and die with an assertion if we have not. */
 +static void
 +assert_identity_keys_ok(void)
 +{
 +  tor_assert(client_identitykey);
 +  if (public_server_mode(get_options())) {
 +    /* assert that we have set the client and server keys to be equal */
 +    tor_assert(server_identitykey);
 +    tor_assert(0==crypto_pk_cmp_keys(client_identitykey, server_identitykey));
 +  } else {
 +    /* assert that we have set the client and server keys to be unequal */
 +    if (server_identitykey)
 +       tor_assert(0!=crypto_pk_cmp_keys(client_identitykey,
 +                                        server_identitykey));
 +  }
 +}
 +
 +/** Returns the current server identity key; requires that the key has
 + * been set, and that we are running as a Tor server.
 + */
 +crypto_pk_env_t *
 +get_server_identity_key(void)
 +{
 +  tor_assert(server_identitykey);
 +  tor_assert(server_mode(get_options()));
 +  assert_identity_keys_ok();
 +  return server_identitykey;
 +}
 +
 +/** Return true iff the server identity key has been set. */
 +int
 +server_identity_key_is_set(void)
 +{
 +  return server_identitykey != NULL;
 +}
 +
 +/** Set the current client identity key to <b>k</b>.
 + */
 +void
 +set_client_identity_key(crypto_pk_env_t *k)
 +{
 +  crypto_free_pk_env(client_identitykey);
 +  client_identitykey = k;
 +}
 +
 +/** Returns the current client identity key for use on outgoing TLS
 + * connections; requires that the key has been set.
   */
  crypto_pk_env_t *
 -get_identity_key(void)
 +get_tlsclient_identity_key(void)
  {
 -  tor_assert(identitykey);
 -  return identitykey;
 +  tor_assert(client_identitykey);
 +  assert_identity_keys_ok();
 +  return client_identitykey;
  }
  
 -/** Return true iff the identity key has been set. */
 +/** Return true iff the client identity key has been set. */
  int
 -identity_key_is_set(void)
 +client_identity_key_is_set(void)
  {
 -  return identitykey != NULL;
 +  return client_identitykey != NULL;
  }
  
  /** Return the key certificate for this v3 (voting) authority, or NULL
@@@ -268,7 -201,8 +268,7 @@@ rotate_onion_key(void
    }
    log_info(LD_GENERAL, "Rotating onion key");
    tor_mutex_acquire(key_lock);
 -  if (lastonionkey)
 -    crypto_free_pk_env(lastonionkey);
 +  crypto_free_pk_env(lastonionkey);
    lastonionkey = onionkey;
    onionkey = prkey;
    now = time(NULL);
@@@ -397,9 -331,10 +397,9 @@@ load_authority_keyset(int legacy, crypt
      goto done;
    }
  
 -  if (*key_out)
 -    crypto_free_pk_env(*key_out);
 -  if (*cert_out)
 -    authority_cert_free(*cert_out);
 +  crypto_free_pk_env(*key_out);
 +  authority_cert_free(*cert_out);
 +
    *key_out = signing_key;
    *cert_out = parsed;
    r = 0;
@@@ -409,8 -344,10 +409,8 @@@
   done:
    tor_free(fname);
    tor_free(cert);
 -  if (signing_key)
 -    crypto_free_pk_env(signing_key);
 -  if (parsed)
 -    authority_cert_free(parsed);
 +  crypto_free_pk_env(signing_key);
 +  authority_cert_free(parsed);
    return r;
  }
  
@@@ -505,9 -442,7 +505,9 @@@ init_keys(void
      key_lock = tor_mutex_new();
  
    /* There are a couple of paths that put us here before */
 -  if (crypto_global_init(get_options()->HardwareAccel)) {
 +  if (crypto_global_init(get_options()->HardwareAccel,
 +                         get_options()->AccelName,
 +                         get_options()->AccelDir)) {
      log_err(LD_BUG, "Unable to initialize OpenSSL. Exiting.");
      return -1;
    }
@@@ -521,12 -456,9 +521,12 @@@
        crypto_free_pk_env(prkey);
        return -1;
      }
 -    set_identity_key(prkey);
 -    /* Create a TLS context; default the client nickname to "client". */
 -    if (tor_tls_context_new(get_identity_key(), MAX_SSL_KEY_LIFETIME) < 0) {
 +    set_client_identity_key(prkey);
 +    /* Create a TLS context. */
 +    if (tor_tls_context_init(0,
 +                             get_tlsclient_identity_key(),
 +                             NULL,
 +                             MAX_SSL_KEY_LIFETIME) < 0) {
        log_err(LD_GENERAL,"Error creating TLS context for Tor client.");
        return -1;
      }
@@@ -561,28 -493,13 +561,28 @@@
      }
    }
  
 -  /* 1. Read identity key. Make it if none is found. */
 +  /* 1b. Read identity key. Make it if none is found. */
    keydir = get_datadir_fname2("keys", "secret_id_key");
    log_info(LD_GENERAL,"Reading/making identity key \"%s\"...",keydir);
    prkey = init_key_from_file(keydir, 1, LOG_ERR);
    tor_free(keydir);
    if (!prkey) return -1;
 -  set_identity_key(prkey);
 +  set_server_identity_key(prkey);
 +
 +  /* 1c. If we are configured as a bridge, generate a client key;
 +   * otherwise, set the server identity key as our client identity
 +   * key. */
 +  if (public_server_mode(options)) {
 +    set_client_identity_key(crypto_pk_dup_key(prkey)); /* set above */
 +  } else {
 +    if (!(prkey = crypto_new_pk_env()))
 +      return -1;
 +    if (crypto_pk_generate_key(prkey)) {
 +      crypto_free_pk_env(prkey);
 +      return -1;
 +    }
 +    set_client_identity_key(prkey);
 +  }
  
    /* 2. Read onion key.  Make it if none is found. */
    keydir = get_datadir_fname2("keys", "secret_onion_key");
@@@ -619,10 -536,7 +619,10 @@@
    tor_free(keydir);
  
    /* 3. Initialize link key and TLS context. */
 -  if (tor_tls_context_new(get_identity_key(), MAX_SSL_KEY_LIFETIME) < 0) {
 +  if (tor_tls_context_init(public_server_mode(options),
 +                           get_tlsclient_identity_key(),
 +                           get_server_identity_key(),
 +                           MAX_SSL_KEY_LIFETIME) < 0) {
      log_err(LD_GENERAL,"Error initializing TLS context");
      return -1;
    }
@@@ -633,8 -547,7 +633,8 @@@
      const char *m = NULL;
      routerinfo_t *ri;
      /* We need to add our own fingerprint so it gets recognized. */
 -    if (dirserv_add_own_fingerprint(options->Nickname, get_identity_key())) {
 +    if (dirserv_add_own_fingerprint(options->Nickname,
 +                                    get_server_identity_key())) {
        log_err(LD_GENERAL,"Error adding own fingerprint to approved set");
        return -1;
      }
@@@ -655,8 -568,7 +655,8 @@@
    /* 5. Dump fingerprint to 'fingerprint' */
    keydir = get_datadir_fname("fingerprint");
    log_info(LD_GENERAL,"Dumping fingerprint to \"%s\"...",keydir);
 -  if (crypto_pk_get_fingerprint(get_identity_key(), fingerprint, 0)<0) {
 +  if (crypto_pk_get_fingerprint(get_server_identity_key(),
 +                                fingerprint, 0) < 0) {
      log_err(LD_GENERAL,"Error computing fingerprint");
      tor_free(keydir);
      return -1;
@@@ -694,7 -606,7 +694,7 @@@
      return -1;
    }
    /* 6b. [authdirserver only] add own key to approved directories. */
 -  crypto_pk_get_digest(get_identity_key(), digest);
 +  crypto_pk_get_digest(get_server_identity_key(), digest);
    type = ((options->V1AuthoritativeDir ? V1_AUTHORITY : NO_AUTHORITY) |
            (options->V2AuthoritativeDir ? V2_AUTHORITY : NO_AUTHORITY) |
            (options->V3AuthoritativeDir ? V3_AUTHORITY : NO_AUTHORITY) |
@@@ -1050,28 -962,6 +1050,28 @@@ server_mode(or_options_t *options
    return (options->ORPort != 0 || options->ORListenAddress);
  }
  
 +/** Return true iff we are trying to be a non-bridge server.
 + */
 +int
 +public_server_mode(or_options_t *options)
 +{
 +  if (!server_mode(options)) return 0;
 +  return (!options->BridgeRelay);
 +}
 +
 +/** Return true iff the combination of options in <b>options</b> and parameters
 + * in the consensus mean that we don't want to allow exits from circuits
 + * we got from addresses not known to be servers. */
 +int
 +should_refuse_unknown_exits(or_options_t *options)
 +{
 +  if (options->RefuseUnknownExits_ != -1) {
 +    return options->RefuseUnknownExits_;
 +  } else {
 +    return networkstatus_get_param(NULL, "refuseunknownexits", 1);
 +  }
 +}
 +
  /** Remember if we've advertised ourselves to the dirservers. */
  static int server_is_advertised=0;
  
@@@ -1099,7 -989,7 +1099,7 @@@ proxy_mode(or_options_t *options
  {
    return (options->SocksPort != 0 || options->SocksListenAddress ||
            options->TransPort != 0 || options->TransListenAddress ||
 -          options->NatdPort != 0 || options->NatdListenAddress ||
 +          options->NATDPort != 0 || options->NATDListenAddress ||
            options->DNSPort != 0 || options->DNSListenAddress);
  }
  
@@@ -1234,24 -1124,12 +1234,24 @@@ router_compare_to_my_exit_policy(edge_c
                     desc_routerinfo->exit_policy) != ADDR_POLICY_ACCEPTED;
  }
  
 +/** Return true iff my exit policy is reject *:*.  Return -1 if we don't
 + * have a descriptor */
 +int
 +router_my_exit_policy_is_reject_star(void)
 +{
 +  if (!router_get_my_routerinfo()) /* make sure desc_routerinfo exists */
 +    return -1;
 +
 +  return desc_routerinfo->policy_is_reject_star;
 +}
 +
  /** Return true iff I'm a server and <b>digest</b> is equal to
 - * my identity digest. */
 + * my server identity key digest. */
  int
  router_digest_is_me(const char *digest)
  {
 -  return identitykey && !memcmp(identitykey_digest, digest, DIGEST_LEN);
 +  return (server_identitykey &&
 +          !memcmp(server_identitykey_digest, digest, DIGEST_LEN));
  }
  
  /** Return true iff I'm a server and <b>digest</b> is equal to
@@@ -1341,8 -1219,6 +1341,8 @@@ static int router_guess_address_from_di
  int
  router_pick_published_address(or_options_t *options, uint32_t *addr)
  {
 +  char buf[INET_NTOA_BUF_LEN];
 +  struct in_addr a;
    if (resolve_my_address(LOG_INFO, options, addr, NULL) < 0) {
      log_info(LD_CONFIG, "Could not determine our address locally. "
               "Checking if directory headers provide any hints.");
@@@ -1352,9 -1228,6 +1352,9 @@@
        return -1;
      }
    }
 +  a.s_addr = htonl(*addr);
 +  tor_inet_ntoa(&a, buf, sizeof(buf));
 +  log_info(LD_CONFIG,"Success: chose address '%s'.", buf);
    return 0;
  }
  
@@@ -1377,7 -1250,7 +1377,7 @@@ router_rebuild_descriptor(int force
  
    if (router_pick_published_address(options, &addr) < 0) {
      /* Stop trying to rebuild our descriptor every second. We'll
 -     * learn that it's time to try again when server_has_changed_ip()
 +     * learn that it's time to try again when ip_address_changed()
       * marks it dirty. */
      desc_clean_since = time(NULL);
      return -1;
@@@ -1393,7 -1266,7 +1393,7 @@@
    ri->cache_info.published_on = time(NULL);
    ri->onion_pkey = crypto_pk_dup_key(get_onion_key()); /* must invoke from
                                                          * main thread */
 -  ri->identity_pkey = crypto_pk_dup_key(get_identity_key());
 +  ri->identity_pkey = crypto_pk_dup_key(get_server_identity_key());
    if (crypto_pk_get_digest(ri->identity_pkey,
                             ri->cache_info.identity_digest)<0) {
      routerinfo_free(ri);
@@@ -1412,9 -1285,7 +1412,9 @@@
  
    policies_parse_exit_policy(options->ExitPolicy, &ri->exit_policy,
                               options->ExitPolicyRejectPrivate,
 -                             ri->address);
 +                             ri->address, !options->BridgeRelay);
 +  ri->policy_is_reject_star =
 +    policy_is_reject_star(ri->exit_policy);
  
    if (desc_routerinfo) { /* inherit values */
      ri->is_valid = desc_routerinfo->is_valid;
@@@ -1485,30 -1356,26 +1485,30 @@@
    ei->cache_info.published_on = ri->cache_info.published_on;
    memcpy(ei->cache_info.identity_digest, ri->cache_info.identity_digest,
           DIGEST_LEN);
 -  ei->cache_info.signed_descriptor_body = tor_malloc(8192);
 -  if (extrainfo_dump_to_string(ei->cache_info.signed_descriptor_body, 8192,
 -                               ei, get_identity_key()) < 0) {
 +  if (extrainfo_dump_to_string(&ei->cache_info.signed_descriptor_body,
 +                               ei, get_server_identity_key()) < 0) {
      log_warn(LD_BUG, "Couldn't generate extra-info descriptor.");
 -    routerinfo_free(ri);
      extrainfo_free(ei);
 -    return -1;
 +    ei = NULL;
 +  } else {
 +    ei->cache_info.signed_descriptor_len =
 +      strlen(ei->cache_info.signed_descriptor_body);
 +    router_get_extrainfo_hash(ei->cache_info.signed_descriptor_body,
 +                              ei->cache_info.signed_descriptor_digest);
    }
 -  ei->cache_info.signed_descriptor_len =
 -    strlen(ei->cache_info.signed_descriptor_body);
 -  router_get_extrainfo_hash(ei->cache_info.signed_descriptor_body,
 -                            ei->cache_info.signed_descriptor_digest);
  
    /* Now finish the router descriptor. */
 -  memcpy(ri->cache_info.extra_info_digest,
 -         ei->cache_info.signed_descriptor_digest,
 -         DIGEST_LEN);
 +  if (ei) {
 +    memcpy(ri->cache_info.extra_info_digest,
 +           ei->cache_info.signed_descriptor_digest,
 +           DIGEST_LEN);
 +  } else {
 +    /* ri was allocated with tor_malloc_zero, so there is no need to
 +     * zero ri->cache_info.extra_info_digest here. */
 +  }
    ri->cache_info.signed_descriptor_body = tor_malloc(8192);
    if (router_dump_router_to_string(ri->cache_info.signed_descriptor_body, 8192,
 -                                   ri, get_identity_key())<0) {
 +                                   ri, get_server_identity_key()) < 0) {
      log_warn(LD_BUG, "Couldn't generate router descriptor.");
      routerinfo_free(ri);
      extrainfo_free(ei);
@@@ -1523,7 -1390,7 +1523,7 @@@
    /* Let bridges serve their own descriptors unencrypted, so they can
     * pass reachability testing. (If they want to be harder to notice,
     * they can always leave the DirPort off). */
 -  if (!options->BridgeRelay)
 +  if (ei && !options->BridgeRelay)
      ei->cache_info.send_unencrypted = 1;
  
    router_get_router_hash(ri->cache_info.signed_descriptor_body,
@@@ -1532,13 -1399,13 +1532,13 @@@
  
    routerinfo_set_country(ri);
  
 -  tor_assert(! routerinfo_incompatible_with_extrainfo(ri, ei, NULL, NULL));
 +  if (ei) {
 +    tor_assert(! routerinfo_incompatible_with_extrainfo(ri, ei, NULL, NULL));
 +  }
  
 -  if (desc_routerinfo)
 -    routerinfo_free(desc_routerinfo);
 +  routerinfo_free(desc_routerinfo);
    desc_routerinfo = ri;
 -  if (desc_extrainfo)
 -    extrainfo_free(desc_extrainfo);
 +  extrainfo_free(desc_extrainfo);
    desc_extrainfo = ei;
  
    desc_clean_since = time(NULL);
@@@ -1719,6 -1586,8 +1719,6 @@@ router_guess_address_from_dir_headers(u
    return -1;
  }
  
 -extern const char tor_svn_revision[]; /* from tor_main.c */
 -
  /** Set <b>platform</b> (max length <b>len</b>) to a NUL-terminated short
   * string describing the version of Tor and the operating system we're
   * currently running on.
@@@ -1749,7 -1618,6 +1749,7 @@@ router_dump_router_to_string(char *s, s
    char digest[DIGEST_LEN];
    char published[ISO_TIME_LEN+1];
    char fingerprint[FINGERPRINT_LEN+1];
 +  int has_extra_info_digest;
    char extra_info_digest[HEX_DIGEST_LEN+1];
    size_t onion_pkeylen, identity_pkeylen;
    size_t written;
@@@ -1778,7 -1646,7 +1778,7 @@@
      return -1;
    }
  
 -  /* PEM-encode the identity key key */
 +  /* PEM-encode the identity key */
    if (crypto_pk_write_public_key_to_string(router->identity_pkey,
                                          &identity_pkey,&identity_pkeylen)<0) {
      log_warn(LD_BUG,"write identity_pkey to string failed!");
@@@ -1800,13 -1668,8 +1800,13 @@@
      family_line = tor_strdup("");
    }
  
 -  base16_encode(extra_info_digest, sizeof(extra_info_digest),
 -                router->cache_info.extra_info_digest, DIGEST_LEN);
 +  has_extra_info_digest =
 +    ! tor_digest_is_zero(router->cache_info.extra_info_digest);
 +
 +  if (has_extra_info_digest) {
 +    base16_encode(extra_info_digest, sizeof(extra_info_digest),
 +                  router->cache_info.extra_info_digest, DIGEST_LEN);
 +  }
  
    /* Generate the easy portion of the router descriptor. */
    result = tor_snprintf(s, maxlen,
@@@ -1817,7 -1680,7 +1817,7 @@@
                      "opt fingerprint %s\n"
                      "uptime %ld\n"
                      "bandwidth %d %d %d\n"
 -                    "opt extra-info-digest %s\n%s"
 +                    "%s%s%s%s"
                      "onion-key\n%s"
                      "signing-key\n%s"
                      "%s%s%s%s",
@@@ -1832,9 -1695,7 +1832,9 @@@
      (int) router->bandwidthrate,
      (int) router->bandwidthburst,
      (int) router->bandwidthcapacity,
 -    extra_info_digest,
 +    has_extra_info_digest ? "opt extra-info-digest " : "",
 +    has_extra_info_digest ? extra_info_digest : "",
 +    has_extra_info_digest ? "\n" : "",
      options->DownloadExtraInfo ? "opt caches-extra-info\n" : "",
      onion_pkey, identity_pkey,
      family_line,
@@@ -1891,8 -1752,7 +1891,8 @@@
      }
    }
  
 -  if (written+256 > maxlen) { /* Not enough room for signature. */
 +  if (written + DIROBJ_MAX_SIG_LEN > maxlen) {
 +    /* Not enough room for signature. */
      log_warn(LD_BUG,"not enough room left in descriptor for signature!");
      return -1;
    }
@@@ -1907,7 -1767,7 +1907,7 @@@
  
    note_crypto_pk_op(SIGN_RTR);
    if (router_append_dirobj_signature(s+written,maxlen-written,
 -                                     digest,ident_key)<0) {
 +                                     digest,DIGEST_LEN,ident_key)<0) {
      log_warn(LD_BUG, "Couldn't sign router descriptor");
      return -1;
    }
@@@ -1942,62 -1802,11 +1942,62 @@@
    return (int)written+1;
  }
  
 -/** Write the contents of <b>extrainfo</b> to the <b>maxlen</b>-byte string
 - * <b>s</b>, signing them with <b>ident_key</b>.  Return 0 on success,
 - * negative on failure. */
 +/** Load the contents of <b>filename</b>, find the last line starting with
 + * <b>end_line</b>, ensure that its timestamp is not more than 25 hours in
 + * the past or more than 1 hour in the future with respect to <b>now</b>,
 + * and write the file contents starting with that line to *<b>out</b>.
 + * Return 1 for success, 0 if the file does not exist, or -1 if the file
 + * does not contain a line matching these criteria or other failure. */
 +static int
 +load_stats_file(const char *filename, const char *end_line, time_t now,
 +                char **out)
 +{
 +  int r = -1;
 +  char *fname = get_datadir_fname(filename);
 +  char *contents, *start = NULL, *tmp, timestr[ISO_TIME_LEN+1];
 +  time_t written;
 +  switch (file_status(fname)) {
 +    case FN_FILE:
 +      /* X022 Find an alternative to reading the whole file to memory. */
 +      if ((contents = read_file_to_str(fname, 0, NULL))) {
 +        tmp = strstr(contents, end_line);
 +        /* Find last block starting with end_line */
 +        while (tmp) {
 +          start = tmp;
 +          tmp = strstr(tmp + 1, end_line);
 +        }
 +        if (!start)
 +          goto notfound;
 +        if (strlen(start) < strlen(end_line) + 1 + sizeof(timestr))
 +          goto notfound;
 +        strlcpy(timestr, start + 1 + strlen(end_line), sizeof(timestr));
 +        if (parse_iso_time(timestr, &written) < 0)
 +          goto notfound;
 +        if (written < now - (25*60*60) || written > now + (1*60*60))
 +          goto notfound;
 +        *out = tor_strdup(start);
 +        r = 1;
 +      }
 +     notfound:
 +      tor_free(contents);
 +      break;
 +    case FN_NOENT:
 +      r = 0;
 +      break;
 +    case FN_ERROR:
 +    case FN_DIR:
 +    default:
 +      break;
 +  }
 +  tor_free(fname);
 +  return r;
 +}
 +
 +/** Write the contents of <b>extrainfo</b> and aggregated statistics to
 + * *<b>s_out</b>, signing them with <b>ident_key</b>. Return 0 on
 + * success, negative on failure. */
  int
 -extrainfo_dump_to_string(char *s, size_t maxlen, extrainfo_t *extrainfo,
 +extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
                           crypto_pk_env_t *ident_key)
  {
    or_options_t *options = get_options();
@@@ -2006,128 -1815,87 +2006,128 @@@
    char digest[DIGEST_LEN];
    char *bandwidth_usage;
    int result;
 -  size_t len;
 +  static int write_stats_to_extrainfo = 1;
 +  char sig[DIROBJ_MAX_SIG_LEN+1];
 +  char *s, *pre, *contents, *cp, *s_dup = NULL;
 +  time_t now = time(NULL);
 +  smartlist_t *chunks = smartlist_create();
 +  extrainfo_t *ei_tmp = NULL;
  
    base16_encode(identity, sizeof(identity),
                  extrainfo->cache_info.identity_digest, DIGEST_LEN);
    format_iso_time(published, extrainfo->cache_info.published_on);
 -  bandwidth_usage = rep_hist_get_bandwidth_lines(1);
 +  bandwidth_usage = rep_hist_get_bandwidth_lines();
  
 -  result = tor_snprintf(s, maxlen,
 -                        "extra-info %s %s\n"
 -                        "published %s\n%s",
 -                        extrainfo->nickname, identity,
 -                        published, bandwidth_usage);
 +  tor_asprintf(&pre, "extra-info %s %s\npublished %s\n%s",
 +               extrainfo->nickname, identity,
 +               published, bandwidth_usage);
    tor_free(bandwidth_usage);
 -  if (result<0)
 -    return -1;
 +  smartlist_add(chunks, pre);
 +
 +  if (options->ExtraInfoStatistics && write_stats_to_extrainfo) {
 +    log_info(LD_GENERAL, "Adding stats to extra-info descriptor.");
 +    if (options->DirReqStatistics &&
 +        load_stats_file("stats"PATH_SEPARATOR"dirreq-stats",
 +                        "dirreq-stats-end", now, &contents) > 0) {
 +      smartlist_add(chunks, contents);
 +    }
 +    if (options->EntryStatistics &&
 +        load_stats_file("stats"PATH_SEPARATOR"entry-stats",
 +                        "entry-stats-end", now, &contents) > 0) {
 +      smartlist_add(chunks, contents);
 +    }
 +    if (options->CellStatistics &&
 +        load_stats_file("stats"PATH_SEPARATOR"buffer-stats",
 +                        "cell-stats-end", now, &contents) > 0) {
 +      smartlist_add(chunks, contents);
 +    }
 +    if (options->ExitPortStatistics &&
 +        load_stats_file("stats"PATH_SEPARATOR"exit-stats",
 +                        "exit-stats-end", now, &contents) > 0) {
 +      smartlist_add(chunks, contents);
 +    }
 +  }
  
 -  if (should_record_bridge_info(options)) {
 -    char *geoip_summary = extrainfo_get_client_geoip_summary(time(NULL));
 -    if (geoip_summary) {
 -      char geoip_start[ISO_TIME_LEN+1];
 -      format_iso_time(geoip_start, geoip_get_history_start());
 -      result = tor_snprintf(s+strlen(s), maxlen-strlen(s),
 -                            "geoip-start-time %s\n"
 -                            "geoip-client-origins %s\n",
 -                            geoip_start, geoip_summary);
 -      control_event_clients_seen(geoip_start, geoip_summary);
 -      tor_free(geoip_summary);
 -      if (result<0)
 -        return -1;
 +  if (should_record_bridge_info(options) && write_stats_to_extrainfo) {
 +    const char *bridge_stats = geoip_get_bridge_stats_extrainfo(now);
 +    if (bridge_stats) {
 +      smartlist_add(chunks, tor_strdup(bridge_stats));
      }
    }
  
 -  len = strlen(s);
 -  strlcat(s+len, "router-signature\n", maxlen-len);
 -  len += strlen(s+len);
 -  if (router_get_extrainfo_hash(s, digest)<0)
 -    return -1;
 -  if (router_append_dirobj_signature(s+len, maxlen-len, digest, ident_key)<0)
 -    return -1;
 +  smartlist_add(chunks, tor_strdup("router-signature\n"));
 +  s = smartlist_join_strings(chunks, "", 0, NULL);
 +
 +  while (strlen(s) > MAX_EXTRAINFO_UPLOAD_SIZE - DIROBJ_MAX_SIG_LEN) {
 +    /* So long as there are at least two chunks (one for the initial
 +     * extra-info line and one for the router-signature), we can keep removing
 +     * things. */
 +    if (smartlist_len(chunks) > 2) {
 +      /* We remove the next-to-last element (remember, len-1 is the last
 +         element), since we need to keep the router-signature element. */
 +      int idx = smartlist_len(chunks) - 2;
 +      char *e = smartlist_get(chunks, idx);
 +      smartlist_del_keeporder(chunks, idx);
 +      log_warn(LD_GENERAL, "We just generated an extra-info descriptor "
 +                           "with statistics that exceeds the 50 KB "
 +                           "upload limit. Removing last added "
 +                           "statistics.");
 +      tor_free(e);
 +      tor_free(s);
 +      s = smartlist_join_strings(chunks, "", 0, NULL);
 +    } else {
 +      log_warn(LD_BUG, "We just generated an extra-info descriptors that "
 +                       "exceeds the 50 KB upload limit.");
 +      goto err;
 +    }
 +  }
  
 -#ifdef DEBUG_ROUTER_DUMP_ROUTER_TO_STRING
 -  {
 -    char *cp, *s_dup;
 -    extrainfo_t *ei_tmp;
 -    cp = s_dup = tor_strdup(s);
 -    ei_tmp = extrainfo_parse_entry_from_string(cp, NULL, 1, NULL);
 -    if (!ei_tmp) {
 -      log_err(LD_BUG,
 -              "We just generated an extrainfo descriptor we can't parse.");
 -      log_err(LD_BUG, "Descriptor was: <<%s>>", s);
 -      tor_free(s_dup);
 -      return -1;
 +  memset(sig, 0, sizeof(sig));
 +  if (router_get_extrainfo_hash(s, digest) < 0 ||
 +      router_append_dirobj_signature(sig, sizeof(sig), digest, DIGEST_LEN,
 +                                     ident_key) < 0) {
 +    log_warn(LD_BUG, "Could not append signature to extra-info "
 +                     "descriptor.");
 +    goto err;
 +  }
 +  smartlist_add(chunks, tor_strdup(sig));
 +  tor_free(s);
 +  s = smartlist_join_strings(chunks, "", 0, NULL);
 +
 +  cp = s_dup = tor_strdup(s);
 +  ei_tmp = extrainfo_parse_entry_from_string(cp, NULL, 1, NULL);
 +  if (!ei_tmp) {
 +    if (write_stats_to_extrainfo) {
 +      log_warn(LD_GENERAL, "We just generated an extra-info descriptor "
 +                           "with statistics that we can't parse. Not "
 +                           "adding statistics to this or any future "
 +                           "extra-info descriptors.");
 +      write_stats_to_extrainfo = 0;
 +      result = extrainfo_dump_to_string(s_out, extrainfo, ident_key);
 +      goto done;
 +    } else {
 +      log_warn(LD_BUG, "We just generated an extrainfo descriptor we "
 +                       "can't parse.");
 +      goto err;
      }
 -    tor_free(s_dup);
 -    extrainfo_free(ei_tmp);
    }
 -#endif
  
 -  return (int)strlen(s)+1;
 -}
 +  *s_out = s;
 +  s = NULL; /* prevent free */
 +  result = 0;
 +  goto done;
  
 -/** Wrapper function for geoip_get_client_history(). It first discards
 - * any items in the client history that are too old -- it dumps anything
 - * more than 48 hours old, but it only considers whether to dump at most
 - * once per 48 hours, so we aren't too precise to an observer (see also
 - * r14780).
 - */
 -char *
 -extrainfo_get_client_geoip_summary(time_t now)
 -{
 -  static time_t last_purged_at = 0;
 -  int geoip_purge_interval = 48*60*60;
 -#ifdef ENABLE_GEOIP_STATS
 -  if (get_options()->DirRecordUsageByCountry)
 -    geoip_purge_interval = get_options()->DirRecordUsageRetainIPs;
 -#endif
 -  if (now > last_purged_at+geoip_purge_interval) {
 -    geoip_remove_old_clients(now-geoip_purge_interval);
 -    last_purged_at = now;
 -  }
 -  return geoip_get_client_history(now, GEOIP_CLIENT_CONNECT);
 + err:
 +  result = -1;
 +
 + done:
 +  tor_free(s);
 +  SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
 +  smartlist_free(chunks);
 +  tor_free(s_dup);
 +  extrainfo_free(ei_tmp);
 +
 +  return result;
  }
  
  /** Return true iff <b>s</b> is a legally valid server nickname. */
@@@ -2254,17 -2022,26 +2254,17 @@@ router_purpose_from_string(const char *
  void
  router_free_all(void)
  {
 -  if (onionkey)
 -    crypto_free_pk_env(onionkey);
 -  if (lastonionkey)
 -    crypto_free_pk_env(lastonionkey);
 -  if (identitykey)
 -    crypto_free_pk_env(identitykey);
 -  if (key_lock)
 -    tor_mutex_free(key_lock);
 -  if (desc_routerinfo)
 -    routerinfo_free(desc_routerinfo);
 -  if (desc_extrainfo)
 -    extrainfo_free(desc_extrainfo);
 -  if (authority_signing_key)
 -    crypto_free_pk_env(authority_signing_key);
 -  if (authority_key_certificate)
 -    authority_cert_free(authority_key_certificate);
 -  if (legacy_signing_key)
 -    crypto_free_pk_env(legacy_signing_key);
 -  if (legacy_key_certificate)
 -    authority_cert_free(legacy_key_certificate);
 +  crypto_free_pk_env(onionkey);
 +  crypto_free_pk_env(lastonionkey);
 +  crypto_free_pk_env(server_identitykey);
 +  crypto_free_pk_env(client_identitykey);
 +  tor_mutex_free(key_lock);
 +  routerinfo_free(desc_routerinfo);
 +  extrainfo_free(desc_extrainfo);
 +  crypto_free_pk_env(authority_signing_key);
 +  authority_cert_free(authority_key_certificate);
 +  crypto_free_pk_env(legacy_signing_key);
 +  authority_cert_free(legacy_key_certificate);
  
    if (warned_nonexistent_family) {
      SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp));
diff --combined src/or/routerlist.c
index 670574a,c6c84a8..253b787
--- a/src/or/routerlist.c
+++ b/src/or/routerlist.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -12,25 -12,6 +12,25 @@@
   **/
  
  #include "or.h"
 +#include "circuitbuild.h"
 +#include "config.h"
 +#include "connection.h"
 +#include "control.h"
 +#include "directory.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "geoip.h"
 +#include "hibernate.h"
 +#include "main.h"
 +#include "networkstatus.h"
 +#include "policies.h"
 +#include "reasons.h"
 +#include "rendcommon.h"
 +#include "rendservice.h"
 +#include "rephist.h"
 +#include "router.h"
 +#include "routerlist.h"
 +#include "routerparse.h"
  
  // #define DEBUG_ROUTERLIST
  
@@@ -45,8 -26,8 +45,8 @@@ static void mark_all_trusteddirservers_
  static int router_nickname_matches(routerinfo_t *router, const char *nickname);
  static void trusted_dir_server_free(trusted_dir_server_t *ds);
  static void launch_router_descriptor_downloads(smartlist_t *downloadable,
 +                                               routerstatus_t *source,
                                                 time_t now);
 -static void update_consensus_router_descriptor_downloads(time_t now);
  static int signed_desc_digest_is_recognized(signed_descriptor_t *desc);
  static void update_router_have_minimum_dir_info(void);
  static const char *signed_descriptor_get_body_impl(signed_descriptor_t *desc,
@@@ -175,24 -156,21 +175,24 @@@ already_have_cert(authority_cert_t *cer
  
  /** Load a bunch of new key certificates from the string <b>contents</b>.  If
   * <b>from_store</b> is true, the certificates are from the cache, and we
 - * don't need to flush them to disk.  If <b>from_store</b> is false, we need
 - * to flush any changed certificates to disk.  Return 0 on success, -1 on
 - * failure. */
 + * don't need to flush them to disk. If <b>flush</b> is true, we need
 + * to flush any changed certificates to disk now.  Return 0 on success, -1
 + * if any certs fail to parse. */
  int
  trusted_dirs_load_certs_from_string(const char *contents, int from_store,
                                      int flush)
  {
    trusted_dir_server_t *ds;
    const char *s, *eos;
 +  int failure_code = 0;
  
    for (s = contents; *s; s = eos) {
      authority_cert_t *cert = authority_cert_parse_from_string(s, &eos);
      cert_list_t *cl;
 -    if (!cert)
 +    if (!cert) {
 +      failure_code = -1;
        break;
 +    }
      ds = trusteddirserver_get_by_v3_auth_digest(
                                         cert->cache_info.identity_digest);
      log_debug(LD_DIR, "Parsed certificate for %s",
@@@ -203,15 -181,15 +203,15 @@@
        log_info(LD_DIR, "Skipping %s certificate for %s that we "
                 "already have.",
                 from_store ? "cached" : "downloaded",
 -               ds ? ds->nickname : "??");
 +               ds ? ds->nickname : "an old or new authority");
  
        /* a duplicate on a download should be treated as a failure, since it
         * probably means we wanted a different secret key or we are trying to
         * replace an expired cert that has not in fact been updated. */
        if (!from_store) {
 -        log_warn(LD_DIR, "Got a certificate for %s that we already have. "
 -                 "Maybe they haven't updated it.  Waiting for a while.",
 -                 ds ? ds->nickname : "??");
 +        log_warn(LD_DIR, "Got a certificate for %s, but we already have it. "
 +                 "Maybe they haven't updated it. Waiting for a while.",
 +                 ds ? ds->nickname : "an old or new authority");
          authority_cert_dl_failed(cert->cache_info.identity_digest, 404);
        }
  
@@@ -246,7 -224,7 +246,7 @@@
             ds->dir_port != cert->dir_port)) {
          char *a = tor_dup_ip(cert->addr);
          log_notice(LD_DIR, "Updating address for directory authority %s "
 -                   "from %s:%d to %s:%d based on in certificate.",
 +                   "from %s:%d to %s:%d based on certificate.",
                     ds->nickname, ds->address, (int)ds->dir_port,
                     a, cert->dir_port);
          tor_free(a);
@@@ -263,11 -241,8 +263,11 @@@
    if (flush)
      trusted_dirs_flush_certs_to_disk();
  
 +  /* call this even if failure_code is <0, since some certs might have
 +   * succeeded. */
    networkstatus_note_certs_arrived();
 -  return 0;
 +
 +  return failure_code;
  }
  
  /** Save all v3 key certificates to the cached-certs file. */
@@@ -440,23 -415,6 +440,23 @@@ authority_cert_dl_failed(const char *id
    download_status_failed(&cl->dl_status, status);
  }
  
 +/** Return true iff when we've been getting enough failures when trying to
 + * download the certificate with ID digest <b>id_digest</b> that we're willing
 + * to start bugging the user about it. */
 +int
 +authority_cert_dl_looks_uncertain(const char *id_digest)
 +{
 +#define N_AUTH_CERT_DL_FAILURES_TO_BUG_USER 2
 +  cert_list_t *cl;
 +  int n_failures;
 +  if (!trusted_dir_certs ||
 +      !(cl = digestmap_get(trusted_dir_certs, id_digest)))
 +    return 0;
 +
 +  n_failures = download_status_get_n_failures(&cl->dl_status);
 +  return n_failures >= N_AUTH_CERT_DL_FAILURES_TO_BUG_USER;
 +}
 +
  /** How many times will we try to fetch a certificate before giving up? */
  #define MAX_CERT_DL_FAILURES 8
  
@@@ -484,18 -442,17 +484,18 @@@ authority_certs_fetch_missing(networkst
  
    list_pending_downloads(pending, DIR_PURPOSE_FETCH_CERTIFICATE, "fp/");
    if (status) {
 -    SMARTLIST_FOREACH(status->voters, networkstatus_voter_info_t *, voter,
 -      {
 -        if (tor_digest_is_zero(voter->signing_key_digest))
 -          continue; /* This authority never signed this consensus, so don't
 -                     * go looking for a cert with key digest 0000000000. */
 -        if (!cache &&
 -            !trusteddirserver_get_by_v3_auth_digest(voter->identity_digest))
 -          continue; /* We are not a cache, and we don't know this authority.*/
 -        cl = get_cert_list(voter->identity_digest);
 +    SMARTLIST_FOREACH_BEGIN(status->voters, networkstatus_voter_info_t *,
 +                            voter) {
 +      if (!smartlist_len(voter->sigs))
 +        continue; /* This authority never signed this consensus, so don't
 +                   * go looking for a cert with key digest 0000000000. */
 +      if (!cache &&
 +          !trusteddirserver_get_by_v3_auth_digest(voter->identity_digest))
 +        continue; /* We are not a cache, and we don't know this authority.*/
 +      cl = get_cert_list(voter->identity_digest);
 +      SMARTLIST_FOREACH_BEGIN(voter->sigs, document_signature_t *, sig) {
          cert = authority_cert_get_by_digests(voter->identity_digest,
 -                                             voter->signing_key_digest);
 +                                             sig->signing_key_digest);
          if (cert) {
            if (now < cert->expires)
              download_status_reset(&cl->dl_status);
@@@ -506,36 -463,37 +506,36 @@@
              !digestmap_get(pending, voter->identity_digest)) {
            log_notice(LD_DIR, "We're missing a certificate from authority "
                       "with signing key %s: launching request.",
 -                     hex_str(voter->signing_key_digest, DIGEST_LEN));
 -          smartlist_add(missing_digests, voter->identity_digest);
 +                     hex_str(sig->signing_key_digest, DIGEST_LEN));
 +          smartlist_add(missing_digests, sig->identity_digest);
          }
 -      });
 +      } SMARTLIST_FOREACH_END(sig);
 +    } SMARTLIST_FOREACH_END(voter);
    }
 -  SMARTLIST_FOREACH(trusted_dir_servers, trusted_dir_server_t *, ds,
 -    {
 -      int found = 0;
 -      if (!(ds->type & V3_AUTHORITY))
 -        continue;
 -      if (smartlist_digest_isin(missing_digests, ds->v3_identity_digest))
 -        continue;
 -      cl = get_cert_list(ds->v3_identity_digest);
 -      SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert,
 -        {
 -          if (!ftime_definitely_after(now, cert->expires)) {
 -            /* It's not expired, and we weren't looking for something to
 -             * verify a consensus with.  Call it done. */
 -            download_status_reset(&cl->dl_status);
 -            found = 1;
 -            break;
 -          }
 -        });
 -      if (!found &&
 -          download_status_is_ready(&cl->dl_status, now,MAX_CERT_DL_FAILURES) &&
 -          !digestmap_get(pending, ds->v3_identity_digest)) {
 -        log_notice(LD_DIR, "No current certificate known for authority %s; "
 -                   "launching request.", ds->nickname);
 -        smartlist_add(missing_digests, ds->v3_identity_digest);
 +  SMARTLIST_FOREACH_BEGIN(trusted_dir_servers, trusted_dir_server_t *, ds) {
 +    int found = 0;
 +    if (!(ds->type & V3_AUTHORITY))
 +      continue;
 +    if (smartlist_digest_isin(missing_digests, ds->v3_identity_digest))
 +      continue;
 +    cl = get_cert_list(ds->v3_identity_digest);
 +    SMARTLIST_FOREACH(cl->certs, authority_cert_t *, cert, {
 +      if (!ftime_definitely_after(now, cert->expires)) {
 +        /* It's not expired, and we weren't looking for something to
 +         * verify a consensus with.  Call it done. */
 +        download_status_reset(&cl->dl_status);
 +        found = 1;
 +        break;
        }
      });
 +    if (!found &&
 +        download_status_is_ready(&cl->dl_status, now,MAX_CERT_DL_FAILURES) &&
 +        !digestmap_get(pending, ds->v3_identity_digest)) {
 +      log_notice(LD_DIR, "No current certificate known for authority %s; "
 +                 "launching request.", ds->nickname);
 +        smartlist_add(missing_digests, ds->v3_identity_digest);
 +    }
 +  } SMARTLIST_FOREACH_END(ds);
  
    if (!smartlist_len(missing_digests)) {
      goto done;
@@@ -791,7 -749,8 +791,7 @@@ router_rebuild_store(int flags, desc_st
    store->journal_len = 0;
    store->bytes_dropped = 0;
   done:
 -  if (signed_descriptors)
 -    smartlist_free(signed_descriptors);
 +  smartlist_free(signed_descriptors);
    tor_free(fname);
    tor_free(fname_tmp);
    if (chunk_list) {
@@@ -1009,9 -968,8 +1009,9 @@@ router_get_trusteddirserver_by_digest(c
    return NULL;
  }
  
 -/** Return the trusted_dir_server_t for the directory authority whose identity
 - * key hashes to <b>digest</b>, or NULL if no such authority is known.
 +/** Return the trusted_dir_server_t for the directory authority whose
 + * v3 identity key hashes to <b>digest</b>, or NULL if no such authority
 + * is known.
   */
  trusted_dir_server_t *
  trusteddirserver_get_by_v3_auth_digest(const char *digest)
@@@ -1131,10 -1089,9 +1131,10 @@@ router_pick_directory_server_impl(autho
    } SMARTLIST_FOREACH_END(status);
  
    if (smartlist_len(tunnel)) {
 -    result = routerstatus_sl_choose_by_bandwidth(tunnel);
 +    result = routerstatus_sl_choose_by_bandwidth(tunnel, WEIGHT_FOR_DIR);
    } else if (smartlist_len(overloaded_tunnel)) {
 -    result = routerstatus_sl_choose_by_bandwidth(overloaded_tunnel);
 +    result = routerstatus_sl_choose_by_bandwidth(overloaded_tunnel,
 +                                                 WEIGHT_FOR_DIR);
    } else if (smartlist_len(trusted_tunnel)) {
      /* FFFF We don't distinguish between trusteds and overloaded trusteds
       * yet. Maybe one day we should. */
@@@ -1142,10 -1099,9 +1142,10 @@@
       * is a feature, but it could easily be a bug. -RD */
      result = smartlist_choose(trusted_tunnel);
    } else if (smartlist_len(direct)) {
 -    result = routerstatus_sl_choose_by_bandwidth(direct);
 +    result = routerstatus_sl_choose_by_bandwidth(direct, WEIGHT_FOR_DIR);
    } else if (smartlist_len(overloaded_direct)) {
 -    result = routerstatus_sl_choose_by_bandwidth(overloaded_direct);
 +    result = routerstatus_sl_choose_by_bandwidth(overloaded_direct,
 +                                                 WEIGHT_FOR_DIR);
    } else {
      result = smartlist_choose(trusted_direct);
    }
@@@ -1274,13 -1230,6 +1274,13 @@@ mark_all_trusteddirservers_up(void
    router_dir_info_changed();
  }
  
 +/** Return true iff r1 and r2 have the same address and OR port. */
 +int
 +routers_have_same_or_addr(const routerinfo_t *r1, const routerinfo_t *r2)
 +{
 +  return r1->addr == r2->addr && r1->or_port == r2->or_port;
 +}
 +
  /** Reset all internal variables used to count failed downloads of network
   * status objects. */
  void
@@@ -1572,29 -1521,6 +1572,29 @@@ router_get_advertised_bandwidth_capped(
    return result;
  }
  
 +/** When weighting bridges, enforce these values as lower and upper
 + * bound for believable bandwidth, because there is no way for us
 + * to verify a bridge's bandwidth currently. */
 +#define BRIDGE_MIN_BELIEVABLE_BANDWIDTH 20000  /* 20 kB/sec */
 +#define BRIDGE_MAX_BELIEVABLE_BANDWIDTH 100000 /* 100 kB/sec */
 +
 +/** Return the smaller of the router's configured BandwidthRate
 + * and its advertised capacity, making sure to stay within the
 + * interval between bridge-min-believe-bw and
 + * bridge-max-believe-bw. */
 +static uint32_t
 +bridge_get_advertised_bandwidth_bounded(routerinfo_t *router)
 +{
 +  uint32_t result = router->bandwidthcapacity;
 +  if (result > router->bandwidthrate)
 +    result = router->bandwidthrate;
 +  if (result > BRIDGE_MAX_BELIEVABLE_BANDWIDTH)
 +    result = BRIDGE_MAX_BELIEVABLE_BANDWIDTH;
 +  else if (result < BRIDGE_MIN_BELIEVABLE_BANDWIDTH)
 +    result = BRIDGE_MIN_BELIEVABLE_BANDWIDTH;
 +  return result;
 +}
 +
  /** Return bw*1000, unless bw*1000 would overflow, in which case return
   * INT32_MAX. */
  static INLINE int32_t
@@@ -1605,219 -1531,6 +1605,219 @@@ kb_to_bytes(uint32_t bw
  
  /** Helper function:
   * choose a random element of smartlist <b>sl</b>, weighted by
 + * the advertised bandwidth of each element using the consensus
 + * bandwidth weights.
 + *
 + * If <b>statuses</b> is zero, then <b>sl</b> is a list of
 + * routerinfo_t's. Otherwise it's a list of routerstatus_t's.
 + *
 + * If <b>rule</b>==WEIGHT_FOR_EXIT. we're picking an exit node: consider all
 + * nodes' bandwidth equally regardless of their Exit status, since there may
 + * be some in the list because they exit to obscure ports. If
 + * <b>rule</b>==NO_WEIGHTING, we're picking a non-exit node: weight
 + * exit-node's bandwidth less depending on the smallness of the fraction of
 + * Exit-to-total bandwidth.  If <b>rule</b>==WEIGHT_FOR_GUARD, we're picking a
 + * guard node: consider all guard's bandwidth equally. Otherwise, weight
 + * guards proportionally less.
 + */
 +static void *
 +smartlist_choose_by_bandwidth_weights(smartlist_t *sl,
 +                                      bandwidth_weight_rule_t rule,
 +                                      int statuses)
 +{
 +  int64_t weight_scale;
 +  int64_t rand_bw;
 +  double Wg = -1, Wm = -1, We = -1, Wd = -1;
 +  double Wgb = -1, Wmb = -1, Web = -1, Wdb = -1;
 +  double weighted_bw = 0;
 +  double *bandwidths;
 +  double tmp = 0;
 +  unsigned int i;
 +  int have_unknown = 0; /* true iff sl contains element not in consensus. */
 +
 +  /* Can't choose exit and guard at same time */
 +  tor_assert(rule == NO_WEIGHTING ||
 +             rule == WEIGHT_FOR_EXIT ||
 +             rule == WEIGHT_FOR_GUARD ||
 +             rule == WEIGHT_FOR_MID ||
 +             rule == WEIGHT_FOR_DIR);
 +
 +  if (smartlist_len(sl) == 0) {
 +    log_info(LD_CIRC,
 +             "Empty routerlist passed in to consensus weight node "
 +             "selection for rule %s",
 +             bandwidth_weight_rule_to_string(rule));
 +    return NULL;
 +  }
 +
 +  weight_scale = networkstatus_get_param(NULL, "bwweightscale",
 +                                         BW_WEIGHT_SCALE);
 +
 +  if (rule == WEIGHT_FOR_GUARD) {
 +    Wg = networkstatus_get_bw_weight(NULL, "Wgg", -1);
 +    Wm = networkstatus_get_bw_weight(NULL, "Wgm", -1); /* Bridges */
 +    We = 0;
 +    Wd = networkstatus_get_bw_weight(NULL, "Wgd", -1);
 +
 +    Wgb = networkstatus_get_bw_weight(NULL, "Wgb", -1);
 +    Wmb = networkstatus_get_bw_weight(NULL, "Wmb", -1);
 +    Web = networkstatus_get_bw_weight(NULL, "Web", -1);
 +    Wdb = networkstatus_get_bw_weight(NULL, "Wdb", -1);
 +  } else if (rule == WEIGHT_FOR_MID) {
 +    Wg = networkstatus_get_bw_weight(NULL, "Wmg", -1);
 +    Wm = networkstatus_get_bw_weight(NULL, "Wmm", -1);
 +    We = networkstatus_get_bw_weight(NULL, "Wme", -1);
 +    Wd = networkstatus_get_bw_weight(NULL, "Wmd", -1);
 +
 +    Wgb = networkstatus_get_bw_weight(NULL, "Wgb", -1);
 +    Wmb = networkstatus_get_bw_weight(NULL, "Wmb", -1);
 +    Web = networkstatus_get_bw_weight(NULL, "Web", -1);
 +    Wdb = networkstatus_get_bw_weight(NULL, "Wdb", -1);
 +  } else if (rule == WEIGHT_FOR_EXIT) {
 +    // Guards CAN be exits if they have weird exit policies
 +    // They are d then I guess...
 +    We = networkstatus_get_bw_weight(NULL, "Wee", -1);
 +    Wm = networkstatus_get_bw_weight(NULL, "Wem", -1); /* Odd exit policies */
 +    Wd = networkstatus_get_bw_weight(NULL, "Wed", -1);
 +    Wg = networkstatus_get_bw_weight(NULL, "Weg", -1); /* Odd exit policies */
 +
 +    Wgb = networkstatus_get_bw_weight(NULL, "Wgb", -1);
 +    Wmb = networkstatus_get_bw_weight(NULL, "Wmb", -1);
 +    Web = networkstatus_get_bw_weight(NULL, "Web", -1);
 +    Wdb = networkstatus_get_bw_weight(NULL, "Wdb", -1);
 +  } else if (rule == WEIGHT_FOR_DIR) {
 +    We = networkstatus_get_bw_weight(NULL, "Wbe", -1);
 +    Wm = networkstatus_get_bw_weight(NULL, "Wbm", -1);
 +    Wd = networkstatus_get_bw_weight(NULL, "Wbd", -1);
 +    Wg = networkstatus_get_bw_weight(NULL, "Wbg", -1);
 +
 +    Wgb = Wmb = Web = Wdb = weight_scale;
 +  } else if (rule == NO_WEIGHTING) {
 +    Wg = Wm = We = Wd = weight_scale;
 +    Wgb = Wmb = Web = Wdb = weight_scale;
 +  }
 +
 +  if (Wg < 0 || Wm < 0 || We < 0 || Wd < 0 || Wgb < 0 || Wmb < 0 || Wdb < 0
 +      || Web < 0) {
 +    log_debug(LD_CIRC,
 +              "Got negative bandwidth weights. Defaulting to old selection"
 +              " algorithm.");
 +    return NULL; // Use old algorithm.
 +  }
 +
 +  Wg /= weight_scale;
 +  Wm /= weight_scale;
 +  We /= weight_scale;
 +  Wd /= weight_scale;
 +
 +  Wgb /= weight_scale;
 +  Wmb /= weight_scale;
 +  Web /= weight_scale;
 +  Wdb /= weight_scale;
 +
 +  bandwidths = tor_malloc_zero(sizeof(double)*smartlist_len(sl));
 +
 +  // Cycle through smartlist and total the bandwidth.
 +  for (i = 0; i < (unsigned)smartlist_len(sl); ++i) {
 +    int is_exit = 0, is_guard = 0, is_dir = 0, this_bw = 0, is_me = 0;
 +    double weight = 1;
 +    if (statuses) {
 +      routerstatus_t *status = smartlist_get(sl, i);
 +      is_exit = status->is_exit;
 +      is_guard = status->is_possible_guard;
 +      is_dir = (status->dir_port != 0);
 +      if (!status->has_bandwidth) {
 +        tor_free(bandwidths);
 +        /* This should never happen, unless all the authorites downgrade
 +         * to 0.2.0 or rogue routerstatuses get inserted into our consensus. */
 +        log_warn(LD_BUG,
 +                 "Consensus is not listing bandwidths. Defaulting back to "
 +                 "old router selection algorithm.");
 +        return NULL;
 +      }
 +      this_bw = kb_to_bytes(status->bandwidth);
 +      if (router_digest_is_me(status->identity_digest))
 +        is_me = 1;
 +    } else {
 +      routerstatus_t *rs;
 +      routerinfo_t *router = smartlist_get(sl, i);
 +      rs = router_get_consensus_status_by_id(
 +             router->cache_info.identity_digest);
 +      is_exit = router->is_exit;
 +      is_guard = router->is_possible_guard;
 +      is_dir = (router->dir_port != 0);
 +      if (rs && rs->has_bandwidth) {
 +        this_bw = kb_to_bytes(rs->bandwidth);
 +      } else { /* bridge or other descriptor not in our consensus */
 +        this_bw = bridge_get_advertised_bandwidth_bounded(router);
 +        have_unknown = 1;
 +      }
 +      if (router_digest_is_me(router->cache_info.identity_digest))
 +        is_me = 1;
 +    }
 +    if (is_guard && is_exit) {
 +      weight = (is_dir ? Wdb*Wd : Wd);
 +    } else if (is_guard) {
 +      weight = (is_dir ? Wgb*Wg : Wg);
 +    } else if (is_exit) {
 +      weight = (is_dir ? Web*We : We);
 +    } else { // middle
 +      weight = (is_dir ? Wmb*Wm : Wm);
 +    }
 +
 +    bandwidths[i] = weight*this_bw;
 +    weighted_bw += weight*this_bw;
 +    if (is_me)
 +      sl_last_weighted_bw_of_me = weight*this_bw;
 +  }
 +
 +  /* XXXX022 this is a kludge to expose these values. */
 +  sl_last_total_weighted_bw = weighted_bw;
 +
 +  log_debug(LD_CIRC, "Choosing node for rule %s based on weights "
 +            "Wg=%lf Wm=%lf We=%lf Wd=%lf with total bw %lf",
 +            bandwidth_weight_rule_to_string(rule),
 +            Wg, Wm, We, Wd, weighted_bw);
 +
 +  /* If there is no bandwidth, choose at random */
 +  if (DBL_TO_U64(weighted_bw) == 0) {
 +    /* Don't warn when using bridges/relays not in the consensus */
 +    if (!have_unknown)
 +      log_warn(LD_CIRC,
 +               "Weighted bandwidth is %lf in node selection for rule %s",
 +               weighted_bw, bandwidth_weight_rule_to_string(rule));
 +    tor_free(bandwidths);
 +    return smartlist_choose(sl);
 +  }
 +
 +  rand_bw = crypto_rand_uint64(DBL_TO_U64(weighted_bw));
 +  rand_bw++; /* crypto_rand_uint64() counts from 0, and we need to count
 +              * from 1 below. See bug 1203 for details. */
 +
 +  /* Last, count through sl until we get to the element we picked */
 +  tmp = 0.0;
 +  for (i=0; i < (unsigned)smartlist_len(sl); i++) {
 +    tmp += bandwidths[i];
 +    if (tmp >= rand_bw)
 +      break;
 +  }
 +
 +  if (i == (unsigned)smartlist_len(sl)) {
 +    /* This was once possible due to round-off error, but shouldn't be able
 +     * to occur any longer. */
 +    tor_fragile_assert();
 +    --i;
 +    log_warn(LD_BUG, "Round-off error in computing bandwidth had an effect on "
 +             " which router we chose. Please tell the developers. "
 +             "%lf " U64_FORMAT " %lf", tmp, U64_PRINTF_ARG(rand_bw),
 +             weighted_bw);
 +  }
 +  tor_free(bandwidths);
 +  return smartlist_get(sl, i);
 +}
 +
 +/** Helper function:
 + * choose a random element of smartlist <b>sl</b>, weighted by
   * the advertised bandwidth of each element.
   *
   * If <b>statuses</b> is zero, then <b>sl</b> is a list of
@@@ -1852,24 -1565,11 +1852,24 @@@ smartlist_choose_by_bandwidth(smartlist
    bitarray_t *guard_bits;
    int me_idx = -1;
  
 +  // This function does not support WEIGHT_FOR_DIR
 +  // or WEIGHT_FOR_MID
 +  if (rule == WEIGHT_FOR_DIR || rule == WEIGHT_FOR_MID) {
 +    rule = NO_WEIGHTING;
 +  }
 +
    /* Can't choose exit and guard at same time */
    tor_assert(rule == NO_WEIGHTING ||
               rule == WEIGHT_FOR_EXIT ||
               rule == WEIGHT_FOR_GUARD);
  
 +  if (smartlist_len(sl) == 0) {
 +    log_info(LD_CIRC,
 +             "Empty routerlist passed in to old node selection for rule %s",
 +             bandwidth_weight_rule_to_string(rule));
 +    return NULL;
 +  }
 +
    /* First count the total bandwidth weight, and make a list
     * of each value.  <0 means "unknown; no routerinfo."  We use the
     * bits of negative values to remember whether the router was fast (-x)&1
@@@ -1920,7 -1620,7 +1920,7 @@@
          flags |= is_exit ? 2 : 0;
          flags |= is_guard ? 4 : 0;
        } else /* bridge or other descriptor not in our consensus */
 -        this_bw = router_get_advertised_bandwidth_capped(router);
 +        this_bw = bridge_get_advertised_bandwidth_bounded(router);
      }
      if (is_exit)
        bitarray_set(exit_bits, i);
@@@ -1928,8 -1628,6 +1928,8 @@@
        bitarray_set(guard_bits, i);
      if (is_known) {
        bandwidths[i] = (int32_t) this_bw; // safe since MAX_BELIEVABLE<INT32_MAX
 +      // XXX this is no longer true! We don't always cap the bw anymore. Can
 +      // a consensus make us overflow?-sh
        tor_assert(bandwidths[i] >= 0);
        if (is_guard)
          total_guard_bw += this_bw;
@@@ -1993,12 -1691,12 +1993,12 @@@
       * For detailed derivation of this formula, see
       *   http://archives.seul.org/or/dev/Jul-2007/msg00056.html
       */
 -    if (rule == WEIGHT_FOR_EXIT)
 +    if (rule == WEIGHT_FOR_EXIT || !total_exit_bw)
        exit_weight = 1.0;
      else
        exit_weight = 1.0 - all_bw/(3.0*exit_bw);
  
 -    if (rule == WEIGHT_FOR_GUARD)
 +    if (rule == WEIGHT_FOR_GUARD || !total_guard_bw)
        guard_weight = 1.0;
      else
        guard_weight = 1.0 - all_bw/(3.0*guard_bw);
@@@ -2047,8 -1745,6 +2047,8 @@@
  
    /* Almost done: choose a random value from the bandwidth weights. */
    rand_bw = crypto_rand_uint64(total_bw);
 +  rand_bw++; /* crypto_rand_uint64() counts from 0, and we need to count
 +              * from 1 below. See bug 1203 for details. */
  
    /* Last, count through sl until we get to the element we picked */
    tmp = 0;
@@@ -2092,34 -1788,26 +2092,34 @@@ routerinfo_t 
  routerlist_sl_choose_by_bandwidth(smartlist_t *sl,
                                    bandwidth_weight_rule_t rule)
  {
 -  return smartlist_choose_by_bandwidth(sl, rule, 0);
 +  routerinfo_t *ret;
 +  if ((ret = smartlist_choose_by_bandwidth_weights(sl, rule, 0))) {
 +    return ret;
 +  } else {
 +    return smartlist_choose_by_bandwidth(sl, rule, 0);
 +  }
  }
  
  /** Choose a random element of status list <b>sl</b>, weighted by
   * the advertised bandwidth of each status.
   */
  routerstatus_t *
 -routerstatus_sl_choose_by_bandwidth(smartlist_t *sl)
 +routerstatus_sl_choose_by_bandwidth(smartlist_t *sl,
 +                                    bandwidth_weight_rule_t rule)
  {
    /* We are choosing neither exit nor guard here. Weight accordingly. */
 -  return smartlist_choose_by_bandwidth(sl, NO_WEIGHTING, 1);
 +  routerstatus_t *ret;
 +  if ((ret = smartlist_choose_by_bandwidth_weights(sl, rule, 1))) {
 +    return ret;
 +  } else {
 +    return smartlist_choose_by_bandwidth(sl, rule, 1);
 +  }
  }
  
 -/** Return a random running router from the routerlist.  If any node
 - * named in <b>preferred</b> is available, pick one of those.  Never
 +/** Return a random running router from the routerlist. Never
   * pick a node whose routerinfo is in
   * <b>excludedsmartlist</b>, or whose routerinfo matches <b>excludedset</b>,
 - * even if they are the only nodes
 - * available.  If <b>CRN_STRICT_PREFERRED</b> is set in flags, never pick
 - * any node besides those in <b>preferred</b>.
 + * even if they are the only nodes available.
   * If <b>CRN_NEED_UPTIME</b> is set in flags and any router has more than
   * a minimum uptime, return one of those.
   * If <b>CRN_NEED_CAPACITY</b> is set in flags, weight your choice by the
@@@ -2132,7 -1820,8 +2132,7 @@@
   * node (that is, possibly discounting exit nodes).
   */
  routerinfo_t *
 -router_choose_random_node(const char *preferred,
 -                          smartlist_t *excludedsmartlist,
 +router_choose_random_node(smartlist_t *excludedsmartlist,
                            routerset_t *excludedset,
                            router_crn_flags_t flags)
  {
@@@ -2140,16 -1829,18 +2140,16 @@@
    const int need_capacity = (flags & CRN_NEED_CAPACITY) != 0;
    const int need_guard = (flags & CRN_NEED_GUARD) != 0;
    const int allow_invalid = (flags & CRN_ALLOW_INVALID) != 0;
 -  const int strict = (flags & CRN_STRICT_PREFERRED) != 0;
    const int weight_for_exit = (flags & CRN_WEIGHT_AS_EXIT) != 0;
  
 -  smartlist_t *sl, *excludednodes;
 +  smartlist_t *sl=smartlist_create(),
 +              *excludednodes=smartlist_create();
    routerinfo_t *choice = NULL, *r;
    bandwidth_weight_rule_t rule;
  
    tor_assert(!(weight_for_exit && need_guard));
    rule = weight_for_exit ? WEIGHT_FOR_EXIT :
 -    (need_guard ? WEIGHT_FOR_GUARD : NO_WEIGHTING);
 -
 -  excludednodes = smartlist_create();
 +    (need_guard ? WEIGHT_FOR_GUARD : WEIGHT_FOR_MID);
  
    /* Exclude relays that allow single hop exit circuits, if the user
     * wants to (such relays might be risky) */
@@@ -2166,35 -1857,60 +2166,35 @@@
      routerlist_add_family(excludednodes, r);
    }
  
 -  /* Try the preferred nodes first. Ignore need_uptime and need_capacity
 -   * and need_guard, since the user explicitly asked for these nodes. */
 -  if (preferred) {
 -    sl = smartlist_create();
 -    add_nickname_list_to_smartlist(sl,preferred,1);
 -    smartlist_subtract(sl,excludednodes);
 -    if (excludedsmartlist)
 -      smartlist_subtract(sl,excludedsmartlist);
 -    if (excludedset)
 -      routerset_subtract_routers(sl,excludedset);
 -    choice = smartlist_choose(sl);
 -    smartlist_free(sl);
 -  }
 -  if (!choice && !strict) {
 -    /* Then give up on our preferred choices: any node
 -     * will do that has the required attributes. */
 -    sl = smartlist_create();
 -    router_add_running_routers_to_smartlist(sl, allow_invalid,
 -                                            need_uptime, need_capacity,
 -                                            need_guard);
 -    smartlist_subtract(sl,excludednodes);
 -    if (excludedsmartlist)
 -      smartlist_subtract(sl,excludedsmartlist);
 -    if (excludedset)
 -      routerset_subtract_routers(sl,excludedset);
 -
 -    if (need_capacity || need_guard)
 -      choice = routerlist_sl_choose_by_bandwidth(sl, rule);
 -    else
 -      choice = smartlist_choose(sl);
 -
 -    smartlist_free(sl);
 -    if (!choice && (need_uptime || need_capacity || need_guard)) {
 -      /* try once more -- recurse but with fewer restrictions. */
 -      log_info(LD_CIRC,
 -               "We couldn't find any live%s%s%s routers; falling back "
 -               "to list of all routers.",
 -               need_capacity?", fast":"",
 -               need_uptime?", stable":"",
 -               need_guard?", guard":"");
 -      flags &= ~ (CRN_NEED_UPTIME|CRN_NEED_CAPACITY|CRN_NEED_GUARD);
 -      choice = router_choose_random_node(
 -                       NULL, excludedsmartlist, excludedset, flags);
 -    }
 +  router_add_running_routers_to_smartlist(sl, allow_invalid,
 +                                          need_uptime, need_capacity,
 +                                          need_guard);
 +  smartlist_subtract(sl,excludednodes);
 +  if (excludedsmartlist)
 +    smartlist_subtract(sl,excludedsmartlist);
 +  if (excludedset)
 +    routerset_subtract_routers(sl,excludedset);
 +
 +  // Always weight by bandwidth
 +  choice = routerlist_sl_choose_by_bandwidth(sl, rule);
 +
 +  smartlist_free(sl);
 +  if (!choice && (need_uptime || need_capacity || need_guard)) {
 +    /* try once more -- recurse but with fewer restrictions. */
 +    log_info(LD_CIRC,
 +             "We couldn't find any live%s%s%s routers; falling back "
 +             "to list of all routers.",
 +             need_capacity?", fast":"",
 +             need_uptime?", stable":"",
 +             need_guard?", guard":"");
 +    flags &= ~ (CRN_NEED_UPTIME|CRN_NEED_CAPACITY|CRN_NEED_GUARD);
 +    choice = router_choose_random_node(
 +                     excludedsmartlist, excludedset, flags);
    }
    smartlist_free(excludednodes);
    if (!choice) {
 -    if (strict) {
 -      log_warn(LD_CIRC, "All preferred nodes were down when trying to choose "
 -               "node, and the Strict[...]Nodes option is set. Failing.");
 -    } else {
 -      log_warn(LD_CIRC,
 -               "No available nodes when trying to choose node. Failing.");
 -    }
 +    log_warn(LD_CIRC,
 +             "No available nodes when trying to choose node. Failing.");
    }
    return choice;
  }
@@@ -2273,6 -1989,9 +2273,6 @@@ router_get_by_nickname(const char *nick
      return router_get_by_hexdigest(nickname);
    if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME))
      return NULL;
 -  if (server_mode(get_options()) &&
 -      !strcasecmp(nickname, get_options()->Nickname))
 -    return router_get_my_routerinfo();
  
    maybedigest = (strlen(nickname) >= HEX_DIGEST_LEN) &&
      (base16_decode(digest,DIGEST_LEN,nickname,HEX_DIGEST_LEN) == 0);
@@@ -2651,9 -2370,6 +2651,9 @@@ extrainfo_free(extrainfo_t *extrainfo
  static void
  signed_descriptor_free(signed_descriptor_t *sd)
  {
 +  if (!sd)
 +    return;
 +
    tor_free(sd->signed_descriptor_body);
  
    /* XXXX remove this once more bugs go away. */
@@@ -2661,15 -2377,12 +2661,15 @@@
    tor_free(sd);
  }
  
 -/** Extract a signed_descriptor_t from a routerinfo, and free the routerinfo.
 +/** Extract a signed_descriptor_t from a general routerinfo, and free the
 + * routerinfo.
   */
  static signed_descriptor_t *
  signed_descriptor_from_routerinfo(routerinfo_t *ri)
  {
 -  signed_descriptor_t *sd = tor_malloc_zero(sizeof(signed_descriptor_t));
 +  signed_descriptor_t *sd;
 +  tor_assert(ri->purpose == ROUTER_PURPOSE_GENERAL);
 +  sd = tor_malloc_zero(sizeof(signed_descriptor_t));
    memcpy(sd, &(ri->cache_info), sizeof(signed_descriptor_t));
    sd->routerlist_index = -1;
    ri->cache_info.signed_descriptor_body = NULL;
@@@ -2688,8 -2401,7 +2688,8 @@@ _extrainfo_free(void *e
  void
  routerlist_free(routerlist_t *rl)
  {
 -  tor_assert(rl);
 +  if (!rl)
 +    return;
    rimap_free(rl->identity_map, NULL);
    sdmap_free(rl->desc_digest_map, NULL);
    sdmap_free(rl->desc_by_eid_map, NULL);
@@@ -2728,6 -2440,46 +2728,6 @@@ dump_routerlist_mem_usage(int severity
        "In %d old descriptors: "U64_FORMAT" bytes.",
        smartlist_len(routerlist->routers), U64_PRINTF_ARG(livedescs),
        smartlist_len(routerlist->old_routers), U64_PRINTF_ARG(olddescs));
 -
 -#if 0
 -  {
 -    const smartlist_t *networkstatus_v2_list = networkstatus_get_v2_list();
 -    networkstatus_t *consensus = networkstatus_get_latest_consensus();
 -    log(severity, LD_DIR, "Now let's look through old_descriptors!");
 -    SMARTLIST_FOREACH(routerlist->old_routers, signed_descriptor_t *, sd, {
 -        int in_v2 = 0;
 -        int in_v3 = 0;
 -        char published[ISO_TIME_LEN+1];
 -        char last_valid_until[ISO_TIME_LEN+1];
 -        char last_served_at[ISO_TIME_LEN+1];
 -        char id[HEX_DIGEST_LEN+1];
 -        routerstatus_t *rs;
 -        format_iso_time(published, sd->published_on);
 -        format_iso_time(last_valid_until, sd->last_listed_as_valid_until);
 -        format_iso_time(last_served_at, sd->last_served_at);
 -        base16_encode(id, sizeof(id), sd->identity_digest, DIGEST_LEN);
 -        SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
 -          {
 -            rs = networkstatus_v2_find_entry(ns, sd->identity_digest);
 -            if (rs && !memcmp(rs->descriptor_digest,
 -                              sd->signed_descriptor_digest, DIGEST_LEN)) {
 -              in_v2 = 1; break;
 -            }
 -          });
 -        if (consensus) {
 -          rs = networkstatus_vote_find_entry(consensus, sd->identity_digest);
 -          if (rs && !memcmp(rs->descriptor_digest,
 -                            sd->signed_descriptor_digest, DIGEST_LEN))
 -            in_v3 = 1;
 -        }
 -        log(severity, LD_DIR,
 -            "Old descriptor for %s (published %s) %sin v2 ns, %sin v3 "
 -            "consensus.  Last valid until %s; last served at %s.",
 -            id, published, in_v2 ? "" : "not ", in_v3 ? "" : "not ",
 -            last_valid_until, last_served_at);
 -    });
 -  }
 -#endif
  }
  
  /** Debugging helper: If <b>idx</b> is nonnegative, assert that <b>ri</b> is
@@@ -2760,7 -2512,6 +2760,7 @@@ static voi
  routerlist_insert(routerlist_t *rl, routerinfo_t *ri)
  {
    routerinfo_t *ri_old;
 +  signed_descriptor_t *sd_old;
    {
      /* XXXX Remove if this slows us down. */
      routerinfo_t *ri_generated = router_get_my_routerinfo();
@@@ -2770,16 -2521,8 +2770,16 @@@
  
    ri_old = rimap_set(rl->identity_map, ri->cache_info.identity_digest, ri);
    tor_assert(!ri_old);
 -  sdmap_set(rl->desc_digest_map, ri->cache_info.signed_descriptor_digest,
 -            &(ri->cache_info));
 +
 +  sd_old = sdmap_set(rl->desc_digest_map,
 +                     ri->cache_info.signed_descriptor_digest,
 +                     &(ri->cache_info));
 +  if (sd_old) {
 +    rl->desc_store.bytes_dropped += sd_old->signed_descriptor_len;
 +    sdmap_remove(rl->desc_by_eid_map, sd_old->extra_info_digest);
 +    signed_descriptor_free(sd_old);
 +  }
 +
    if (!tor_digest_is_zero(ri->cache_info.extra_info_digest))
      sdmap_set(rl->desc_by_eid_map, ri->cache_info.extra_info_digest,
                &ri->cache_info);
@@@ -2998,7 -2741,6 +2998,7 @@@ routerlist_replace(routerlist_t *rl, ro
                     routerinfo_t *ri_new)
  {
    int idx;
 +  int same_descriptors;
  
    routerinfo_t *ri_tmp;
    extrainfo_t *ei_tmp;
@@@ -3043,15 -2785,8 +3043,15 @@@
                &ri_new->cache_info);
    }
  
 +  same_descriptors = ! memcmp(ri_old->cache_info.signed_descriptor_digest,
 +                              ri_new->cache_info.signed_descriptor_digest,
 +                              DIGEST_LEN);
 +
    if (should_cache_old_descriptors() &&
 -      ri_old->purpose == ROUTER_PURPOSE_GENERAL) {
 +      ri_old->purpose == ROUTER_PURPOSE_GENERAL &&
 +      !same_descriptors) {
 +    /* ri_old is going to become a signed_descriptor_t and go into
 +     * old_routers */
      signed_descriptor_t *sd = signed_descriptor_from_routerinfo(ri_old);
      smartlist_add(rl->old_routers, sd);
      sd->routerlist_index = smartlist_len(rl->old_routers)-1;
@@@ -3059,27 -2794,24 +3059,27 @@@
      if (!tor_digest_is_zero(sd->extra_info_digest))
        sdmap_set(rl->desc_by_eid_map, sd->extra_info_digest, sd);
    } else {
 -    if (memcmp(ri_old->cache_info.signed_descriptor_digest,
 -               ri_new->cache_info.signed_descriptor_digest,
 -               DIGEST_LEN)) {
 -      /* digests don't match; digestmap_set didn't replace */
 +    /* We're dropping ri_old. */
 +    if (!same_descriptors) {
 +      /* digests don't match; The sdmap_set above didn't replace */
        sdmap_remove(rl->desc_digest_map,
                     ri_old->cache_info.signed_descriptor_digest);
 -    }
  
 -    ei_tmp = eimap_remove(rl->extra_info_map,
 -                          ri_old->cache_info.extra_info_digest);
 -    if (ei_tmp) {
 -      rl->extrainfo_store.bytes_dropped +=
 -        ei_tmp->cache_info.signed_descriptor_len;
 -      extrainfo_free(ei_tmp);
 -    }
 -    if (!tor_digest_is_zero(ri_old->cache_info.extra_info_digest)) {
 -      sdmap_remove(rl->desc_by_eid_map,
 -                   ri_old->cache_info.extra_info_digest);
 +      if (memcmp(ri_old->cache_info.extra_info_digest,
 +                 ri_new->cache_info.extra_info_digest, DIGEST_LEN)) {
 +        ei_tmp = eimap_remove(rl->extra_info_map,
 +                              ri_old->cache_info.extra_info_digest);
 +        if (ei_tmp) {
 +          rl->extrainfo_store.bytes_dropped +=
 +            ei_tmp->cache_info.signed_descriptor_len;
 +          extrainfo_free(ei_tmp);
 +        }
 +      }
 +
 +      if (!tor_digest_is_zero(ri_old->cache_info.extra_info_digest)) {
 +        sdmap_remove(rl->desc_by_eid_map,
 +                     ri_old->cache_info.extra_info_digest);
 +      }
      }
      rl->desc_store.bytes_dropped += ri_old->cache_info.signed_descriptor_len;
      routerinfo_free(ri_old);
@@@ -3117,7 -2849,8 +3117,7 @@@ routerlist_reparse_old(routerlist_t *rl
  void
  routerlist_free_all(void)
  {
 -  if (routerlist)
 -    routerlist_free(routerlist);
 +  routerlist_free(routerlist);
    routerlist = NULL;
    if (warned_nicknames) {
      SMARTLIST_FOREACH(warned_nicknames, char *, cp, tor_free(cp));
@@@ -3224,33 -2957,15 +3224,33 @@@ router_add_to_routerlist(routerinfo_t *
  
    id_digest = router->cache_info.identity_digest;
  
 +  old_router = router_get_by_digest(id_digest);
 +
    /* Make sure that we haven't already got this exact descriptor. */
    if (sdmap_get(routerlist->desc_digest_map,
                  router->cache_info.signed_descriptor_digest)) {
 -    log_info(LD_DIR,
 -             "Dropping descriptor that we already have for router '%s'",
 -             router->nickname);
 -    *msg = "Router descriptor was not new.";
 -    routerinfo_free(router);
 -    return ROUTER_WAS_NOT_NEW;
 +    /* If we have this descriptor already and the new descriptor is a bridge
 +     * descriptor, replace it. If we had a bridge descriptor before and the
 +     * new one is not a bridge descriptor, don't replace it. */
 +
 +    /* Only members of routerlist->identity_map can be bridges; we don't
 +     * put bridges in old_routers. */
 +    const int was_bridge = old_router &&
 +      old_router->purpose == ROUTER_PURPOSE_BRIDGE;
 +
 +    if (routerinfo_is_a_configured_bridge(router) &&
 +        router->purpose == ROUTER_PURPOSE_BRIDGE &&
 +        !was_bridge) {
 +      log_info(LD_DIR, "Replacing non-bridge descriptor with bridge "
 +               "descriptor for router '%s'", router->nickname);
 +    } else {
 +      log_info(LD_DIR,
 +               "Dropping descriptor that we already have for router '%s'",
 +               router->nickname);
 +      *msg = "Router descriptor was not new.";
 +      routerinfo_free(router);
 +      return ROUTER_WAS_NOT_NEW;
 +    }
    }
  
    if (authdir) {
@@@ -3287,14 -3002,15 +3287,14 @@@
    SMARTLIST_FOREACH(networkstatus_v2_list, networkstatus_v2_t *, ns,
    {
      routerstatus_t *rs =
 -      networkstatus_v2_find_entry(ns, router->cache_info.identity_digest);
 +      networkstatus_v2_find_entry(ns, id_digest);
      if (rs && !memcmp(rs->descriptor_digest,
                        router->cache_info.signed_descriptor_digest,
                        DIGEST_LEN))
        rs->need_to_mirror = 0;
    });
    if (consensus) {
 -    routerstatus_t *rs = networkstatus_vote_find_entry(consensus,
 -                                        router->cache_info.identity_digest);
 +    routerstatus_t *rs = networkstatus_vote_find_entry(consensus, id_digest);
      if (rs && !memcmp(rs->descriptor_digest,
                        router->cache_info.signed_descriptor_digest,
                        DIGEST_LEN)) {
@@@ -3316,11 -3032,13 +3316,11 @@@
    }
  
    /* If we have a router with the same identity key, choose the newer one. */
 -  old_router = rimap_get(routerlist->identity_map,
 -                         router->cache_info.identity_digest);
    if (old_router) {
      if (!in_consensus && (router->cache_info.published_on <=
                            old_router->cache_info.published_on)) {
        /* Same key, but old.  This one is not listed in the consensus. */
 -      log_debug(LD_DIR, "Skipping not-new descriptor for router '%s'",
 +      log_debug(LD_DIR, "Not-new descriptor for router '%s'",
                  router->nickname);
        /* Only journal this desc if we'll be serving it. */
        if (!from_cache && should_cache_old_descriptors())
@@@ -3334,7 -3052,8 +3334,7 @@@
        log_debug(LD_DIR, "Replacing entry for router '%s/%s' [%s]",
                  router->nickname, old_router->nickname,
                  hex_str(id_digest,DIGEST_LEN));
 -      if (router->addr == old_router->addr &&
 -          router->or_port == old_router->or_port) {
 +      if (routers_have_same_or_addr(router, old_router)) {
          /* these carry over when the address and orport are unchanged. */
          router->last_reachable = old_router->last_reachable;
          router->testing_since = old_router->testing_since;
@@@ -3362,10 -3081,9 +3362,10 @@@
    /* We haven't seen a router with this identity before. Add it to the end of
     * the list. */
    routerlist_insert(routerlist, router);
 -  if (!from_cache)
 +  if (!from_cache) {
      signed_desc_append_to_journal(&router->cache_info,
                                    &routerlist->desc_store);
 +  }
    directory_set_dirty();
    return ROUTER_ADDED_SUCCESSFULLY;
  }
@@@ -3682,19 -3400,15 +3682,19 @@@ routerlist_remove_old_routers(void
  
  /** We just added a new set of descriptors. Take whatever extra steps
   * we need. */
 -static void
 +void
  routerlist_descriptors_added(smartlist_t *sl, int from_cache)
  {
    tor_assert(sl);
    control_event_descriptors_changed(sl);
 -  SMARTLIST_FOREACH(sl, routerinfo_t *, ri,
 +  SMARTLIST_FOREACH_BEGIN(sl, routerinfo_t *, ri) {
      if (ri->purpose == ROUTER_PURPOSE_BRIDGE)
        learned_bridge_descriptor(ri, from_cache);
 -  );
 +    if (ri->needs_retest_if_added) {
 +      ri->needs_retest_if_added = 0;
 +      dirserv_single_reachability_test(approx_time(), ri);
 +    }
 +  } SMARTLIST_FOREACH_END(ri);
  }
  
  /**
@@@ -4025,8 -3739,12 +4025,8 @@@ add_trusted_dir_server(const char *nick
  
    if (ent->or_port)
      ent->fake_status.version_supports_begindir = 1;
 -/* XX021 - wait until authorities are upgraded */
 -#if 0
 +
    ent->fake_status.version_supports_conditional_consensus = 1;
 -#else
 -  ent->fake_status.version_supports_conditional_consensus = 0;
 -#endif
  
    smartlist_add(trusted_dir_servers, ent);
    router_dir_info_changed();
@@@ -4041,8 -3759,10 +4041,8 @@@ authority_cert_free(authority_cert_t *c
      return;
  
    tor_free(cert->cache_info.signed_descriptor_body);
 -  if (cert->signing_key)
 -    crypto_free_pk_env(cert->signing_key);
 -  if (cert->identity_key)
 -    crypto_free_pk_env(cert->identity_key);
 +  crypto_free_pk_env(cert->signing_key);
 +  crypto_free_pk_env(cert->identity_key);
  
    tor_free(cert);
  }
@@@ -4051,9 -3771,6 +4051,9 @@@
  static void
  trusted_dir_server_free(trusted_dir_server_t *ds)
  {
 +  if (!ds)
 +    return;
 +
    tor_free(ds->nickname);
    tor_free(ds->description);
    tor_free(ds->address);
@@@ -4107,7 -3824,7 +4107,7 @@@ list_pending_downloads(digestmap_t *res
        const char *resource = TO_DIR_CONN(conn)->requested_resource;
        if (!strcmpstart(resource, prefix))
          dir_split_resource_into_fingerprints(resource + p_len,
 -                                             tmp, NULL, 1, 0);
 +                                             tmp, NULL, DSR_HEX);
      }
    });
    SMARTLIST_FOREACH(tmp, char *, d,
@@@ -4209,7 -3926,7 +4209,7 @@@ client_would_use_router(routerstatus_t 
   * this number per server. */
  #define MIN_DL_PER_REQUEST 4
  /** To prevent a single screwy cache from confusing us by selective reply,
 - * try to split our requests into at least this this many requests. */
 + * try to split our requests into at least this many requests. */
  #define MIN_REQUESTS 3
  /** If we want fewer than this many descriptors, wait until we
   * want more, or until MAX_CLIENT_INTERVAL_WITHOUT_REQUEST has
@@@ -4223,8 -3940,7 +4223,8 @@@
   * whether to delay fetching until we have more.  If we don't want to delay,
   * launch one or more requests to the appropriate directory authorities. */
  static void
 -launch_router_descriptor_downloads(smartlist_t *downloadable, time_t now)
 +launch_router_descriptor_downloads(smartlist_t *downloadable,
 +                                   routerstatus_t *source, time_t now)
  {
    int should_delay = 0, n_downloadable;
    or_options_t *options = get_options();
@@@ -4277,7 -3993,7 +4277,7 @@@
        pds_flags |= PDS_NO_EXISTING_SERVERDESC_FETCH;
      }
  
 -    n_per_request = (n_downloadable+MIN_REQUESTS-1) / MIN_REQUESTS;
 +    n_per_request = CEIL_DIV(n_downloadable, MIN_REQUESTS);
      if (n_per_request > MAX_DL_PER_REQUEST)
        n_per_request = MAX_DL_PER_REQUEST;
      if (n_per_request < MIN_DL_PER_REQUEST)
@@@ -4290,11 -4006,11 +4290,11 @@@
  
      log_info(LD_DIR,
               "Launching %d request%s for %d router%s, %d at a time",
 -             (n_downloadable+n_per_request-1)/n_per_request,
 +             CEIL_DIV(n_downloadable, n_per_request),
               req_plural, n_downloadable, rtr_plural, n_per_request);
      smartlist_sort_digests(downloadable);
      for (i=0; i < n_downloadable; i += n_per_request) {
 -      initiate_descriptor_downloads(NULL, DIR_PURPOSE_FETCH_SERVERDESC,
 +      initiate_descriptor_downloads(source, DIR_PURPOSE_FETCH_SERVERDESC,
                                      downloadable, i, i+n_per_request,
                                      pds_flags);
      }
@@@ -4450,18 -4166,18 +4450,18 @@@ update_router_descriptor_cache_download
    digestmap_free(map,NULL);
  }
  
 -/** For any descriptor that we want that's currently listed in the live
 - * consensus, download it as appropriate. */
 -static void
 -update_consensus_router_descriptor_downloads(time_t now)
 +/** For any descriptor that we want that's currently listed in
 + * <b>consensus</b>, download it as appropriate. */
 +void
 +update_consensus_router_descriptor_downloads(time_t now, int is_vote,
 +                                             networkstatus_t *consensus)
  {
    or_options_t *options = get_options();
    digestmap_t *map = NULL;
    smartlist_t *no_longer_old = smartlist_create();
    smartlist_t *downloadable = smartlist_create();
 +  routerstatus_t *source = NULL;
    int authdir = authdir_mode(options);
 -  networkstatus_t *consensus =
 -    networkstatus_get_reasonably_live_consensus(now);
    int n_delayed=0, n_have=0, n_would_reject=0, n_wouldnt_use=0,
      n_inprogress=0, n_in_oldrouters=0;
  
@@@ -4470,24 -4186,10 +4470,24 @@@
    if (!consensus)
      goto done;
  
 +  if (is_vote) {
 +    /* where's it from, so we know whom to ask for descriptors */
 +    trusted_dir_server_t *ds;
 +    networkstatus_voter_info_t *voter = smartlist_get(consensus->voters, 0);
 +    tor_assert(voter);
 +    ds = trusteddirserver_get_by_v3_auth_digest(voter->identity_digest);
 +    if (ds)
 +      source = &(ds->fake_status);
 +    else
 +      log_warn(LD_DIR, "couldn't lookup source from vote?");
 +  }
 +
    map = digestmap_new();
    list_pending_descriptor_downloads(map, 0);
 -  SMARTLIST_FOREACH(consensus->routerstatus_list, routerstatus_t *, rs,
 +  SMARTLIST_FOREACH(consensus->routerstatus_list, void *, rsp,
      {
 +      routerstatus_t *rs =
 +        is_vote ? &(((vote_routerstatus_t *)rsp)->status) : rsp;
        signed_descriptor_t *sd;
        if ((sd = router_get_by_descriptor_digest(rs->descriptor_digest))) {
          routerinfo_t *ri;
@@@ -4522,18 -4224,6 +4522,18 @@@
          ++n_wouldnt_use;
          continue; /* We would never use it ourself. */
        }
 +      if (is_vote && source) {
 +        char time_bufnew[ISO_TIME_LEN+1];
 +        char time_bufold[ISO_TIME_LEN+1];
 +        routerinfo_t *oldrouter = router_get_by_digest(rs->identity_digest);
 +        format_iso_time(time_bufnew, rs->published_on);
 +        if (oldrouter)
 +          format_iso_time(time_bufold, oldrouter->cache_info.published_on);
 +        log_info(LD_DIR, "Learned about %s (%s vs %s) from %s's vote (%s)",
 +                 rs->nickname, time_bufnew,
 +                 oldrouter ? time_bufold : "none",
 +                 source->nickname, oldrouter ? "known" : "unknown");
 +      }
        smartlist_add(downloadable, rs->descriptor_digest);
      });
  
@@@ -4567,7 -4257,7 +4567,7 @@@
             smartlist_len(downloadable), n_delayed, n_have, n_in_oldrouters,
             n_would_reject, n_wouldnt_use, n_inprogress);
  
 -  launch_router_descriptor_downloads(downloadable, now);
 +  launch_router_descriptor_downloads(downloadable, source, now);
  
    digestmap_free(map, NULL);
   done:
@@@ -4592,8 -4282,7 +4592,8 @@@ update_router_descriptor_downloads(time
    if (directory_fetches_dir_info_early(options)) {
      update_router_descriptor_cache_downloads_v2(now);
    }
 -  update_consensus_router_descriptor_downloads(now);
 +  update_consensus_router_descriptor_downloads(now, 0,
 +    networkstatus_get_reasonably_live_consensus(now));
  
    /* XXXX021 we could be smarter here; see notes on bug 652. */
    /* If we're a server that doesn't have a configured address, we rely on
@@@ -4730,21 -4419,16 +4730,21 @@@ get_dir_info_status_string(void
  /** Iterate over the servers listed in <b>consensus</b>, and count how many of
   * them seem like ones we'd use, and how many of <em>those</em> we have
   * descriptors for.  Store the former in *<b>num_usable</b> and the latter in
 - * *<b>num_present</b>.  */
 + * *<b>num_present</b>.  If <b>in_set</b> is non-NULL, only consider those
 + * routers in <b>in_set</b>.
 + */
  static void
  count_usable_descriptors(int *num_present, int *num_usable,
                           const networkstatus_t *consensus,
 -                         or_options_t *options, time_t now)
 +                         or_options_t *options, time_t now,
 +                         routerset_t *in_set)
  {
    *num_present = 0, *num_usable=0;
  
    SMARTLIST_FOREACH(consensus->routerstatus_list, routerstatus_t *, rs,
       {
 +       if (in_set && ! routerset_contains_routerstatus(in_set, rs))
 +         continue;
         if (client_would_use_router(rs, now, options)) {
           ++*num_usable; /* the consensus says we want it. */
           if (router_get_by_descriptor_digest(rs->descriptor_digest)) {
@@@ -4773,7 -4457,7 +4773,7 @@@ count_loading_descriptors_progress(void
      return 0; /* can't count descriptors if we have no list of them */
  
    count_usable_descriptors(&num_present, &num_usable,
 -                           consensus, get_options(), now);
 +                           consensus, get_options(), now, NULL);
  
    if (num_usable == 0)
      return 0; /* don't div by 0 */
@@@ -4817,39 -4501,22 +4817,39 @@@ update_router_have_minimum_dir_info(voi
      goto done;
    }
  
 -  count_usable_descriptors(&num_present, &num_usable, consensus, options, now);
 +  count_usable_descriptors(&num_present, &num_usable, consensus, options, now,
 +                           NULL);
  
    if (num_present < num_usable/4) {
      tor_snprintf(dir_info_status, sizeof(dir_info_status),
              "We have only %d/%d usable descriptors.", num_present, num_usable);
      res = 0;
      control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_DESCRIPTORS, 0);
 +    goto done;
    } else if (num_present < 2) {
      tor_snprintf(dir_info_status, sizeof(dir_info_status),
                   "Only %d descriptor%s here and believed reachable!",
                   num_present, num_present ? "" : "s");
      res = 0;
 -  } else {
 -    res = 1;
 +    goto done;
    }
  
 +  /* Check for entry nodes. */
 +  if (options->EntryNodes) {
 +    count_usable_descriptors(&num_present, &num_usable, consensus, options,
 +                             now, options->EntryNodes);
 +
 +    if (!num_usable || !num_present) {
 +      tor_snprintf(dir_info_status, sizeof(dir_info_status),
 +                   "We have only %d/%d usable entry node descriptors.",
 +                   num_present, num_usable);
 +      res = 0;
 +      goto done;
 +    }
 +  }
 +
 +  res = 1;
 +
   done:
    if (res && !have_min_dir_info) {
      log(LOG_NOTICE, LD_DIR,
@@@ -4862,13 -4529,6 +4862,13 @@@
      log(quiet ? LOG_INFO : LOG_NOTICE, LD_DIR,
          "Our directory information is no longer up-to-date "
          "enough to build circuits: %s", dir_info_status);
 +
 +    /* a) make us log when we next complete a circuit, so we know when Tor
 +     * is back up and usable, and b) disable some activities that Tor
 +     * should only do while circuits are working, like reachability tests
 +     * and fetching bridge descriptors only over circuits. */
 +    can_complete_circuit = 0;
 +
      control_event_client_status(LOG_NOTICE, "NOT_ENOUGH_DIR_INFO");
    }
    have_min_dir_info = res;
@@@ -5154,8 -4814,8 +5154,8 @@@ esc_router_info(routerinfo_t *router
    static char *info=NULL;
    char *esc_contact, *esc_platform;
    size_t len;
 -  if (info)
 -    tor_free(info);
 +  tor_free(info);
 +
    if (!router)
      return NULL; /* we're exiting; just free the memory we use */
  
@@@ -5290,8 -4950,9 +5290,8 @@@ voi
  routerset_refresh_countries(routerset_t *target)
  {
    int cc;
 -  if (target->countries) {
 -    bitarray_free(target->countries);
 -  }
 +  bitarray_free(target->countries);
 +
    if (!geoip_is_loaded()) {
      target->countries = NULL;
      target->n_countries = 0;
@@@ -5365,9 -5026,7 +5365,9 @@@ routerset_parse(routerset_t *target, co
    return r;
  }
  
 -/** DOCDOC */
 +/** Called when we change a node set, or when we reload the geoip list:
 + * recompute all country info in all configuration node sets and in the
 + * routerlist. */
  void
  refresh_all_country_info(void)
  {
@@@ -5535,13 -5194,9 +5535,13 @@@ routerset_get_all_routers(smartlist_t *
    }
  }
  
 -/** Add to <b>target</b> every routerinfo_t from <b>source</b> that is in
 - * <b>include</b>, but not excluded in a more specific fashion by
 - * <b>exclude</b>.  If <b>running_only</b>, only include running routers.
 +/** Add to <b>target</b> every routerinfo_t from <b>source</b> except:
 + *
 + * 1) Don't add it if <b>include</b> is non-empty and the relay isn't in
 + * <b>include</b>; and
 + * 2) Don't add it if <b>exclude</b> is non-empty and the relay is
 + * excluded in a more specific fashion by <b>exclude</b>.
 + * 3) If <b>running_only</b>, don't add non-running routers.
   */
  void
  routersets_get_disjunction(smartlist_t *target,
@@@ -5611,15 -5266,35 +5611,15 @@@ routerset_equal(const routerset_t *old
    });
  
    return 1;
 -
 -#if 0
 -  /* XXXX: This won't work if the names/digests are identical but in a
 -     different order. Checking for exact equality would be heavy going,
 -     is it worth it? -RH*/
 -  /* This code is totally bogus; sizeof doesn't work even remotely like this
 -   * code seems to think.  Let's revert to a string-based comparison for
 -   * now. -NM*/
 -  if (sizeof(old->names) != sizeof(new->names))
 -    return 0;
 -
 -  if (memcmp(old->names,new->names,sizeof(new->names)))
 -    return 0;
 -  if (sizeof(old->digests) != sizeof(new->digests))
 -    return 0;
 -  if (memcmp(old->digests,new->digests,sizeof(new->digests)))
 -    return 0;
 -  if (sizeof(old->countries) != sizeof(new->countries))
 -    return 0;
 -  if (memcmp(old->countries,new->countries,sizeof(new->countries)))
 -    return 0;
 -  return 1;
 -#endif
  }
  
  /** Free all storage held in <b>routerset</b>. */
  void
  routerset_free(routerset_t *routerset)
  {
 +  if (!routerset)
 +    return;
 +
    SMARTLIST_FOREACH(routerset->list, char *, cp, tor_free(cp));
    smartlist_free(routerset->list);
    SMARTLIST_FOREACH(routerset->policies, addr_policy_t *, p,
@@@ -5630,7 -5305,8 +5630,7 @@@
  
    strmap_free(routerset->names, NULL);
    digestmap_free(routerset->digests, NULL);
 -  if (routerset->countries)
 -    bitarray_free(routerset->countries);
 +  bitarray_free(routerset->countries);
    tor_free(routerset);
  }
  
diff --combined src/or/routerparse.c
index 1ce105a,e580df2..691b9be
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@@ -1,7 -1,7 +1,7 @@@
  /* Copyright (c) 2001 Matej Pfajfar.
   * Copyright (c) 2001-2004, Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /**
@@@ -10,20 -10,7 +10,20 @@@
   **/
  
  #include "or.h"
 +#include "config.h"
 +#include "dirserv.h"
 +#include "dirvote.h"
 +#include "policies.h"
 +#include "rendcommon.h"
 +#include "router.h"
 +#include "routerlist.h"
  #include "memarea.h"
 +#include "microdesc.h"
 +#include "networkstatus.h"
 +#include "rephist.h"
 +#include "routerparse.h"
 +#undef log
 +#include <math.h>
  
  /****************************************************************************/
  
@@@ -68,7 -55,6 +68,7 @@@ typedef enum 
    K_S,
    K_V,
    K_W,
 +  K_M,
    K_EVENTDNS,
    K_EXTRA_INFO,
    K_EXTRA_INFO_DIGEST,
@@@ -76,31 -62,6 +76,31 @@@
    K_HIDDEN_SERVICE_DIR,
    K_ALLOW_SINGLE_HOP_EXITS,
  
 +  K_DIRREQ_END,
 +  K_DIRREQ_V2_IPS,
 +  K_DIRREQ_V3_IPS,
 +  K_DIRREQ_V2_REQS,
 +  K_DIRREQ_V3_REQS,
 +  K_DIRREQ_V2_SHARE,
 +  K_DIRREQ_V3_SHARE,
 +  K_DIRREQ_V2_RESP,
 +  K_DIRREQ_V3_RESP,
 +  K_DIRREQ_V2_DIR,
 +  K_DIRREQ_V3_DIR,
 +  K_DIRREQ_V2_TUN,
 +  K_DIRREQ_V3_TUN,
 +  K_ENTRY_END,
 +  K_ENTRY_IPS,
 +  K_CELL_END,
 +  K_CELL_PROCESSED,
 +  K_CELL_QUEUED,
 +  K_CELL_TIME,
 +  K_CELL_CIRCS,
 +  K_EXIT_END,
 +  K_EXIT_WRITTEN,
 +  K_EXIT_READ,
 +  K_EXIT_OPENED,
 +
    K_DIR_KEY_CERTIFICATE_VERSION,
    K_DIR_IDENTITY_KEY,
    K_DIR_KEY_PUBLISHED,
@@@ -117,18 -78,13 +117,18 @@@
  
    K_KNOWN_FLAGS,
    K_PARAMS,
 +  K_BW_WEIGHTS,
    K_VOTE_DIGEST,
    K_CONSENSUS_DIGEST,
 +  K_ADDITIONAL_DIGEST,
 +  K_ADDITIONAL_SIGNATURE,
    K_CONSENSUS_METHODS,
    K_CONSENSUS_METHOD,
    K_LEGACY_DIR_KEY,
 +  K_DIRECTORY_FOOTER,
  
    A_PURPOSE,
 +  A_LAST_LISTED,
    _A_UNKNOWN,
  
    R_RENDEZVOUS_SERVICE_DESCRIPTOR,
@@@ -166,7 -122,7 +166,7 @@@
   * type.
   *
   * This structure is only allocated in memareas; do not allocate it on
 - * the heap, or token_free() won't work.
 + * the heap, or token_clear() won't work.
   */
  typedef struct directory_token_t {
    directory_keyword tp;        /**< Type of the token. */
@@@ -302,31 -258,6 +302,31 @@@ static token_rule_t extrainfo_token_tab
    T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
    T01("read-history",        K_READ_HISTORY,        ARGS,    NO_OBJ ),
    T01("write-history",       K_WRITE_HISTORY,       ARGS,    NO_OBJ ),
 +  T01("dirreq-stats-end",    K_DIRREQ_END,          ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-ips",       K_DIRREQ_V2_IPS,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-ips",       K_DIRREQ_V3_IPS,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-reqs",      K_DIRREQ_V2_REQS,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-reqs",      K_DIRREQ_V3_REQS,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-share",     K_DIRREQ_V2_SHARE,     ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-share",     K_DIRREQ_V3_SHARE,     ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-resp",      K_DIRREQ_V2_RESP,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-resp",      K_DIRREQ_V3_RESP,      ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-direct-dl", K_DIRREQ_V2_DIR,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-direct-dl", K_DIRREQ_V3_DIR,       ARGS,    NO_OBJ ),
 +  T01("dirreq-v2-tunneled-dl", K_DIRREQ_V2_TUN,     ARGS,    NO_OBJ ),
 +  T01("dirreq-v3-tunneled-dl", K_DIRREQ_V3_TUN,     ARGS,    NO_OBJ ),
 +  T01("entry-stats-end",     K_ENTRY_END,           ARGS,    NO_OBJ ),
 +  T01("entry-ips",           K_ENTRY_IPS,           ARGS,    NO_OBJ ),
 +  T01("cell-stats-end",      K_CELL_END,            ARGS,    NO_OBJ ),
 +  T01("cell-processed-cells", K_CELL_PROCESSED,     ARGS,    NO_OBJ ),
 +  T01("cell-queued-cells",   K_CELL_QUEUED,         ARGS,    NO_OBJ ),
 +  T01("cell-time-in-queue",  K_CELL_TIME,           ARGS,    NO_OBJ ),
 +  T01("cell-circuits-per-decile", K_CELL_CIRCS,     ARGS,    NO_OBJ ),
 +  T01("exit-stats-end",      K_EXIT_END,            ARGS,    NO_OBJ ),
 +  T01("exit-kibibytes-written", K_EXIT_WRITTEN,     ARGS,    NO_OBJ ),
 +  T01("exit-kibibytes-read", K_EXIT_READ,           ARGS,    NO_OBJ ),
 +  T01("exit-streams-opened", K_EXIT_OPENED,         ARGS,    NO_OBJ ),
 +
    T1_START( "extra-info",          K_EXTRA_INFO,          GE(2),   NO_OBJ ),
  
    END_OF_TABLE
@@@ -336,11 -267,10 +336,11 @@@
   * documents. */
  static token_rule_t rtrstatus_token_table[] = {
    T01("p",                   K_P,               CONCAT_ARGS, NO_OBJ ),
 -  T1( "r",                   K_R,                   GE(8),   NO_OBJ ),
 +  T1( "r",                   K_R,                   GE(7),   NO_OBJ ),
    T1( "s",                   K_S,                   ARGS,    NO_OBJ ),
    T01("v",                   K_V,               CONCAT_ARGS, NO_OBJ ),
    T01("w",                   K_W,                   ARGS,    NO_OBJ ),
 +  T0N("m",                   K_M,               CONCAT_ARGS, NO_OBJ ),
    T0N("opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK ),
    END_OF_TABLE
  };
@@@ -445,7 -375,7 +445,7 @@@ static token_rule_t client_keys_token_t
  
  /** List of tokens allowed in V3 networkstatus votes. */
  static token_rule_t networkstatus_token_table[] = {
 -  T1("network-status-version", K_NETWORK_STATUS_VERSION,
 +  T1_START("network-status-version", K_NETWORK_STATUS_VERSION,
                                                     GE(1),       NO_OBJ ),
    T1("vote-status",            K_VOTE_STATUS,      GE(1),       NO_OBJ ),
    T1("published",              K_PUBLISHED,        CONCAT_ARGS, NO_OBJ ),
@@@ -473,7 -403,7 +473,7 @@@
  
  /** List of tokens allowed in V3 networkstatus consensuses. */
  static token_rule_t networkstatus_consensus_token_table[] = {
 -  T1("network-status-version", K_NETWORK_STATUS_VERSION,
 +  T1_START("network-status-version", K_NETWORK_STATUS_VERSION,
                                                     GE(1),       NO_OBJ ),
    T1("vote-status",            K_VOTE_STATUS,      GE(1),       NO_OBJ ),
    T1("valid-after",            K_VALID_AFTER,      CONCAT_ARGS, NO_OBJ ),
@@@ -500,29 -430,17 +500,29 @@@
  /** List of tokens allowable in the footer of v1/v2 directory/networkstatus
   * footers. */
  static token_rule_t networkstatus_vote_footer_token_table[] = {
 -  T(  "directory-signature", K_DIRECTORY_SIGNATURE, GE(2),   NEED_OBJ ),
 +  T01("directory-footer",    K_DIRECTORY_FOOTER,    NO_ARGS,   NO_OBJ ),
 +  T01("bandwidth-weights",   K_BW_WEIGHTS,          ARGS,      NO_OBJ ),
 +  T(  "directory-signature", K_DIRECTORY_SIGNATURE, GE(2),     NEED_OBJ ),
    END_OF_TABLE
  };
  
  /** List of tokens allowable in detached networkstatus signature documents. */
  static token_rule_t networkstatus_detached_signature_token_table[] = {
    T1_START("consensus-digest", K_CONSENSUS_DIGEST, GE(1),       NO_OBJ ),
 +  T("additional-digest",       K_ADDITIONAL_DIGEST,GE(3),       NO_OBJ ),
    T1("valid-after",            K_VALID_AFTER,      CONCAT_ARGS, NO_OBJ ),
    T1("fresh-until",            K_FRESH_UNTIL,      CONCAT_ARGS, NO_OBJ ),
    T1("valid-until",            K_VALID_UNTIL,      CONCAT_ARGS, NO_OBJ ),
 -  T1N("directory-signature", K_DIRECTORY_SIGNATURE, GE(2),   NEED_OBJ ),
 +  T("additional-signature",  K_ADDITIONAL_SIGNATURE, GE(4),   NEED_OBJ ),
 +  T1N("directory-signature", K_DIRECTORY_SIGNATURE,  GE(2),   NEED_OBJ ),
 +  END_OF_TABLE
 +};
 +
 +static token_rule_t microdesc_token_table[] = {
 +  T1_START("onion-key",        K_ONION_KEY,        NO_ARGS,     NEED_KEY_1024),
 +  T01("family",                K_FAMILY,           ARGS,        NO_OBJ ),
 +  T01("p",                     K_P,                CONCAT_ARGS, NO_OBJ ),
 +  A01("@last-listed",          A_LAST_LISTED,      CONCAT_ARGS, NO_OBJ ),
    END_OF_TABLE
  };
  
@@@ -535,13 -453,9 +535,13 @@@ static addr_policy_t *router_parse_addr
  
  static int router_get_hash_impl(const char *s, size_t s_len, char *digest,
                                  const char *start_str, const char *end_str,
 -                                char end_char);
 -
 -static void token_free(directory_token_t *tok);
 +                                char end_char,
 +                                digest_algorithm_t alg);
 +static int router_get_hashes_impl(const char *s, size_t s_len,
 +                                  digests_t *digests,
 +                                  const char *start_str, const char *end_str,
 +                                  char end_char);
 +static void token_clear(directory_token_t *tok);
  static smartlist_t *find_all_exitpolicy(smartlist_t *s);
  static directory_token_t *_find_by_keyword(smartlist_t *s,
                                             directory_keyword keyword,
@@@ -565,7 -479,6 +565,7 @@@ static directory_token_t *get_next_toke
  #define CST_CHECK_AUTHORITY   (1<<0)
  #define CST_NO_CHECK_OBJTYPE  (1<<1)
  static int check_signature_token(const char *digest,
 +                                 ssize_t digest_len,
                                   directory_token_t *tok,
                                   crypto_pk_env_t *pkey,
                                   int flags,
@@@ -586,34 -499,6 +586,34 @@@ static int tor_version_same_series(tor_
  #define DUMP_AREA(a,name) STMT_NIL
  #endif
  
 +/** Last time we dumped a descriptor to disk. */
 +static time_t last_desc_dumped = 0;
 +
 +/** For debugging purposes, dump unparseable descriptor *<b>desc</b> of
 + * type *<b>type</b> to file $DATADIR/unparseable-desc. Do not write more
 + * than one descriptor to disk per minute. If there is already such a
 + * file in the data directory, overwrite it. */
 +static void
 +dump_desc(const char *desc, const char *type)
 +{
 +  time_t now = time(NULL);
 +  tor_assert(desc);
 +  tor_assert(type);
 +  if (!last_desc_dumped || last_desc_dumped + 60 < now) {
 +    char *debugfile = get_datadir_fname("unparseable-desc");
 +    size_t filelen = 50 + strlen(type) + strlen(desc);
 +    char *content = tor_malloc_zero(filelen);
 +    tor_snprintf(content, filelen, "Unable to parse descriptor of type "
 +                 "%s:\n%s", type, desc);
 +    write_str_to_file(debugfile, content, 0);
 +    log_info(LD_DIR, "Unable to parse descriptor of type %s. See file "
 +             "unparseable-desc in data directory for details.", type);
 +    tor_free(content);
 +    tor_free(debugfile);
 +    last_desc_dumped = now;
 +  }
 +}
 +
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the directory in
   * <b>s</b>.  Return 0 on success, -1 on failure.
   */
@@@ -621,8 -506,7 +621,8 @@@ in
  router_get_dir_hash(const char *s, char *digest)
  {
    return router_get_hash_impl(s, strlen(s), digest,
 -                              "signed-directory","\ndirectory-signature",'\n');
 +                              "signed-directory","\ndirectory-signature",'\n',
 +                              DIGEST_SHA1);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the first router in
@@@ -632,8 -516,7 +632,8 @@@ in
  router_get_router_hash(const char *s, size_t s_len, char *digest)
  {
    return router_get_hash_impl(s, s_len, digest,
 -                              "router ","\nrouter-signature", '\n');
 +                              "router ","\nrouter-signature", '\n',
 +                              DIGEST_SHA1);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the running-routers
@@@ -643,8 -526,7 +643,8 @@@ in
  router_get_runningrouters_hash(const char *s, char *digest)
  {
    return router_get_hash_impl(s, strlen(s), digest,
 -                              "network-status","\ndirectory-signature", '\n');
 +                              "network-status","\ndirectory-signature", '\n',
 +                              DIGEST_SHA1);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the network-status
@@@ -654,31 -536,18 +654,31 @@@ router_get_networkstatus_v2_hash(const 
  {
    return router_get_hash_impl(s, strlen(s), digest,
                                "network-status-version","\ndirectory-signature",
 -                              '\n');
 +                              '\n',
 +                              DIGEST_SHA1);
 +}
 +
 +/** Set <b>digests</b> to all the digests of the consensus document in
 + * <b>s</b> */
 +int
 +router_get_networkstatus_v3_hashes(const char *s, digests_t *digests)
 +{
 +  return router_get_hashes_impl(s,strlen(s),digests,
 +                                "network-status-version",
 +                                "\ndirectory-signature",
 +                                ' ');
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the network-status
   * string in <b>s</b>.  Return 0 on success, -1 on failure. */
  int
 -router_get_networkstatus_v3_hash(const char *s, char *digest)
 +router_get_networkstatus_v3_hash(const char *s, char *digest,
 +                                 digest_algorithm_t alg)
  {
    return router_get_hash_impl(s, strlen(s), digest,
                                "network-status-version",
                                "\ndirectory-signature",
 -                              ' ');
 +                              ' ', alg);
  }
  
  /** Set <b>digest</b> to the SHA-1 digest of the hash of the extrainfo
@@@ -687,7 -556,7 +687,7 @@@ in
  router_get_extrainfo_hash(const char *s, char *digest)
  {
    return router_get_hash_impl(s, strlen(s), digest, "extra-info",
 -                              "\nrouter-signature",'\n');
 +                              "\nrouter-signature",'\n', DIGEST_SHA1);
  }
  
  /** Helper: used to generate signatures for routers, directories and
@@@ -699,15 -568,14 +699,15 @@@
   */
  int
  router_append_dirobj_signature(char *buf, size_t buf_len, const char *digest,
 -                               crypto_pk_env_t *private_key)
 +                               size_t digest_len, crypto_pk_env_t *private_key)
  {
    char *signature;
    size_t i;
 +  int siglen;
  
    signature = tor_malloc(crypto_pk_keysize(private_key));
 -  if (crypto_pk_private_sign(private_key, signature, digest, DIGEST_LEN) < 0) {
 -
 +  siglen = crypto_pk_private_sign(private_key, signature, digest, digest_len);
 +  if (siglen < 0) {
      log_warn(LD_BUG,"Couldn't sign digest.");
      goto err;
    }
@@@ -715,7 -583,7 +715,7 @@@
      goto truncated;
  
    i = strlen(buf);
 -  if (base64_encode(buf+i, buf_len-i, signature, 128) < 0) {
 +  if (base64_encode(buf+i, buf_len-i, signature, siglen) < 0) {
      log_warn(LD_BUG,"couldn't base64-encode signature");
      goto err;
    }
@@@ -822,7 -690,7 +822,7 @@@ router_parse_directory(const char *str
    char digest[DIGEST_LEN];
    time_t published_on;
    int r;
 -  const char *end, *cp;
 +  const char *end, *cp, *str_dup = str;
    smartlist_t *tokens = NULL;
    crypto_pk_env_t *declared_key = NULL;
    memarea_t *area = memarea_new();
@@@ -858,11 -726,11 +858,11 @@@
    }
    declared_key = find_dir_signing_key(str, str+strlen(str));
    note_crypto_pk_op(VERIFY_DIR);
 -  if (check_signature_token(digest, tok, declared_key,
 +  if (check_signature_token(digest, DIGEST_LEN, tok, declared_key,
                              CST_CHECK_AUTHORITY, "directory")<0)
      goto err;
  
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_clear(tokens);
    memarea_clear(area);
  
@@@ -895,12 -763,11 +895,12 @@@
    r = 0;
    goto done;
   err:
 +  dump_desc(str_dup, "v1 directory");
    r = -1;
   done:
    if (declared_key) crypto_free_pk_env(declared_key);
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area) {
@@@ -922,7 -789,7 +922,7 @@@ router_parse_runningrouters(const char 
    int r = -1;
    crypto_pk_env_t *declared_key = NULL;
    smartlist_t *tokens = NULL;
 -  const char *eos = str + strlen(str);
 +  const char *eos = str + strlen(str), *str_dup = str;
    memarea_t *area = NULL;
  
    if (router_get_runningrouters_hash(str, digest)) {
@@@ -951,7 -818,7 +951,7 @@@
    }
    declared_key = find_dir_signing_key(str, eos);
    note_crypto_pk_op(VERIFY_DIR);
 -  if (check_signature_token(digest, tok, declared_key,
 +  if (check_signature_token(digest, DIGEST_LEN, tok, declared_key,
                              CST_CHECK_AUTHORITY, "running-routers")
        < 0)
      goto err;
@@@ -963,10 -830,9 +963,10 @@@
  
    r = 0;
   err:
 +  dump_desc(str_dup, "v1 running-routers");
    if (declared_key) crypto_free_pk_env(declared_key);
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area) {
@@@ -1016,7 -882,7 +1016,7 @@@ find_dir_signing_key(const char *str, c
    }
  
   done:
 -  if (tok) token_free(tok);
 +  if (tok) token_clear(tok);
    if (area) {
      DUMP_AREA(area, "dir-signing-key token");
      memarea_drop_all(area);
@@@ -1052,7 -918,6 +1052,7 @@@ dir_signing_key_is_trusted(crypto_pk_en
   */
  static int
  check_signature_token(const char *digest,
 +                      ssize_t digest_len,
                        directory_token_t *tok,
                        crypto_pk_env_t *pkey,
                        int flags,
@@@ -1083,14 -948,14 +1083,14 @@@
    signed_digest = tor_malloc(tok->object_size);
    if (crypto_pk_public_checksig(pkey, signed_digest, tok->object_body,
                                  tok->object_size)
 -      != DIGEST_LEN) {
 +      < digest_len) {
      log_warn(LD_DIR, "Error reading %s: invalid signature.", doctype);
      tor_free(signed_digest);
      return -1;
    }
  //  log_debug(LD_DIR,"Signed %s hash starts %s", doctype,
  //            hex_str(signed_digest,4));
 -  if (memcmp(digest, signed_digest, DIGEST_LEN)) {
 +  if (memcmp(digest, signed_digest, digest_len)) {
      log_warn(LD_DIR, "Error reading %s: signature does not match.", doctype);
      tor_free(signed_digest);
      return -1;
@@@ -1276,7 -1141,7 +1276,7 @@@ router_parse_entry_from_string(const ch
    smartlist_t *tokens = NULL, *exit_policy_tokens = NULL;
    directory_token_t *tok;
    struct in_addr in;
 -  const char *start_of_annotations, *cp;
 +  const char *start_of_annotations, *cp, *s_dup = s;
    size_t prepend_len = prepend_annotations ? strlen(prepend_annotations) : 0;
    int ok = 1;
    memarea_t *area = NULL;
@@@ -1554,7 -1419,7 +1554,7 @@@
      verified_digests = digestmap_new();
    digestmap_set(verified_digests, signed_digest, (void*)(uintptr_t)1);
  #endif
 -  if (check_signature_token(digest, tok, router->identity_pkey, 0,
 +  if (check_signature_token(digest, DIGEST_LEN, tok, router->identity_pkey, 0,
                              "router descriptor") < 0)
      goto err;
  
@@@ -1572,15 -1437,16 +1572,15 @@@
    goto done;
  
   err:
 +  dump_desc(s_dup, "router descriptor");
    routerinfo_free(router);
    router = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
 -  if (exit_policy_tokens) {
 -    smartlist_free(exit_policy_tokens);
 -  }
 +  smartlist_free(exit_policy_tokens);
    if (area) {
      DUMP_AREA(area, "routerinfo");
      memarea_drop_all(area);
@@@ -1605,7 -1471,6 +1605,7 @@@ extrainfo_parse_entry_from_string(cons
    crypto_pk_env_t *key = NULL;
    routerinfo_t *router = NULL;
    memarea_t *area = NULL;
 +  const char *s_dup = s;
  
    if (!end) {
      end = s + strlen(s);
@@@ -1680,8 -1545,7 +1680,8 @@@
  
    if (key) {
      note_crypto_pk_op(VERIFY_RTR);
 -    if (check_signature_token(digest, tok, key, 0, "extra-info") < 0)
 +    if (check_signature_token(digest, DIGEST_LEN, tok, key, 0,
 +                              "extra-info") < 0)
        goto err;
  
      if (router)
@@@ -1695,12 -1559,12 +1695,12 @@@
  
    goto done;
   err:
 -  if (extrainfo)
 -    extrainfo_free(extrainfo);
 +  dump_desc(s_dup, "extra-info descriptor");
 +  extrainfo_free(extrainfo);
    extrainfo = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area) {
@@@ -1724,7 -1588,6 +1724,7 @@@ authority_cert_parse_from_string(const 
    size_t len;
    int found;
    memarea_t *area = NULL;
 +  const char *s_dup = s;
  
    s = eat_whitespace(s);
    eos = strstr(s, "\ndir-key-certification");
@@@ -1749,7 -1612,7 +1749,7 @@@
      goto err;
    }
    if (router_get_hash_impl(s, strlen(s), digest, "dir-key-certificate-version",
 -                           "\ndir-key-certification", '\n') < 0)
 +                           "\ndir-key-certification", '\n', DIGEST_SHA1) < 0)
      goto err;
    tok = smartlist_get(tokens, 0);
    if (tok->tp != K_DIR_KEY_CERTIFICATE_VERSION || strcmp(tok->args[0], "3")) {
@@@ -1842,7 -1705,7 +1842,7 @@@
      }
    }
    if (!found) {
 -    if (check_signature_token(digest, tok, cert->identity_key, 0,
 +    if (check_signature_token(digest, DIGEST_LEN, tok, cert->identity_key, 0,
                                "key certificate")) {
        goto err;
      }
@@@ -1851,7 -1714,6 +1851,7 @@@
        /* XXXX Once all authorities generate cross-certified certificates,
         * make this field mandatory. */
        if (check_signature_token(cert->cache_info.identity_digest,
 +                                DIGEST_LEN,
                                  tok,
                                  cert->signing_key,
                                  CST_NO_CHECK_OBJTYPE,
@@@ -1871,7 -1733,7 +1871,7 @@@
    if (end_of_string) {
      *end_of_string = eat_whitespace(eos);
    }
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area) {
      DUMP_AREA(area, "authority cert");
@@@ -1879,9 -1741,8 +1879,9 @@@
    }
    return cert;
   err:
 +  dump_desc(s_dup, "authority cert");
    authority_cert_free(cert);
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area) {
      DUMP_AREA(area, "authority cert");
@@@ -1892,28 -1753,23 +1892,28 @@@
  
  /** Helper: given a string <b>s</b>, return the start of the next router-status
   * object (starting with "r " at the start of a line).  If none is found,
 - * return the start of the next directory signature.  If none is found, return
 - * the end of the string. */
 + * return the start of the directory footer, or the next directory signature.
 + * If none is found, return the end of the string. */
  static INLINE const char *
  find_start_of_next_routerstatus(const char *s)
  {
 -  const char *eos = strstr(s, "\nr ");
 -  if (eos) {
 -    const char *eos2 = tor_memstr(s, eos-s, "\ndirectory-signature");
 -    if (eos2 && eos2 < eos)
 -      return eos2;
 -    else
 -      return eos+1;
 -  } else {
 -    if ((eos = strstr(s, "\ndirectory-signature")))
 -      return eos+1;
 -    return s + strlen(s);
 -  }
 +  const char *eos, *footer, *sig;
 +  if ((eos = strstr(s, "\nr ")))
 +    ++eos;
 +  else
 +    eos = s + strlen(s);
 +
 +  footer = tor_memstr(s, eos-s, "\ndirectory-footer");
 +  sig = tor_memstr(s, eos-s, "\ndirectory-signature");
 +
 +  if (footer && sig)
 +    return MIN(footer, sig) + 1;
 +  else if (footer)
 +    return footer+1;
 +  else if (sig)
 +    return sig+1;
 +  else
 +    return eos;
  }
  
  /** Given a string at *<b>s</b>, containing a routerstatus object, and an
@@@ -1927,29 -1783,22 +1927,29 @@@
   * If <b>consensus_method</b> is nonzero, this routerstatus is part of a
   * consensus, and we should parse it according to the method used to
   * make that consensus.
 + *
 + * Parse according to the syntax used by the consensus flavor <b>flav</b>.
   **/
  static routerstatus_t *
  routerstatus_parse_entry_from_string(memarea_t *area,
                                       const char **s, smartlist_t *tokens,
                                       networkstatus_t *vote,
                                       vote_routerstatus_t *vote_rs,
 -                                     int consensus_method)
 +                                     int consensus_method,
 +                                     consensus_flavor_t flav)
  {
 -  const char *eos;
 +  const char *eos, *s_dup = *s;
    routerstatus_t *rs = NULL;
    directory_token_t *tok;
    char timebuf[ISO_TIME_LEN+1];
    struct in_addr in;
 +  int offset = 0;
    tor_assert(tokens);
    tor_assert(bool_eq(vote, vote_rs));
  
 +  if (!consensus_method)
 +    flav = FLAV_NS;
 +
    eos = find_start_of_next_routerstatus(*s);
  
    if (tokenize_string(area,*s, eos, tokens, rtrstatus_token_table,0)) {
@@@ -1961,15 -1810,7 +1961,15 @@@
      goto err;
    }
    tok = find_by_keyword(tokens, K_R);
 -  tor_assert(tok->n_args >= 8);
 +  tor_assert(tok->n_args >= 7);
 +  if (flav == FLAV_NS) {
 +    if (tok->n_args < 8) {
 +      log_warn(LD_DIR, "Too few arguments to r");
 +      goto err;
 +    }
 +  } else {
 +    offset = -1;
 +  }
    if (vote_rs) {
      rs = &vote_rs->status;
    } else {
@@@ -1990,34 -1831,29 +1990,34 @@@
      goto err;
    }
  
 -  if (digest_from_base64(rs->descriptor_digest, tok->args[2])) {
 -    log_warn(LD_DIR, "Error decoding descriptor digest %s",
 -             escaped(tok->args[2]));
 -    goto err;
 +  if (flav == FLAV_NS) {
 +    if (digest_from_base64(rs->descriptor_digest, tok->args[2])) {
 +      log_warn(LD_DIR, "Error decoding descriptor digest %s",
 +               escaped(tok->args[2]));
 +      goto err;
 +    }
    }
  
    if (tor_snprintf(timebuf, sizeof(timebuf), "%s %s",
 -                   tok->args[3], tok->args[4]) < 0 ||
 +                   tok->args[3+offset], tok->args[4+offset]) < 0 ||
        parse_iso_time(timebuf, &rs->published_on)<0) {
 -    log_warn(LD_DIR, "Error parsing time '%s %s'",
 -             tok->args[3], tok->args[4]);
 +    log_warn(LD_DIR, "Error parsing time '%s %s' [%d %d]",
 +             tok->args[3+offset], tok->args[4+offset],
 +             offset, (int)flav);
      goto err;
    }
  
 -  if (tor_inet_aton(tok->args[5], &in) == 0) {
 +  if (tor_inet_aton(tok->args[5+offset], &in) == 0) {
      log_warn(LD_DIR, "Error parsing router address in network-status %s",
 -             escaped(tok->args[5]));
 +             escaped(tok->args[5+offset]));
      goto err;
    }
    rs->addr = ntohl(in.s_addr);
  
 -  rs->or_port =(uint16_t) tor_parse_long(tok->args[6],10,0,65535,NULL,NULL);
 -  rs->dir_port = (uint16_t) tor_parse_long(tok->args[7],10,0,65535,NULL,NULL);
 +  rs->or_port = (uint16_t) tor_parse_long(tok->args[6+offset],
 +                                         10,0,65535,NULL,NULL);
 +  rs->dir_port = (uint16_t) tor_parse_long(tok->args[7+offset],
 +                                           10,0,65535,NULL,NULL);
  
    tok = find_opt_by_keyword(tokens, K_S);
    if (tok && vote) {
@@@ -2103,17 -1939,6 +2103,17 @@@
            goto err;
          }
          rs->has_bandwidth = 1;
 +      } else if (!strcmpstart(tok->args[i], "Measured=")) {
 +        int ok;
 +        rs->measured_bw =
 +            (uint32_t)tor_parse_ulong(strchr(tok->args[i], '=')+1,
 +                                      10, 0, UINT32_MAX, &ok, NULL);
 +        if (!ok) {
 +          log_warn(LD_DIR, "Invalid Measured Bandwidth %s",
 +                   escaped(tok->args[i]));
 +          goto err;
 +        }
 +        rs->has_measured_bw = 1;
        }
      }
    }
@@@ -2135,29 -1960,16 +2135,29 @@@
      rs->has_exitsummary = 1;
    }
  
 +  if (vote_rs) {
 +    SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, t) {
 +      if (t->tp == K_M && t->n_args) {
 +        vote_microdesc_hash_t *line =
 +          tor_malloc(sizeof(vote_microdesc_hash_t));
 +        line->next = vote_rs->microdesc;
 +        line->microdesc_hash_line = tor_strdup(t->args[0]);
 +        vote_rs->microdesc = line;
 +      }
 +    } SMARTLIST_FOREACH_END(t);
 +  }
 +
    if (!strcasecmp(rs->nickname, UNNAMED_ROUTER_NICKNAME))
      rs->is_named = 0;
  
    goto done;
   err:
 +  dump_desc(s_dup, "routerstatus entry");
    if (rs && !vote_rs)
      routerstatus_free(rs);
    rs = NULL;
   done:
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_clear(tokens);
    if (area) {
      DUMP_AREA(area, "routerstatus entry");
@@@ -2169,8 -1981,8 +2169,8 @@@
  }
  
  /** Helper to sort a smartlist of pointers to routerstatus_t */
 -static int
 -_compare_routerstatus_entries(const void **_a, const void **_b)
 +int
 +compare_routerstatus_entries(const void **_a, const void **_b)
  {
    const routerstatus_t *a = *_a, *b = *_b;
    return memcmp(a->identity_digest, b->identity_digest, DIGEST_LEN);
@@@ -2194,7 -2006,7 +2194,7 @@@ _free_duplicate_routerstatus_entry(voi
  networkstatus_v2_t *
  networkstatus_v2_parse_from_string(const char *s)
  {
 -  const char *eos;
 +  const char *eos, *s_dup = s;
    smartlist_t *tokens = smartlist_create();
    smartlist_t *footer_tokens = smartlist_create();
    networkstatus_v2_t *ns = NULL;
@@@ -2308,17 -2120,17 +2308,17 @@@
  
    ns->entries = smartlist_create();
    s = eos;
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_clear(tokens);
    memarea_clear(area);
    while (!strcmpstart(s, "r ")) {
      routerstatus_t *rs;
      if ((rs = routerstatus_parse_entry_from_string(area, &s, tokens,
 -                                                   NULL, NULL, 0)))
 +                                                   NULL, NULL, 0, 0)))
        smartlist_add(ns->entries, rs);
    }
 -  smartlist_sort(ns->entries, _compare_routerstatus_entries);
 -  smartlist_uniq(ns->entries, _compare_routerstatus_entries,
 +  smartlist_sort(ns->entries, compare_routerstatus_entries);
 +  smartlist_uniq(ns->entries, compare_routerstatus_entries,
                   _free_duplicate_routerstatus_entry);
  
    if (tokenize_string(area,s, NULL, footer_tokens, dir_footer_token_table,0)) {
@@@ -2337,19 -2149,19 +2337,19 @@@
    }
  
    note_crypto_pk_op(VERIFY_DIR);
 -  if (check_signature_token(ns_digest, tok, ns->signing_key, 0,
 +  if (check_signature_token(ns_digest, DIGEST_LEN, tok, ns->signing_key, 0,
                              "network-status") < 0)
      goto err;
  
    goto done;
   err:
 -  if (ns)
 -    networkstatus_v2_free(ns);
 +  dump_desc(s_dup, "v2 networkstatus");
 +  networkstatus_v2_free(ns);
    ns = NULL;
   done:
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
 -  SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(footer_tokens);
    if (area) {
      DUMP_AREA(area, "v2 networkstatus");
@@@ -2358,394 -2170,6 +2358,394 @@@
    return ns;
  }
  
 +/** Verify the bandwidth weights of a network status document */
 +int
 +networkstatus_verify_bw_weights(networkstatus_t *ns)
 +{
 +  int64_t weight_scale;
 +  int64_t G=0, M=0, E=0, D=0, T=0;
 +  double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed;
 +  double Gtotal=0, Mtotal=0, Etotal=0;
 +  const char *casename = NULL;
 +  int valid = 1;
 +
 +  weight_scale = networkstatus_get_param(ns, "bwweightscale", BW_WEIGHT_SCALE);
 +  Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1);
 +  Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1);
 +  Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1);
 +  Wmg = networkstatus_get_bw_weight(ns, "Wmg", -1);
 +  Wmm = networkstatus_get_bw_weight(ns, "Wmm", -1);
 +  Wme = networkstatus_get_bw_weight(ns, "Wme", -1);
 +  Wmd = networkstatus_get_bw_weight(ns, "Wmd", -1);
 +  Weg = networkstatus_get_bw_weight(ns, "Weg", -1);
 +  Wem = networkstatus_get_bw_weight(ns, "Wem", -1);
 +  Wee = networkstatus_get_bw_weight(ns, "Wee", -1);
 +  Wed = networkstatus_get_bw_weight(ns, "Wed", -1);
 +
 +  if (Wgg<0 || Wgm<0 || Wgd<0 || Wmg<0 || Wmm<0 || Wme<0 || Wmd<0 || Weg<0
 +          || Wem<0 || Wee<0 || Wed<0) {
 +    log_warn(LD_BUG, "No bandwidth weights produced in consensus!");
 +    return 0;
 +  }
 +
 +  // First, sanity check basic summing properties that hold for all cases
 +  // We use > 1 as the check for these because they are computed as integers.
 +  // Sometimes there are rounding errors.
 +  if (fabs(Wmm - weight_scale) > 1) {
 +    log_warn(LD_BUG, "Wmm=%lf != "I64_FORMAT,
 +             Wmm, I64_PRINTF_ARG(weight_scale));
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wem - Wee) > 1) {
 +    log_warn(LD_BUG, "Wem=%lf != Wee=%lf", Wem, Wee);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wgm - Wgg) > 1) {
 +    log_warn(LD_BUG, "Wgm=%lf != Wgg=%lf", Wgm, Wgg);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Weg - Wed) > 1) {
 +    log_warn(LD_BUG, "Wed=%lf != Weg=%lf", Wed, Weg);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wgg + Wmg - weight_scale) > 0.001*weight_scale) {
 +    log_warn(LD_BUG, "Wgg=%lf != "I64_FORMAT" - Wmg=%lf", Wgg,
 +             I64_PRINTF_ARG(weight_scale), Wmg);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wee + Wme - weight_scale) > 0.001*weight_scale) {
 +    log_warn(LD_BUG, "Wee=%lf != "I64_FORMAT" - Wme=%lf", Wee,
 +             I64_PRINTF_ARG(weight_scale), Wme);
 +    valid = 0;
 +  }
 +
 +  if (fabs(Wgd + Wmd + Wed - weight_scale) > 0.001*weight_scale) {
 +    log_warn(LD_BUG, "Wgd=%lf + Wmd=%lf + Wed=%lf != "I64_FORMAT,
 +             Wgd, Wmd, Wed, I64_PRINTF_ARG(weight_scale));
 +    valid = 0;
 +  }
 +
 +  Wgg /= weight_scale;
 +  Wgm /= weight_scale;
 +  Wgd /= weight_scale;
 +
 +  Wmg /= weight_scale;
 +  Wmm /= weight_scale;
 +  Wme /= weight_scale;
 +  Wmd /= weight_scale;
 +
 +  Weg /= weight_scale;
 +  Wem /= weight_scale;
 +  Wee /= weight_scale;
 +  Wed /= weight_scale;
 +
 +  // Then, gather G, M, E, D, T to determine case
 +  SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) {
 +    if (rs->has_bandwidth) {
 +      T += rs->bandwidth;
 +      if (rs->is_exit && rs->is_possible_guard) {
 +        D += rs->bandwidth;
 +        Gtotal += Wgd*rs->bandwidth;
 +        Mtotal += Wmd*rs->bandwidth;
 +        Etotal += Wed*rs->bandwidth;
 +      } else if (rs->is_exit) {
 +        E += rs->bandwidth;
 +        Mtotal += Wme*rs->bandwidth;
 +        Etotal += Wee*rs->bandwidth;
 +      } else if (rs->is_possible_guard) {
 +        G += rs->bandwidth;
 +        Gtotal += Wgg*rs->bandwidth;
 +        Mtotal += Wmg*rs->bandwidth;
 +      } else {
 +        M += rs->bandwidth;
 +        Mtotal += Wmm*rs->bandwidth;
 +      }
 +    } else {
 +      log_warn(LD_BUG, "Missing consensus bandwidth for router %s",
 +          rs->nickname);
 +    }
 +  } SMARTLIST_FOREACH_END(rs);
 +
 +  // Finally, check equality conditions depending upon case 1, 2 or 3
 +  // Full equality cases: 1, 3b
 +  // Partial equality cases: 2b (E=G), 3a (M=E)
 +  // Fully unknown: 2a
 +  if (3*E >= T && 3*G >= T) {
 +    // Case 1: Neither are scarce
 +    casename = "Case 1";
 +    if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) {
 +      log_warn(LD_DIR,
 +               "Bw Weight Failure for %s: Etotal %lf != Mtotal %lf. "
 +               "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +               " T="I64_FORMAT". "
 +               "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +               casename, Etotal, Mtotal,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +               Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +      valid = 0;
 +    }
 +    if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +      log_warn(LD_DIR,
 +               "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +               "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +               " T="I64_FORMAT". "
 +               "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +               casename, Etotal, Gtotal,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +               Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +      valid = 0;
 +    }
 +    if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) {
 +      log_warn(LD_DIR,
 +               "Bw Weight Failure for %s: Mtotal %lf != Gtotal %lf. "
 +               "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +               " T="I64_FORMAT". "
 +               "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +               casename, Mtotal, Gtotal,
 +               I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +               I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +               Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +      valid = 0;
 +    }
 +  } else if (3*E < T && 3*G < T) {
 +    int64_t R = MIN(E, G);
 +    int64_t S = MAX(E, G);
 +    /*
 +     * Case 2: Both Guards and Exits are scarce
 +     * Balance D between E and G, depending upon
 +     * D capacity and scarcity. Devote no extra
 +     * bandwidth to middle nodes.
 +     */
 +    if (R+D < S) { // Subcase a
 +      double Rtotal, Stotal;
 +      if (E < G) {
 +        Rtotal = Etotal;
 +        Stotal = Gtotal;
 +      } else {
 +        Rtotal = Gtotal;
 +        Stotal = Etotal;
 +      }
 +      casename = "Case 2a";
 +      // Rtotal < Stotal
 +      if (Rtotal > Stotal) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Rtotal %lf > Stotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Rtotal, Stotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      // Rtotal < T/3
 +      if (3*Rtotal > T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Rtotal %lf > T "
 +                   I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                   " D="I64_FORMAT" T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Rtotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      // Stotal < T/3
 +      if (3*Stotal > T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Stotal %lf > T "
 +                   I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                   " D="I64_FORMAT" T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Stotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      // Mtotal > T/3
 +      if (3*Mtotal < T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Mtotal %lf < T "
 +                   I64_FORMAT". "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Mtotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +    } else { // Subcase b: R+D > S
 +      casename = "Case 2b";
 +
 +      /* Check the rare-M redirect case. */
 +      if (D != 0 && 3*M < T) {
 +        casename = "Case 2b (balanced)";
 +        if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Etotal %lf != Mtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Etotal, Mtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +        if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Etotal, Gtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +        if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Mtotal %lf != Gtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Mtotal, Gtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      } else {
 +        if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Etotal, Gtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      }
 +    }
 +  } else { // if (E < T/3 || G < T/3) {
 +    int64_t S = MIN(E, G);
 +    int64_t NS = MAX(E, G);
 +    if (3*(S+D) < T) { // Subcase a:
 +      double Stotal;
 +      double NStotal;
 +      if (G < E) {
 +        casename = "Case 3a (G scarce)";
 +        Stotal = Gtotal;
 +        NStotal = Etotal;
 +      } else { // if (G >= E) {
 +        casename = "Case 3a (E scarce)";
 +        NStotal = Gtotal;
 +        Stotal = Etotal;
 +      }
 +      // Stotal < T/3
 +      if (3*Stotal > T) {
 +        log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: 3*Stotal %lf > T "
 +                   I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT
 +                   " D="I64_FORMAT" T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, Stotal*3, I64_PRINTF_ARG(T),
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      if (NS >= M) {
 +        if (fabs(NStotal-Mtotal) > 0.01*MAX(NStotal,Mtotal)) {
 +          log_warn(LD_DIR,
 +                   "Bw Weight Failure for %s: NStotal %lf != Mtotal %lf. "
 +                   "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                   " T="I64_FORMAT". "
 +                   "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                   casename, NStotal, Mtotal,
 +                   I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                   I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                   Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      } else {
 +        // if NS < M, NStotal > T/3 because only one of G or E is scarce
 +        if (3*NStotal < T) {
 +          log_warn(LD_DIR,
 +                     "Bw Weight Failure for %s: 3*NStotal %lf < T "
 +                     I64_FORMAT". G="I64_FORMAT" M="I64_FORMAT
 +                     " E="I64_FORMAT" D="I64_FORMAT" T="I64_FORMAT". "
 +                     "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                     casename, NStotal*3, I64_PRINTF_ARG(T),
 +                     I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                     I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                     Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +          valid = 0;
 +        }
 +      }
 +    } else { // Subcase b: S+D >= T/3
 +      casename = "Case 3b";
 +      if (fabs(Etotal-Mtotal) > 0.01*MAX(Etotal,Mtotal)) {
 +        log_warn(LD_DIR,
 +                 "Bw Weight Failure for %s: Etotal %lf != Mtotal %lf. "
 +                 "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                 " T="I64_FORMAT". "
 +                 "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                 casename, Etotal, Mtotal,
 +                 I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                 I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                 Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      if (fabs(Etotal-Gtotal) > 0.01*MAX(Etotal,Gtotal)) {
 +        log_warn(LD_DIR,
 +                 "Bw Weight Failure for %s: Etotal %lf != Gtotal %lf. "
 +                 "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                 " T="I64_FORMAT". "
 +                 "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                 casename, Etotal, Gtotal,
 +                 I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                 I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                 Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +      if (fabs(Gtotal-Mtotal) > 0.01*MAX(Gtotal,Mtotal)) {
 +        log_warn(LD_DIR,
 +                 "Bw Weight Failure for %s: Mtotal %lf != Gtotal %lf. "
 +                 "G="I64_FORMAT" M="I64_FORMAT" E="I64_FORMAT" D="I64_FORMAT
 +                 " T="I64_FORMAT". "
 +                 "Wgg=%lf Wgd=%lf Wmg=%lf Wme=%lf Wmd=%lf Wee=%lf Wed=%lf",
 +                 casename, Mtotal, Gtotal,
 +                 I64_PRINTF_ARG(G), I64_PRINTF_ARG(M), I64_PRINTF_ARG(E),
 +                 I64_PRINTF_ARG(D), I64_PRINTF_ARG(T),
 +                 Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed);
 +        valid = 0;
 +      }
 +    }
 +  }
 +
 +  if (valid)
 +    log_notice(LD_DIR, "Bandwidth-weight %s is verified and valid.",
 +               casename);
 +
 +  return valid;
 +}
  
  /** Parse a v3 networkstatus vote, opinion, or consensus (depending on
   * ns_type), from <b>s</b>, and return the result.  Return NULL on failure. */
@@@ -2757,21 -2181,19 +2757,21 @@@ networkstatus_parse_vote_from_string(co
    smartlist_t *rs_tokens = NULL, *footer_tokens = NULL;
    networkstatus_voter_info_t *voter = NULL;
    networkstatus_t *ns = NULL;
 -  char ns_digest[DIGEST_LEN];
 -  const char *cert, *end_of_header, *end_of_footer;
 +  digests_t ns_digests;
 +  const char *cert, *end_of_header, *end_of_footer, *s_dup = s;
    directory_token_t *tok;
    int ok;
    struct in_addr in;
    int i, inorder, n_signatures = 0;
    memarea_t *area = NULL, *rs_area = NULL;
 +  consensus_flavor_t flav = FLAV_NS;
 +
    tor_assert(s);
  
    if (eos_out)
      *eos_out = NULL;
  
 -  if (router_get_networkstatus_v3_hash(s, ns_digest)) {
 +  if (router_get_networkstatus_v3_hashes(s, &ns_digests)) {
      log_warn(LD_DIR, "Unable to compute digest of network-status");
      goto err;
    }
@@@ -2787,23 -2209,7 +2787,23 @@@
    }
  
    ns = tor_malloc_zero(sizeof(networkstatus_t));
 -  memcpy(ns->networkstatus_digest, ns_digest, DIGEST_LEN);
 +  memcpy(&ns->digests, &ns_digests, sizeof(ns_digests));
 +
 +  tok = find_by_keyword(tokens, K_NETWORK_STATUS_VERSION);
 +  tor_assert(tok);
 +  if (tok->n_args > 1) {
 +    int flavor = networkstatus_parse_flavor_name(tok->args[1]);
 +    if (flavor < 0) {
 +      log_warn(LD_DIR, "Can't parse document with unknown flavor %s",
 +               escaped(tok->args[2]));
 +      goto err;
 +    }
 +    ns->flavor = flav = flavor;
 +  }
 +  if (flav != FLAV_NS && ns_type != NS_TYPE_CONSENSUS) {
 +    log_warn(LD_DIR, "Flavor found on non-consenus networkstatus.");
 +    goto err;
 +  }
  
    if (ns_type != NS_TYPE_CONSENSUS) {
      const char *end_of_cert = NULL;
@@@ -2957,9 -2363,8 +2957,9 @@@
        if (voter)
          smartlist_add(ns->voters, voter);
        voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
 +      voter->sigs = smartlist_create();
        if (ns->type != NS_TYPE_CONSENSUS)
 -        memcpy(voter->vote_digest, ns_digest, DIGEST_LEN);
 +        memcpy(voter->vote_digest, ns_digests.d[DIGEST_SHA1], DIGEST_LEN);
  
        voter->nickname = tor_strdup(tok->args[0]);
        if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
@@@ -3051,7 -2456,7 +3051,7 @@@
      if (ns->type != NS_TYPE_CONSENSUS) {
        vote_routerstatus_t *rs = tor_malloc_zero(sizeof(vote_routerstatus_t));
        if (routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens, ns,
 -                                               rs, 0))
 +                                               rs, 0, 0))
          smartlist_add(ns->routerstatus_list, rs);
        else {
          tor_free(rs->version);
@@@ -3061,8 -2466,7 +3061,8 @@@
        routerstatus_t *rs;
        if ((rs = routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens,
                                                       NULL, NULL,
 -                                                     ns->consensus_method)))
 +                                                     ns->consensus_method,
 +                                                     flav)))
          smartlist_add(ns->routerstatus_list, rs);
      }
    }
@@@ -3095,73 -2499,14 +3095,73 @@@
      goto err;
    }
  
 -  SMARTLIST_FOREACH(footer_tokens, directory_token_t *, _tok,
    {
 +    int found_sig = 0;
 +    SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) {
 +      tok = _tok;
 +      if (tok->tp == K_DIRECTORY_SIGNATURE)
 +        found_sig = 1;
 +      else if (found_sig) {
 +        log_warn(LD_DIR, "Extraneous token after first directory-signature");
 +        goto err;
 +      }
 +    } SMARTLIST_FOREACH_END(_tok);
 +  }
 +
 +  if ((tok = find_opt_by_keyword(footer_tokens, K_DIRECTORY_FOOTER))) {
 +    if (tok != smartlist_get(footer_tokens, 0)) {
 +      log_warn(LD_DIR, "Misplaced directory-footer token");
 +      goto err;
 +    }
 +  }
 +
 +  tok = find_opt_by_keyword(footer_tokens, K_BW_WEIGHTS);
 +  if (tok) {
 +    ns->weight_params = smartlist_create();
 +    for (i = 0; i < tok->n_args; ++i) {
 +      int ok=0;
 +      char *eq = strchr(tok->args[i], '=');
 +      if (!eq) {
 +        log_warn(LD_DIR, "Bad element '%s' in weight params",
 +                 escaped(tok->args[i]));
 +        goto err;
 +      }
 +      tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL);
 +      if (!ok) {
 +        log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
 +        goto err;
 +      }
 +      smartlist_add(ns->weight_params, tor_strdup(tok->args[i]));
 +    }
 +  }
 +
 +  SMARTLIST_FOREACH_BEGIN(footer_tokens, directory_token_t *, _tok) {
      char declared_identity[DIGEST_LEN];
      networkstatus_voter_info_t *v;
 +    document_signature_t *sig;
 +    const char *id_hexdigest = NULL;
 +    const char *sk_hexdigest = NULL;
 +    digest_algorithm_t alg = DIGEST_SHA1;
      tok = _tok;
      if (tok->tp != K_DIRECTORY_SIGNATURE)
        continue;
      tor_assert(tok->n_args >= 2);
 +    if (tok->n_args == 2) {
 +      id_hexdigest = tok->args[0];
 +      sk_hexdigest = tok->args[1];
 +    } else {
 +      const char *algname = tok->args[0];
 +      int a;
 +      id_hexdigest = tok->args[1];
 +      sk_hexdigest = tok->args[2];
 +      a = crypto_digest_algorithm_parse_name(algname);
 +      if (a<0) {
 +        log_warn(LD_DIR, "Unknown digest algorithm %s; skipping",
 +                 escaped(algname));
 +        continue;
 +      }
 +      alg = a;
 +    }
  
      if (!tok->object_type ||
          strcmp(tok->object_type, "SIGNATURE") ||
@@@ -3170,11 -2515,11 +3170,11 @@@
        goto err;
      }
  
 -    if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
 +    if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
          base16_decode(declared_identity, sizeof(declared_identity),
 -                      tok->args[0], HEX_DIGEST_LEN) < 0) {
 +                      id_hexdigest, HEX_DIGEST_LEN) < 0) {
        log_warn(LD_DIR, "Error decoding declared identity %s in "
 -               "network-status vote.", escaped(tok->args[0]));
 +               "network-status vote.", escaped(id_hexdigest));
        goto err;
      }
      if (!(v = networkstatus_get_voter_by_id(ns, declared_identity))) {
@@@ -3182,15 -2527,11 +3182,15 @@@
                 "any declared directory source.");
        goto err;
      }
 -    if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
 -        base16_decode(v->signing_key_digest, sizeof(v->signing_key_digest),
 -                      tok->args[1], HEX_DIGEST_LEN) < 0) {
 -      log_warn(LD_DIR, "Error decoding declared digest %s in "
 -               "network-status vote.", escaped(tok->args[1]));
 +    sig = tor_malloc_zero(sizeof(document_signature_t));
 +    memcpy(sig->identity_digest, v->identity_digest, DIGEST_LEN);
 +    sig->alg = alg;
 +    if (strlen(sk_hexdigest) != HEX_DIGEST_LEN ||
 +        base16_decode(sig->signing_key_digest, sizeof(sig->signing_key_digest),
 +                      sk_hexdigest, HEX_DIGEST_LEN) < 0) {
 +      log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
 +               "network-status vote.", escaped(sk_hexdigest));
 +      tor_free(sig);
        goto err;
      }
  
@@@ -3199,49 -2540,35 +3199,49 @@@
                   DIGEST_LEN)) {
          log_warn(LD_DIR, "Digest mismatch between declared and actual on "
                   "network-status vote.");
 +        tor_free(sig);
          goto err;
        }
      }
  
 +    if (voter_get_sig_by_algorithm(v, sig->alg)) {
 +      /* We already parsed a vote with this algorithm from this voter. Use the
 +         first one. */
 +      log_fn(LOG_PROTOCOL_WARN, LD_DIR, "We received a networkstatus "
 +             "that contains two votes from the same voter with the same "
 +             "algorithm. Ignoring the second vote.");
 +      tor_free(sig);
 +      continue;
 +    }
 +
      if (ns->type != NS_TYPE_CONSENSUS) {
 -      if (check_signature_token(ns_digest, tok, ns->cert->signing_key, 0,
 -                                "network-status vote"))
 +      if (check_signature_token(ns_digests.d[DIGEST_SHA1], DIGEST_LEN,
 +                                tok, ns->cert->signing_key, 0,
 +                                "network-status vote")) {
 +        tor_free(sig);
          goto err;
 -      v->good_signature = 1;
 +      }
 +      sig->good_signature = 1;
      } else {
 -      if (tok->object_size >= INT_MAX)
 +      if (tok->object_size >= INT_MAX) {
 +        tor_free(sig);
          goto err;
 -      /* We already parsed a vote from this voter. Use the first one. */
 -      if (v->signature) {
 -        log_fn(LOG_PROTOCOL_WARN, LD_DIR, "We received a networkstatus "
 -                   "that contains two votes from the same voter. Ignoring "
 -                   "the second vote.");
 -        continue;
        }
 -
 -      v->signature = tor_memdup(tok->object_body, tok->object_size);
 -      v->signature_len = (int) tok->object_size;
 +      sig->signature = tor_memdup(tok->object_body, tok->object_size);
 +      sig->signature_len = (int) tok->object_size;
      }
 +    smartlist_add(v->sigs, sig);
 +
      ++n_signatures;
 -  });
 +  } SMARTLIST_FOREACH_END(_tok);
  
    if (! n_signatures) {
      log_warn(LD_DIR, "No signatures on networkstatus vote.");
      goto err;
 +  } else if (ns->type == NS_TYPE_VOTE && n_signatures != 1) {
 +    log_warn(LD_DIR, "Received more than one signature on a "
 +             "network-status vote.");
 +    goto err;
    }
  
    if (eos_out)
@@@ -3249,31 -2576,27 +3249,31 @@@
  
    goto done;
   err:
 -  if (ns)
 -    networkstatus_vote_free(ns);
 +  dump_desc(s_dup, "v3 networkstatus");
 +  networkstatus_vote_free(ns);
    ns = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (voter) {
 +    if (voter->sigs) {
 +      SMARTLIST_FOREACH(voter->sigs, document_signature_t *, sig,
 +                        document_signature_free(sig));
 +      smartlist_free(voter->sigs);
 +    }
      tor_free(voter->nickname);
      tor_free(voter->address);
      tor_free(voter->contact);
      tor_free(voter);
    }
    if (rs_tokens) {
 -    SMARTLIST_FOREACH(rs_tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(rs_tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(rs_tokens);
    }
    if (footer_tokens) {
 -    SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(footer_tokens);
    }
    if (area) {
@@@ -3286,35 -2609,6 +3286,35 @@@
    return ns;
  }
  
 +/** Return the digests_t that holds the digests of the
 + * <b>flavor_name</b>-flavored networkstatus according to the detached
 + * signatures document <b>sigs</b>, allocating a new digests_t as neeeded. */
 +static digests_t *
 +detached_get_digests(ns_detached_signatures_t *sigs, const char *flavor_name)
 +{
 +  digests_t *d = strmap_get(sigs->digests, flavor_name);
 +  if (!d) {
 +    d = tor_malloc_zero(sizeof(digests_t));
 +    strmap_set(sigs->digests, flavor_name, d);
 +  }
 +  return d;
 +}
 +
 +/** Return the list of signatures of the <b>flavor_name</b>-flavored
 + * networkstatus according to the detached signatures document <b>sigs</b>,
 + * allocating a new digests_t as neeeded. */
 +static smartlist_t *
 +detached_get_signatures(ns_detached_signatures_t *sigs,
 +                        const char *flavor_name)
 +{
 +  smartlist_t *sl = strmap_get(sigs->signatures, flavor_name);
 +  if (!sl) {
 +    sl = smartlist_create();
 +    strmap_set(sigs->signatures, flavor_name, sl);
 +  }
 +  return sl;
 +}
 +
  /** Parse a detached v3 networkstatus signature document between <b>s</b> and
   * <b>eos</b> and return the result.  Return -1 on failure. */
  ns_detached_signatures_t *
@@@ -3324,13 -2618,10 +3324,13 @@@ networkstatus_parse_detached_signatures
     * networkstatus_parse_vote_from_string(). */
    directory_token_t *tok;
    memarea_t *area = NULL;
 +  digests_t *digests;
  
    smartlist_t *tokens = smartlist_create();
    ns_detached_signatures_t *sigs =
      tor_malloc_zero(sizeof(ns_detached_signatures_t));
 +  sigs->digests = strmap_new();
 +  sigs->signatures = strmap_new();
  
    if (!eos)
      eos = s + strlen(s);
@@@ -3342,57 -2633,18 +3342,57 @@@
      goto err;
    }
  
 -  tok = find_by_keyword(tokens, K_CONSENSUS_DIGEST);
 -  if (strlen(tok->args[0]) != HEX_DIGEST_LEN) {
 -    log_warn(LD_DIR, "Wrong length on consensus-digest in detached "
 -             "networkstatus signatures");
 -    goto err;
 -  }
 -  if (base16_decode(sigs->networkstatus_digest, DIGEST_LEN,
 -                    tok->args[0], strlen(tok->args[0])) < 0) {
 -    log_warn(LD_DIR, "Bad encoding on on consensus-digest in detached "
 -             "networkstatus signatures");
 -    goto err;
 -  }
 +  /* Grab all the digest-like tokens. */
 +  SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {
 +    const char *algname;
 +    digest_algorithm_t alg;
 +    const char *flavor;
 +    const char *hexdigest;
 +    size_t expected_length;
 +
 +    tok = _tok;
 +
 +    if (tok->tp == K_CONSENSUS_DIGEST) {
 +      algname = "sha1";
 +      alg = DIGEST_SHA1;
 +      flavor = "ns";
 +      hexdigest = tok->args[0];
 +    } else if (tok->tp == K_ADDITIONAL_DIGEST) {
 +      int a = crypto_digest_algorithm_parse_name(tok->args[1]);
 +      if (a<0) {
 +        log_warn(LD_DIR, "Unrecognized algorithm name %s", tok->args[0]);
 +        continue;
 +      }
 +      alg = (digest_algorithm_t) a;
 +      flavor = tok->args[0];
 +      algname = tok->args[1];
 +      hexdigest = tok->args[2];
 +    } else {
 +      continue;
 +    }
 +
 +    expected_length =
 +      (alg == DIGEST_SHA1) ? HEX_DIGEST_LEN : HEX_DIGEST256_LEN;
 +
 +    if (strlen(hexdigest) != expected_length) {
 +      log_warn(LD_DIR, "Wrong length on consensus-digest in detached "
 +               "networkstatus signatures");
 +      goto err;
 +    }
 +    digests = detached_get_digests(sigs, flavor);
 +    tor_assert(digests);
 +    if (!tor_mem_is_zero(digests->d[alg], DIGEST256_LEN)) {
 +      log_warn(LD_DIR, "Multiple digests for %s with %s on detached "
 +               "signatures document", flavor, algname);
 +      continue;
 +    }
 +    if (base16_decode(digests->d[alg], DIGEST256_LEN,
 +                      hexdigest, strlen(hexdigest)) < 0) {
 +      log_warn(LD_DIR, "Bad encoding on consensus-digest in detached "
 +               "networkstatus signatures");
 +      goto err;
 +    }
 +  } SMARTLIST_FOREACH_END(_tok);
  
    tok = find_by_keyword(tokens, K_VALID_AFTER);
    if (parse_iso_time(tok->args[0], &sigs->valid_after)) {
@@@ -3412,102 -2664,57 +3412,102 @@@
      goto err;
    }
  
 -  sigs->signatures = smartlist_create();
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, _tok,
 -    {
 -      char id_digest[DIGEST_LEN];
 -      char sk_digest[DIGEST_LEN];
 -      networkstatus_voter_info_t *voter;
 +  SMARTLIST_FOREACH_BEGIN(tokens, directory_token_t *, _tok) {
 +    const char *id_hexdigest;
 +    const char *sk_hexdigest;
 +    const char *algname;
 +    const char *flavor;
 +    digest_algorithm_t alg;
 +
 +    char id_digest[DIGEST_LEN];
 +    char sk_digest[DIGEST_LEN];
 +    smartlist_t *siglist;
 +    document_signature_t *sig;
 +    int is_duplicate;
  
 -      tok = _tok;
 -      if (tok->tp != K_DIRECTORY_SIGNATURE)
 -        continue;
 +    tok = _tok;
 +    if (tok->tp == K_DIRECTORY_SIGNATURE) {
        tor_assert(tok->n_args >= 2);
 +      flavor = "ns";
 +      algname = "sha1";
 +      id_hexdigest = tok->args[0];
 +      sk_hexdigest = tok->args[1];
 +    } else if (tok->tp == K_ADDITIONAL_SIGNATURE) {
 +      tor_assert(tok->n_args >= 4);
 +      flavor = tok->args[0];
 +      algname = tok->args[1];
 +      id_hexdigest = tok->args[2];
 +      sk_hexdigest = tok->args[3];
 +    } else {
 +      continue;
 +    }
  
 -      if (!tok->object_type ||
 -          strcmp(tok->object_type, "SIGNATURE") ||
 -          tok->object_size < 128 || tok->object_size > 512) {
 -        log_warn(LD_DIR, "Bad object type or length on directory-signature");
 -        goto err;
 +    {
 +      int a = crypto_digest_algorithm_parse_name(algname);
 +      if (a<0) {
 +        log_warn(LD_DIR, "Unrecognized algorithm name %s", algname);
 +        continue;
        }
 +      alg = (digest_algorithm_t) a;
 +    }
  
 -      if (strlen(tok->args[0]) != HEX_DIGEST_LEN ||
 -          base16_decode(id_digest, sizeof(id_digest),
 -                        tok->args[0], HEX_DIGEST_LEN) < 0) {
 -        log_warn(LD_DIR, "Error decoding declared identity %s in "
 -                 "network-status vote.", escaped(tok->args[0]));
 -        goto err;
 -      }
 -      if (strlen(tok->args[1]) != HEX_DIGEST_LEN ||
 -          base16_decode(sk_digest, sizeof(sk_digest),
 -                        tok->args[1], HEX_DIGEST_LEN) < 0) {
 -        log_warn(LD_DIR, "Error decoding declared digest %s in "
 -                 "network-status vote.", escaped(tok->args[1]));
 -        goto err;
 -      }
 +    if (!tok->object_type ||
 +        strcmp(tok->object_type, "SIGNATURE") ||
 +        tok->object_size < 128 || tok->object_size > 512) {
 +      log_warn(LD_DIR, "Bad object type or length on directory-signature");
 +      goto err;
 +    }
  
 -      voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
 -      memcpy(voter->identity_digest, id_digest, DIGEST_LEN);
 -      memcpy(voter->signing_key_digest, sk_digest, DIGEST_LEN);
 -      if (tok->object_size >= INT_MAX)
 -        goto err;
 -      voter->signature = tor_memdup(tok->object_body, tok->object_size);
 -      voter->signature_len = (int) tok->object_size;
 +    if (strlen(id_hexdigest) != HEX_DIGEST_LEN ||
 +        base16_decode(id_digest, sizeof(id_digest),
 +                      id_hexdigest, HEX_DIGEST_LEN) < 0) {
 +      log_warn(LD_DIR, "Error decoding declared identity %s in "
 +               "network-status vote.", escaped(id_hexdigest));
 +      goto err;
 +    }
 +    if (strlen(sk_hexdigest) != HEX_DIGEST_LEN ||
 +        base16_decode(sk_digest, sizeof(sk_digest),
 +                      sk_hexdigest, HEX_DIGEST_LEN) < 0) {
 +      log_warn(LD_DIR, "Error decoding declared signing key digest %s in "
 +               "network-status vote.", escaped(sk_hexdigest));
 +      goto err;
 +    }
  
 -      smartlist_add(sigs->signatures, voter);
 +    siglist = detached_get_signatures(sigs, flavor);
 +    is_duplicate = 0;
 +    SMARTLIST_FOREACH(siglist, document_signature_t *, s, {
 +      if (s->alg == alg &&
 +          !memcmp(id_digest, s->identity_digest, DIGEST_LEN) &&
 +          !memcmp(sk_digest, s->signing_key_digest, DIGEST_LEN)) {
 +        is_duplicate = 1;
 +      }
      });
 +    if (is_duplicate) {
 +      log_warn(LD_DIR, "Two signatures with identical keys and algorithm "
 +               "found.");
 +      continue;
 +    }
 +
 +    sig = tor_malloc_zero(sizeof(document_signature_t));
 +    sig->alg = alg;
 +    memcpy(sig->identity_digest, id_digest, DIGEST_LEN);
 +    memcpy(sig->signing_key_digest, sk_digest, DIGEST_LEN);
 +    if (tok->object_size >= INT_MAX) {
 +      tor_free(sig);
 +      goto err;
 +    }
 +    sig->signature = tor_memdup(tok->object_body, tok->object_size);
 +    sig->signature_len = (int) tok->object_size;
 +
 +    smartlist_add(siglist, sig);
 +  } SMARTLIST_FOREACH_END(_tok);
  
    goto done;
   err:
    ns_detached_signatures_free(sigs);
    sigs = NULL;
   done:
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area) {
      DUMP_AREA(area, "detached signatures");
@@@ -3563,7 -2770,7 +3563,7 @@@ router_parse_addr_policy_item_from_stri
   err:
    r = NULL;
   done:
 -  token_free(tok);
 +  token_clear(tok);
    if (area) {
      DUMP_AREA(area, "policy item");
      memarea_drop_all(area);
@@@ -3686,8 -2893,9 +3686,8 @@@ assert_addr_policy_ok(smartlist_t *lst
  
  /** Free all resources allocated for <b>tok</b> */
  static void
 -token_free(directory_token_t *tok)
 +token_clear(directory_token_t *tok)
  {
 -  tor_assert(tok);
    if (tok->key)
      crypto_free_pk_env(tok->key);
  }
@@@ -3699,7 -2907,7 +3699,7 @@@
  
  #define RET_ERR(msg)                                               \
    STMT_BEGIN                                                       \
 -    if (tok) token_free(tok);                                      \
 +    if (tok) token_clear(tok);                                      \
      tok = ALLOC_ZERO(sizeof(directory_token_t));                   \
      tok->tp = _ERR;                                                \
      tok->error = STRDUP(msg);                                      \
@@@ -3980,7 -3188,7 +3980,7 @@@ tokenize_string(memarea_t *area
      tok = get_next_token(area, s, end, table);
      if (tok->tp == _ERR) {
        log_warn(LD_DIR, "parse error: %s", tok->error);
 -      token_free(tok);
 +      token_clear(tok);
        return -1;
      }
      ++counts[tok->tp];
@@@ -4094,11 -3302,17 +4094,11 @@@ find_all_exitpolicy(smartlist_t *s
    return out;
  }
  
 -/** Compute the SHA-1 digest of the substring of <b>s</b> taken from the first
 - * occurrence of <b>start_str</b> through the first instance of c after the
 - * first subsequent occurrence of <b>end_str</b>; store the 20-byte result in
 - * <b>digest</b>; return 0 on success.
 - *
 - * If no such substring exists, return -1.
 - */
  static int
 -router_get_hash_impl(const char *s, size_t s_len, char *digest,
 +router_get_hash_impl_helper(const char *s, size_t s_len,
                              const char *start_str,
 -                            const char *end_str, char end_c)
 +                            const char *end_str, char end_c,
 +                            const char **start_out, const char **end_out)
  {
    const char *start, *end;
    start = tor_memstr(s, s_len, start_str);
@@@ -4125,214 -3339,14 +4125,214 @@@
    }
    ++end;
  
 -  if (crypto_digest(digest, start, end-start)) {
 -    log_warn(LD_BUG,"couldn't compute digest");
 +  *start_out = start;
 +  *end_out = end;
 +  return 0;
 +}
 +
 +/** Compute the digest of the substring of <b>s</b> taken from the first
 + * occurrence of <b>start_str</b> through the first instance of c after the
 + * first subsequent occurrence of <b>end_str</b>; store the 20-byte result in
 + * <b>digest</b>; return 0 on success.
 + *
 + * If no such substring exists, return -1.
 + */
 +static int
 +router_get_hash_impl(const char *s, size_t s_len, char *digest,
 +                     const char *start_str,
 +                     const char *end_str, char end_c,
 +                     digest_algorithm_t alg)
 +{
 +  const char *start=NULL, *end=NULL;
 +  if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,
 +                                  &start,&end)<0)
      return -1;
 +
 +  if (alg == DIGEST_SHA1) {
 +    if (crypto_digest(digest, start, end-start)) {
 +      log_warn(LD_BUG,"couldn't compute digest");
 +      return -1;
 +    }
 +  } else {
 +    if (crypto_digest256(digest, start, end-start, alg)) {
 +      log_warn(LD_BUG,"couldn't compute digest");
 +      return -1;
 +    }
    }
  
    return 0;
  }
  
 +/** As router_get_hash_impl, but compute all hashes. */
 +static int
 +router_get_hashes_impl(const char *s, size_t s_len, digests_t *digests,
 +                       const char *start_str,
 +                       const char *end_str, char end_c)
 +{
 +  const char *start=NULL, *end=NULL;
 +  if (router_get_hash_impl_helper(s,s_len,start_str,end_str,end_c,
 +                                  &start,&end)<0)
 +    return -1;
 +
 +  if (crypto_digest_all(digests, start, end-start)) {
 +    log_warn(LD_BUG,"couldn't compute digests");
 +    return -1;
 +  }
 +
 +  return 0;
 +}
 +
 +/** Assuming that s starts with a microdesc, return the start of the
 + * *NEXT* one.  Return NULL on "not found." */
 +static const char *
 +find_start_of_next_microdesc(const char *s, const char *eos)
 +{
 +  int started_with_annotations;
 +  s = eat_whitespace_eos(s, eos);
 +  if (!s)
 +    return NULL;
 +
 +#define CHECK_LENGTH() STMT_BEGIN \
 +    if (s+32 > eos)               \
 +      return NULL;                \
 +  STMT_END
 +
 +#define NEXT_LINE() STMT_BEGIN            \
 +    s = memchr(s, '\n', eos-s);           \
 +    if (!s || s+1 >= eos)                 \
 +      return NULL;                        \
 +    s++;                                  \
 +  STMT_END
 +
 +  CHECK_LENGTH();
 +
 +  started_with_annotations = (*s == '@');
 +
 +  if (started_with_annotations) {
 +    /* Start by advancing to the first non-annotation line. */
 +    while (*s == '@')
 +      NEXT_LINE();
 +  }
 +  CHECK_LENGTH();
 +
 +  /* Now we should be pointed at an onion-key line.  If we are, then skip
 +   * it. */
 +  if (!strcmpstart(s, "onion-key"))
 +    NEXT_LINE();
 +
 +  /* Okay, now we're pointed at the first line of the microdescriptor which is
 +     not an annotation or onion-key.  The next line that _is_ an annotation or
 +     onion-key is the start of the next microdescriptor. */
 +  while (s+32 < eos) {
 +    if (*s == '@' || !strcmpstart(s, "onion-key"))
 +      return s;
 +    NEXT_LINE();
 +  }
 +  return NULL;
 +
 +#undef CHECK_LENGTH
 +#undef NEXT_LINE
 +}
 +
 +/** Parse as many microdescriptors as are found from the string starting at
 + * <b>s</b> and ending at <b>eos</b>.  If allow_annotations is set, read any
 + * annotations we recognize and ignore ones we don't.  If <b>copy_body</b> is
 + * true, then strdup the bodies of the microdescriptors.  Return all newly
 + * parsed microdescriptors in a newly allocated smartlist_t. */
 +smartlist_t *
 +microdescs_parse_from_string(const char *s, const char *eos,
 +                             int allow_annotations, int copy_body)
 +{
 +  smartlist_t *tokens;
 +  smartlist_t *result;
 +  microdesc_t *md = NULL;
 +  memarea_t *area;
 +  const char *start = s;
 +  const char *start_of_next_microdesc;
 +  int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0;
 +
 +  directory_token_t *tok;
 +
 +  if (!eos)
 +    eos = s + strlen(s);
 +
 +  s = eat_whitespace_eos(s, eos);
 +  area = memarea_new();
 +  result = smartlist_create();
 +  tokens = smartlist_create();
 +
 +  while (s < eos) {
 +    start_of_next_microdesc = find_start_of_next_microdesc(s, eos);
 +    if (!start_of_next_microdesc)
 +      start_of_next_microdesc = eos;
 +
 +    if (tokenize_string(area, s, start_of_next_microdesc, tokens,
 +                        microdesc_token_table, flags)) {
 +      log_warn(LD_DIR, "Unparseable microdescriptor");
 +      goto next;
 +    }
 +
 +    md = tor_malloc_zero(sizeof(microdesc_t));
 +    {
 +      const char *cp = tor_memstr(s, start_of_next_microdesc-s,
 +                                  "onion-key");
 +      tor_assert(cp);
 +
 +      md->bodylen = start_of_next_microdesc - cp;
 +      if (copy_body)
 +        md->body = tor_strndup(cp, md->bodylen);
 +      else
 +        md->body = (char*)cp;
 +      md->off = cp - start;
 +    }
 +
 +    if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) {
 +      if (parse_iso_time(tok->args[0], &md->last_listed)) {
 +        log_warn(LD_DIR, "Bad last-listed time in microdescriptor");
 +        goto next;
 +      }
 +    }
 +
 +    tok = find_by_keyword(tokens, K_ONION_KEY);
 +    md->onion_pkey = tok->key;
 +    tok->key = NULL;
 +
 +    if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) {
 +      int i;
 +      md->family = smartlist_create();
 +      for (i=0;i<tok->n_args;++i) {
 +        if (!is_legal_nickname_or_hexdigest(tok->args[i])) {
 +          log_warn(LD_DIR, "Illegal nickname %s in family line",
 +                   escaped(tok->args[i]));
 +          goto next;
 +        }
 +        smartlist_add(md->family, tor_strdup(tok->args[i]));
 +      }
 +    }
 +
 +    if ((tok = find_opt_by_keyword(tokens, K_P))) {
 +      md->exitsummary = tor_strdup(tok->args[0]);
 +    }
 +
 +    crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256);
 +
 +    smartlist_add(result, md);
 +
 +    md = NULL;
 +  next:
 +    microdesc_free(md);
 +
 +    memarea_clear(area);
 +    smartlist_clear(tokens);
 +    s = start_of_next_microdesc;
 +  }
 +
 +  memarea_drop_all(area);
 +  smartlist_free(tokens);
 +
 +  return result;
 +}
 +
  /** Parse the Tor version of the platform string <b>platform</b>,
   * and compare it to the version in <b>cutoff</b>. Return 1 if
   * the router is at least as new as the cutoff, else return 0.
@@@ -4357,7 -3371,7 +4357,7 @@@ tor_version_as_new_as(const char *platf
    if (!*start) return 0;
    s = (char *)find_whitespace(start); /* also finds '\0', which is fine */
    s2 = (char*)eat_whitespace(s);
 -  if (!strcmpstart(s2, "(r"))
 +  if (!strcmpstart(s2, "(r") || !strcmpstart(s2, "(git-"))
      s = (char*)find_whitespace(s2);
  
    if ((size_t)(s-start+1) >= sizeof(tmp)) /* too big, no */
@@@ -4453,23 -3467,6 +4453,23 @@@ tor_version_parse(const char *s, tor_ve
    if (!strcmpstart(cp, "(r")) {
      cp += 2;
      out->svn_revision = (int) strtol(cp,&eos,10);
 +  } else if (!strcmpstart(cp, "(git-")) {
 +    char *close_paren = strchr(cp, ')');
 +    int hexlen;
 +    char digest[DIGEST_LEN];
 +    if (! close_paren)
 +      return -1;
 +    cp += 5;
 +    if (close_paren-cp > HEX_DIGEST_LEN)
 +      return -1;
 +    hexlen = (int)(close_paren-cp);
 +    memset(digest, 0, sizeof(digest));
 +    if ( hexlen == 0 || (hexlen % 2) == 1)
 +      return -1;
 +    if (base16_decode(digest, hexlen/2, cp, hexlen))
 +      return -1;
 +    memcpy(out->git_tag, digest, hexlen/2);
 +    out->git_tag_len = hexlen/2;
    }
  
    return 0;
@@@ -4495,14 -3492,8 +4495,14 @@@ tor_version_compare(tor_version_t *a, t
      return i;
    else if ((i = strcmp(a->status_tag, b->status_tag)))
      return i;
 +  else if ((i = a->svn_revision - b->svn_revision))
 +    return i;
 +  else if ((i = a->git_tag_len - b->git_tag_len))
 +    return i;
 +  else if (a->git_tag_len)
 +    return memcmp(a->git_tag, b->git_tag, a->git_tag_len);
    else
 -    return a->svn_revision - b->svn_revision;
 +    return 0;
  }
  
  /** Return true iff versions <b>a</b> and <b>b</b> belong to the same series.
@@@ -4591,7 -3582,7 +4591,7 @@@ rend_parse_v2_service_descriptor(rend_s
    /* Compute descriptor hash for later validation. */
    if (router_get_hash_impl(desc, strlen(desc), desc_hash,
                             "rendezvous-service-descriptor ",
 -                           "\nsignature", '\n') < 0) {
 +                           "\nsignature", '\n', DIGEST_SHA1) < 0) {
      log_warn(LD_REND, "Couldn't compute descriptor hash.");
      goto err;
    }
@@@ -4710,7 -3701,7 +4710,7 @@@
    /* Parse and verify signature. */
    tok = find_by_keyword(tokens, R_SIGNATURE);
    note_crypto_pk_op(VERIFY_RTR);
 -  if (check_signature_token(desc_hash, tok, result->pk, 0,
 +  if (check_signature_token(desc_hash, DIGEST_LEN, tok, result->pk, 0,
                              "v2 rendezvous service descriptor") < 0)
      goto err;
    /* Verify that descriptor ID belongs to public key and secret ID part. */
@@@ -4724,11 -3715,12 +4724,11 @@@
    }
    goto done;
   err:
 -  if (result)
 -    rend_service_descriptor_free(result);
 +  rend_service_descriptor_free(result);
    result = NULL;
   done:
    if (tokens) {
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_free(tokens);
    }
    if (area)
@@@ -4886,7 -3878,7 +4886,7 @@@ rend_parse_introduction_points(rend_ser
        eos = eos+1;
      tor_assert(eos <= intro_points_encoded+intro_points_encoded_size);
      /* Free tokens and clear token list. */
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_clear(tokens);
      memarea_clear(area);
      /* Tokenize string. */
@@@ -4959,7 -3951,7 +4959,7 @@@
  
   done:
    /* Free tokens and clear token list. */
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area)
      memarea_drop_all(area);
@@@ -4998,7 -3990,7 +4998,7 @@@ rend_parse_client_keys(strmap_t *parsed
      else
        eos = eos + 1;
      /* Free tokens and clear token list. */
 -    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +    SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
      smartlist_clear(tokens);
      memarea_clear(area);
      /* Tokenize string. */
@@@ -5070,7 -4062,7 +5070,7 @@@
    result = -1;
   done:
    /* Free tokens and clear token list. */
 -  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t));
 +  SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
    smartlist_free(tokens);
    if (area)
      memarea_drop_all(area);
diff --combined src/or/tor_main.c
index 117369c,8fd3804..1ce14ab
--- a/src/or/tor_main.c
+++ b/src/or/tor_main.c
@@@ -1,13 -1,13 +1,13 @@@
  /* Copyright 2001-2004 Roger Dingledine.
   * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
+  * Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  /** String describing which Tor subversion repository version the source was
   * built from.  This string is generated by a bit of shell kludging int
   * src/or/Makefile.am, and is usually right.
   */
 -const char tor_svn_revision[] =
 +const char tor_git_revision[] =
  #ifndef _MSC_VER
  #include "micro-revision.i"
  #endif
diff --combined src/test/test_data.c
index f926ee1,0000000..fc85857
mode 100644,000000..100644
--- a/src/test/test_data.c
+++ b/src/test/test_data.c
@@@ -1,173 -1,0 +1,173 @@@
 +/* Copyright 2001-2004 Roger Dingledine.
 + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
-  * Copyright (c) 2007-2010, The Tor Project, Inc. */
++ * Copyright (c) 2007-2011, The Tor Project, Inc. */
 +/* See LICENSE for licensing information */
 +
 +/** First of 3 example authority certificates for unit testing. */
 +const char AUTHORITY_CERT_1[] =
 +"dir-key-certificate-version 3\n"
 +"fingerprint D867ACF56A9D229B35C25F0090BC9867E906BE69\n"
 +"dir-key-published 2008-12-12 18:07:24\n"
 +"dir-key-expires 2009-12-12 18:07:24\n"
 +"dir-identity-key\n"
 +"-----BEGIN RSA PUBLIC KEY-----\n"
 +"MIIBigKCAYEAveMpKlw8oD1YqFqpJchuwSR82BDhutbqgHiez3QO9FmzOctJpV+Y\n"
 +"mpTYIJLS/qC+4GBKFF1VK0C4SoBrS3zri0qdXdE+vBGcyrxrjMklpxoqSKRY2011\n"
 +"4eqYPghKlo5RzuqteBclGCHyNxWjUJeRKDWgvh+U/gr2uYM6fRm5q0fCzg4aECE7\n"
 +"VP6fDGZrMbQI8jHpiMSoC9gkUASNEa6chLInlnP8/H5qUEW4TB9CN/q095pefuwL\n"
 +"P+F+1Nz5hnM7fa5XmeMB8iM4RriUmOQlLBZgpQBMpEfWMIPcR9F1Gh3MxERqqUcH\n"
 +"tmij+IZdeXg9OkCXykcabaYIhZD3meErn9Tax4oA/THduLfgli9zM0ExwzH1OooN\n"
 +"L8rIcJ+2eBo3bQiQUbdYW71sl9w7nSPtircbJUa1mUvWYLPWQxFliPiQSetgJLMj\n"
 +"VQqtPmV2hvN2Xk3lLfJO50qMTK7w7Gsaw8UtV4YDM1Hcjp/hQaIB1xfwhXgl+eUU\n"
 +"btUa4c+cUTjHAgMBAAE=\n"
 +"-----END RSA PUBLIC KEY-----\n"
 +"dir-signing-key\n"
 +"-----BEGIN RSA PUBLIC KEY-----\n"
 +"MIGJAoGBALPSUInyuEu6NV3NjozplaniIEBzQXEjv1x9/+mqnwZABpYVmuy9A8nx\n"
 +"eoyY3sZFsnYwNW/IZjAgG23pEmevu3F+L4myMjjaa6ORl3MgRYQ4gmuFqpefrGdm\n"
 +"ywRCleh2JerkQ4VxOuq10dn/abITzLyaZzMw30KXWp5pxKXOLtxFAgMBAAE=\n"
 +"-----END RSA PUBLIC KEY-----\n"
 +"dir-key-crosscert\n"
 +"-----BEGIN ID SIGNATURE-----\n"
 +"FTBJNR/Hlt4T53yUMp1r/QCSMCpkHJCbYBT0R0pvYqhqFfYN5qHRSICRXaFFImIF\n"
 +"0DGWmwRza6DxPKNzkm5/b7I0de9zJW1jNNdQAQK5xppAtQcAafRdu8cBonnmh9KX\n"
 +"k1NrAK/X00FYywju3yl/SxCn1GddVNkHYexEudmJMPM=\n"
 +"-----END ID SIGNATURE-----\n"
 +"dir-key-certification\n"
 +"-----BEGIN SIGNATURE-----\n"
 +"pjWguLFBfELZDc6DywL6Do21SCl7LcutfpM92MEn4WYeSNcTXNR6lRX7reOEJk4e\n"
 +"NwEaMt+Hl7slgeR5wjnW3OmMmRPZK9bquNWbfD+sAOV9bRFZTpXIdleAQFPlwvMF\n"
 +"z/Gzwspzn4i2Yh6hySShrctMmW8YL3OM8LsBXzBhp/rG2uHlsxmIsc13DA6HWt61\n"
 +"ffY72uNE6KckDGsQ4wPGP9q69y6g+X+TNio1KPbsILbePv6EjbO+rS8FiS4njPlg\n"
 +"SPYry1RaUvxzxTkswIzdE1tjJrUiqpbWlTGxrH9N4OszoLm45Pc784KLULrjKIoi\n"
 +"Q+vRsGrcMBAa+kDowWU6H1ryKR7KOhzRTcf2uqLE/W3ezaRwmOG+ETmoVFwbhk2X\n"
 +"OlbXEM9fWP+INvFkr6Z93VYL2jGkCjV7e3xXmre/Lb92fUcYi6t5dwzfV8gJnIoG\n"
 +"eCHd0K8NrQK0ipVk/7zcPDKOPeo9Y5aj/f6X/pDHtb+Dd5sT+l82G/Tqy4DIYUYR\n"
 +"-----END SIGNATURE-----\n";
 +
 +/** The private signing key for AUTHORITY_CERT_1 */
 +const char AUTHORITY_SIGNKEY_1[] =
 +"-----BEGIN RSA PRIVATE KEY-----\n"
 +"MIICWwIBAAKBgQCz0lCJ8rhLujVdzY6M6ZWp4iBAc0FxI79cff/pqp8GQAaWFZrs\n"
 +"vQPJ8XqMmN7GRbJ2MDVvyGYwIBtt6RJnr7txfi+JsjI42mujkZdzIEWEOIJrhaqX\n"
 +"n6xnZssEQpXodiXq5EOFcTrqtdHZ/2myE8y8mmczMN9Cl1qeacSlzi7cRQIDAQAB\n"
 +"AoGASpzUkDinIbzU0eQt5ugxEnliOnvYRpK3nzAk1JbYPyan1PSIAPz4qn1JBTeV\n"
 +"EB3xS7r7ITO8uvFHkFZqLZ2sH1uE6e4sAytJGO+kyqnlkiDTPEXpcGe99j8PH1yj\n"
 +"xUOrHRlAYWjG8NEkQi+APA+HZkswE3L/viFwR2AARoE2ac0CQQDsOLdNJa+mqn6N\n"
 +"1L76nEl/YgXHtKUks+beOR3IgknKEjcsJJEUHyiu0wjbXZV6gTtyQvcAePglUUD1\n"
 +"R2OkOOADAkEAwuCxvHEAPeQbVt8fSvxw74vqew6LITP2Utb1dQK0E26IRPF36BsJ\n"
 +"buO/gqMZv6ALq+/KxpA/pUsApbgog9uUFwJAYvHCvbrKX1pM1iXFtP1fv86UMzlU\n"
 +"bxI34t8zvXftZonIuGG8rxv6E3hr3k7NvNmCx/KKuZTyA9eMCPFVKEV2dwJACn8j\n"
 +"06yagLrqphE6lEVop953cM1lvRIZcHjXm8fbfzhy6pO/C6d5KJnn1NeIKYQrXMV7\n"
 +"vJpEc1jI3iQ/Omr3XQJAEBIt5MlP2wlrX9om7B+32XBygUssY3cw/bXybZrtSU0/\n"
 +"Yx4lqK0ca5IkTp3HevwnlWaJgbaOTUspCVshzJBhDA==\n"
 +"-----END RSA PRIVATE KEY-----\n";
 +
 +/** Second of 3 example authority certificates for unit testing. */
 +const char AUTHORITY_CERT_2[] =
 +"dir-key-certificate-version 3\n"
 +"fingerprint 4D44AE0470B9E88FD4558EFEC82698FB33715400\n"
 +"dir-key-published 2007-06-13 16:52:32\n"
 +"dir-key-expires 2008-06-13 16:52:32\n"
 +"dir-identity-key\n"
 +"-----BEGIN RSA PUBLIC KEY-----\n"
 +"MIIBigKCAYEAqukDwQRm1Oy1pPY+7GNRnRNFJzEVPUBfJwC4tBH19tkvdRQPuIGI\n"
 +"2jiTy/rmZ6CLcl1G0oulSgxfKEX75QdptOasZu+rKUrRRSxx0QrXhs9a7up0rpXh\n"
 +"13fw3mh1Vl/As3rJYF30Hjk01BTOJMxi/HY2y0ALQytFWjiMGY74A9Y6+uDcHkB2\n"
 +"KflBjxIl8zpCsXsTTnUhN5kXqaOOnK46XaUShSpXsyOxTMJXuJEtgLz9XCyA8XjW\n"
 +"d75QLHucEnlTqxUAdI5YSN2KIlIJiySCVnAorDpJey2mE9VncpHQWMCv/FPFdnSU\n"
 +"EMMPUc4bBShcoNFf0mMJeV2sv+dBkgKAL0GLM19PuJIThJhfN/B6+YQTxw4HEpPV\n"
 +"plfUqYRN0fYC+5hCTS6rroO/uCfDR7NBtoeDNm9dQrvjfk3b/Mywah1rdWNjnVqs\n"
 +"tPJaz3fc/CVBOUUexhmyktgLuwSNEYIQilQ+BydkWN/4RObhV+YSV5BgekEDVaoS\n"
 +"RHw4IbYBDHVxAgMBAAE=\n"
 +"-----END RSA PUBLIC KEY-----\n"
 +"dir-signing-key\n"
 +"-----BEGIN RSA PUBLIC KEY-----\n"
 +"MIGJAoGBAOu3dgrQth3iqvi/UzfywaANw0bBUuMOBhnMBeiLEcRLneJHUJkVvrpR\n"
 +"/EDQkdMov1e7CX6aqBKygVnbDNYjJ+bcQej8MKpuuW+zIknnz5lfnAVZO5uAmo3Y\n"
 +"DpG574oQ2FFMdkWHSBloIRxSj/E4Jn1M2qJjElBXP0E33Ka/Noo7AgMBAAE=\n"
 +"-----END RSA PUBLIC KEY-----\n"
 +"dir-key-certification\n"
 +"-----BEGIN SIGNATURE-----\n"
 +"Fv0Li68QUdAiChY3OklZOakHzwXAUfCzDNxkqe+HLC0n6ZECE9ZCvLVo69XmgVhH\n"
 +"L5qYr2rxT6QpF+9yuOHbN9gWn8EsDcli06MlhX9TUt/IYVxHa/9tJwNoTfEw2w2D\n"
 +"tyHhWm94IfOK7/Sea6jHnjckl80X+kk0ZNtAGs3/6fP4iltKNGXnvBwfgLpEgW7X\n"
 +"NpDl0OLeDuA79zem2GogwQZQdoDbePByU0TJVx9jYi2Bzx2Nb2H0hRTPP6+dY0HQ\n"
 +"MHb7yyyTQRad5iAUnExKhhyt22p7X3a6lgkAhq4YrNn/zVPkpnT2dzjsOydTHOW8\n"
 +"2BQs33QlGNe095i47pJBDYsUgmJaXfqB/RG6dFg7jwIsc3/7dZcvcqfxY7wKcD/T\n"
 +"wtogCIKxDvWbZn7f0hqYkT6uQC8Zom8bcnedmyzufOZCyA2SqQ2wvio6lznR4RIB\n"
 +"a8qDHR0tPS9/VkqTPcvUWCZeY3UiDeWPjoK1nea1pz6DHDWglKPx86a0amjjayZQ\n"
 +"-----END SIGNATURE-----\n";
 +
 +/** The private signing key for AUTHORITY_CERT_2 */
 +const char AUTHORITY_SIGNKEY_2[] =
 +"-----BEGIN RSA PRIVATE KEY-----\n"
 +"MIICXgIBAAKBgQDrt3YK0LYd4qr4v1M38sGgDcNGwVLjDgYZzAXoixHES53iR1CZ\n"
 +"Fb66UfxA0JHTKL9Xuwl+mqgSsoFZ2wzWIyfm3EHo/DCqbrlvsyJJ58+ZX5wFWTub\n"
 +"gJqN2A6Rue+KENhRTHZFh0gZaCEcUo/xOCZ9TNqiYxJQVz9BN9ymvzaKOwIDAQAB\n"
 +"AoGAJ+I9/ex8tCfTSA2PdisEKiHKBeHWNYb870Z/RW6qje1BhLUOZSixwfL3XLwt\n"
 +"wG3nml+SZrKid69uhZaz4FPIf0tqCgURf6dDrF5vuzzr7VLVqkZHYSBp0vE6bu0R\n"
 +"Sgc5QNxI2talgc4bsp0O0C+Zd4n3Yto0pXl/I6NHVAxlFBECQQD2mahkY+QEHWPV\n"
 +"yRY3w3HhRmWBcrkY2zVyvPpqfn/sdHRPYW/yj4Xr/d1CO9VyFmEs4k324lIvu6LT\n"
 +"WDdpPlcJAkEA9LOZv5aNeAm8ckvvXH7iv8KiONiSz0n9wlisxMhNYTEkOCo1g7jG\n"
 +"AX5ZknRC9s4sWCPOBpMhloUvemdQ5FCEIwJBAMqCFwoSCf7jD8hRcUBr7QodoF/0\n"
 +"kVJ7OeI2lMJ9jZnlbFp/3snn2Qeam2e38SnWfQi582KKKwnt4eIDMMXpntkCQQDI\n"
 +"v1Lh11wl3y7nQZ6T7lCNatp08k+2mQgCWYcbRQweMRd6sD4I2xwt+372ZETPfyLo\n"
 +"CC+sOyYx+v+RVpMJS3irAkEA6l98nMteZKmhOgyKSjdolP+ahpZunb+WnCdAtP97\n"
 +"rjZyXmEZS3oe7TRCDD28GAGMmxSDvNfOOpyn14ishEs5AQ==\n"
 +"-----END RSA PRIVATE KEY-----\n";
 +
 +/** Third of 3 example authority certificates for unit testing. */
 +const char AUTHORITY_CERT_3[] =
 +"dir-key-certificate-version 3\n"
 +"fingerprint ED3719BF554DE9D7D59F5CA5A4F5AD121D020ED9\n"
 +"dir-key-published 2007-06-13 16:52:40\n"
 +"dir-key-expires 2008-06-13 16:52:40\n"
 +"dir-identity-key\n"
 +"-----BEGIN RSA PUBLIC KEY-----\n"
 +"MIIBigKCAYEAtB+yw4BNxtZAG4cPaedkhWNmeij7IuNWmXjh58ZYEGurvGyHs1w4\n"
 +"QlwNYI2UftSIeIGdWZ5fJ17h9P3xvO6eeJuOt4KPrNOxUbSGrELEx1Lje1fDAJ1X\n"
 +"SvN+dvptusxtyFUr8afgTPrFIvYuazQ6q/Rw+NDagjmDx3h/A/enihpBnjwzeH8j\n"
 +"Xzu7b+HKnzFnNfveTDdvSy0NSC6tCOnrfXo31XbXRXtlesnMIpbJClUcAv55eyai\n"
 +"/PrVPCCUz8mk0sQnn2Xhv1YJmwOlQTGMfg0a0kWLmh+UWcHsGQ4VWxBZJcuzgFHG\n"
 +"hu2/Fz6DXSpX5Q6B9HKoGmnH1oBh24l0kUW1jL8BxPY4YDU1Lt5t3qgcDn9dXYcI\n"
 +"o8VvyI0ecSc26Q2PYFWX1hpN4VIBZ8uGaW3IpyTdNiRq0g3iMGRFEXcDlWuyMB9E\n"
 +"EbSM7m/79V/z7SjDd75EP8Z0qDPESEVB8a8LbuSJtzFVE0KHd7RzkIEN5sorXspZ\n"
 +"/THukftSmkIvAgMBAAE=\n"
 +"-----END RSA PUBLIC KEY-----\n"
 +"dir-signing-key\n"
 +"-----BEGIN RSA PUBLIC KEY-----\n"
 +"MIGJAoGBANrSZlUq38Boz3iuUOydYTJV57rTbq1bz805FP2QG2Z+2bwpgKIOZag/\n"
 +"gN2A1ySJaIYLgZIg9irxrLkqlY/UAjC23y6V9fJXP1S3TXoqLmHleW8PsaDLuwTo\n"
 +"hCWaR61Mx9WG7IXcodn2Z7RiCfZpSW4Rztbk5WtjQa5jPXSFOuBJAgMBAAE=\n"
 +"-----END RSA PUBLIC KEY-----\n"
 +"dir-key-certification\n"
 +"-----BEGIN SIGNATURE-----\n"
 +"UNXZy+4OQ8iat+gw+vg2ynvKj2BYbqZt+EAZAV3rmw6gux44U9TLRECRd6LsA08N\n"
 +"4+Vz01TU81xqMgfrUy94ei2YvcfpO8art9/muWHTP9SmOX8S1uqDqLWA+n723C9A\n"
 +"HyVXn4aINncO2081gJcIW5+Ul8WTCeZe/n3LVPTCKbTdqxvmrPUdCWlJTQUmb19M\n"
 +"T+kcCjaEfgQGLC+Y2MHqYe/nxz+aBKqpjiWUDdjc35va6r/2e3c0jGi1B1xRZxN1\n"
 +"xThPZ+CifjDoWBxJdDGlIfZRK1lMnOCJY9w9ibTXQ1UnvE4whFvmB55/t9/XLq4q\n"
 +"3pnZz0H7funey3+ilmTxDohoAYT1GX+4a+3xYH07UmAFqlTzqKClj84XEHn+Cer7\n"
 +"Nun9kJlJFuBgUpQjwCkzedFZKKLOHgB2h7trJfnqcBpAM8Rup1Bb5u/RcBx9gy1q\n"
 +"pMc65FviIrc/Q5TUku6NNbCbnGll1599PvWuUzkG42lJ17V6psKHIsqGtVdHlCUc\n"
 +"-----END SIGNATURE-----\n";
 +
 +/** The private signing key for AUTHORITY_CERT_3 */
 +const char AUTHORITY_SIGNKEY_3[] =
 +"-----BEGIN RSA PRIVATE KEY-----\n"
 +"MIICXgIBAAKBgQDa0mZVKt/AaM94rlDsnWEyVee6026tW8/NORT9kBtmftm8KYCi\n"
 +"DmWoP4DdgNckiWiGC4GSIPYq8ay5KpWP1AIwtt8ulfXyVz9Ut016Ki5h5XlvD7Gg\n"
 +"y7sE6IQlmketTMfVhuyF3KHZ9me0Ygn2aUluEc7W5OVrY0GuYz10hTrgSQIDAQAB\n"
 +"AoGBAIyoeG1AnQmildKeQpiGZackf0uhg2BeRwpFKg//5Q0Sd0Wza+M/2+q1v1Ei\n"
 +"86ihxxV7KfPTykk6hmuUSwVkI28Z+5J9NYTr35EzPiUlqpo0iclTkFqrlbqSPULx\n"
 +"9fQhvcOGv1c0m5CnYrHsM8eu3tagLg+6OE4abLOYX4Az5pkxAkEA/NwHhVaVJrXH\n"
 +"lGDrRAfGtaD5Tzeeg1H9DNZi5lmFiSNR0O11sgDLkiZNP5oM8knyqo8Gq08hwxEb\n"
 +"yqMXM3XtJQJBAN2KJbFhOjDIkvJyYvbmcP6P7vV2c9j+oUTKkFMF7vvfWunxMi9j\n"
 +"ghbdUKgl7tU0VFpw7ufDDD0pkN6sua3gp1UCQQCvNzTK861U7p/GtMYyFQVf9JTt\n"
 +"jMf9jYHBNInBvwTme6AFG5bz6tMlif77dJ9GAXHzODrR2Hq3thJA/3RjR3M1AkBg\n"
 +"+6M4ncmtpYC+5lhwob0Bk90WU/6vFflfdhXsYoKWfNb95vsDR9qhS82Nbt25NClh\n"
 +"VmMfzoFDHTkwYgj/F4PpAkEA+RaaSRP7BmbvFNqvlm8J/m0RVdAH4+p/Q5Z5u6Yo\n"
 +"N7xC/gFi0qFPGKsDvD2CncAYmt+KNsd8S0JGDN4eieKn+Q==\n"
 +"-----END RSA PRIVATE KEY-----\n";
 +
diff --combined src/tools/tor-gencert.c
index bc99c24,e6b0996..a04edda
--- a/src/tools/tor-gencert.c
+++ b/src/tools/tor-gencert.c
@@@ -1,4 -1,4 +1,4 @@@
- /* Copyright (c) 2007-2010 The Tor Project, Inc. */
+ /* Copyright (c) 2007-2011, The Tor Project, Inc. */
  /* See LICENSE for licensing information */
  
  #include "orconfig.h"
@@@ -13,7 -13,6 +13,7 @@@
  
  #include <openssl/evp.h>
  #include <openssl/pem.h>
 +#include <openssl/rsa.h>
  #include <openssl/objects.h>
  #include <openssl/obj_mac.h>
  #include <openssl/err.h>
@@@ -28,8 -27,8 +28,8 @@@
  #define CRYPTO_PRIVATE
  
  #include "compat.h"
 -#include "util.h"
 -#include "log.h"
 +#include "../common/util.h"
 +#include "../common/torlog.h"
  #include "crypto.h"
  #include "address.h"
  
@@@ -64,6 -63,7 +64,6 @@@ show_help(void
            "[-c certificate_file]\n"
            "        [-m lifetime_in_months] [-a address:port] "
            "[--passphrase-fd <fd>]\n");
 -
  }
  
  /* XXXX copied from crypto.c */
@@@ -218,20 -218,6 +218,20 @@@ parse_commandline(int argc, char **argv
    return 0;
  }
  
 +static RSA *
 +generate_key(int bits)
 +{
 +  RSA *rsa = NULL;
 +  crypto_pk_env_t *env = crypto_new_pk_env();
 +  if (crypto_pk_generate_key_with_bits(env,bits)<0)
 +    goto done;
 +  rsa = _crypto_pk_env_get_rsa(env);
 +  rsa = RSAPrivateKey_dup(rsa);
 + done:
 +  crypto_free_pk_env(env);
 +  return rsa;
 +}
 +
  /** Try to read the identity key from <b>identity_key_file</b>.  If no such
   * file exists and create_identity_key is set, make a new identity key and
   * store it.  Return 0 on success, nonzero on failure.
@@@ -252,7 -238,7 +252,7 @@@ load_identity_key(void
      }
      log_notice(LD_GENERAL, "Generating %d-bit RSA identity key.",
                 IDENTITY_KEY_BITS);
 -    if (!(key = RSA_generate_key(IDENTITY_KEY_BITS, 65537, NULL, NULL))) {
 +    if (!(key = generate_key(IDENTITY_KEY_BITS))) {
        log_err(LD_GENERAL, "Couldn't generate identity key.");
        crypto_log_errors(LOG_ERR, "Generating identity key");
        return 1;
@@@ -337,7 -323,7 +337,7 @@@ generate_signing_key(void
    RSA *key;
    log_notice(LD_GENERAL, "Generating %d-bit RSA signing key.",
               SIGNING_KEY_BITS);
 -  if (!(key = RSA_generate_key(SIGNING_KEY_BITS, 65537, NULL, NULL))) {
 +  if (!(key = generate_key(SIGNING_KEY_BITS))) {
      log_err(LD_GENERAL, "Couldn't generate signing key.");
      crypto_log_errors(LOG_ERR, "Generating signing key");
      return 1;
@@@ -407,6 -393,7 +407,6 @@@ get_fingerprint(EVP_PKEY *pkey, char *o
    return r;
  }
  
 -
  /** Set <b>out</b> to the hex-encoded fingerprint of <b>pkey</b>. */
  static int
  get_digest(EVP_PKEY *pkey, char *out)
@@@ -500,6 -487,7 +500,6 @@@ generate_certificate(void
    return 0;
  }
  
 -
  /** Entry point to tor-gencert */
  int
  main(int argc, char **argv)
@@@ -508,7 -496,7 +508,7 @@@
    init_logging();
  
    /* Don't bother using acceleration. */
 -  if (crypto_global_init(0)) {
 +  if (crypto_global_init(0, NULL, NULL)) {
      fprintf(stderr, "Couldn't initialize crypto library.\n");
      return 1;
    }
diff --combined src/tools/tor-resolve.c
index f5b0bec,4d9d57a..12349d9
--- a/src/tools/tor-resolve.c
+++ b/src/tools/tor-resolve.c
@@@ -1,14 -1,14 +1,14 @@@
  /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson
-  * Copyright (c) 2007-2010, The Tor Project, Inc.
+  * Copyright (c) 2007-2011, The Tor Project, Inc.
   */
  /* See LICENSE for licensing information */
  
  #include "orconfig.h"
  
  #include "compat.h"
 -#include "util.h"
 +#include "../common/util.h"
  #include "address.h"
 -#include "log.h"
 +#include "../common/torlog.h"
  
  #include <stdio.h>
  #include <stdlib.h>
@@@ -148,7 -148,7 +148,7 @@@ parse_socks4a_resolve_response(const ch
  static const char *
  socks5_reason_to_string(char reason)
  {
 -  switch(reason) {
 +  switch (reason) {
      case SOCKS5_SUCCEEDED:
        return "succeeded";
      case SOCKS5_GENERAL_ERROR:
@@@ -251,7 -251,7 +251,7 @@@ do_resolve(const char *hostname, uint32
      }
      if (parse_socks4a_resolve_response(hostname,
                                         reply_buf, RESPONSE_LEN_4,
 -                                       result_addr)<0){
 +                                       result_addr)<0) {
        return -1;
      }
    } else {





More information about the tor-commits mailing list