[tor-commits] [stem/master] stem.descriptor.networkstatus_descriptor => stem.descriptor.networkstatus

atagar at torproject.org atagar at torproject.org
Sat Oct 13 18:35:44 UTC 2012


commit 4e29ae8dfc69cb68a25c9cf159e56db4e943565b
Author: Ravi Chandra Padmala <neenaoffline at gmail.com>
Date:   Tue Aug 7 12:09:25 2012 +0530

    stem.descriptor.networkstatus_descriptor => stem.descriptor.networkstatus
---
 run_tests.py                                      |    4 +-
 stem/descriptor/__init__.py                       |    2 +-
 stem/descriptor/networkstatus.py                  |  614 +++++++++++++++++++++
 stem/descriptor/networkstatus_descriptor.py       |  614 ---------------------
 test/integ/descriptor/networkstatus.py            |  228 ++++++++
 test/integ/descriptor/networkstatus_descriptor.py |  228 --------
 6 files changed, 845 insertions(+), 845 deletions(-)

diff --git a/run_tests.py b/run_tests.py
index 10aa24c..8d115f1 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -46,7 +46,7 @@ import test.integ.socket.control_socket
 import test.integ.descriptor.reader
 import test.integ.descriptor.server_descriptor
 import test.integ.descriptor.extrainfo_descriptor
-import test.integ.descriptor.networkstatus_descriptor
+import test.integ.descriptor.networkstatus
 import test.integ.response.protocolinfo
 import test.integ.util.conf
 import test.integ.util.proc
@@ -135,7 +135,7 @@ INTEG_TESTS = (
   test.integ.descriptor.reader.TestDescriptorReader,
   test.integ.descriptor.server_descriptor.TestServerDescriptor,
   test.integ.descriptor.extrainfo_descriptor.TestExtraInfoDescriptor,
-  test.integ.descriptor.networkstatus_descriptor.TestNetworkStatusDocument,
+  test.integ.descriptor.networkstatus.TestNetworkStatusDocument,
   test.integ.version.TestVersion,
   test.integ.response.protocolinfo.TestProtocolInfo,
   test.integ.process.TestProcess,
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index 5afe214..903a877 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -17,7 +17,7 @@ __all__ = [
   "reader",
   "extrainfo_descriptor",
   "server_descriptor",
-  "networkstatus_descriptor",
+  "networkstatus",
   "parse_file",
   "Descriptor",
 ]
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
new file mode 100644
index 0000000..8daf6f7
--- /dev/null
+++ b/stem/descriptor/networkstatus.py
@@ -0,0 +1,614 @@
+"""
+Parsing for Tor network status documents. Currently supports parsing v3 network
+status documents (both votes and consensus').
+
+The network status documents also contain a list of router descriptors,
+directory authorities, signatures etc.
+
+The votes and consensus' can be obtained from any of the following sources...
+
+* the 'cached-consensus' file in tor's data directory
+* tor metrics, at https://metrics.torproject.org/data.html
+* directory authorities and mirrors via their DirPort
+
+**Module Overview:**
+
+::
+
+  parse_file - parses a network status file and provides a NetworkStatusDocument
+  NetworkStatusDocument - Tor v3 network status document
+    +- MicrodescriptorConsensus - Tor microdescriptor consensus document
+  RouterDescriptor - Router descriptor; contains information about a Tor relay
+    +- RouterMicrodescriptor - Router microdescriptor; contains information that doesn't change often
+  DirectorySignature
+  DirectoryAuthority
+"""
+
+import re
+import base64
+import hashlib
+import datetime
+
+import stem.prereq
+import stem.descriptor
+import stem.descriptor.extrainfo_descriptor
+import stem.version
+import stem.exit_policy
+import stem.util.log as log
+import stem.util.connection
+import stem.util.tor_tools
+
+_bandwidth_weights_regex = re.compile(" ".join(["W%s=\d+" % weight for weight in ["bd",
+  "be", "bg", "bm", "db", "eb", "ed", "ee", "eg", "em", "gb", "gd", "gg", "gm", "mb", "md", "me", "mg", "mm"]]))
+
+def parse_file(document_file, validate = True):
+  """
+  Iterates over the router descriptors in a network status document.
+  
+  :param file document_file: file with network status document content
+  :param bool validate: checks the validity of the document's contents if True, skips these checks otherwise
+  
+  :returns: iterator for  :class:`stem.descriptor.networkstatus_descriptor.RouterDescriptor` instances in the file
+  
+  :raises:
+    * ValueError if the contents is malformed and validate is True
+    * IOError if the file can't be read
+  """
+  
+  document = NetworkStatusDocument(document_file.read(), validate)
+  return document.router_descriptors
+
+def _strptime(string, validate = True, optional = False):
+  try:
+    return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
+  except ValueError, exc:
+    if validate or not optional: raise exc
+
+class NetworkStatusDocument(stem.descriptor.Descriptor):
+  """
+  A v3 network status document.
+  
+  This could be a v3 consensus or vote document.
+  
+  :var bool validated: **\*** whether the document is validated
+  :var str network_status_version: **\*** a document format version. For v3 documents this is "3"
+  :var str vote_status: **\*** status of the vote (is either "vote" or "consensus")
+  :var list consensus_methods: A list of supported consensus generation methods (integers)
+  :var datetime published: time when the document was published
+  :var int consensus_method: consensus method used to generate a consensus
+  :var datetime valid_after: **\*** time when the consensus becomes valid
+  :var datetime fresh_until: **\*** time until when the consensus is considered to be fresh
+  :var datetime valid_until: **\*** time until when the consensus is valid
+  :var int vote_delay: **\*** number of seconds allowed for collecting votes from all authorities
+  :var int dist_delay: number of seconds allowed for collecting signatures from all authorities
+  :var list client_versions: list of recommended Tor client versions
+  :var list server_versions: list of recommended Tor server versions
+  :var list known_flags: **\*** list of known router flags
+  :var list params: dict of parameter(str) => value(int) mappings
+  :var list router_descriptors: **\*** list of RouterDescriptor objects defined in the document
+  :var list directory_authorities: **\*** list of DirectoryAuthority objects that have generated this document
+  :var dict bandwidth_weights: dict of weight(str) => value(int) mappings
+  :var list directory_signatures: **\*** list of signatures this document has
+  
+  **\*** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined
+  """
+  
+  def __init__(self, raw_content, validate = True):
+    """
+    Parse a v3 network status document and provide a new NetworkStatusDocument object.
+    
+    :param str raw_content: raw network status document data
+    :param bool validate: True if the document is to be validated, False otherwise
+    
+    :raises: ValueError if the document is invalid
+    """
+    
+    super(NetworkStatusDocument, self).__init__(raw_content)
+    
+    self.router_descriptors = []
+    self.directory_authorities = []
+    self.directory_signatures = []
+    self.validated = validate
+    
+    self.network_status_version = None
+    self.vote_status = None
+    self.consensus_methods = []
+    self.published = None
+    self.consensus_method = None
+    self.valid_after = None
+    self.fresh_until = None
+    self.valid_until = None
+    self.vote_delay = None
+    self.dist_delay = None
+    self.client_versions = []
+    self.server_versions = []
+    self.known_flags = []
+    self.params = {}
+    self.bandwidth_weights = {}
+    
+    self._parse(raw_content)
+  
+  def _generate_router(self, raw_content, vote, validate):
+    return RouterDescriptor(raw_content, vote, validate)
+  
+  def _validate_network_status_version(self):
+    return self.network_status_version == "3"
+  
+  def get_unrecognized_lines(self):
+    """
+    Returns any unrecognized trailing lines.
+    
+    :returns: a list of unrecognized trailing lines
+    """
+    
+    return self._unrecognized_lines
+  
+  def _parse(self, raw_content):
+    # preamble
+    validate = self.validated
+    doc_parser = stem.descriptor.DescriptorParser(raw_content, validate)
+    
+    read_keyword_line = lambda keyword, optional = False: setattr(self, keyword.replace("-", "_"), doc_parser.read_keyword_line(keyword, optional))
+    
+    map(read_keyword_line, ["network-status-version", "vote-status"])
+    if validate and not self._validate_network_status_version():
+      raise ValueError("Invalid network-status-version: %s" % self.network_status_version)
+    
+    if self.vote_status == "vote": vote = True
+    elif self.vote_status == "consensus": vote = False
+    elif validate: raise ValueError("Unrecognized document type specified in vote-status")
+    
+    if vote:
+      read_keyword_line("consensus-methods", True)
+      self.consensus_methods = [int(method) for method in self.consensus_methods.split(" ")]
+      self.published = _strptime(doc_parser.read_keyword_line("published", True), validate, True)
+    else:
+      self.consensus_method = int(doc_parser.read_keyword_line("consensus-method", True))
+    
+    map(read_keyword_line, ["valid-after", "fresh-until", "valid-until"])
+    self.valid_after = _strptime(self.valid_after, validate)
+    self.fresh_until = _strptime(self.fresh_until, validate)
+    self.valid_until = _strptime(self.valid_until, validate)
+    voting_delay = doc_parser.read_keyword_line("voting-delay")
+    self.vote_delay, self.dist_delay = [int(delay) for delay in voting_delay.split(" ")]
+    
+    client_versions = doc_parser.read_keyword_line("client-versions", True)
+    if client_versions:
+      self.client_versions = [stem.version.Version(version_string) for version_string in client_versions.split(",")]
+    server_versions = doc_parser.read_keyword_line("server-versions", True)
+    if server_versions:
+      self.server_versions = [stem.version.Version(version_string) for version_string in server_versions.split(",")]
+    self.known_flags = doc_parser.read_keyword_line("known-flags").split(" ")
+    read_keyword_line("params", True)
+    if self.params:
+      self.params = dict([(param.split("=")[0], int(param.split("=")[1])) for param in self.params.split(" ")])
+    
+    # authority section
+    while doc_parser.line.startswith("dir-source "):
+      dirauth_data = doc_parser.read_until(["dir-source", "r"])
+      self.directory_authorities.append(DirectoryAuthority(dirauth_data, vote, validate))
+    
+    def _router_desc_generator(raw_content, vote, validate):
+      parser = stem.descriptor.DescriptorParser(raw_content, validate)
+      while parser.line != None:
+        descriptor = parser.read_until("r")
+        yield self._generate_router(descriptor, vote, validate)
+    
+    # router descriptors
+    if doc_parser.peek_keyword() == "r":
+      router_descriptors_data = doc_parser.read_until(["bandwidth-weights", "directory-footer", "directory-signature"])
+      self.router_descriptors = _router_desc_generator(router_descriptors_data, vote, validate)
+    elif validate:
+      raise ValueError("No router descriptors found")
+    
+    # footer section
+    if self.consensus_method > 9 or vote and filter(lambda x: x >= 9, self.consensus_methods):
+      if doc_parser.line == "directory-footer":
+        doc_parser.read_line()
+      elif validate:
+        raise ValueError("Network status document missing directory-footer")
+    
+    if not vote:
+      read_keyword_line("bandwidth-weights", True)
+      if _bandwidth_weights_regex.match(self.bandwidth_weights):
+        self.bandwidth_weights = dict([(weight.split("=")[0], int(weight.split("=")[1])) for weight in self.bandwidth_weights.split(" ")])
+      elif validate:
+        raise ValueError("Invalid bandwidth-weights line")
+    
+    while doc_parser.line.startswith("directory-signature "):
+      signature_data = doc_parser.read_until(["directory-signature"])
+      self.directory_signatures.append(DirectorySignature(signature_data))
+    
+    self._unrecognized_lines = doc_parser.remaining()
+    if validate and self._unrecognized_lines: raise ValueError("Unrecognized trailing data")
+
+class DirectoryAuthority(stem.descriptor.Descriptor):
+  """
+  Contains directory authority information obtained from v3 network status
+  documents.
+  
+  :var str nickname: directory authority's nickname
+  :var str identity: uppercase hex fingerprint of the authority's identity key
+  :var str address: hostname
+  :var str ip: current IP address
+  :var int dirport: current directory port
+  :var int orport: current orport
+  :var str contact: directory authority's contact information
+  :var str legacy_dir_key: fingerprint of and obsolete identity key
+  :var :class:`stem.descriptor.networkstatus_descriptor.KeyCertificate` key_certificate: directory authority's current key certificate
+  :var str vote_digest: digest of the authority that contributed to the consensus
+  """
+  
+  def __init__(self, raw_content, vote = True, validate = True):
+    """
+    Parse a directory authority entry in a v3 network status document and
+    provide a DirectoryAuthority object.
+    
+    :param str raw_content: raw directory authority entry information
+    :param bool validate: True if the document is to be validated, False otherwise
+    
+    :raises: ValueError if the raw data is invalid
+    """
+    
+    super(DirectoryAuthority, self).__init__(raw_content)
+    self.nickname, self.identity, self.address, self.ip = None, None, None, None
+    self.dirport, self.orport, self.legacy_dir_key = None, None, None
+    self.key_certificate, self.contact, self.vote_digest = None, None, None
+    parser = stem.descriptor.DescriptorParser(raw_content, validate)
+    
+    dir_source = parser.read_keyword_line("dir-source")
+    self.nickname, self.identity, self.address, self.ip, self.dirport, self.orport = dir_source.split(" ")
+    self.dirport = int(self.dirport)
+    self.orport = int(self.orport)
+    
+    self.contact = parser.read_keyword_line("contact")
+    if vote:
+      self.legacy_dir_key = parser.read_keyword_line("legacy-dir-key", True)
+      self.key_certificate = KeyCertificate("\n".join(parser.remaining()), validate)
+    else:
+      self.vote_digest = parser.read_keyword_line("vote-digest", True)
+    rmng = parser.remaining()
+    if rmng and validate:
+      raise ValueError("Unrecognized trailing data in directory authority information")
+
+class KeyCertificate(stem.descriptor.Descriptor):
+  """
+  Directory key certificate.
+  
+  :var str key_certificate_version: **\*** version of the key certificate (Should be "3")
+  :var str ip: IP address on which the directory authority is listening
+  :var int port: port on which the directory authority is listening
+  :var str fingerprint: **\*** hex encoded fingerprint of the authority's identity key
+  :var str identity_key: **\*** long term authority identity key
+  :var datetime published: **\*** time (in GMT) when this document & the key were last generated
+  :var str expires: **\*** time (in GMT) after which this key becomes invalid
+  :var str signing_key: **\*** directory server's public signing key
+  :var str crosscert: signature made using certificate's signing key
+  :var str certification: **\*** signature of this key certificate signed with the identity key
+  
+  **\*** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined
+  """
+  
+  def __init__(self, raw_content, validate = True):
+    """
+    Parse a key certificate entry and provide a KeyCertificate object.
+    
+    :param str raw_content: raw key certificate information
+    :param bool validate: True if the document is to be validated, False otherwise
+    
+    :raises: ValueError if the raw data is invalid
+    """
+    
+    super(KeyCertificate, self).__init__(raw_content)
+    self.key_certificate_version, self.ip, self.port = None, None, None
+    self.fingerprint, self.identity_key, self.published = None, None, None
+    self.expires, self.signing_key, self.crosscert = None, None, None
+    self.certification = None
+    parser = stem.descriptor.DescriptorParser(raw_content, validate)
+    peek_check_kw = lambda keyword: keyword == parser.peek_keyword()
+    seen_keywords = set()
+    
+    self.key_certificate_version = parser.read_keyword_line("dir-key-certificate-version")
+    if validate and self.key_certificate_version != "3": raise ValueError("Unrecognized dir-key-certificate-version")
+    
+    def _read_keyword_line(keyword):
+      if validate and keyword in seen_keywords:
+        raise ValueError("Invalid key certificate: '%s' appears twice" % keyword)
+      seen_keywords.add(keyword)
+      return parser.read_keyword_line(keyword)
+    
+    while parser.line:
+      if peek_check_kw("dir-address"):
+        line = _read_keyword_line("dir-address")
+        try:
+          self.ip, self.port = line.rsplit(":", 1)
+          self.port = int(self.port)
+        except Exception:
+          if validate: raise ValueError("Invalid dir-address line: %s" % line)
+      
+      elif peek_check_kw("fingerprint"):
+        self.fingerprint = _read_keyword_line("fingerprint")
+      
+      elif peek_check_kw("dir-identity-key"):
+        _read_keyword_line("dir-identity-key")
+        self.identity_key = parser.read_block("RSA PUBLIC KEY")
+      
+      elif peek_check_kw("dir-key-published"):
+        self.published = _strptime(_read_keyword_line("dir-key-published"))
+      
+      elif peek_check_kw("dir-key-expires"):
+        self.expires = _strptime(_read_keyword_line("dir-key-expires"))
+      
+      elif peek_check_kw("dir-signing-key"):
+        _read_keyword_line("dir-signing-key")
+        self.signing_key = parser.read_block("RSA PUBLIC KEY")
+      
+      elif peek_check_kw("dir-key-crosscert"):
+        _read_keyword_line("dir-key-crosscert")
+        self.crosscert = parser.read_block("ID SIGNATURE")
+      
+      elif peek_check_kw("dir-key-certification"):
+        _read_keyword_line("dir-key-certification")
+        self.certification = parser.read_block("SIGNATURE")
+        break
+      
+      elif validate:
+        raise ValueError("Key certificate contains unrecognized lines: %s" % parser.line)
+      
+      else:
+        # ignore unrecognized lines if we aren't validating
+        self._unrecognized_lines.append(parser.read_line())
+    
+    self._unrecognized_lines = parser.remaining()
+    if self._unrecognized_lines and validate:
+      raise ValueError("Unrecognized trailing data in key certificate")
+  
+  def get_unrecognized_lines(self):
+    """
+    Returns any unrecognized lines.
+    
+    :returns: a list of unrecognized lines
+    """
+    
+    return self._unrecognized_lines
+
+class DirectorySignature(stem.descriptor.Descriptor):
+  """
+  Contains directory signature information described in a v3 network status
+  document.
+  
+  :var str identity: signature identity
+  :var str key_digest: signature key digest
+  :var str method: method used to generate the signature
+  :var str signature: the signature data
+  """
+  
+  def __init__(self, raw_content, validate = True):
+    """
+    Parse a directory signature entry in a v3 network status document and
+    provide a DirectorySignature object.
+    
+    :param str raw_content: raw directory signature entry information
+    :param bool validate: True if the document is to be validated, False otherwise
+    
+    :raises: ValueError if the raw data is invalid
+    """
+    
+    super(DirectorySignature, self).__init__(raw_content)
+    self.identity, self.key_digest, self.method, self.signature = None, None, None, None
+    parser = stem.descriptor.DescriptorParser(raw_content, validate)
+    
+    signature_line = parser.read_keyword_line("directory-signature").split(" ")
+    
+    if len(signature_line) == 2:
+      self.identity, self.key_digest = signature_line
+    if len(signature_line) == 3: # for microdescriptor consensuses
+      self.method, self.identity, self.key_digest = signature_line
+    
+    self.signature = parser.read_block("SIGNATURE")
+    self._unrecognized_lines = parser.remaining()
+    if self._unrecognized_lines and validate:
+      raise ValueError("Unrecognized trailing data in directory signature")
+
+class RouterDescriptor(stem.descriptor.Descriptor):
+  """
+  Router descriptor object. Parses and stores router information in a router
+  entry read from a v3 network status document.
+  
+  :var str nickname: **\*** router's nickname
+  :var str identity: **\*** router's identity
+  :var str digest: **\*** router's digest
+  :var datetime publication: **\*** router's publication
+  :var str ip: **\*** router's IP address
+  :var int orport: **\*** router's ORPort
+  :var int dirport: **\*** router's DirPort
+  
+  :var bool is_valid: **\*** router is valid
+  :var bool is_guard: **\*** router is suitable for use as an entry guard
+  :var bool is_named: **\*** router is named
+  :var bool is_unnamed: **\*** router is unnamed
+  :var bool is_running: **\*** router is running and currently usable
+  :var bool is_stable: **\*** router is stable, i.e., it's suitable for for long-lived circuits
+  :var bool is_exit: **\*** router is an exit router
+  :var bool is_fast: **\*** router is Fast, i.e., it's usable for high-bandwidth circuits
+  :var bool is_authority: **\*** router is a directory authority
+  :var bool supports_v2dir: **\*** router supports v2dir
+  :var bool supports_v3dir: **\*** router supports v3dir
+  :var bool is_hsdir: **\*** router is a hidden status
+  :var bool is_badexit: **\*** router is marked a bad exit
+  :var bool is_baddirectory: **\*** router is a bad directory
+  
+  :var :class:`stem.version.Version`,str version: Version of the Tor protocol this router is running
+  
+  :var int bandwidth: router's claimed bandwidth
+  :var int measured_bandwidth: router's measured bandwidth
+  
+  :var :class:`stem.exit_policy.MicrodescriptorExitPolicy` exitpolicy: router's exitpolicy
+  
+  :var str microdescriptor_hashes: a list of two-tuples with a list of consensus methods(int) that may produce the digest and a dict with algorithm(str) => digest(str) mappings. algorithm is the hashing algorithm (usually "sha256") that is used to produce digest (the base64 encoding of the hash of the router's microdescriptor with trailing =s omitted).
+  
+  **\*** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined
+  """
+  
+  def __init__(self, raw_contents, vote = True, validate = True):
+    """
+    Parse a router descriptor in a v3 network status document and provide a new
+    RouterDescriptor object.
+    
+    :param str raw_content: router descriptor content to be parsed
+    :param bool validate: whether the router descriptor should be validated
+    """
+    
+    super(RouterDescriptor, self).__init__(raw_contents)
+    
+    self.nickname = None
+    self.identity = None
+    self.digest = None
+    self.publication = None
+    self.ip = None
+    self.orport = None
+    self.dirport = None
+    
+    self.is_valid = False
+    self.is_guard = False
+    self.is_named = False
+    self.is_unnamed = False
+    self.is_running = False
+    self.is_stable = False
+    self.is_exit = False
+    self.is_fast = False
+    self.is_authority = False
+    self.supports_v2dir = False
+    self.supports_v3dir = False
+    self.is_hsdir = False
+    self.is_badexit = False
+    self.is_baddirectory = False
+    
+    self.version = None
+    
+    self.bandwidth = None
+    self.measured_bandwidth = None
+    
+    self.exit_policy = None
+    
+    self.microdescriptor_hashes = []
+    
+    self._parse(raw_contents, vote, validate)
+  
+  def _parse(self, raw_content, vote, validate):
+    """
+    :param dict raw_content: iptor contents to be applied
+    :param bool validate: checks the validity of descriptor content if True
+    
+    :raises: ValueError if an error occures in validation
+    """
+    
+    parser = stem.descriptor.DescriptorParser(raw_content, validate)
+    seen_keywords = set()
+    peek_check_kw = lambda keyword: keyword == parser.peek_keyword()
+    
+    r = parser.read_keyword_line("r")
+    # r mauer BD7xbfsCFku3+tgybEZsg8Yjhvw itcuKQ6PuPLJ7m/Oi928WjO2j8g 2012-06-22 13:19:32 80.101.105.103 9001 0
+    # "r" SP nickname SP identity SP digest SP publication SP IP SP ORPort SP DirPort NL
+    seen_keywords.add("r")
+    if r:
+      values = r.split(" ")
+      self.nickname, self.identity, self.digest = values[0], values[1], values[2]
+      self.publication = _strptime(" ".join((values[3], values[4])), validate)
+      self.ip, self.orport, self.dirport = values[5], int(values[6]), int(values[7])
+      if self.dirport == 0: self.dirport = None
+    elif validate: raise ValueError("Invalid router descriptor: empty 'r' line" )
+    
+    while parser.line:
+      if peek_check_kw("s"):
+        if "s" in seen_keywords: raise ValueError("Invalid router descriptor: 's' line appears twice")
+        line = parser.read_keyword_line("s")
+        if not line: continue
+        seen_keywords.add("s")
+        # s Named Running Stable Valid
+        #A series of space-separated status flags, in *lexical order*
+        flags = line.split(" ")
+        flag_map = {
+          "Valid": "is_valid",
+          "Guard": "is_guard",
+          "Named": "is_named",
+          "Unnamed": "is_unnamed",
+          "Running": "is_running",
+          "Stable": "is_stable",
+          "Exit": "is_exit",
+          "Fast": "is_fast",
+          "Authority": "is_authority",
+          "V2Dir": "supports_v2dir",
+          "V3Dir": "supports_v3dir",
+          "HSDir": "is_hsdir",
+          "BadExit": "is_badexit",
+          "BadDirectory": "is_baddirectory",
+        }
+        map(lambda flag: setattr(self, flag_map[flag], True), flags)
+        
+        if self.is_unnamed: self.is_named = False
+        elif self.is_named: self.is_unnamed = False
+      
+      elif peek_check_kw("v"):
+        if "v" in seen_keywords: raise ValueError("Invalid router descriptor: 'v' line appears twice")
+        line = parser.read_keyword_line("v", True)
+        seen_keywords.add("v")
+        # v Tor 0.2.2.35
+        if line:
+          if line.startswith("Tor "):
+            self.version = stem.version.Version(line[4:])
+          else:
+            self.version = line
+        elif validate: raise ValueError("Invalid router descriptor: empty 'v' line" )
+      
+      elif peek_check_kw("w"):
+        if "w" in seen_keywords: raise ValueError("Invalid router descriptor: 'w' line appears twice")
+        w = parser.read_keyword_line("w", True)
+        # "w" SP "Bandwidth=" INT [SP "Measured=" INT] NL
+        seen_keywords.add("w")
+        if w:
+          values = w.split(" ")
+          if len(values) <= 2 and len(values) > 0:
+            key, value = values[0].split("=")
+            if key == "Bandwidth": self.bandwidth = int(value)
+            elif validate: raise ValueError("Router descriptor contains invalid 'w' line: expected Bandwidth, read " + key)
+            
+            if len(values) == 2:
+              key, value = values[1].split("=")
+              if key == "Measured": self.measured_bandwidth = int(value)
+              elif validate: raise ValueError("Router descriptor contains invalid 'w' line: expected Measured, read " + key)
+          elif validate: raise ValueError("Router descriptor contains invalid 'w' line")
+        elif validate: raise ValueError("Router descriptor contains empty 'w' line")
+      
+      elif peek_check_kw("p"):
+        if "p" in seen_keywords: raise ValueError("Invalid router descriptor: 'p' line appears twice")
+        p = parser.read_keyword_line("p", True)
+        seen_keywords.add("p")
+        # "p" SP ("accept" / "reject") SP PortList NL
+        if p:
+          self.exit_policy = stem.exit_policy.MicrodescriptorExitPolicy(p)
+          #self.exit_policy = p
+      
+      elif vote and peek_check_kw("m"):
+        # microdescriptor hashes
+        m = parser.read_keyword_line("m", True)
+        methods, digests = m.split(" ", 1)
+        method_list = methods.split(",")
+        digest_dict = [digest.split("=", 1) for digest in digests.split(" ")]
+        self.microdescriptor_hashes.append((method_list, digest_dict))
+      
+      elif validate:
+        raise ValueError("Router descriptor contains unrecognized trailing lines: %s" % parser.line)
+      
+      else:
+        self._unrecognized_lines.append(parser.read_line()) # ignore unrecognized lines if we aren't validating
+  
+  def get_unrecognized_lines(self):
+    """
+    Returns any unrecognized lines.
+    
+    :returns: a list of unrecognized lines
+    """
+    
+    return self._unrecognized_lines
+
diff --git a/stem/descriptor/networkstatus_descriptor.py b/stem/descriptor/networkstatus_descriptor.py
deleted file mode 100644
index 8daf6f7..0000000
--- a/stem/descriptor/networkstatus_descriptor.py
+++ /dev/null
@@ -1,614 +0,0 @@
-"""
-Parsing for Tor network status documents. Currently supports parsing v3 network
-status documents (both votes and consensus').
-
-The network status documents also contain a list of router descriptors,
-directory authorities, signatures etc.
-
-The votes and consensus' can be obtained from any of the following sources...
-
-* the 'cached-consensus' file in tor's data directory
-* tor metrics, at https://metrics.torproject.org/data.html
-* directory authorities and mirrors via their DirPort
-
-**Module Overview:**
-
-::
-
-  parse_file - parses a network status file and provides a NetworkStatusDocument
-  NetworkStatusDocument - Tor v3 network status document
-    +- MicrodescriptorConsensus - Tor microdescriptor consensus document
-  RouterDescriptor - Router descriptor; contains information about a Tor relay
-    +- RouterMicrodescriptor - Router microdescriptor; contains information that doesn't change often
-  DirectorySignature
-  DirectoryAuthority
-"""
-
-import re
-import base64
-import hashlib
-import datetime
-
-import stem.prereq
-import stem.descriptor
-import stem.descriptor.extrainfo_descriptor
-import stem.version
-import stem.exit_policy
-import stem.util.log as log
-import stem.util.connection
-import stem.util.tor_tools
-
-_bandwidth_weights_regex = re.compile(" ".join(["W%s=\d+" % weight for weight in ["bd",
-  "be", "bg", "bm", "db", "eb", "ed", "ee", "eg", "em", "gb", "gd", "gg", "gm", "mb", "md", "me", "mg", "mm"]]))
-
-def parse_file(document_file, validate = True):
-  """
-  Iterates over the router descriptors in a network status document.
-  
-  :param file document_file: file with network status document content
-  :param bool validate: checks the validity of the document's contents if True, skips these checks otherwise
-  
-  :returns: iterator for  :class:`stem.descriptor.networkstatus_descriptor.RouterDescriptor` instances in the file
-  
-  :raises:
-    * ValueError if the contents is malformed and validate is True
-    * IOError if the file can't be read
-  """
-  
-  document = NetworkStatusDocument(document_file.read(), validate)
-  return document.router_descriptors
-
-def _strptime(string, validate = True, optional = False):
-  try:
-    return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
-  except ValueError, exc:
-    if validate or not optional: raise exc
-
-class NetworkStatusDocument(stem.descriptor.Descriptor):
-  """
-  A v3 network status document.
-  
-  This could be a v3 consensus or vote document.
-  
-  :var bool validated: **\*** whether the document is validated
-  :var str network_status_version: **\*** a document format version. For v3 documents this is "3"
-  :var str vote_status: **\*** status of the vote (is either "vote" or "consensus")
-  :var list consensus_methods: A list of supported consensus generation methods (integers)
-  :var datetime published: time when the document was published
-  :var int consensus_method: consensus method used to generate a consensus
-  :var datetime valid_after: **\*** time when the consensus becomes valid
-  :var datetime fresh_until: **\*** time until when the consensus is considered to be fresh
-  :var datetime valid_until: **\*** time until when the consensus is valid
-  :var int vote_delay: **\*** number of seconds allowed for collecting votes from all authorities
-  :var int dist_delay: number of seconds allowed for collecting signatures from all authorities
-  :var list client_versions: list of recommended Tor client versions
-  :var list server_versions: list of recommended Tor server versions
-  :var list known_flags: **\*** list of known router flags
-  :var list params: dict of parameter(str) => value(int) mappings
-  :var list router_descriptors: **\*** list of RouterDescriptor objects defined in the document
-  :var list directory_authorities: **\*** list of DirectoryAuthority objects that have generated this document
-  :var dict bandwidth_weights: dict of weight(str) => value(int) mappings
-  :var list directory_signatures: **\*** list of signatures this document has
-  
-  **\*** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined
-  """
-  
-  def __init__(self, raw_content, validate = True):
-    """
-    Parse a v3 network status document and provide a new NetworkStatusDocument object.
-    
-    :param str raw_content: raw network status document data
-    :param bool validate: True if the document is to be validated, False otherwise
-    
-    :raises: ValueError if the document is invalid
-    """
-    
-    super(NetworkStatusDocument, self).__init__(raw_content)
-    
-    self.router_descriptors = []
-    self.directory_authorities = []
-    self.directory_signatures = []
-    self.validated = validate
-    
-    self.network_status_version = None
-    self.vote_status = None
-    self.consensus_methods = []
-    self.published = None
-    self.consensus_method = None
-    self.valid_after = None
-    self.fresh_until = None
-    self.valid_until = None
-    self.vote_delay = None
-    self.dist_delay = None
-    self.client_versions = []
-    self.server_versions = []
-    self.known_flags = []
-    self.params = {}
-    self.bandwidth_weights = {}
-    
-    self._parse(raw_content)
-  
-  def _generate_router(self, raw_content, vote, validate):
-    return RouterDescriptor(raw_content, vote, validate)
-  
-  def _validate_network_status_version(self):
-    return self.network_status_version == "3"
-  
-  def get_unrecognized_lines(self):
-    """
-    Returns any unrecognized trailing lines.
-    
-    :returns: a list of unrecognized trailing lines
-    """
-    
-    return self._unrecognized_lines
-  
-  def _parse(self, raw_content):
-    # preamble
-    validate = self.validated
-    doc_parser = stem.descriptor.DescriptorParser(raw_content, validate)
-    
-    read_keyword_line = lambda keyword, optional = False: setattr(self, keyword.replace("-", "_"), doc_parser.read_keyword_line(keyword, optional))
-    
-    map(read_keyword_line, ["network-status-version", "vote-status"])
-    if validate and not self._validate_network_status_version():
-      raise ValueError("Invalid network-status-version: %s" % self.network_status_version)
-    
-    if self.vote_status == "vote": vote = True
-    elif self.vote_status == "consensus": vote = False
-    elif validate: raise ValueError("Unrecognized document type specified in vote-status")
-    
-    if vote:
-      read_keyword_line("consensus-methods", True)
-      self.consensus_methods = [int(method) for method in self.consensus_methods.split(" ")]
-      self.published = _strptime(doc_parser.read_keyword_line("published", True), validate, True)
-    else:
-      self.consensus_method = int(doc_parser.read_keyword_line("consensus-method", True))
-    
-    map(read_keyword_line, ["valid-after", "fresh-until", "valid-until"])
-    self.valid_after = _strptime(self.valid_after, validate)
-    self.fresh_until = _strptime(self.fresh_until, validate)
-    self.valid_until = _strptime(self.valid_until, validate)
-    voting_delay = doc_parser.read_keyword_line("voting-delay")
-    self.vote_delay, self.dist_delay = [int(delay) for delay in voting_delay.split(" ")]
-    
-    client_versions = doc_parser.read_keyword_line("client-versions", True)
-    if client_versions:
-      self.client_versions = [stem.version.Version(version_string) for version_string in client_versions.split(",")]
-    server_versions = doc_parser.read_keyword_line("server-versions", True)
-    if server_versions:
-      self.server_versions = [stem.version.Version(version_string) for version_string in server_versions.split(",")]
-    self.known_flags = doc_parser.read_keyword_line("known-flags").split(" ")
-    read_keyword_line("params", True)
-    if self.params:
-      self.params = dict([(param.split("=")[0], int(param.split("=")[1])) for param in self.params.split(" ")])
-    
-    # authority section
-    while doc_parser.line.startswith("dir-source "):
-      dirauth_data = doc_parser.read_until(["dir-source", "r"])
-      self.directory_authorities.append(DirectoryAuthority(dirauth_data, vote, validate))
-    
-    def _router_desc_generator(raw_content, vote, validate):
-      parser = stem.descriptor.DescriptorParser(raw_content, validate)
-      while parser.line != None:
-        descriptor = parser.read_until("r")
-        yield self._generate_router(descriptor, vote, validate)
-    
-    # router descriptors
-    if doc_parser.peek_keyword() == "r":
-      router_descriptors_data = doc_parser.read_until(["bandwidth-weights", "directory-footer", "directory-signature"])
-      self.router_descriptors = _router_desc_generator(router_descriptors_data, vote, validate)
-    elif validate:
-      raise ValueError("No router descriptors found")
-    
-    # footer section
-    if self.consensus_method > 9 or vote and filter(lambda x: x >= 9, self.consensus_methods):
-      if doc_parser.line == "directory-footer":
-        doc_parser.read_line()
-      elif validate:
-        raise ValueError("Network status document missing directory-footer")
-    
-    if not vote:
-      read_keyword_line("bandwidth-weights", True)
-      if _bandwidth_weights_regex.match(self.bandwidth_weights):
-        self.bandwidth_weights = dict([(weight.split("=")[0], int(weight.split("=")[1])) for weight in self.bandwidth_weights.split(" ")])
-      elif validate:
-        raise ValueError("Invalid bandwidth-weights line")
-    
-    while doc_parser.line.startswith("directory-signature "):
-      signature_data = doc_parser.read_until(["directory-signature"])
-      self.directory_signatures.append(DirectorySignature(signature_data))
-    
-    self._unrecognized_lines = doc_parser.remaining()
-    if validate and self._unrecognized_lines: raise ValueError("Unrecognized trailing data")
-
-class DirectoryAuthority(stem.descriptor.Descriptor):
-  """
-  Contains directory authority information obtained from v3 network status
-  documents.
-  
-  :var str nickname: directory authority's nickname
-  :var str identity: uppercase hex fingerprint of the authority's identity key
-  :var str address: hostname
-  :var str ip: current IP address
-  :var int dirport: current directory port
-  :var int orport: current orport
-  :var str contact: directory authority's contact information
-  :var str legacy_dir_key: fingerprint of and obsolete identity key
-  :var :class:`stem.descriptor.networkstatus_descriptor.KeyCertificate` key_certificate: directory authority's current key certificate
-  :var str vote_digest: digest of the authority that contributed to the consensus
-  """
-  
-  def __init__(self, raw_content, vote = True, validate = True):
-    """
-    Parse a directory authority entry in a v3 network status document and
-    provide a DirectoryAuthority object.
-    
-    :param str raw_content: raw directory authority entry information
-    :param bool validate: True if the document is to be validated, False otherwise
-    
-    :raises: ValueError if the raw data is invalid
-    """
-    
-    super(DirectoryAuthority, self).__init__(raw_content)
-    self.nickname, self.identity, self.address, self.ip = None, None, None, None
-    self.dirport, self.orport, self.legacy_dir_key = None, None, None
-    self.key_certificate, self.contact, self.vote_digest = None, None, None
-    parser = stem.descriptor.DescriptorParser(raw_content, validate)
-    
-    dir_source = parser.read_keyword_line("dir-source")
-    self.nickname, self.identity, self.address, self.ip, self.dirport, self.orport = dir_source.split(" ")
-    self.dirport = int(self.dirport)
-    self.orport = int(self.orport)
-    
-    self.contact = parser.read_keyword_line("contact")
-    if vote:
-      self.legacy_dir_key = parser.read_keyword_line("legacy-dir-key", True)
-      self.key_certificate = KeyCertificate("\n".join(parser.remaining()), validate)
-    else:
-      self.vote_digest = parser.read_keyword_line("vote-digest", True)
-    rmng = parser.remaining()
-    if rmng and validate:
-      raise ValueError("Unrecognized trailing data in directory authority information")
-
-class KeyCertificate(stem.descriptor.Descriptor):
-  """
-  Directory key certificate.
-  
-  :var str key_certificate_version: **\*** version of the key certificate (Should be "3")
-  :var str ip: IP address on which the directory authority is listening
-  :var int port: port on which the directory authority is listening
-  :var str fingerprint: **\*** hex encoded fingerprint of the authority's identity key
-  :var str identity_key: **\*** long term authority identity key
-  :var datetime published: **\*** time (in GMT) when this document & the key were last generated
-  :var str expires: **\*** time (in GMT) after which this key becomes invalid
-  :var str signing_key: **\*** directory server's public signing key
-  :var str crosscert: signature made using certificate's signing key
-  :var str certification: **\*** signature of this key certificate signed with the identity key
-  
-  **\*** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined
-  """
-  
-  def __init__(self, raw_content, validate = True):
-    """
-    Parse a key certificate entry and provide a KeyCertificate object.
-    
-    :param str raw_content: raw key certificate information
-    :param bool validate: True if the document is to be validated, False otherwise
-    
-    :raises: ValueError if the raw data is invalid
-    """
-    
-    super(KeyCertificate, self).__init__(raw_content)
-    self.key_certificate_version, self.ip, self.port = None, None, None
-    self.fingerprint, self.identity_key, self.published = None, None, None
-    self.expires, self.signing_key, self.crosscert = None, None, None
-    self.certification = None
-    parser = stem.descriptor.DescriptorParser(raw_content, validate)
-    peek_check_kw = lambda keyword: keyword == parser.peek_keyword()
-    seen_keywords = set()
-    
-    self.key_certificate_version = parser.read_keyword_line("dir-key-certificate-version")
-    if validate and self.key_certificate_version != "3": raise ValueError("Unrecognized dir-key-certificate-version")
-    
-    def _read_keyword_line(keyword):
-      if validate and keyword in seen_keywords:
-        raise ValueError("Invalid key certificate: '%s' appears twice" % keyword)
-      seen_keywords.add(keyword)
-      return parser.read_keyword_line(keyword)
-    
-    while parser.line:
-      if peek_check_kw("dir-address"):
-        line = _read_keyword_line("dir-address")
-        try:
-          self.ip, self.port = line.rsplit(":", 1)
-          self.port = int(self.port)
-        except Exception:
-          if validate: raise ValueError("Invalid dir-address line: %s" % line)
-      
-      elif peek_check_kw("fingerprint"):
-        self.fingerprint = _read_keyword_line("fingerprint")
-      
-      elif peek_check_kw("dir-identity-key"):
-        _read_keyword_line("dir-identity-key")
-        self.identity_key = parser.read_block("RSA PUBLIC KEY")
-      
-      elif peek_check_kw("dir-key-published"):
-        self.published = _strptime(_read_keyword_line("dir-key-published"))
-      
-      elif peek_check_kw("dir-key-expires"):
-        self.expires = _strptime(_read_keyword_line("dir-key-expires"))
-      
-      elif peek_check_kw("dir-signing-key"):
-        _read_keyword_line("dir-signing-key")
-        self.signing_key = parser.read_block("RSA PUBLIC KEY")
-      
-      elif peek_check_kw("dir-key-crosscert"):
-        _read_keyword_line("dir-key-crosscert")
-        self.crosscert = parser.read_block("ID SIGNATURE")
-      
-      elif peek_check_kw("dir-key-certification"):
-        _read_keyword_line("dir-key-certification")
-        self.certification = parser.read_block("SIGNATURE")
-        break
-      
-      elif validate:
-        raise ValueError("Key certificate contains unrecognized lines: %s" % parser.line)
-      
-      else:
-        # ignore unrecognized lines if we aren't validating
-        self._unrecognized_lines.append(parser.read_line())
-    
-    self._unrecognized_lines = parser.remaining()
-    if self._unrecognized_lines and validate:
-      raise ValueError("Unrecognized trailing data in key certificate")
-  
-  def get_unrecognized_lines(self):
-    """
-    Returns any unrecognized lines.
-    
-    :returns: a list of unrecognized lines
-    """
-    
-    return self._unrecognized_lines
-
-class DirectorySignature(stem.descriptor.Descriptor):
-  """
-  Contains directory signature information described in a v3 network status
-  document.
-  
-  :var str identity: signature identity
-  :var str key_digest: signature key digest
-  :var str method: method used to generate the signature
-  :var str signature: the signature data
-  """
-  
-  def __init__(self, raw_content, validate = True):
-    """
-    Parse a directory signature entry in a v3 network status document and
-    provide a DirectorySignature object.
-    
-    :param str raw_content: raw directory signature entry information
-    :param bool validate: True if the document is to be validated, False otherwise
-    
-    :raises: ValueError if the raw data is invalid
-    """
-    
-    super(DirectorySignature, self).__init__(raw_content)
-    self.identity, self.key_digest, self.method, self.signature = None, None, None, None
-    parser = stem.descriptor.DescriptorParser(raw_content, validate)
-    
-    signature_line = parser.read_keyword_line("directory-signature").split(" ")
-    
-    if len(signature_line) == 2:
-      self.identity, self.key_digest = signature_line
-    if len(signature_line) == 3: # for microdescriptor consensuses
-      self.method, self.identity, self.key_digest = signature_line
-    
-    self.signature = parser.read_block("SIGNATURE")
-    self._unrecognized_lines = parser.remaining()
-    if self._unrecognized_lines and validate:
-      raise ValueError("Unrecognized trailing data in directory signature")
-
-class RouterDescriptor(stem.descriptor.Descriptor):
-  """
-  Router descriptor object. Parses and stores router information in a router
-  entry read from a v3 network status document.
-  
-  :var str nickname: **\*** router's nickname
-  :var str identity: **\*** router's identity
-  :var str digest: **\*** router's digest
-  :var datetime publication: **\*** router's publication
-  :var str ip: **\*** router's IP address
-  :var int orport: **\*** router's ORPort
-  :var int dirport: **\*** router's DirPort
-  
-  :var bool is_valid: **\*** router is valid
-  :var bool is_guard: **\*** router is suitable for use as an entry guard
-  :var bool is_named: **\*** router is named
-  :var bool is_unnamed: **\*** router is unnamed
-  :var bool is_running: **\*** router is running and currently usable
-  :var bool is_stable: **\*** router is stable, i.e., it's suitable for for long-lived circuits
-  :var bool is_exit: **\*** router is an exit router
-  :var bool is_fast: **\*** router is Fast, i.e., it's usable for high-bandwidth circuits
-  :var bool is_authority: **\*** router is a directory authority
-  :var bool supports_v2dir: **\*** router supports v2dir
-  :var bool supports_v3dir: **\*** router supports v3dir
-  :var bool is_hsdir: **\*** router is a hidden status
-  :var bool is_badexit: **\*** router is marked a bad exit
-  :var bool is_baddirectory: **\*** router is a bad directory
-  
-  :var :class:`stem.version.Version`,str version: Version of the Tor protocol this router is running
-  
-  :var int bandwidth: router's claimed bandwidth
-  :var int measured_bandwidth: router's measured bandwidth
-  
-  :var :class:`stem.exit_policy.MicrodescriptorExitPolicy` exitpolicy: router's exitpolicy
-  
-  :var str microdescriptor_hashes: a list of two-tuples with a list of consensus methods(int) that may produce the digest and a dict with algorithm(str) => digest(str) mappings. algorithm is the hashing algorithm (usually "sha256") that is used to produce digest (the base64 encoding of the hash of the router's microdescriptor with trailing =s omitted).
-  
-  **\*** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined
-  """
-  
-  def __init__(self, raw_contents, vote = True, validate = True):
-    """
-    Parse a router descriptor in a v3 network status document and provide a new
-    RouterDescriptor object.
-    
-    :param str raw_content: router descriptor content to be parsed
-    :param bool validate: whether the router descriptor should be validated
-    """
-    
-    super(RouterDescriptor, self).__init__(raw_contents)
-    
-    self.nickname = None
-    self.identity = None
-    self.digest = None
-    self.publication = None
-    self.ip = None
-    self.orport = None
-    self.dirport = None
-    
-    self.is_valid = False
-    self.is_guard = False
-    self.is_named = False
-    self.is_unnamed = False
-    self.is_running = False
-    self.is_stable = False
-    self.is_exit = False
-    self.is_fast = False
-    self.is_authority = False
-    self.supports_v2dir = False
-    self.supports_v3dir = False
-    self.is_hsdir = False
-    self.is_badexit = False
-    self.is_baddirectory = False
-    
-    self.version = None
-    
-    self.bandwidth = None
-    self.measured_bandwidth = None
-    
-    self.exit_policy = None
-    
-    self.microdescriptor_hashes = []
-    
-    self._parse(raw_contents, vote, validate)
-  
-  def _parse(self, raw_content, vote, validate):
-    """
-    :param dict raw_content: iptor contents to be applied
-    :param bool validate: checks the validity of descriptor content if True
-    
-    :raises: ValueError if an error occures in validation
-    """
-    
-    parser = stem.descriptor.DescriptorParser(raw_content, validate)
-    seen_keywords = set()
-    peek_check_kw = lambda keyword: keyword == parser.peek_keyword()
-    
-    r = parser.read_keyword_line("r")
-    # r mauer BD7xbfsCFku3+tgybEZsg8Yjhvw itcuKQ6PuPLJ7m/Oi928WjO2j8g 2012-06-22 13:19:32 80.101.105.103 9001 0
-    # "r" SP nickname SP identity SP digest SP publication SP IP SP ORPort SP DirPort NL
-    seen_keywords.add("r")
-    if r:
-      values = r.split(" ")
-      self.nickname, self.identity, self.digest = values[0], values[1], values[2]
-      self.publication = _strptime(" ".join((values[3], values[4])), validate)
-      self.ip, self.orport, self.dirport = values[5], int(values[6]), int(values[7])
-      if self.dirport == 0: self.dirport = None
-    elif validate: raise ValueError("Invalid router descriptor: empty 'r' line" )
-    
-    while parser.line:
-      if peek_check_kw("s"):
-        if "s" in seen_keywords: raise ValueError("Invalid router descriptor: 's' line appears twice")
-        line = parser.read_keyword_line("s")
-        if not line: continue
-        seen_keywords.add("s")
-        # s Named Running Stable Valid
-        #A series of space-separated status flags, in *lexical order*
-        flags = line.split(" ")
-        flag_map = {
-          "Valid": "is_valid",
-          "Guard": "is_guard",
-          "Named": "is_named",
-          "Unnamed": "is_unnamed",
-          "Running": "is_running",
-          "Stable": "is_stable",
-          "Exit": "is_exit",
-          "Fast": "is_fast",
-          "Authority": "is_authority",
-          "V2Dir": "supports_v2dir",
-          "V3Dir": "supports_v3dir",
-          "HSDir": "is_hsdir",
-          "BadExit": "is_badexit",
-          "BadDirectory": "is_baddirectory",
-        }
-        map(lambda flag: setattr(self, flag_map[flag], True), flags)
-        
-        if self.is_unnamed: self.is_named = False
-        elif self.is_named: self.is_unnamed = False
-      
-      elif peek_check_kw("v"):
-        if "v" in seen_keywords: raise ValueError("Invalid router descriptor: 'v' line appears twice")
-        line = parser.read_keyword_line("v", True)
-        seen_keywords.add("v")
-        # v Tor 0.2.2.35
-        if line:
-          if line.startswith("Tor "):
-            self.version = stem.version.Version(line[4:])
-          else:
-            self.version = line
-        elif validate: raise ValueError("Invalid router descriptor: empty 'v' line" )
-      
-      elif peek_check_kw("w"):
-        if "w" in seen_keywords: raise ValueError("Invalid router descriptor: 'w' line appears twice")
-        w = parser.read_keyword_line("w", True)
-        # "w" SP "Bandwidth=" INT [SP "Measured=" INT] NL
-        seen_keywords.add("w")
-        if w:
-          values = w.split(" ")
-          if len(values) <= 2 and len(values) > 0:
-            key, value = values[0].split("=")
-            if key == "Bandwidth": self.bandwidth = int(value)
-            elif validate: raise ValueError("Router descriptor contains invalid 'w' line: expected Bandwidth, read " + key)
-            
-            if len(values) == 2:
-              key, value = values[1].split("=")
-              if key == "Measured": self.measured_bandwidth = int(value)
-              elif validate: raise ValueError("Router descriptor contains invalid 'w' line: expected Measured, read " + key)
-          elif validate: raise ValueError("Router descriptor contains invalid 'w' line")
-        elif validate: raise ValueError("Router descriptor contains empty 'w' line")
-      
-      elif peek_check_kw("p"):
-        if "p" in seen_keywords: raise ValueError("Invalid router descriptor: 'p' line appears twice")
-        p = parser.read_keyword_line("p", True)
-        seen_keywords.add("p")
-        # "p" SP ("accept" / "reject") SP PortList NL
-        if p:
-          self.exit_policy = stem.exit_policy.MicrodescriptorExitPolicy(p)
-          #self.exit_policy = p
-      
-      elif vote and peek_check_kw("m"):
-        # microdescriptor hashes
-        m = parser.read_keyword_line("m", True)
-        methods, digests = m.split(" ", 1)
-        method_list = methods.split(",")
-        digest_dict = [digest.split("=", 1) for digest in digests.split(" ")]
-        self.microdescriptor_hashes.append((method_list, digest_dict))
-      
-      elif validate:
-        raise ValueError("Router descriptor contains unrecognized trailing lines: %s" % parser.line)
-      
-      else:
-        self._unrecognized_lines.append(parser.read_line()) # ignore unrecognized lines if we aren't validating
-  
-  def get_unrecognized_lines(self):
-    """
-    Returns any unrecognized lines.
-    
-    :returns: a list of unrecognized lines
-    """
-    
-    return self._unrecognized_lines
-
diff --git a/test/integ/descriptor/networkstatus.py b/test/integ/descriptor/networkstatus.py
new file mode 100644
index 0000000..e65d960
--- /dev/null
+++ b/test/integ/descriptor/networkstatus.py
@@ -0,0 +1,228 @@
+"""
+Integration tests for stem.descriptor.server_descriptor.
+"""
+
+from __future__ import with_statement
+
+import os
+import resource
+import datetime
+import unittest
+
+import stem.exit_policy
+import stem.version
+import stem.descriptor.networkstatus
+import test.integ.descriptor
+
+def _strptime(string):
+  return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
+
+class TestNetworkStatusDocument(unittest.TestCase):
+  def test_metrics_consensus(self):
+    """
+    Checks if consensus documents from Metrics are parsed properly.
+    """
+    
+    descriptor_path = test.integ.descriptor.get_resource("metrics_consensus")
+    
+    with file(descriptor_path) as descriptor_file:
+      desc = stem.descriptor.parse_file(descriptor_path, descriptor_file)
+      
+      router = next(desc)
+      self.assertEquals("sumkledi", router.nickname)
+      self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router.identity)
+      self.assertEquals("8mCr8Sl7RF4ENU4jb0FZFA/3do8", router.digest)
+      self.assertEquals(_strptime("2012-07-12 04:01:55"), router.publication)
+      self.assertEquals("178.218.213.229", router.ip)
+      self.assertEquals(80, router.orport)
+      self.assertEquals(None, router.dirport)
+  
+  def test_cached_consensus(self):
+    """
+    Parses the cached-consensus file in our data directory.
+    """
+    
+    # lengthy test and uneffected by targets, so only run once
+    if test.runner.only_run_once(self, "test_cached_consensus"): return
+    
+    descriptor_path = test.runner.get_runner().get_test_dir("cached-consensus")
+    
+    if not os.path.exists(descriptor_path):
+      test.runner.skip(self, "(no cached-consensus)")
+    
+    if stem.util.system.is_windows():
+      # might hog memory and hang the system
+      # and we aren't checking for memory usage in windows, so, skip.
+      test.runner.skip(self, "(unavailable on windows)")
+    
+    count = 0
+    with open(descriptor_path) as descriptor_file:
+      for desc in stem.descriptor.networkstatus.parse_file(descriptor_file):
+        if resource.getrusage(resource.RUSAGE_SELF).ru_maxrss > 100000:
+          # if we're using > 100 MB we should fail
+          self.fail()
+        assert desc.nickname # check that the router has a nickname
+        count += 1
+    
+    assert count > 100 # sanity check - assuming atleast 100 relays in the Tor network
+  
+  def test_consensus(self):
+    """
+    Checks that consensus documents are properly parsed.
+    """
+    
+    descriptor_path = test.integ.descriptor.get_resource("consensus")
+    
+    descriptor_file = file(descriptor_path)
+    desc = stem.descriptor.networkstatus.NetworkStatusDocument(descriptor_file.read())
+    descriptor_file.close()
+    
+    self.assertEquals(True, desc.validated)
+    self.assertEquals("3", desc.network_status_version)
+    self.assertEquals("consensus", desc.vote_status)
+    self.assertEquals([], desc.consensus_methods)
+    self.assertEquals(None, desc.published)
+    self.assertEquals(12, desc.consensus_method)
+    self.assertEquals(_strptime("2012-07-12 10:00:00"), desc.valid_after)
+    self.assertEquals(_strptime("2012-07-12 11:00:00"), desc.fresh_until)
+    self.assertEquals(_strptime("2012-07-12 13:00:00"), desc.valid_until)
+    self.assertEquals(300, desc.vote_delay)
+    self.assertEquals(300, desc.dist_delay)
+    expected_client_versions = [stem.version.Version(version_string) for version_string in ["0.2.2.35",
+      "0.2.2.36", "0.2.2.37", "0.2.3.10-alpha", "0.2.3.11-alpha", "0.2.3.12-alpha",
+      "0.2.3.13-alpha", "0.2.3.14-alpha", "0.2.3.15-alpha", "0.2.3.16-alpha", "0.2.3.17-beta",
+      "0.2.3.18-rc", "0.2.3.19-rc"]]
+    expected_server_versions = [stem.version.Version(version_string) for version_string in ["0.2.2.35",
+      "0.2.2.36", "0.2.2.37", "0.2.3.10-alpha", "0.2.3.11-alpha", "0.2.3.12-alpha",
+      "0.2.3.13-alpha", "0.2.3.14-alpha", "0.2.3.15-alpha", "0.2.3.16-alpha", "0.2.3.17-beta",
+      "0.2.3.18-rc", "0.2.3.19-rc"]]
+    self.assertEquals(expected_client_versions, desc.client_versions)
+    self.assertEquals(expected_server_versions, desc.server_versions)
+    known_flags = ["Authority", "BadExit", "Exit", "Fast", "Guard", "HSDir", "Named", "Running", "Stable", "Unnamed", "V2Dir", "Valid"]
+    self.assertEquals(known_flags, desc.known_flags)
+    expected_params = {"CircuitPriorityHalflifeMsec": 30000, "bwauthpid": 1}
+    self.assertEquals(expected_params, desc.params)
+    router1 = next(desc.router_descriptors)
+    self.assertEquals("sumkledi", router1.nickname)
+    self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router1.identity)
+    self.assertEquals("8mCr8Sl7RF4ENU4jb0FZFA/3do8", router1.digest)
+    self.assertEquals(_strptime("2012-07-12 04:01:55"), router1.publication)
+    self.assertEquals("178.218.213.229", router1.ip)
+    self.assertEquals(80, router1.orport)
+    self.assertEquals(None, router1.dirport)
+    
+    self.assertEquals(8, len(desc.directory_authorities))
+    self.assertEquals("tor26", desc.directory_authorities[0].nickname)
+    self.assertEquals("14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4", desc.directory_authorities[0].identity)
+    self.assertEquals("86.59.21.38", desc.directory_authorities[0].address)
+    self.assertEquals("86.59.21.38", desc.directory_authorities[0].ip)
+    self.assertEquals(80, desc.directory_authorities[0].dirport)
+    self.assertEquals(443, desc.directory_authorities[0].orport)
+    self.assertEquals("Peter Palfrader", desc.directory_authorities[0].contact)
+    self.assertEquals(None, desc.directory_authorities[0].legacy_dir_key)
+    self.assertEquals(None, desc.directory_authorities[0].key_certificate)
+    self.assertEquals("0B6D1E9A300B895AA2D0B427F92917B6995C3C1C", desc.directory_authorities[0].vote_digest)
+    expected_bandwidth_weights = {
+        "Wbd": 3335, "Wbe": 0, "Wbg": 3536, "Wbm": 10000, "Wdb": 10000, "Web": 10000,
+        "Wed": 3329, "Wee": 10000, "Weg": 3329, "Wem": 10000, "Wgb": 10000, "Wgd": 3335,
+        "Wgg": 6464, "Wgm": 6464, "Wmb": 10000, "Wmd": 3335, "Wme": 0, "Wmg": 3536, "Wmm": 10000
+        }
+    self.assertEquals(expected_bandwidth_weights, desc.bandwidth_weights)
+    
+    expected_signature = """HFXB4497LzESysYJ/4jJY83E5vLjhv+igIxD9LU6lf6ftkGeF+lNmIAIEKaMts8H
+mfWcW0b+jsrXcJoCxV5IrwCDF3u1aC3diwZY6yiG186pwWbOwE41188XI2DeYPwE
+I/TJmV928na7RLZe2mGHCAW3VQOvV+QkCfj05VZ8CsY="""
+    self.assertEquals(8, len(desc.directory_signatures))
+    self.assertEquals("14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4", desc.directory_signatures[0].identity)
+    self.assertEquals("BF112F1C6D5543CFD0A32215ACABD4197B5279AD", desc.directory_signatures[0].key_digest)
+    self.assertEquals(expected_signature, desc.directory_signatures[0].signature)
+  
+  def test_vote(self):
+    """
+    Checks that vote documents are properly parsed.
+    """
+    
+    descriptor_path = test.integ.descriptor.get_resource("vote")
+    
+    descriptor_file = file(descriptor_path)
+    desc = stem.descriptor.networkstatus.NetworkStatusDocument(descriptor_file.read())
+    descriptor_file.close()
+    
+    self.assertEquals(True, desc.validated)
+    self.assertEquals("3", desc.network_status_version)
+    self.assertEquals("vote", desc.vote_status)
+    self.assertEquals(range(1, 13), desc.consensus_methods)
+    self.assertEquals(_strptime("2012-07-11 23:50:01"), desc.published)
+    self.assertEquals(None, desc.consensus_method)
+    self.assertEquals(_strptime("2012-07-12 00:00:00"), desc.valid_after)
+    self.assertEquals(_strptime("2012-07-12 01:00:00"), desc.fresh_until)
+    self.assertEquals(_strptime("2012-07-12 03:00:00"), desc.valid_until)
+    self.assertEquals(300, desc.vote_delay)
+    self.assertEquals(300, desc.dist_delay)
+    self.assertEquals([], desc.client_versions)
+    self.assertEquals([], desc.server_versions)
+    known_flags = ["Authority", "BadExit", "Exit", "Fast", "Guard", "HSDir", "Running", "Stable", "V2Dir", "Valid"]
+    self.assertEquals(known_flags, desc.known_flags)
+    expected_params = {"CircuitPriorityHalflifeMsec": 30000, "bwauthpid": 1}
+    self.assertEquals(expected_params, desc.params)
+    router1 = next(desc.router_descriptors)
+    self.assertEquals("sumkledi", router1.nickname)
+    self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router1.identity)
+    self.assertEquals("B5n4BiALAF8B5AqafxohyYiuj7E", router1.digest)
+    self.assertEquals(_strptime("2012-07-11 04:22:53"), router1.publication)
+    self.assertEquals("178.218.213.229", router1.ip)
+    self.assertEquals(80, router1.orport)
+    self.assertEquals(None, router1.dirport)
+    
+    self.assertEquals(1, len(desc.directory_authorities))
+    self.assertEquals("turtles", desc.directory_authorities[0].nickname)
+    self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_authorities[0].identity)
+    self.assertEquals("76.73.17.194", desc.directory_authorities[0].address)
+    self.assertEquals("76.73.17.194", desc.directory_authorities[0].ip)
+    self.assertEquals(9030, desc.directory_authorities[0].dirport)
+    self.assertEquals(9090, desc.directory_authorities[0].orport)
+    self.assertEquals("Mike Perry <email>", desc.directory_authorities[0].contact)
+    self.assertEquals(None, desc.directory_authorities[0].legacy_dir_key)
+    
+    expected_identity_key = """MIIBigKCAYEA6uSmsoxj2MiJ3qyZq0qYXlRoG8o82SNqg+22m+t1c7MlQOZWPJYn
+XeMcBCt8xrTeIt2ZI+Q/Kt2QJSeD9WZRevTKk/kn5Tg2+xXPogalUU47y5tUohGz
++Q8+CxtRSXpDxBHL2P8rLHvGrI69wbNHGoQkce/7gJy9vw5Ie2qzbyXk1NG6V8Fb
+pr6A885vHo6TbhUnolz2Wqt/kN+UorjLkN2H3fV+iGcQFv42SyHYGDLa0WwL3PJJ
+r/veu36S3VaHBrfhutfioi+d3d4Ya0bKwiWi5Lm2CHuuRTgMpHLU9vlci8Hunuxq
+HsULe2oMsr4VEic7sW5SPC5Obpx6hStHdNv1GxoSEm3/vIuPM8pINpU5ZYAyH9yO
+Ef22ZHeiVMMKmpV9TtFyiFqvlI6GpQn3mNbsQqF1y3XCA3Q4vlRAkpgJVUSvTxFP
+2bNDobOyVCpCM/rwxU1+RCNY5MFJ/+oktUY+0ydvTen3gFdZdgNqCYjKPLfBNm9m
+RGL7jZunMUNvAgMBAAE="""
+    expected_signing_key = """MIGJAoGBAJ5itcJRYNEM3Qf1OVWLRkwjqf84oXPc2ZusaJ5zOe7TVvBMra9GNyc0
+NM9y6zVkHCAePAjr4KbW/8P1olA6FUE2LV9bozaU1jFf6K8B2OELKs5FUEW+n+ic
+GM0x6MhngyXonWOcKt5Gj+mAu5lrno9tpNbPkz2Utr/Pi0nsDhWlAgMBAAE="""
+    expected_key_crosscert = """RHYImGTwg36wmEdAn7qaRg2sAfql7ZCtPIL/O3lU5OIdXXp0tNn/K00Bamqohjk+
+Tz4FKsKXGDlbGv67PQcZPOK6NF0GRkNh4pk89prrDO4XwtEn7rkHHdBH6/qQ7IRG
+GdDZHtZ1a69oFZvPWD3hUaB50xeIe7GoKdKIfdNNJ+8="""
+    expected_key_certification = """fasWOGyUZ3iMCYpDfJ+0JcMiTH25sXPWzvlHorEOyOMbaMqRYpZU4GHzt1jLgdl6
+AAoR6KdamsLg5VE8xzst48a4UFuzHFlklZ5O8om2rcvDd5DhSnWWYZnYJecqB+bo
+dNisPmaIVSAWb29U8BpNRj4GMC9KAgGYUj8aE/KtutAeEekFfFEHTfWZ2fFp4j3m
+9rY8FWraqyiF+Emq1T8pAAgMQ+79R3oZxq0TXS42Z4Anhms735ccauKhI3pDKjbl
+tD5vAzIHOyjAOXj7a6jY/GrnaBNuJ4qe/4Hf9UmzK/jKKwG95BPJtPTT4LoFwEB0
+KG2OUeQUNoCck4nDpsZwFqPlrWCHcHfTV2iDYFV1HQWDTtZz/qf+GtB8NXsq+I1w
+brADmvReM2BD6p/13h0QURCI5hq7ZYlIKcKrBa0jn1d9cduULl7vgKsRCJDls/ID
+emBZ6pUxMpBmV0v+PrA3v9w4DlE7GHAq61FF/zju2kpqj6MInbEvI/E+e438sWsL"""
+    self.assertEquals("3", desc.directory_authorities[0].key_certificate.key_certificate_version)
+    self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_authorities[0].key_certificate.fingerprint)
+    self.assertEquals(_strptime("2011-11-28 21:51:04"), desc.directory_authorities[0].key_certificate.published)
+    self.assertEquals(_strptime("2012-11-28 21:51:04"), desc.directory_authorities[0].key_certificate.expires)
+    self.assertEquals(expected_identity_key, desc.directory_authorities[0].key_certificate.identity_key)
+    self.assertEquals(expected_signing_key, desc.directory_authorities[0].key_certificate.signing_key)
+    self.assertEquals(expected_key_crosscert, desc.directory_authorities[0].key_certificate.crosscert)
+    self.assertEquals(expected_key_certification, desc.directory_authorities[0].key_certificate.certification)
+    self.assertEquals(None, desc.directory_authorities[0].vote_digest)
+    self.assertEquals({}, desc.bandwidth_weights)
+    
+    expected_signature = """fskXN84wB3mXfo+yKGSt0AcDaaPuU3NwMR3ROxWgLN0KjAaVi2eV9PkPCsQkcgw3
+JZ/1HL9sHyZfo6bwaC6YSM9PNiiY6L7rnGpS7UkHiFI+M96VCMorvjm5YPs3FioJ
+DnN5aFtYKiTc19qIC7Nmo+afPdDEf0MlJvEOP5EWl3w="""
+    self.assertEquals(1, len(desc.directory_signatures))
+    self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_signatures[0].identity)
+    self.assertEquals("D5C30C15BB3F1DA27669C2D88439939E8F418FCF", desc.directory_signatures[0].key_digest)
+    self.assertEquals(expected_signature, desc.directory_signatures[0].signature)
+
diff --git a/test/integ/descriptor/networkstatus_descriptor.py b/test/integ/descriptor/networkstatus_descriptor.py
deleted file mode 100644
index 46019ea..0000000
--- a/test/integ/descriptor/networkstatus_descriptor.py
+++ /dev/null
@@ -1,228 +0,0 @@
-"""
-Integration tests for stem.descriptor.server_descriptor.
-"""
-
-from __future__ import with_statement
-
-import os
-import resource
-import datetime
-import unittest
-
-import stem.exit_policy
-import stem.version
-import stem.descriptor.networkstatus_descriptor
-import test.integ.descriptor
-
-def _strptime(string):
-  return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
-
-class TestNetworkStatusDocument(unittest.TestCase):
-  def test_metrics_consensus(self):
-    """
-    Checks if consensus documents from Metrics are parsed properly.
-    """
-    
-    descriptor_path = test.integ.descriptor.get_resource("metrics_consensus")
-    
-    with file(descriptor_path) as descriptor_file:
-      desc = stem.descriptor.parse_file(descriptor_path, descriptor_file)
-      
-      router = next(desc)
-      self.assertEquals("sumkledi", router.nickname)
-      self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router.identity)
-      self.assertEquals("8mCr8Sl7RF4ENU4jb0FZFA/3do8", router.digest)
-      self.assertEquals(_strptime("2012-07-12 04:01:55"), router.publication)
-      self.assertEquals("178.218.213.229", router.ip)
-      self.assertEquals(80, router.orport)
-      self.assertEquals(None, router.dirport)
-  
-  def test_cached_consensus(self):
-    """
-    Parses the cached-consensus file in our data directory.
-    """
-    
-    # lengthy test and uneffected by targets, so only run once
-    if test.runner.only_run_once(self, "test_cached_consensus"): return
-    
-    descriptor_path = test.runner.get_runner().get_test_dir("cached-consensus")
-    
-    if not os.path.exists(descriptor_path):
-      test.runner.skip(self, "(no cached-consensus)")
-    
-    if stem.util.system.is_windows():
-      # might hog memory and hang the system
-      # and we aren't checking for memory usage in windows, so, skip.
-      test.runner.skip(self, "(unavailable on windows)")
-    
-    count = 0
-    with open(descriptor_path) as descriptor_file:
-      for desc in stem.descriptor.networkstatus_descriptor.parse_file(descriptor_file):
-        if resource.getrusage(resource.RUSAGE_SELF).ru_maxrss > 100000:
-          # if we're using > 100 MB we should fail
-          self.fail()
-        assert desc.nickname # check that the router has a nickname
-        count += 1
-    
-    assert count > 100 # sanity check - assuming atleast 100 relays in the Tor network
-  
-  def test_consensus(self):
-    """
-    Checks that consensus documents are properly parsed.
-    """
-    
-    descriptor_path = test.integ.descriptor.get_resource("consensus")
-    
-    descriptor_file = file(descriptor_path)
-    desc = stem.descriptor.networkstatus_descriptor.NetworkStatusDocument(descriptor_file.read())
-    descriptor_file.close()
-    
-    self.assertEquals(True, desc.validated)
-    self.assertEquals("3", desc.network_status_version)
-    self.assertEquals("consensus", desc.vote_status)
-    self.assertEquals([], desc.consensus_methods)
-    self.assertEquals(None, desc.published)
-    self.assertEquals(12, desc.consensus_method)
-    self.assertEquals(_strptime("2012-07-12 10:00:00"), desc.valid_after)
-    self.assertEquals(_strptime("2012-07-12 11:00:00"), desc.fresh_until)
-    self.assertEquals(_strptime("2012-07-12 13:00:00"), desc.valid_until)
-    self.assertEquals(300, desc.vote_delay)
-    self.assertEquals(300, desc.dist_delay)
-    expected_client_versions = [stem.version.Version(version_string) for version_string in ["0.2.2.35",
-      "0.2.2.36", "0.2.2.37", "0.2.3.10-alpha", "0.2.3.11-alpha", "0.2.3.12-alpha",
-      "0.2.3.13-alpha", "0.2.3.14-alpha", "0.2.3.15-alpha", "0.2.3.16-alpha", "0.2.3.17-beta",
-      "0.2.3.18-rc", "0.2.3.19-rc"]]
-    expected_server_versions = [stem.version.Version(version_string) for version_string in ["0.2.2.35",
-      "0.2.2.36", "0.2.2.37", "0.2.3.10-alpha", "0.2.3.11-alpha", "0.2.3.12-alpha",
-      "0.2.3.13-alpha", "0.2.3.14-alpha", "0.2.3.15-alpha", "0.2.3.16-alpha", "0.2.3.17-beta",
-      "0.2.3.18-rc", "0.2.3.19-rc"]]
-    self.assertEquals(expected_client_versions, desc.client_versions)
-    self.assertEquals(expected_server_versions, desc.server_versions)
-    known_flags = ["Authority", "BadExit", "Exit", "Fast", "Guard", "HSDir", "Named", "Running", "Stable", "Unnamed", "V2Dir", "Valid"]
-    self.assertEquals(known_flags, desc.known_flags)
-    expected_params = {"CircuitPriorityHalflifeMsec": 30000, "bwauthpid": 1}
-    self.assertEquals(expected_params, desc.params)
-    router1 = next(desc.router_descriptors)
-    self.assertEquals("sumkledi", router1.nickname)
-    self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router1.identity)
-    self.assertEquals("8mCr8Sl7RF4ENU4jb0FZFA/3do8", router1.digest)
-    self.assertEquals(_strptime("2012-07-12 04:01:55"), router1.publication)
-    self.assertEquals("178.218.213.229", router1.ip)
-    self.assertEquals(80, router1.orport)
-    self.assertEquals(None, router1.dirport)
-    
-    self.assertEquals(8, len(desc.directory_authorities))
-    self.assertEquals("tor26", desc.directory_authorities[0].nickname)
-    self.assertEquals("14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4", desc.directory_authorities[0].identity)
-    self.assertEquals("86.59.21.38", desc.directory_authorities[0].address)
-    self.assertEquals("86.59.21.38", desc.directory_authorities[0].ip)
-    self.assertEquals(80, desc.directory_authorities[0].dirport)
-    self.assertEquals(443, desc.directory_authorities[0].orport)
-    self.assertEquals("Peter Palfrader", desc.directory_authorities[0].contact)
-    self.assertEquals(None, desc.directory_authorities[0].legacy_dir_key)
-    self.assertEquals(None, desc.directory_authorities[0].key_certificate)
-    self.assertEquals("0B6D1E9A300B895AA2D0B427F92917B6995C3C1C", desc.directory_authorities[0].vote_digest)
-    expected_bandwidth_weights = {
-        "Wbd": 3335, "Wbe": 0, "Wbg": 3536, "Wbm": 10000, "Wdb": 10000, "Web": 10000,
-        "Wed": 3329, "Wee": 10000, "Weg": 3329, "Wem": 10000, "Wgb": 10000, "Wgd": 3335,
-        "Wgg": 6464, "Wgm": 6464, "Wmb": 10000, "Wmd": 3335, "Wme": 0, "Wmg": 3536, "Wmm": 10000
-        }
-    self.assertEquals(expected_bandwidth_weights, desc.bandwidth_weights)
-    
-    expected_signature = """HFXB4497LzESysYJ/4jJY83E5vLjhv+igIxD9LU6lf6ftkGeF+lNmIAIEKaMts8H
-mfWcW0b+jsrXcJoCxV5IrwCDF3u1aC3diwZY6yiG186pwWbOwE41188XI2DeYPwE
-I/TJmV928na7RLZe2mGHCAW3VQOvV+QkCfj05VZ8CsY="""
-    self.assertEquals(8, len(desc.directory_signatures))
-    self.assertEquals("14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4", desc.directory_signatures[0].identity)
-    self.assertEquals("BF112F1C6D5543CFD0A32215ACABD4197B5279AD", desc.directory_signatures[0].key_digest)
-    self.assertEquals(expected_signature, desc.directory_signatures[0].signature)
-  
-  def test_vote(self):
-    """
-    Checks that vote documents are properly parsed.
-    """
-    
-    descriptor_path = test.integ.descriptor.get_resource("vote")
-    
-    descriptor_file = file(descriptor_path)
-    desc = stem.descriptor.networkstatus_descriptor.NetworkStatusDocument(descriptor_file.read())
-    descriptor_file.close()
-    
-    self.assertEquals(True, desc.validated)
-    self.assertEquals("3", desc.network_status_version)
-    self.assertEquals("vote", desc.vote_status)
-    self.assertEquals(range(1, 13), desc.consensus_methods)
-    self.assertEquals(_strptime("2012-07-11 23:50:01"), desc.published)
-    self.assertEquals(None, desc.consensus_method)
-    self.assertEquals(_strptime("2012-07-12 00:00:00"), desc.valid_after)
-    self.assertEquals(_strptime("2012-07-12 01:00:00"), desc.fresh_until)
-    self.assertEquals(_strptime("2012-07-12 03:00:00"), desc.valid_until)
-    self.assertEquals(300, desc.vote_delay)
-    self.assertEquals(300, desc.dist_delay)
-    self.assertEquals([], desc.client_versions)
-    self.assertEquals([], desc.server_versions)
-    known_flags = ["Authority", "BadExit", "Exit", "Fast", "Guard", "HSDir", "Running", "Stable", "V2Dir", "Valid"]
-    self.assertEquals(known_flags, desc.known_flags)
-    expected_params = {"CircuitPriorityHalflifeMsec": 30000, "bwauthpid": 1}
-    self.assertEquals(expected_params, desc.params)
-    router1 = next(desc.router_descriptors)
-    self.assertEquals("sumkledi", router1.nickname)
-    self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router1.identity)
-    self.assertEquals("B5n4BiALAF8B5AqafxohyYiuj7E", router1.digest)
-    self.assertEquals(_strptime("2012-07-11 04:22:53"), router1.publication)
-    self.assertEquals("178.218.213.229", router1.ip)
-    self.assertEquals(80, router1.orport)
-    self.assertEquals(None, router1.dirport)
-    
-    self.assertEquals(1, len(desc.directory_authorities))
-    self.assertEquals("turtles", desc.directory_authorities[0].nickname)
-    self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_authorities[0].identity)
-    self.assertEquals("76.73.17.194", desc.directory_authorities[0].address)
-    self.assertEquals("76.73.17.194", desc.directory_authorities[0].ip)
-    self.assertEquals(9030, desc.directory_authorities[0].dirport)
-    self.assertEquals(9090, desc.directory_authorities[0].orport)
-    self.assertEquals("Mike Perry <email>", desc.directory_authorities[0].contact)
-    self.assertEquals(None, desc.directory_authorities[0].legacy_dir_key)
-    
-    expected_identity_key = """MIIBigKCAYEA6uSmsoxj2MiJ3qyZq0qYXlRoG8o82SNqg+22m+t1c7MlQOZWPJYn
-XeMcBCt8xrTeIt2ZI+Q/Kt2QJSeD9WZRevTKk/kn5Tg2+xXPogalUU47y5tUohGz
-+Q8+CxtRSXpDxBHL2P8rLHvGrI69wbNHGoQkce/7gJy9vw5Ie2qzbyXk1NG6V8Fb
-pr6A885vHo6TbhUnolz2Wqt/kN+UorjLkN2H3fV+iGcQFv42SyHYGDLa0WwL3PJJ
-r/veu36S3VaHBrfhutfioi+d3d4Ya0bKwiWi5Lm2CHuuRTgMpHLU9vlci8Hunuxq
-HsULe2oMsr4VEic7sW5SPC5Obpx6hStHdNv1GxoSEm3/vIuPM8pINpU5ZYAyH9yO
-Ef22ZHeiVMMKmpV9TtFyiFqvlI6GpQn3mNbsQqF1y3XCA3Q4vlRAkpgJVUSvTxFP
-2bNDobOyVCpCM/rwxU1+RCNY5MFJ/+oktUY+0ydvTen3gFdZdgNqCYjKPLfBNm9m
-RGL7jZunMUNvAgMBAAE="""
-    expected_signing_key = """MIGJAoGBAJ5itcJRYNEM3Qf1OVWLRkwjqf84oXPc2ZusaJ5zOe7TVvBMra9GNyc0
-NM9y6zVkHCAePAjr4KbW/8P1olA6FUE2LV9bozaU1jFf6K8B2OELKs5FUEW+n+ic
-GM0x6MhngyXonWOcKt5Gj+mAu5lrno9tpNbPkz2Utr/Pi0nsDhWlAgMBAAE="""
-    expected_key_crosscert = """RHYImGTwg36wmEdAn7qaRg2sAfql7ZCtPIL/O3lU5OIdXXp0tNn/K00Bamqohjk+
-Tz4FKsKXGDlbGv67PQcZPOK6NF0GRkNh4pk89prrDO4XwtEn7rkHHdBH6/qQ7IRG
-GdDZHtZ1a69oFZvPWD3hUaB50xeIe7GoKdKIfdNNJ+8="""
-    expected_key_certification = """fasWOGyUZ3iMCYpDfJ+0JcMiTH25sXPWzvlHorEOyOMbaMqRYpZU4GHzt1jLgdl6
-AAoR6KdamsLg5VE8xzst48a4UFuzHFlklZ5O8om2rcvDd5DhSnWWYZnYJecqB+bo
-dNisPmaIVSAWb29U8BpNRj4GMC9KAgGYUj8aE/KtutAeEekFfFEHTfWZ2fFp4j3m
-9rY8FWraqyiF+Emq1T8pAAgMQ+79R3oZxq0TXS42Z4Anhms735ccauKhI3pDKjbl
-tD5vAzIHOyjAOXj7a6jY/GrnaBNuJ4qe/4Hf9UmzK/jKKwG95BPJtPTT4LoFwEB0
-KG2OUeQUNoCck4nDpsZwFqPlrWCHcHfTV2iDYFV1HQWDTtZz/qf+GtB8NXsq+I1w
-brADmvReM2BD6p/13h0QURCI5hq7ZYlIKcKrBa0jn1d9cduULl7vgKsRCJDls/ID
-emBZ6pUxMpBmV0v+PrA3v9w4DlE7GHAq61FF/zju2kpqj6MInbEvI/E+e438sWsL"""
-    self.assertEquals("3", desc.directory_authorities[0].key_certificate.key_certificate_version)
-    self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_authorities[0].key_certificate.fingerprint)
-    self.assertEquals(_strptime("2011-11-28 21:51:04"), desc.directory_authorities[0].key_certificate.published)
-    self.assertEquals(_strptime("2012-11-28 21:51:04"), desc.directory_authorities[0].key_certificate.expires)
-    self.assertEquals(expected_identity_key, desc.directory_authorities[0].key_certificate.identity_key)
-    self.assertEquals(expected_signing_key, desc.directory_authorities[0].key_certificate.signing_key)
-    self.assertEquals(expected_key_crosscert, desc.directory_authorities[0].key_certificate.crosscert)
-    self.assertEquals(expected_key_certification, desc.directory_authorities[0].key_certificate.certification)
-    self.assertEquals(None, desc.directory_authorities[0].vote_digest)
-    self.assertEquals({}, desc.bandwidth_weights)
-    
-    expected_signature = """fskXN84wB3mXfo+yKGSt0AcDaaPuU3NwMR3ROxWgLN0KjAaVi2eV9PkPCsQkcgw3
-JZ/1HL9sHyZfo6bwaC6YSM9PNiiY6L7rnGpS7UkHiFI+M96VCMorvjm5YPs3FioJ
-DnN5aFtYKiTc19qIC7Nmo+afPdDEf0MlJvEOP5EWl3w="""
-    self.assertEquals(1, len(desc.directory_signatures))
-    self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_signatures[0].identity)
-    self.assertEquals("D5C30C15BB3F1DA27669C2D88439939E8F418FCF", desc.directory_signatures[0].key_digest)
-    self.assertEquals(expected_signature, desc.directory_signatures[0].signature)
-





More information about the tor-commits mailing list