[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Parsing and tests for network status document v2
commit 372ee9836b98af582eecf3d35844397d8935bd9b
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Thu Oct 11 19:27:36 2012 -0700
Parsing and tests for network status document v2
Parser, unit, and integ test for version 2 network status documents. These
documents are deprecated and no longer generated, however we still need a
parser to read older consensuses.
Unlike the v3 parser I'm cutting a few corners...
- not validating parameter ordering
- no validation that header/footer parameters haven't swapped places
- only the bare minimum unit test, no tests for invalid content
We can remedy these if necessary but with the growing irrelevance of v2
consensus parsing I doubt we ever will. Plenty of more important things to do.
---
run_tests.py | 2 +
stem/descriptor/networkstatus.py | 207 ++++++++++++++++++++-
test/integ/descriptor/data/cached-consensus-v2 | 27 +++
test/integ/descriptor/networkstatus.py | 80 ++++++++-
test/mocking.py | 34 ++++
test/unit/descriptor/networkstatus/document_v2.py | 32 ++++
6 files changed, 371 insertions(+), 11 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index 2475254..ef172bf 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -23,6 +23,7 @@ import test.unit.descriptor.extrainfo_descriptor
import test.unit.descriptor.router_status_entry
import test.unit.descriptor.networkstatus.directory_authority
import test.unit.descriptor.networkstatus.key_certificate
+import test.unit.descriptor.networkstatus.document_v2
import test.unit.descriptor.networkstatus.document_v3
import test.unit.response.control_line
import test.unit.response.control_message
@@ -121,6 +122,7 @@ UNIT_TESTS = (
test.unit.descriptor.router_status_entry.TestRouterStatusEntry,
test.unit.descriptor.networkstatus.directory_authority.TestDirectoryAuthority,
test.unit.descriptor.networkstatus.key_certificate.TestKeyCertificate,
+ test.unit.descriptor.networkstatus.document_v2.TestNetworkStatusDocument,
test.unit.descriptor.networkstatus.document_v3.TestNetworkStatusDocument,
test.unit.exit_policy.rule.TestExitPolicyRule,
test.unit.exit_policy.policy.TestExitPolicy,
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index 2f6972f..a028572 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -58,19 +58,39 @@ routers. Those routers refer to a 'thin' document, which doesn't have a
::
parse_file - parses a network status file, providing an iterator for its routers
- NetworkStatusDocumentV3 - Version 3 network status document.
+
+ NetworkStatusDocument - Network status document.
+ |- NetworkStatusDocumentV2 - Version 2 network status document.
+ +- NetworkStatusDocumentV3 - Version 3 network status document.
+
DocumentSignature - Signature of a document by a directory authority.
DirectoryAuthority - Directory authority as defined in a v3 network status document.
"""
import datetime
-from StringIO import StringIO
+import StringIO
import stem.descriptor
import stem.descriptor.router_status_entry
import stem.version
import stem.util.tor_tools
+# Version 2 network status document fields, tuples of the form...
+# (keyword, is_mandatory)
+
+NETWORK_STATUS_V2_FIELDS = (
+ ("network-status-version", True),
+ ("dir-source", True),
+ ("fingerprint", True),
+ ("contact", True),
+ ("dir-signing-key", True),
+ ("client-versions", False),
+ ("server-versions", False),
+ ("published", True),
+ ("dir-options", False),
+ ("directory-signature", True),
+)
+
# Network status document are either a 'vote' or 'consensus', with different
# mandatory fields for each. Both though require that their fields appear in a
# specific order. This is an ordered listing of the following...
@@ -105,6 +125,7 @@ FOOTER_FIELDS = [attr[0] for attr in FOOTER_STATUS_DOCUMENT_FIELDS]
AUTH_START = "dir-source"
ROUTERS_START = "r"
FOOTER_START = "directory-footer"
+V2_FOOTER_START = "directory-signature"
DEFAULT_PARAMS = {
"bwweightscale": 10000,
@@ -229,7 +250,179 @@ def _get_entries(document_file, validate, entry_class, entry_keyword, start_posi
desc_content = "".join(stem.descriptor._read_until_keywords(entry_keyword, document_file, ignore_first = True, end_position = end_position))
yield entry_class(desc_content, validate, *extra_args)
-class NetworkStatusDocumentV3(stem.descriptor.Descriptor):
+class NetworkStatusDocument(stem.descriptor.Descriptor):
+ """
+ Common parent for network status documents.
+ """
+
+ def __init__(self, raw_content):
+ super(NetworkStatusDocument, self).__init__(raw_content)
+ self._unrecognized_lines = []
+
+ def get_unrecognized_lines(self):
+ return list(self._unrecognized_lines)
+
+class NetworkStatusDocumentV2(NetworkStatusDocument):
+ """
+ Version 2 network status document. These have been deprecated and are no
+ longer generated by Tor.
+
+ :var tuple routers: RouterStatusEntryV2 contained in the document
+
+ :var int version: **\*** document version
+
+ :var str hostname: **\*** hostname of the authority
+ :var str address: **\*** authority's IP address
+ :var int dir_port: **\*** authority's DirPort
+ :var str fingerprint: **\*** authority's fingerprint
+ :var str contact: **\*** authority's contact information
+ :var str signing_key: **\*** authority's public signing key
+
+ :var list client_versions: list of recommended client tor version strings
+ :var list server_versions: list of recommended server tor version strings
+ :var datetime published: **\*** time when the document was published
+ :var list options: **\*** list of things that this authority decides
+
+ :var str signing_authority: **\*** name of the authority signing the document
+ :var str signature: **\*** authority's signature for the document
+
+ **\*** 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):
+ super(NetworkStatusDocumentV2, self).__init__(raw_content)
+
+ self.version = None
+ self.hostname = None
+ self.address = None
+ self.dir_port = None
+ self.fingerprint = None
+ self.contact = None
+ self.signing_key = None
+
+ self.client_versions = []
+ self.server_versions = []
+ self.published = None
+ self.options = []
+
+ self.signing_authority = None
+ self.signatures = None
+
+ # Splitting the document from the routers. Unlike v3 documents we're not
+ # bending over backwards on the validation by checking the field order or
+ # that header/footer attributes aren't in the wrong section. This is a
+ # deprecated descriptor type - patches welcome if you want those checks.
+
+ document_file = StringIO.StringIO(raw_content)
+ document_content = "".join(stem.descriptor._read_until_keywords((ROUTERS_START, V2_FOOTER_START), document_file))
+
+ self.routers = tuple(_get_entries(
+ document_file,
+ validate,
+ entry_class = stem.descriptor.router_status_entry.RouterStatusEntryV2,
+ entry_keyword = ROUTERS_START,
+ section_end_keywords = V2_FOOTER_START,
+ extra_args = (self,),
+ ))
+
+ document_content += "\n" + document_file.read()
+
+ entries = stem.descriptor._get_descriptor_components(document_content, validate)
+ if validate: self._check_constraints(entries)
+ self._parse(entries, validate)
+
+ def _parse(self, entries, validate):
+ for keyword, values in entries.items():
+ value, block_contents = values[0]
+
+ line = "%s %s" % (keyword, value) # original line
+ if block_contents: line += "\n%s" % block_contents
+
+ if keyword == "network-status-version":
+ if not value.isdigit():
+ if not validate: continue
+ raise ValueError("Network status document has a non-numeric version: %s" % line)
+
+ self.version = int(value)
+
+ if validate and self.version != 2:
+ raise ValueError("Expected a version 2 network status document, got version '%s' instead" % self.version)
+ elif keyword == "dir-source":
+ dir_source_comp = value.split()
+
+ if len(dir_source_comp) < 3:
+ if not validate: continue
+ raise ValueError("The 'dir-source' line of a v2 network status document must have three values: %s" % line)
+
+ if validate:
+ if not dir_source_comp[0]:
+ # https://trac.torproject.org/7055
+ raise ValueError("Authority's hostname can't be blank: %s" % line)
+ elif not stem.util.connection.is_valid_ip_address(dir_source_comp[1]):
+ raise ValueError("Authority's address isn't a valid IPv4 address: %s" % dir_source_comp[1])
+ elif not stem.util.connection.is_valid_port(dir_source_comp[2], allow_zero = True):
+ raise ValueError("Authority's DirPort is invalid: %s" % dir_source_comp[2])
+ elif not dir_source_comp[2].isdigit():
+ continue
+
+ self.hostname = dir_source_comp[0]
+ self.address = dir_source_comp[1]
+ self.dir_port = None if dir_source_comp[2] == '0' else int(dir_source_comp[2])
+ elif keyword == "fingerprint":
+ if validate and not stem.util.tor_tools.is_valid_fingerprint(value):
+ raise ValueError("Authority's fingerprint in a v2 network status document is malformed: %s" % line)
+
+ self.fingerprint = value
+ elif keyword == "contact":
+ self.contact = value
+ elif keyword == "dir-signing-key":
+ self.signing_key = block_contents
+ elif keyword in ("client-versions", "server-versions"):
+ # v2 documents existed while there were tor versions using the 'old'
+ # style, hence we aren't attempting to parse them
+
+ for version_str in value.split(","):
+ if keyword == 'client-versions':
+ self.client_versions.append(version_str)
+ elif keyword == 'server-versions':
+ self.server_versions.append(version_str)
+ elif keyword == "published":
+ try:
+ self.published = datetime.datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
+ except ValueError:
+ if validate:
+ raise ValueError("Versino 2 network status document's 'published' time wasn't parseable: %s" % value)
+ elif keyword == "dir-options":
+ self.options = value.split()
+ elif keyword == "directory-signature":
+ self.signing_authority = value
+ self.signature = block_contents
+ else:
+ self._unrecognized_lines.append(line)
+
+ # 'client-versions' and 'server-versions' are only required if "Versions"
+ # is among the options
+
+ if validate and "Versions" in self.options:
+ if not ('client-versions' in entries and 'server-versions' in entries):
+ raise ValueError("Version 2 network status documents must have a 'client-versions' and 'server-versions' when 'Versions' is listed among its dir-options:\n%s" % str(self))
+
+ def _check_constraints(self, entries):
+ required_fields = [field for (field, is_mandatory) in NETWORK_STATUS_V2_FIELDS if is_mandatory]
+ for keyword in required_fields:
+ if not keyword in entries:
+ raise ValueError("Network status document (v2) must have a '%s' line:\n%s" % (keyword, str(self)))
+
+ # all recognized fields can only appear once
+ single_fields = [field for (field, _) in NETWORK_STATUS_V2_FIELDS]
+ for keyword in single_fields:
+ if keyword in entries and len(entries[keyword]) > 1:
+ raise ValueError("Network status document (v2) can only have a single '%s' line, got %i:\n%s" % (keyword, len(entries[keyword]), str(self)))
+
+ if 'network-status-version' != entries.keys()[0]:
+ raise ValueError("Network status document (v2) are expected to start with a 'network-status-version' line:\n%s" % str(self))
+
+class NetworkStatusDocumentV3(NetworkStatusDocument):
"""
Version 3 network status document. This could be either a vote or consensus.
@@ -276,10 +469,9 @@ class NetworkStatusDocumentV3(stem.descriptor.Descriptor):
"""
super(NetworkStatusDocumentV3, self).__init__(raw_content)
- document_file = StringIO(raw_content)
+ document_file = StringIO.StringIO(raw_content)
self._header = _DocumentHeader(document_file, validate, default_params)
- self._unrecognized_lines = []
# merge header attributes into us
for attr, value in vars(self._header).items():
@@ -333,9 +525,6 @@ class NetworkStatusDocumentV3(stem.descriptor.Descriptor):
return self._header.meets_consensus_method(method)
- def get_unrecognized_lines(self):
- return list(self._unrecognized_lines)
-
def __cmp__(self, other):
if not isinstance(other, NetworkStatusDocumentV3):
return 1
@@ -404,7 +593,7 @@ class _DocumentHeader(object):
self.is_microdescriptor = flavor == 'microdesc'
if validate and self.version != 3:
- raise ValueError("Expected a version 3 network status documents, got version '%s' instead" % self.version)
+ raise ValueError("Expected a version 3 network status document, got version '%s' instead" % self.version)
elif keyword == 'vote-status':
# "vote-status" type
#
diff --git a/test/integ/descriptor/data/cached-consensus-v2 b/test/integ/descriptor/data/cached-consensus-v2
new file mode 100644
index 0000000..4f8cbce
--- /dev/null
+++ b/test/integ/descriptor/data/cached-consensus-v2
@@ -0,0 +1,27 @@
+network-status-version 2
+dir-source 18.244.0.114 18.244.0.114 80
+fingerprint 719BE45DE224B607C53707D0E2143E2D423E74CF
+contact arma at mit dot edu
+published 2005-12-16 00:13:46
+dir-options Names Versions
+client-versions 0.0.9rc2,0.0.9rc3,0.0.9rc4-cvs,0.0.9rc4,0.0.9rc5-cvs,0.0.9rc5,0.0.9rc6-cvs,0.0.9rc6,0.0.9rc7-cvs,0.0.9rc7,0.0.9,0.0.9.1,0.0.9.2,0.0.9.3,0.0.9.4,0.0.9.5,0.0.9.6,0.0.9.7,0.0.9.8,0.0.9.9,0.0.9.10,0.1.0.0-alpha-cvs,0.1.0.1-rc,0.1.0.1-rc-cvs,0.1.0.2-rc,0.1.0.2-rc-cvs,0.1.0.3-rc,0.1.0.3-rc-cvs,0.1.0.4-rc,0.1.0.4-rc-cvs,0.1.0.5-rc,0.1.0.5-rc-cvs,0.1.0.6-rc,0.1.0.6-rc-cvs,0.1.0.7-rc,0.1.0.7-rc-cvs,0.1.0.8-rc,0.1.0.8-rc-cvs,0.1.0.9-rc,0.1.0.10,0.1.0.11,0.1.0.12,0.1.0.13,0.1.0.14,0.1.0.15,0.1.0.16,0.1.1.0-alpha-cvs,0.1.1.1-alpha,0.1.1.1-alpha-cvs,0.1.1.2-alpha,0.1.1.2-alpha-cvs,0.1.1.3-alpha,0.1.1.3-alpha-cvs,0.1.1.4-alpha,0.1.1.4-alpha-cvs,0.1.1.5-alpha,0.1.1.5-alpha-cvs,0.1.1.6-alpha,0.1.1.6-alpha-cvs,0.1.1.7-alpha,0.1.1.7-alpha-cvs,0.1.1.8-alpha,0.1.1.8-alpha-cvs,0.1.1.9-alpha,0.1.1.9-alpha-cvs,0.1.1.10-alpha,0.1.1.10-alpha-cvs
+server-versions 0.0.9rc2,0.0.9rc3,0.0.9rc4-cvs,0.0.9rc4,0.0.9rc5-cvs,0.0.9rc5,0.0.9rc6-cvs,0.0.9rc6,0.0.9rc7-cvs,0.0.9rc7,0.0.9,0.0.9.1,0.0.9.2,0.0.9.3,0.0.9.4,0.0.9.5,0.0.9.6,0.0.9.7,0.0.9.8,0.0.9.9,0.0.9.10,0.1.0.0-alpha-cvs,0.1.0.1-rc,0.1.0.1-rc-cvs,0.1.0.2-rc,0.1.0.2-rc-cvs,0.1.0.3-rc,0.1.0.3-rc-cvs,0.1.0.4-rc,0.1.0.4-rc-cvs,0.1.0.5-rc,0.1.0.5-rc-cvs,0.1.0.6-rc,0.1.0.6-rc-cvs,0.1.0.7-rc,0.1.0.7-rc-cvs,0.1.0.8-rc,0.1.0.8-rc-cvs,0.1.0.9-rc,0.1.0.10,0.1.0.11,0.1.0.12,0.1.0.13,0.1.0.14,0.1.0.15,0.1.0.16,0.1.1.0-alpha-cvs,0.1.1.1-alpha,0.1.1.1-alpha-cvs,0.1.1.2-alpha,0.1.1.2-alpha-cvs,0.1.1.3-alpha,0.1.1.3-alpha-cvs,0.1.1.4-alpha,0.1.1.4-alpha-cvs,0.1.1.5-alpha,0.1.1.5-alpha-cvs,0.1.1.6-alpha,0.1.1.6-alpha-cvs,0.1.1.7-alpha,0.1.1.7-alpha-cvs,0.1.1.8-alpha,0.1.1.8-alpha-cvs,0.1.1.9-alpha,0.1.1.9-alpha-cvs,0.1.1.10-alpha,0.1.1.10-alpha-cvs
+dir-signing-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBAOcrht/y5rkaahfX7sMe2qnpqoPibsjTSJaDvsUtaNP/Bq0MgNDGOR48
+rtwfqTRff275Edkp/UYw3G3vSgKCJr76/bqOHCmkiZrnPV1zxNfrK18gNw2Cxre0
+nTA+fD8JQqpPtb8b0SnG9kwy75eS//sRu7TErie2PzGMxrf9LH0LAgMBAAE=
+-----END RSA PUBLIC KEY-----
+
+r moria2 cZvkXeIktgfFNwfQ4hQ+LUI+dM8 t/Pwl1uHiJ3RKF/Vehsbthf2VDI 2005-12-15 06:57:18 18.244.0.114 443 80
+s Authority Fast Named Running Valid V2Dir
+r stnv CSi6RnBWxKaJ/uTvXXFIK2KJw9U ItGn7UGZvaftbEFu7NdpwY4fKlo 2005-12-15 16:24:42 84.16.236.173 9001 0
+s Named Valid
+r nggrplz CehYL/Dm+F4rjkHA3AucncRuaWg swLCsByU85jj7ziTlSawZR+CTdY 2005-12-15 23:25:50 194.109.109.109 9001 0
+s Fast Stable Running Valid
+directory-signature moria2
+-----BEGIN SIGNATURE-----
+2nXCxVje3wzn6HrIFRNMc0nc48AhMVpHZyPwRKGXkuYfTQG55uvwQDaFgJHud4RT
+27QhWltau3K1evhnzhKcpbTXwkVv1TBYJSzL6rEeAn8cQ7ZiCyqf4EJCaNcem3d2
+TpQQk3nNQF8z6UIvdlvP+DnJV4izWVkQEZgUZgIVM0E=
+-----END SIGNATURE-----
diff --git a/test/integ/descriptor/networkstatus.py b/test/integ/descriptor/networkstatus.py
index e174f25..e0ecd00 100644
--- a/test/integ/descriptor/networkstatus.py
+++ b/test/integ/descriptor/networkstatus.py
@@ -108,9 +108,9 @@ class TestNetworkStatus(unittest.TestCase):
self.assertEquals(80, router.or_port)
self.assertEquals(None, router.dir_port)
- def test_consensus(self):
+ def test_consensus_v3(self):
"""
- Checks that consensus documents are properly parsed.
+ Checks that version 3 consensus documents are properly parsed.
"""
# the document's expected client and server versions are the same
@@ -193,6 +193,82 @@ I/TJmV928na7RLZe2mGHCAW3VQOvV+QkCfj05VZ8CsY=
self.assertEquals("BF112F1C6D5543CFD0A32215ACABD4197B5279AD", signature.key_digest)
self.assertEquals(expected_signature, signature.signature)
+ def test_consensus_v2(self):
+ """
+ Checks that version 2 consensus documents are properly parsed.
+ """
+
+ expected_signing_key = """-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBAOcrht/y5rkaahfX7sMe2qnpqoPibsjTSJaDvsUtaNP/Bq0MgNDGOR48
+rtwfqTRff275Edkp/UYw3G3vSgKCJr76/bqOHCmkiZrnPV1zxNfrK18gNw2Cxre0
+nTA+fD8JQqpPtb8b0SnG9kwy75eS//sRu7TErie2PzGMxrf9LH0LAgMBAAE=
+-----END RSA PUBLIC KEY-----"""
+
+ expected_signature = """-----BEGIN SIGNATURE-----
+2nXCxVje3wzn6HrIFRNMc0nc48AhMVpHZyPwRKGXkuYfTQG55uvwQDaFgJHud4RT
+27QhWltau3K1evhnzhKcpbTXwkVv1TBYJSzL6rEeAn8cQ7ZiCyqf4EJCaNcem3d2
+TpQQk3nNQF8z6UIvdlvP+DnJV4izWVkQEZgUZgIVM0E=
+-----END SIGNATURE-----"""
+
+ consensus_path = test.integ.descriptor.get_resource("cached-consensus-v2")
+
+ with open(consensus_path) as descriptor_file:
+ document = stem.descriptor.networkstatus.NetworkStatusDocumentV2(descriptor_file.read())
+
+ self.assertEquals(2, document.version)
+ self.assertEquals("18.244.0.114", document.hostname)
+ self.assertEquals("18.244.0.114", document.address)
+ self.assertEquals(80, document.dir_port)
+ self.assertEquals("719BE45DE224B607C53707D0E2143E2D423E74CF", document.fingerprint)
+ self.assertEquals("arma at mit dot edu", document.contact)
+ self.assertEquals(expected_signing_key, document.signing_key)
+
+ self.assertEquals(67, len(document.client_versions))
+ self.assertEquals("0.0.9rc2", document.client_versions[0])
+ self.assertEquals("0.1.1.10-alpha-cvs", document.client_versions[-1])
+
+ self.assertEquals(67, len(document.server_versions))
+ self.assertEquals("0.0.9rc2", document.server_versions[0])
+ self.assertEquals("0.1.1.10-alpha-cvs", document.server_versions[-1])
+
+ self.assertEquals(datetime.datetime(2005, 12, 16, 0, 13, 46), document.published)
+ self.assertEquals(["Names", "Versions"], document.options)
+ self.assertEquals("moria2", document.signing_authority)
+ self.assertEquals(expected_signature, document.signature)
+ self.assertEquals([], document.get_unrecognized_lines())
+
+ self.assertEqual(3, len(document.routers))
+
+ router1 = document.routers[0]
+ self.assertEquals("moria2", router1.nickname)
+ self.assertEquals("719BE45DE224B607C53707D0E2143E2D423E74CF", router1.fingerprint)
+ self.assertEquals("t/Pwl1uHiJ3RKF/Vehsbthf2VDI", router1.digest)
+ self.assertEquals(datetime.datetime(2005, 12, 15, 6, 57, 18), router1.published)
+ self.assertEquals("18.244.0.114", router1.address)
+ self.assertEquals(443, router1.or_port)
+ self.assertEquals(80, router1.dir_port)
+ self.assertEquals(set(["Authority", "Fast", "Named", "Running", "Valid", "V2Dir"]), set(router1.flags))
+
+ router2 = document.routers[1]
+ self.assertEquals("stnv", router2.nickname)
+ self.assertEquals("0928BA467056C4A689FEE4EF5D71482B6289C3D5", router2.fingerprint)
+ self.assertEquals("ItGn7UGZvaftbEFu7NdpwY4fKlo", router2.digest)
+ self.assertEquals(datetime.datetime(2005, 12, 15, 16, 24, 42), router2.published)
+ self.assertEquals("84.16.236.173", router2.address)
+ self.assertEquals(9001, router2.or_port)
+ self.assertEquals(None, router2.dir_port)
+ self.assertEquals(set(["Named", "Valid"]), set(router2.flags))
+
+ router3 = document.routers[2]
+ self.assertEquals("nggrplz", router3.nickname)
+ self.assertEquals("09E8582FF0E6F85E2B8E41C0DC0B9C9DC46E6968", router3.fingerprint)
+ self.assertEquals("swLCsByU85jj7ziTlSawZR+CTdY", router3.digest)
+ self.assertEquals(datetime.datetime(2005, 12, 15, 23, 25, 50), router3.published)
+ self.assertEquals("194.109.109.109", router3.address)
+ self.assertEquals(9001, router3.or_port)
+ self.assertEquals(None, router3.dir_port)
+ self.assertEquals(set(["Fast", "Stable", "Running", "Valid"]), set(router3.flags))
+
def test_metrics_vote(self):
"""
Checks if vote documents from Metrics are parsed properly.
diff --git a/test/mocking.py b/test/mocking.py
index ab31eef..1a8aa41 100644
--- a/test/mocking.py
+++ b/test/mocking.py
@@ -36,6 +36,7 @@ calling :func:`test.mocking.revert_mocking`.
stem.descriptor.networkstatus
get_directory_authority - DirectoryAuthority
get_key_certificate - KeyCertificate
+ get_network_status_document_v2 - NetworkStatusDocumentV2
get_network_status_document_v3 - NetworkStatusDocumentV3
stem.descriptor.router_status_entry
@@ -152,6 +153,19 @@ KEY_CERTIFICATE_FOOTER = (
("dir-key-certification", "\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB),
)
+NETWORK_STATUS_DOCUMENT_HEADER_V2 = (
+ ("network-status-version", "2"),
+ ("dir-source", "18.244.0.114 18.244.0.114 80"),
+ ("fingerprint", "719BE45DE224B607C53707D0E2143E2D423E74CF"),
+ ("contact", "arma at mit dot edu"),
+ ("published", "2005-12-16 00:13:46"),
+ ("dir-signing-key", "\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----" % CRYPTO_BLOB),
+)
+
+NETWORK_STATUS_DOCUMENT_FOOTER_V2 = (
+ ("directory-signature", "moria2\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB),
+)
+
NETWORK_STATUS_DOCUMENT_HEADER = (
("network-status-version", "3"),
("vote-status", "consensus"),
@@ -655,6 +669,26 @@ def get_key_certificate(attr = None, exclude = (), content = False):
else:
return stem.descriptor.networkstatus.KeyCertificate(desc_content, validate = True)
+def get_network_status_document_v2(attr = None, exclude = (), routers = None, content = False):
+ """
+ Provides the descriptor content for...
+ stem.descriptor.networkstatus.NetworkStatusDocumentV2
+
+ :param dict attr: keyword/value mappings to be included in the descriptor
+ :param list exclude: mandatory keywords to exclude from the descriptor
+ :param list routers: router status entries to include in the document
+ :param bool content: provides the str content of the descriptor rather than the class if True
+
+ :returns: NetworkStatusDocumentV2 for the requested descriptor content
+ """
+
+ desc_content = _get_descriptor_content(attr, exclude, NETWORK_STATUS_DOCUMENT_HEADER_V2, NETWORK_STATUS_DOCUMENT_FOOTER_V2)
+
+ if content:
+ return desc_content
+ else:
+ return stem.descriptor.networkstatus.NetworkStatusDocumentV2(desc_content, validate = True)
+
def get_network_status_document_v3(attr = None, exclude = (), authorities = None, routers = None, content = False):
"""
Provides the descriptor content for...
diff --git a/test/unit/descriptor/networkstatus/document_v2.py b/test/unit/descriptor/networkstatus/document_v2.py
new file mode 100644
index 0000000..70f904e
--- /dev/null
+++ b/test/unit/descriptor/networkstatus/document_v2.py
@@ -0,0 +1,32 @@
+"""
+Unit tests for the NetworkStatusDocumentV2 of stem.descriptor.networkstatus.
+"""
+
+import datetime
+import unittest
+
+from test.mocking import get_network_status_document_v2, NETWORK_STATUS_DOCUMENT_HEADER_V2, NETWORK_STATUS_DOCUMENT_FOOTER_V2
+
+class TestNetworkStatusDocument(unittest.TestCase):
+ def test_minimal_document(self):
+ """
+ Parses a minimal v2 network status document.
+ """
+
+ document = get_network_status_document_v2()
+
+ self.assertEquals((), document.routers)
+ self.assertEquals(2, document.version)
+ self.assertEquals("18.244.0.114", document.hostname)
+ self.assertEquals("18.244.0.114", document.address)
+ self.assertEquals(80, document.dir_port)
+ self.assertEquals("719BE45DE224B607C53707D0E2143E2D423E74CF", document.fingerprint)
+ self.assertEquals("arma at mit dot edu", document.contact)
+ self.assertEquals(NETWORK_STATUS_DOCUMENT_HEADER_V2[5][1][1:], document.signing_key)
+ self.assertEquals([], document.client_versions)
+ self.assertEquals([], document.server_versions)
+ self.assertEquals(datetime.datetime(2005, 12, 16, 0, 13, 46), document.published)
+ self.assertEquals([], document.options)
+ self.assertEquals("moria2", document.signing_authority)
+ self.assertEquals(NETWORK_STATUS_DOCUMENT_FOOTER_V2[0][1][7:], document.signature)
+
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits