tor-commits
Threads by month
- ----- 2025 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
October 2012
- 20 participants
- 1288 discussions
commit 802f96e94c81dc7270d1c8311485e3cd1bf2e10d
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Sep 17 08:55:53 2012 -0700
Parsing the bandwidth-wights attribute
The 'bandwidth-wights' line is pretty similar to the 'params', so sharing most
of the parsing code between them. Testing for the following...
- negative and zero values
- malformed entries
- ordering
- that this can't appear in a vote
- missing values or empty content
---
stem/descriptor/networkstatus.py | 117 ++++++++++++++----------
test/unit/descriptor/networkstatus/document.py | 99 ++++++++++++++++++++-
2 files changed, 167 insertions(+), 49 deletions(-)
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index ae52c2f..2061bd5 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -57,9 +57,6 @@ import stem.util.tor_tools
from stem.descriptor import _read_until_keywords, _peek_keyword, _strptime
from stem.descriptor import _read_keyword_line, _read_keyword_line_str, _get_pseudo_pgp_block, _peek_line
-_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"]]))
-
# 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...
@@ -84,7 +81,7 @@ HEADER_STATUS_DOCUMENT_FIELDS = (
FOOTER_STATUS_DOCUMENT_FIELDS = (
("directory-footer", True, True, True),
- ("bandwidths-weights", False, True, False),
+ ("bandwidth-weights", False, True, False),
("directory-signature", True, True, True),
)
@@ -106,6 +103,14 @@ DEFAULT_PARAMS = {
"cbtinitialtimeout": 60000,
}
+BANDWIDTH_WEIGHT_ENTRIES = (
+ "Wbd", "Wbe", "Wbg", "Wbm",
+ "Wdb",
+ "Web", "Wed", "Wee", "Weg", "Wem",
+ "Wgb", "Wgd", "Wgg", "Wgm",
+ "Wmb", "Wmd", "Wme", "Wmg", "Wmm",
+)
+
def parse_file(document_file, validate = True, is_microdescriptor = False):
"""
Parses a network status and iterates over the RouterStatusEntry or
@@ -203,12 +208,9 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
:var str version: **\*** document version
:var bool is_consensus: **\*** true if the document is a consensus
:var bool is_vote: **\*** true if the document is a vote
- :var int consensus_method: **~** consensus method used to generate a consensus
- :var list consensus_methods: **^** A list of supported consensus generation methods (integers)
- :var datetime published: **^** time when the document was published
- :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 datetime valid_after: **\*** time when the consensus became valid
+ :var datetime fresh_until: **\*** time when the next consensus should be produced
+ :var datetime valid_until: **\*** time when this consensus becomes obsolete
: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 client tor versions
@@ -216,12 +218,17 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
:var list known_flags: **\*** list of known router flags
:var list params: **\*** dict of parameter(str) => value(int) mappings
: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
- | **^** attribute appears only in votes
- | **~** attribute appears only in consensuses
+ **Consensus Attributes:**
+ :var int consensus_method: method version used to generate this consensus
+ :var dict bandwidth_weights: dict of weight(str) => value(int) mappings
+
+ **Vote Attributes:**
+ :var list consensus_methods: list of ints for the supported method versions
+ :var datetime published: time when the document was published
+
+ **\*** 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, default_params = True):
@@ -420,34 +427,7 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
# skip if this is a blank line
if value == "": continue
- seen_keys = []
- for entry in value.split(" "):
- try:
- if not '=' in entry:
- raise ValueError("must only have 'key=value' entries")
-
- entry_key, entry_value = entry.split("=", 1)
-
- try:
- # the int() function accepts things like '+123', but we don't want to
- if entry_value.startswith('+'):
- raise ValueError()
-
- entry_value = int(entry_value)
- except ValueError:
- raise ValueError("'%s' is a non-numeric value" % entry_value)
-
- if validate:
- # parameters should be in ascending order by their key
- for prior_key in seen_keys:
- if prior_key > entry_key:
- raise ValueError("parameters must be sorted by their key")
-
- self.params[entry_key] = entry_value
- seen_keys.append(entry_key)
- except ValueError, exc:
- if not validate: continue
- raise ValueError("Unable to parse network status document's 'params' line (%s): %s'" % (exc, line))
+ self.params.update(self._parse_int_mappings(keyword, value, validate))
if validate:
self._check_params_constraints()
@@ -456,6 +436,17 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
if validate and value:
raise ValueError("A network status document's 'directory-footer' line shouldn't have any content, got '%s'" % line)
+ elif keyword == "bandwidth-weights":
+ self.bandwidth_weights = self._parse_int_mappings(keyword, value, validate)
+
+ if validate:
+ weight_keys = tuple(sorted(self.bandwidth_weights.keys()))
+
+ if weight_keys != BANDWIDTH_WEIGHT_ENTRIES:
+ expected_label = ', '.join(BANDWIDTH_WEIGHT_ENTRIES)
+ actual_label = ', '.join(weight_keys)
+
+ raise ValueError("A network status document's 'bandwidth-weights' entries should be '%s', got '%s'" % (expected_label, actual_label))
# doing this validation afterward so we know our 'is_consensus' and
# 'is_vote' attributes
@@ -493,11 +484,7 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
self.directory_authorities.append(DirectoryAuthority(dirauth_data, vote, validate))
_read_keyword_line("directory-footer", content, False, True)
-
- if not vote:
- read_keyword_line("bandwidth-weights", True)
- if self.bandwidth_weights != None and _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(" ")])
+ _read_keyword_line("bandwidth-weights", content, False, True)
while _peek_keyword(content) == "directory-signature":
signature_data = _read_until_keywords("directory-signature", content, False, True)
@@ -620,6 +607,42 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
if value < minimum or value > maximum:
raise ValueError("'%s' value on the params line must be in the range of %i - %i, was %i" % (key, minimum, maximum, value))
+
+ def _parse_int_mappings(self, keyword, value, validate):
+ # Parse a series of 'key=value' entries, checking the following:
+ # - values are integers
+ # - keys are sorted in lexical order
+
+ results, seen_keys = {}, []
+ for entry in value.split(" "):
+ try:
+ if not '=' in entry:
+ raise ValueError("must only have 'key=value' entries")
+
+ entry_key, entry_value = entry.split("=", 1)
+
+ try:
+ # the int() function accepts things like '+123', but we don't want to
+ if entry_value.startswith('+'):
+ raise ValueError()
+
+ entry_value = int(entry_value)
+ except ValueError:
+ raise ValueError("'%s' is a non-numeric value" % entry_value)
+
+ if validate:
+ # parameters should be in ascending order by their key
+ for prior_key in seen_keys:
+ if prior_key > entry_key:
+ raise ValueError("parameters must be sorted by their key")
+
+ results[entry_key] = entry_value
+ seen_keys.append(entry_key)
+ except ValueError, exc:
+ if not validate: continue
+ raise ValueError("Unable to parse network status document's '%s' line (%s): %s'" % (keyword, exc, value))
+
+ return results
class DirectoryAuthority(stem.descriptor.Descriptor):
"""
diff --git a/test/unit/descriptor/networkstatus/document.py b/test/unit/descriptor/networkstatus/document.py
index 6d93624..8ed1f0e 100644
--- a/test/unit/descriptor/networkstatus/document.py
+++ b/test/unit/descriptor/networkstatus/document.py
@@ -7,7 +7,7 @@ import unittest
import stem.version
from stem.descriptor import Flag
-from stem.descriptor.networkstatus import HEADER_STATUS_DOCUMENT_FIELDS, FOOTER_STATUS_DOCUMENT_FIELDS, DEFAULT_PARAMS, NetworkStatusDocument, DirectorySignature
+from stem.descriptor.networkstatus import HEADER_STATUS_DOCUMENT_FIELDS, FOOTER_STATUS_DOCUMENT_FIELDS, DEFAULT_PARAMS, BANDWIDTH_WEIGHT_ENTRIES, NetworkStatusDocument, DirectorySignature
NETWORK_STATUS_DOCUMENT_ATTR = {
"network-status-version": "3",
@@ -118,7 +118,7 @@ class TestNetworkStatusDocument(unittest.TestCase):
self.assertEqual(expected_known_flags, document.known_flags)
self.assertEqual(DEFAULT_PARAMS, document.params)
self.assertEqual([], document.directory_authorities)
- self.assertEqual(None, document.bandwidth_weights)
+ self.assertEqual({}, document.bandwidth_weights)
self.assertEqual([SIG], document.directory_signatures)
self.assertEqual([], document.get_unrecognized_lines())
@@ -554,4 +554,99 @@ class TestNetworkStatusDocument(unittest.TestCase):
document = NetworkStatusDocument(content, False)
self.assertEqual([SIG], document.directory_signatures)
self.assertEqual([], document.get_unrecognized_lines())
+
+ def test_bandwidth_wights_ok(self):
+ """
+ Parses a properly formed 'bandwidth-wights' line. Negative bandwidth
+ weights might or might not be valid. The spec doesn't say, so making sure
+ that we accept them.
+ """
+
+ weight_entries, expected = [], {}
+
+ for i in xrange(len(BANDWIDTH_WEIGHT_ENTRIES)):
+ key, value = BANDWIDTH_WEIGHT_ENTRIES[i], i - 5
+
+ weight_entries.append("%s=%i" % (key, value))
+ expected[key] = value
+
+ content = get_network_status_document({"bandwidth-weights": " ".join(weight_entries)})
+ document = NetworkStatusDocument(content)
+ self.assertEquals(expected, document.bandwidth_weights)
+
+ def test_bandwidth_wights_malformed(self):
+ """
+ Provides malformed content in the 'bandwidth-wights' line.
+ """
+
+ test_values = (
+ "Wbe",
+ "Wbe=",
+ "Wbe=a",
+ "Wbe=+7",
+ )
+
+ base_weight_entry = " ".join(["%s=5" % e for e in BANDWIDTH_WEIGHT_ENTRIES])
+ expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES if e != "Wbe"])
+
+ for test_value in test_values:
+ weight_entry = base_weight_entry.replace("Wbe=5", test_value)
+ content = get_network_status_document({"bandwidth-weights": weight_entry})
+
+ self.assertRaises(ValueError, NetworkStatusDocument, content)
+ document = NetworkStatusDocument(content, False)
+ self.assertEquals(expected, document.bandwidth_weights)
+
+ def test_bandwidth_wights_misordered(self):
+ """
+ Check that the 'bandwidth-wights' line is rejected if out of order.
+ """
+
+ weight_entry = " ".join(["%s=5" % e for e in reversed(BANDWIDTH_WEIGHT_ENTRIES)])
+ expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES])
+
+ content = get_network_status_document({"bandwidth-weights": weight_entry})
+ self.assertRaises(ValueError, NetworkStatusDocument, content)
+
+ document = NetworkStatusDocument(content, False)
+ self.assertEquals(expected, document.bandwidth_weights)
+
+ def test_bandwidth_wights_in_vote(self):
+ """
+ Tries adding a 'bandwidth-wights' line to a vote.
+ """
+
+ weight_entry = " ".join(["%s=5" % e for e in BANDWIDTH_WEIGHT_ENTRIES])
+ expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES])
+
+ content = get_network_status_document({"vote-status": "vote", "bandwidth-weights": weight_entry})
+ self.assertRaises(ValueError, NetworkStatusDocument, content)
+
+ document = NetworkStatusDocument(content, False)
+ self.assertEquals(expected, document.bandwidth_weights)
+
+ def test_bandwidth_wights_omissions(self):
+ """
+ Leaves entries out of the 'bandwidth-wights' line.
+ """
+
+ # try parsing an empty value
+
+ content = get_network_status_document({"bandwidth-weights": ""})
+ self.assertRaises(ValueError, NetworkStatusDocument, content)
+
+ document = NetworkStatusDocument(content, False)
+ self.assertEquals({}, document.bandwidth_weights)
+
+ # drop individual values
+
+ for missing_entry in BANDWIDTH_WEIGHT_ENTRIES:
+ weight_entries = ["%s=5" % e for e in BANDWIDTH_WEIGHT_ENTRIES if e != missing_entry]
+ expected = dict([(e, 5) for e in BANDWIDTH_WEIGHT_ENTRIES if e != missing_entry])
+
+ content = get_network_status_document({"bandwidth-weights": " ".join(weight_entries)})
+ self.assertRaises(ValueError, NetworkStatusDocument, content)
+
+ document = NetworkStatusDocument(content, False)
+ self.assertEquals(expected, document.bandwidth_weights)
1
0

13 Oct '12
commit 71240065e2a89ab1c8eeb9f865fddc294742cb8a
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Sep 22 12:46:03 2012 -0700
Refactoring how authority information is fetched
Authorities are another section, like the header, entries, and footer so
fetching it from the _get_document_content() helper. I'm not really happy with
this code yet, but it's a step in the right direction and lets us finally get
rid of _parse_old().
---
stem/descriptor/networkstatus.py | 99 +++++++++++++++++++------------------
1 files changed, 51 insertions(+), 48 deletions(-)
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index 33b0440..f279805 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -88,6 +88,10 @@ FOOTER_STATUS_DOCUMENT_FIELDS = (
HEADER_FIELDS = [attr[0] for attr in HEADER_STATUS_DOCUMENT_FIELDS]
FOOTER_FIELDS = [attr[0] for attr in FOOTER_STATUS_DOCUMENT_FIELDS]
+AUTH_START = "dir-source"
+ROUTERS_START = "r"
+FOOTER_START = "directory-footer"
+
DEFAULT_PARAMS = {
"bwweightscale": 10000,
"cbtdisabled": 0,
@@ -127,8 +131,8 @@ def parse_file(document_file, validate = True, is_microdescriptor = False):
* IOError if the file can't be read
"""
- header, footer, routers_end = _get_document_content(document_file, validate)
- document_data = header + footer
+ header, authorities, footer, routers_end = _get_document_content(document_file, validate)
+ document_data = header + authorities + footer
if not is_microdescriptor:
document = NetworkStatusDocument(document_data, validate)
@@ -142,9 +146,14 @@ def parse_file(document_file, validate = True, is_microdescriptor = False):
def _get_document_content(document_file, validate):
"""
- Network status documents consist of three sections: header, router entries,
- and the footer. This provides back a tuple with the following...
- (header, footer, routers_end)
+ Network status documents consist of four sections:
+ * header
+ * authority entries
+ * router entries
+ * footer
+
+ This provides back a tuple with the following...
+ (header, authorities, footer, routers_end)
This leaves the document_file at the start of the router entries.
@@ -158,22 +167,21 @@ def _get_document_content(document_file, validate):
* IOError if the file can't be read
"""
- # parse until the first router record
+ # parse until the first record of a following section
+ header = _read_until_keywords((AUTH_START, ROUTERS_START, FOOTER_START), document_file)
+ authorities = _read_until_keywords((ROUTERS_START, FOOTER_START), document_file)
- header = _read_until_keywords(("r", "directory-footer", "directory-signature"), document_file)
+ # skip router section, just taking note of the position
routers_start = document_file.tell()
-
- # figure out the network status version
-
- # TODO: we should pick either 'directory-footer' or 'directory-signature'
- # based on the header's network-status-version
-
- _read_until_keywords(("directory-footer", "directory-signature"), document_file, skip = True)
+ _read_until_keywords(FOOTER_START, document_file, skip = True)
routers_end = document_file.tell()
+
footer = document_file.readlines()
+ # leave our position at the start of the router section
document_file.seek(routers_start)
- return ("".join(header), "".join(footer), routers_end)
+
+ return ("".join(header), "".join(authorities), "".join(footer), routers_end)
def _get_routers(document_file, validate, document, end_position, router_type):
"""
@@ -198,6 +206,32 @@ def _get_routers(document_file, validate, document, end_position, router_type):
desc_content = "".join(_read_until_keywords("r", document_file, ignore_first = True, end_position = end_position))
yield router_type(desc_content, document, validate)
+def _get_authorities(authority_lines, is_vote, validate):
+ """
+ Iterates over the authoritiy entries in given content.
+
+ :param list authority_lines: lines of content to be parsed
+ :param bool is_vote: indicates if this is for a vote or contensus document
+ :param bool validate: True if the document is to be validated, False otherwise
+
+ :returns: DirectoryAuthority entries represented by the content
+
+ :raises: ValueError if the document is invalid
+ """
+
+ auth_buffer = []
+
+ for line in authority_lines:
+ if not line: continue
+ elif line.startswith(AUTH_START) and auth_buffer:
+ yield DirectoryAuthority("\n".join(auth_buffer), is_vote, validate)
+ auth_buffer = []
+
+ auth_buffer.append(line)
+
+ if auth_buffer:
+ yield DirectoryAuthority("\n".join(auth_buffer), is_vote, validate)
+
class NetworkStatusDocument(stem.descriptor.Descriptor):
"""
Version 3 network status document. This could be either a vote or consensus.
@@ -247,10 +281,11 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
self._unrecognized_lines = []
document_file = StringIO(raw_content)
- header_content, footer_content, routers_end = _get_document_content(document_file, validate)
+ header_content, authority_content, footer_content, routers_end = _get_document_content(document_file, validate)
self._header = _DocumentHeader(header_content, validate, default_params)
self._footer = _DocumentFooter(footer_content, validate, self._header)
+ self.directory_authorities = list(_get_authorities(authority_content.split("\n"), self._header.is_vote, validate))
for attr, value in vars(self._header).items() + vars(self._footer).items():
if attr != "_unrecognized_lines":
@@ -258,8 +293,6 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
else:
self._unrecognized_lines += value
- self._parse_old(header_content + footer_content, validate)
-
if document_file.tell() < routers_end:
self.routers = tuple(_get_routers(document_file, validate, self, routers_end, self._get_router_type()))
else:
@@ -283,36 +316,6 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
def get_unrecognized_lines(self):
return list(self._unrecognized_lines)
-
- def _parse_old(self, raw_content, validate):
- # preamble
- content = StringIO(raw_content)
- read_keyword_line = lambda keyword, optional = False: setattr(self, keyword.replace("-", "_"), _read_keyword_line(keyword, content, validate, optional))
-
- # ignore things the parse() method handles
- _read_keyword_line("network-status-version", content, False, True)
- _read_keyword_line("vote-status", content, False, True)
- _read_keyword_line("consensus-methods", content, False, True)
- _read_keyword_line("consensus-method", content, False, True)
- _read_keyword_line("published", content, False, True)
- _read_keyword_line("valid-after", content, False, True)
- _read_keyword_line("fresh-until", content, False, True)
- _read_keyword_line("valid-until", content, False, True)
- _read_keyword_line("voting-delay", content, False, True)
- _read_keyword_line("client-versions", content, False, True)
- _read_keyword_line("server-versions", content, False, True)
- _read_keyword_line("known-flags", content, False, True)
- _read_keyword_line("params", content, False, True)
-
- # authority section
- while _peek_keyword(content) == "dir-source":
- dirauth_data = _read_until_keywords(["dir-source", "r", "directory-footer", "directory-signature", "bandwidth-weights"], content, False, True)
- dirauth_data = "".join(dirauth_data).rstrip()
- self.directory_authorities.append(DirectoryAuthority(dirauth_data, self.is_vote, validate))
-
- _read_keyword_line("directory-footer", content, False, True)
- _read_keyword_line("bandwidth-weights", content, False, True)
- _read_keyword_line("directory-signature", content, False, True)
class _DocumentHeader(object):
def __init__(self, content, validate, default_params):
1
0

13 Oct '12
commit 72c561b9adb7e3963aa14da98b8d9bc77df533dd
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Sep 22 17:57:49 2012 -0700
Unit tests for parse_file() and router entries
On reflection the reason that the prior changes passed the unit tests so easily
was because the parse_file() function and inclusion of router status entries
was completely untested by my unit tests. The RouterStatusEntry class itself it
tested, but not its inclusion in a document. Integ tests would certainly cover
this, but I want the unit tests to exercise everything too.
Adding the missing tests and some fixes for issues that they revealed.
---
stem/descriptor/networkstatus.py | 16 ++++++-
test/unit/descriptor/networkstatus/document.py | 57 ++++++++++++++++++++++-
2 files changed, 68 insertions(+), 5 deletions(-)
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index 8774b95..2514a62 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -140,7 +140,7 @@ def parse_file(document_file, validate = True, is_microdescriptor = False):
routers_end = document_file.tell()
footer = document_file.readlines()
- document_content = header + footer
+ document_content = "".join(header + footer)
if not is_microdescriptor:
document = NetworkStatusDocument(document_content, validate)
@@ -200,7 +200,7 @@ def _get_entries(document_file, validate, entry_class, entry_keyword, start_posi
document_file.seek(start_position)
while document_file.tell() < end_position:
desc_content = "".join(_read_until_keywords(entry_keyword, document_file, ignore_first = True, end_position = end_position))
- yield router_type(desc_content, validate, *extra_args)
+ yield entry_class(desc_content, validate, *extra_args)
class NetworkStatusDocument(stem.descriptor.Descriptor):
"""
@@ -296,6 +296,12 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
def get_unrecognized_lines(self):
return list(self._unrecognized_lines)
+
+ def __cmp__(self, other):
+ if not isinstance(other, NetworkStatusDocument):
+ return 1
+
+ return str(self) > str(other)
class _DocumentHeader(object):
def __init__(self, document_file, validate, default_params):
@@ -994,6 +1000,12 @@ class RouterStatusEntry(stem.descriptor.Descriptor):
"""
return list(self._unrecognized_lines)
+
+ def __cmp__(self, other):
+ if not isinstance(other, RouterStatusEntry):
+ return 1
+
+ return str(self) > str(other)
class MicrodescriptorConsensus(NetworkStatusDocument):
"""
diff --git a/test/unit/descriptor/networkstatus/document.py b/test/unit/descriptor/networkstatus/document.py
index ad79dde..7570e8c 100644
--- a/test/unit/descriptor/networkstatus/document.py
+++ b/test/unit/descriptor/networkstatus/document.py
@@ -4,10 +4,12 @@ Unit tests for the NetworkStatusDocument of stem.descriptor.networkstatus.
import datetime
import unittest
+import StringIO
import stem.version
from stem.descriptor import Flag
-from stem.descriptor.networkstatus import HEADER_STATUS_DOCUMENT_FIELDS, FOOTER_STATUS_DOCUMENT_FIELDS, DEFAULT_PARAMS, BANDWIDTH_WEIGHT_ENTRIES, NetworkStatusDocument, DocumentSignature
+from stem.descriptor.networkstatus import HEADER_STATUS_DOCUMENT_FIELDS, FOOTER_STATUS_DOCUMENT_FIELDS, DEFAULT_PARAMS, BANDWIDTH_WEIGHT_ENTRIES, RouterStatusEntry, NetworkStatusDocument, DocumentSignature, parse_file
+from test.unit.descriptor.networkstatus.entry import get_router_status_entry
sig_block = """\
-----BEGIN SIGNATURE-----
@@ -33,7 +35,6 @@ NETWORK_STATUS_DOCUMENT_ATTR = {
"directory-signature": "%s %s\n%s" % (SIG.identity, SIG.key_digest, SIG.signature),
}
-
def get_network_status_document(attr = None, exclude = None, routers = None):
"""
Constructs a minimal network status document with the given attributes. This
@@ -89,7 +90,13 @@ def get_network_status_document(attr = None, exclude = None, routers = None):
if attr_value: attr_value = " %s" % attr_value
remainder.append(attr_keyword + attr_value)
- return "\n".join(header_content + remainder + routers + footer_content)
+ # join the routers into a single block, then split it into lines
+ if routers:
+ router_lines = ("\n".join([str(r) for r in routers])).split("\n")
+ else:
+ router_lines = []
+
+ return "\n".join(header_content + remainder + router_lines + footer_content)
class TestNetworkStatusDocument(unittest.TestCase):
def test_minimal_consensus(self):
@@ -156,6 +163,27 @@ class TestNetworkStatusDocument(unittest.TestCase):
self.assertEqual([SIG], document.signatures)
self.assertEqual([], document.get_unrecognized_lines())
+ def test_parse_file(self):
+ """
+ Try parsing a document via the parse_file() function.
+ """
+
+ entry1 = RouterStatusEntry(get_router_status_entry({'s': "Fast"}))
+ entry2 = RouterStatusEntry(get_router_status_entry({'s': "Valid"}))
+ content = get_network_status_document(routers = (entry1, entry2))
+
+ # the document that the entries refer to should actually be the minimal
+ # descriptor (ie, without the entries)
+
+ expected_document = NetworkStatusDocument(get_network_status_document())
+
+ descriptor_file = StringIO.StringIO(content)
+ entries = list(parse_file(descriptor_file))
+
+ self.assertEquals(entry1, entries[0])
+ self.assertEquals(entry2, entries[1])
+ self.assertEquals(expected_document, entries[0].document)
+
def test_missing_fields(self):
"""
Excludes mandatory fields from both a vote and consensus document.
@@ -680,4 +708,27 @@ class TestNetworkStatusDocument(unittest.TestCase):
content = get_network_status_document({"directory-signature": "%s %s\n%s" % tuple(attrs)})
self.assertRaises(ValueError, NetworkStatusDocument, content)
NetworkStatusDocument(content, False) # checks that it's still parseable without validation
+
+ def test_with_router_status_entries(self):
+ """
+ Includes a router status entry within the document. This isn't to test the
+ RouterStatusEntry parsing but rather the inclusion of it within the
+ document.
+ """
+
+ entry1 = RouterStatusEntry(get_router_status_entry({'s': "Fast"}))
+ entry2 = RouterStatusEntry(get_router_status_entry({'s': "Valid"}))
+ content = get_network_status_document(routers = (entry1, entry2))
+
+ document = NetworkStatusDocument(content)
+ self.assertEquals((entry1, entry2), document.routers)
+
+ # try with an invalid RouterStatusEntry
+
+ entry3 = RouterStatusEntry(get_router_status_entry({'r': "ugabuga"}), False)
+ content = get_network_status_document(routers = (entry3,))
+
+ self.assertRaises(ValueError, NetworkStatusDocument, content)
+ document = NetworkStatusDocument(content, False)
+ self.assertEquals((entry3,), document.routers)
1
0
commit 7a13a14c337ecf30162e82d75c904910af884b11
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Sep 22 18:06:20 2012 -0700
Unit test for misordered content
I added a test a while back to check that we detect misorderd document
attributes, but it didn't pass at the time so I skipped it. The document
parsing rewrite is almost done so enabling the test.
---
test/unit/descriptor/networkstatus/document.py | 5 ++++-
1 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/test/unit/descriptor/networkstatus/document.py b/test/unit/descriptor/networkstatus/document.py
index 7570e8c..42d2842 100644
--- a/test/unit/descriptor/networkstatus/document.py
+++ b/test/unit/descriptor/networkstatus/document.py
@@ -214,12 +214,15 @@ class TestNetworkStatusDocument(unittest.TestCase):
Rearranges our descriptor fields.
"""
- self.skipTest("Needs a parser rewrite first")
for is_consensus in (True, False):
attr = {"vote-status": "consensus"} if is_consensus else {"vote-status": "vote"}
lines = get_network_status_document(attr).split("\n")
for i in xrange(len(lines) - 1):
+ # once we reach the crypto blob we're done since swapping those won't
+ # be detected
+ if lines[i].startswith("e1XH33"): break
+
# swaps this line with the one after it
test_lines = list(lines)
test_lines[i], test_lines[i + 1] = test_lines[i + 1], test_lines[i]
1
0
commit 6ee5abbe5c1137b4a311d5b7ef5003874982008e
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Sep 23 14:20:49 2012 -0700
Initial KeyCertificate implementation
First stab at parsing the authority section's key certificates. This is
completely untested, next step is to write some unit tests for it.
---
stem/descriptor/networkstatus.py | 170 ++++++++++++++++++++++++++++++++++++++
1 files changed, 170 insertions(+), 0 deletions(-)
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index 17a9877..46adbfe 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -106,6 +106,21 @@ DEFAULT_PARAMS = {
"cbtinitialtimeout": 60000,
}
+# KeyCertificate fields, tuple is of the form...
+# (keyword, is_mandatory)
+
+KEY_CERTIFICATE_PARAMS = (
+ ('dir-key-certificate-version', True),
+ ('dir-address', False),
+ ('fingerprint', True),
+ ('dir-identity-key', True),
+ ('dir-key-published', True),
+ ('dir-key-expires', True),
+ ('dir-signing-key', True),
+ ('dir-key-crosscert', False),
+ ('dir-key-certification', True),
+)
+
BANDWIDTH_WEIGHT_ENTRIES = (
"Wbd", "Wbe", "Wbg", "Wbm",
"Wdb",
@@ -768,6 +783,161 @@ class DirectoryAuthority(stem.descriptor.Descriptor):
return self.unrecognized_lines
+class KeyCertificate(stem.descriptor.Descriptor):
+ """
+ Directory key certificate for a v3 network status document.
+
+ :var int version: **\*** version of the key certificate
+ :var str address: authority's IP address
+ :var int dir_port: authority's DirPort
+ :var str fingerprint: **\*** authority's fingerprint
+ :var str identity_key: **\*** long term authority identity key
+ :var datetime published: **\*** time when this key was generated
+ :var datetime expires: **\*** time 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
+
+ **\*** mandatory attribute
+ """
+
+ def __init__(self, raw_content, validate):
+ super(KeyCertificate, self).__init__(raw_content)
+
+ self.version = None
+ self.address = None
+ self.dir_port = None
+ self.fingerprint = None
+ self.identity_key = None
+ self.published = None
+ self.expires = None
+ self.signing_key = None
+ self.crosscert = None
+ self.certification = None
+
+ self._unrecognized_lines = []
+
+ self._parse(raw_contents, validate)
+
+ def _parse(self, content, validate):
+ """
+ Parses the given content and applies the attributes.
+
+ :param str content: descriptor content
+ :param bool validate: checks validity if True
+
+ :raises: ValueError if a validity check fails
+ """
+
+ entries, first_keyword, last_keyword, _ = stem.descriptor._get_descriptor_components(content, validate)
+
+ if validate:
+ if first_keyword != 'dir-key-certificate-version':
+ raise ValueError("Key certificates must start with a 'dir-key-certificate-version' line:\n%s" % (content))
+ elif last_keyword != 'dir-key-certification':
+ raise ValueError("Key certificates must end with a 'dir-key-certification' line:\n%s" % (content))
+
+ # check that we have mandatory fields and that our known fields only
+ # appear once
+
+ for keyword, is_mandatory in KEY_CERTIFICATE_PARAMS:
+ if is_mandatory and not keyword in entries:
+ raise ValueError("Key certificates must have a '%s' line:\n%s" % (keyword, content))
+
+ entry_count = len(entries.get(keyword, []))
+ if entry_count > 1:
+ raise ValueError("Key certificates can only have a single '%s' line, got %i:\n%s" % (keyword, entry_count, content))
+
+ # Check that our field's order matches the spec. This isn't explicitely
+ # stated in the spec, but the network status document requires a specific
+ # order so it stands to reason that the key certificate (which is in it)
+ # needs ot match a prescribed order too.
+
+ fields = [attr[0] for attr in KEY_CERTIFICATE_PARAMS]
+ _check_for_misordered_fields(entries, fields)
+
+ for keyword, values in entries.items():
+ value, block_contents = values[0]
+ line = "%s %s" % (keyword, value)
+
+ if keyword == 'dir-key-certificate-version':
+ # "dir-key-certificate-version" version
+
+ if not value.isdigit():
+ if not validate: continue
+ raise ValueError("Key certificate has a non-integer version: %s" % line)
+
+ self.version = int(value)
+
+ if validate and self.version != 3:
+ raise ValueError("Expected a version 3 key certificate, got version '%i' instead" % self.version)
+ elif keyword == 'dir-address':
+ # "dir-address" IPPort
+
+ if not ':' in value:
+ if not validate: continue
+ raise ValueError("Key certificate's 'dir-address' is expected to be of the form ADDRESS:PORT: %s" % line)
+
+ address, dirport = value.split(':', 1)
+
+ if validate:
+ if not stem.util.connection.is_valid_ip_address(address):
+ raise ValueError("Key certificate's address isn't a valid IPv4 address: %s" % line)
+ elif not stem.util.connection.is_valid_port(dirport):
+ raise ValueError("Key certificate's dirport is invalid: %s" % line)
+
+ self.address = address
+ self.dir_port = dirport
+ elif keyword == 'fingerprint':
+ # "fingerprint" fingerprint
+
+ if validate and not stem.util.tor_tools.is_valid_fingerprint(value):
+ raise ValueError("Key certificate's fingerprint is malformed: %s" % line)
+
+ self.fingerprint = value
+ elif keyword in ('dir-key-published', 'dir-key-expires'):
+ # "dir-key-published" YYYY-MM-DD HH:MM:SS
+ # "dir-key-expires" YYYY-MM-DD HH:MM:SS
+
+ try:
+ date_value = datetime.datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
+
+ if keyword == 'dir-key-published':
+ self.published = date_value
+ elif keyword == 'dir-key-expires':
+ self.expires = date_value
+ except ValueError:
+ if validate:
+ raise ValueError("Key certificate's '%s' time wasn't parseable: %s" % (keyword, value))
+ elif keyword in ('dir-identity-key', 'dir-signing-key', 'dir-key-crosscert', 'dir-key-certification'):
+ # "dir-identity-key" NL a public key in PEM format
+ # "dir-signing-key" NL a key in PEM format
+ # "dir-key-crosscert" NL CrossSignature
+ # "dir-key-certification" NL Signature
+
+ if validate and not block_contents:
+ raise ValueError("Key certificate's '%s' line must be followed by a key block: %s" % (keyword, line))
+
+ if keyword == 'dir-identity-key':
+ self.identity_key = block_contents
+ elif keyword == 'dir-signing-key':
+ self.signing_key = block_contents
+ elif keyword == 'dir-key-crosscert':
+ self.crosscert = block_contents
+ elif keyword == 'dir-key-certification':
+ self.certification = block_contents
+ else:
+ self._unrecognized_lines.append(line)
+
+ def get_unrecognized_lines(self):
+ """
+ Returns any unrecognized lines.
+
+ :returns: a list of unrecognized lines
+ """
+
+ return self.unrecognized_lines
+
# TODO: microdescriptors have a slightly different format (including a
# 'method') - should probably be a subclass
class DocumentSignature(object):
1
0
commit 2e65e58568469f460fedf965164b1355fae61204
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Sep 22 19:01:16 2012 -0700
Begin revising the DirectoryAuthority
God damnit. I thought I was almost done but, on reading the spec some more, the
key certs and authority entries deserve their own descriptor type just like the
router status entries. Does this document have no end?
Saving the top-down approach I was taking but putting it on ice for now. I
should work at this from a bottom-up fashion instead, starting with the key
certs and starting a new batch of tests for it. Fun fun. :(
---
stem/descriptor/networkstatus.py | 103 ++++++++++++++++++++++++++++++--------
1 files changed, 81 insertions(+), 22 deletions(-)
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index 2514a62..17a9877 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -649,37 +649,96 @@ def _parse_int_mappings(keyword, value, validate):
class DirectoryAuthority(stem.descriptor.Descriptor):
"""
- Contains directory authority information obtained from v3 network status
- documents.
-
- :var str nickname: directory authority's nickname
- :var str fingerprint: uppercase hex fingerprint of the authority's identity key
- :var str address: hostname
- :var str ip: current IP address
- :var int dir_port: current directory port
- :var int or_port: 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.KeyCertificate` key_certificate: **^** directory authority's current key certificate
- :var str vote_digest: **~** digest of the authority that contributed to the consensus
-
- | **^** attribute appears only in votes
- | **~** attribute appears only in consensuses
- | legacy_dir_key is the only optional attribute
+ Directory authority information obtained from a v3 network status document.
+
+ :var str nickname: **\*** authority's nickname
+ :var str fingerprint: **\*** authority's fingerprint
+ :var str hostname: **\*** hostname of the authority
+ :var str address: **\*** authority's IP address
+ :var int dir_port: **\*** authority's DirPort
+ :var int or_port: **\*** authority's ORPort
+ :var str contact: **\*** contact information
+
+ **Consensus Attributes:**
+ :var str vote_digest: **\*** digest of the authority that contributed to the consensus
+
+ **Vote Attributes:**
+ :var str legacy_dir_key: fingerprint of and obsolete identity key
+ :var :class:`stem.descriptor.KeyCertificate` key_certificate: **\*** authority's key certificate
+
+ **\*** mandatory attribute
"""
- def __init__(self, raw_content, validate, vote = True):
+ def __init__(self, raw_content, validate, is_vote = True):
"""
- Parse a directory authority entry in a v3 network status document and
- provide a DirectoryAuthority object.
+ Parse a directory authority entry in a v3 network status document.
:param str raw_content: raw directory authority entry information
- :param bool validate: True if the document is to be validated, False otherwise
+ :param bool validate: checks the validity of the content if True, skips these checks otherwise
+ :param bool is_vote: True if this is for a vote, False if it's for a consensus
- :raises: ValueError if the raw data is invalid
+ :raises: ValueError if the descriptor data is invalid
"""
super(DirectoryAuthority, self).__init__(raw_content)
+
+ self.nickname = None
+ self.fingerprint = None
+ self.address = None
+ self.dir_port = None
+ self.or_port = None
+ self.contact = None
+
+ self.vote_digest = None
+
+ self.legacy_dir_key = None
+ self.key_certificate = None
+
+ self._unrecognized_lines = []
+
+ #self._parse(raw_contents, validate, is_vote)
+ self._parse_old(raw_contents, validate, is_vote)
+
+ def _parse(self, content, validate, is_vote):
+ """
+ Parses the given content and applies the attributes.
+
+ :param str content: descriptor content
+ :param bool validate: checks validity if True
+ :param bool is_vote: True if this is for a vote, False if it's for a consensus
+
+ :raises: ValueError if a validity check fails
+ """
+
+ entries, first_keyword, _, _ = stem.descriptor._get_descriptor_components(content, validate)
+
+ if validate and first_keyword != 'dir-source':
+ raise ValueError("Authority entries are expected to start with a 'dir-source' line:\n%s" % (content))
+
+ # check that we have mandatory fields
+ if validate:
+ required_fields = ["dir-source", "contact"]
+
+ if is_vote:
+ pass
+ #required_fields += ... god damnit
+ else:
+ required_fields += ["vote-digest"]
+
+
+ for keyword in required_fields:
+ if not keyword in entries:
+ raise ValueError("Authority entries must have a '%s' line:\n%s" % (keyword, content))
+
+ for keyword, values in entries.items():
+ value, block_contents = values[0]
+ line = "%s %s" % (keyword, value)
+
+ # all known attributes can only appear at most once
+ #if validate and len(values) > 1 and keyword in ('r', 's', 'v', 'w', 'p'):
+ # raise ValueError("Router status entries can only have a single '%s' line, got %i:\n%s" % (key, len(values), content))
+
+ def _parse_old(self, content, validate, is_vote):
self.nickname, self.fingerprint, self.address, self.ip = None, None, None, None
self.dir_port, self.or_port, self.legacy_dir_key = None, None, None
self.key_certificate, self.contact, self.vote_digest = None, None, None
1
0

13 Oct '12
commit 5d1a11fa4f479cac4d4987d9841889ea251b2eba
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Sep 26 09:00:22 2012 -0700
Unit test for parsing a minimal key certificate
Test for minimal key certificate parsing, and related fixes for the
KeyCertificate class. The main gotcha in this is that KeyCertificates don't
need to have a prescribed order (unlike other network status document
fields).
---
run_tests.py | 2 +
stem/descriptor/networkstatus.py | 14 +---
test/unit/descriptor/networkstatus/__init__.py | 2 +-
.../descriptor/networkstatus/key_certificate.py | 79 ++++++++++++++++++++
4 files changed, 85 insertions(+), 12 deletions(-)
diff --git a/run_tests.py b/run_tests.py
index af2d09f..54e74b2 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -21,6 +21,7 @@ import test.unit.descriptor.reader
import test.unit.descriptor.server_descriptor
import test.unit.descriptor.extrainfo_descriptor
import test.unit.descriptor.networkstatus.entry
+import test.unit.descriptor.networkstatus.key_certificate
import test.unit.descriptor.networkstatus.document
import test.unit.response.control_line
import test.unit.response.control_message
@@ -117,6 +118,7 @@ UNIT_TESTS = (
test.unit.descriptor.server_descriptor.TestServerDescriptor,
test.unit.descriptor.extrainfo_descriptor.TestExtraInfoDescriptor,
test.unit.descriptor.networkstatus.entry.TestRouterStatusEntry,
+ test.unit.descriptor.networkstatus.key_certificate.TestKeyCertificate,
test.unit.descriptor.networkstatus.document.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 46adbfe..01fb2f3 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -801,7 +801,7 @@ class KeyCertificate(stem.descriptor.Descriptor):
**\*** mandatory attribute
"""
- def __init__(self, raw_content, validate):
+ def __init__(self, raw_content, validate = True):
super(KeyCertificate, self).__init__(raw_content)
self.version = None
@@ -817,7 +817,7 @@ class KeyCertificate(stem.descriptor.Descriptor):
self._unrecognized_lines = []
- self._parse(raw_contents, validate)
+ self._parse(raw_content, validate)
def _parse(self, content, validate):
"""
@@ -847,14 +847,6 @@ class KeyCertificate(stem.descriptor.Descriptor):
entry_count = len(entries.get(keyword, []))
if entry_count > 1:
raise ValueError("Key certificates can only have a single '%s' line, got %i:\n%s" % (keyword, entry_count, content))
-
- # Check that our field's order matches the spec. This isn't explicitely
- # stated in the spec, but the network status document requires a specific
- # order so it stands to reason that the key certificate (which is in it)
- # needs ot match a prescribed order too.
-
- fields = [attr[0] for attr in KEY_CERTIFICATE_PARAMS]
- _check_for_misordered_fields(entries, fields)
for keyword, values in entries.items():
value, block_contents = values[0]
@@ -936,7 +928,7 @@ class KeyCertificate(stem.descriptor.Descriptor):
:returns: a list of unrecognized lines
"""
- return self.unrecognized_lines
+ return self._unrecognized_lines
# TODO: microdescriptors have a slightly different format (including a
# 'method') - should probably be a subclass
diff --git a/test/unit/descriptor/networkstatus/__init__.py b/test/unit/descriptor/networkstatus/__init__.py
index d8483e2..d8c9657 100644
--- a/test/unit/descriptor/networkstatus/__init__.py
+++ b/test/unit/descriptor/networkstatus/__init__.py
@@ -2,5 +2,5 @@
Unit tests for stem.descriptor.networkstatus.
"""
-__all__ = ["entry", "document"]
+__all__ = ["entry", "key_certificate", "document"]
diff --git a/test/unit/descriptor/networkstatus/key_certificate.py b/test/unit/descriptor/networkstatus/key_certificate.py
new file mode 100644
index 0000000..2d779e7
--- /dev/null
+++ b/test/unit/descriptor/networkstatus/key_certificate.py
@@ -0,0 +1,79 @@
+"""
+Unit tests for the KeyCertificate of stem.descriptor.networkstatus.
+"""
+
+import datetime
+import unittest
+
+from stem.descriptor.networkstatus import KeyCertificate
+
+sig_block = """\
+-----BEGIN %s-----
+MIGJAoGBAJ5itcJRYNEM3Qf1OVWLRkwjqf84oXPc2ZusaJ5zOe7TVvBMra9GNyc0
+NM9y6zVkHCAePAjr4KbW/8P1olA6FUE2LV9bozaU1jFf6K8B2OELKs5FUEW+n+ic
+GM0x6MhngyXonWOcKt5Gj+mAu5lrno9tpNbPkz2Utr/Pi0nsDhWlAgMBAAE=
+-----END %s-----\
+"""
+
+RSA_SIG = sig_block % ("RSA PUBLIC KEY", "RSA PUBLIC KEY")
+KEY_SIG = sig_block % ("SIGNATURE", "SIGNATURE")
+
+KEY_CERTIFICATE_ATTR = (
+ ("dir-key-certificate-version", "3"),
+ ("fingerprint", "27B6B5996C426270A5C95488AA5BCEB6BCC86956"),
+ ("dir-key-published", "2011-11-28 21:51:04"),
+ ("dir-key-expires", "2012-11-28 21:51:04"),
+ ("dir-identity-key", "\n" + RSA_SIG),
+ ("dir-signing-key", "\n" + RSA_SIG),
+ ("dir-key-certification", "\n" + KEY_SIG),
+)
+
+def get_key_certificate(attr = None, exclude = None):
+ """
+ Constructs a minimal key certificate with the given attributes.
+
+ :param dict attr: keyword/value mappings to be included in the entry
+ :param list exclude: mandatory keywords to exclude from the entry
+
+ :returns: str with customized key certificate content
+ """
+
+ descriptor_lines = []
+ if attr is None: attr = {}
+ if exclude is None: exclude = []
+ attr = dict(attr) # shallow copy since we're destructive
+
+ for keyword, value in KEY_CERTIFICATE_ATTR:
+ if keyword in exclude: continue
+ elif keyword in attr:
+ value = attr[keyword]
+ del attr[keyword]
+
+ descriptor_lines.append("%s %s" % (keyword, value))
+
+ # dump in any unused attributes
+ for attr_keyword, attr_value in attr.items():
+ descriptor_lines.append("%s %s" % (attr_keyword, attr_value))
+
+ return "\n".join(descriptor_lines)
+
+class TestKeyCertificate(unittest.TestCase):
+ def test_minimal(self):
+ """
+ Parses a minimal key certificate.
+ """
+
+ certificate = KeyCertificate(get_key_certificate())
+
+ self.assertEqual(3, certificate.version)
+ self.assertEqual(None, certificate.address)
+ self.assertEqual(None, certificate.dir_port)
+ self.assertEqual("27B6B5996C426270A5C95488AA5BCEB6BCC86956", certificate.fingerprint)
+ self.assertEqual(RSA_SIG, certificate.identity_key)
+ self.assertEqual(datetime.datetime(2011, 11, 28, 21, 51, 4), certificate.published)
+ self.assertEqual(datetime.datetime(2012, 11, 28, 21, 51, 4), certificate.expires)
+ self.assertEqual(RSA_SIG, certificate.signing_key)
+ self.assertEqual(None, certificate.crosscert)
+ self.assertEqual(KEY_SIG, certificate.certification)
+ self.assertEqual([], certificate.get_unrecognized_lines())
+
1
0

13 Oct '12
commit a5e8d26381fc9787a8cbc147497e4fa397d70fc5
Author: Damian Johnson <atagar(a)torproject.org>
Date: Thu Sep 27 08:22:12 2012 -0700
Generalizing mocking.get_server_descriptor()
All of the descriptor unit tests (server, extrainfo, and document components)
need a similar type of mock data. I've been doing this in an adhoc fashion but
this can stand for a bit of uniformity. Starting off by cleaning up the server
descriptor mocking.
As per the convention for the mocking module, making it provide a mock object
by default with an option to get the raw content.
---
test/mocking.py | 119 +++++++++++++++++++++-------
test/unit/descriptor/export.py | 17 ++--
test/unit/descriptor/server_descriptor.py | 79 ++++++++-----------
3 files changed, 131 insertions(+), 84 deletions(-)
diff --git a/test/mocking.py b/test/mocking.py
index 1deaf57..d0e6148 100644
--- a/test/mocking.py
+++ b/test/mocking.py
@@ -21,9 +21,10 @@ calling :func:`test.mocking.revert_mocking`.
raise_exception - raises an exception when called
Instance Constructors
- get_message - stem.socket.ControlMessage
- get_protocolinfo_response - stem.response.protocolinfo.ProtocolInfoResponse
- get_server_descriptor - text for a tor server descriptor
+ get_message - stem.socket.ControlMessage
+ get_protocolinfo_response - stem.response.protocolinfo.ProtocolInfoResponse
+ get_relay_server_descriptor - stem.descriptor.server_descriptor.RelayDescriptor
+ get_bridge_server_descriptor - stem.descriptor.server_descriptor.BridgeDescriptor
"""
import inspect
@@ -33,6 +34,7 @@ import __builtin__
import stem.response
import stem.socket
+import stem.descriptor.server_descriptor
# Once we've mocked a function we can't rely on its __module__ or __name__
# attributes, so instead we associate a unique 'mock_id' attribute that maps
@@ -52,17 +54,20 @@ skFtXhOHHqTRN4GPPrZsAIUOQGzQtGb66IQgT4tO/pj+P6QmSCCdTfhvGfgTCsC+
WPi4Fl2qryzTb3QO5r5x7T8OsG2IBUET1bLQzmtbC560SYR49IvVAgMBAAE=
"""
-RELAY_DESCRIPTOR_ATTR = (
+SERVER_DESCRIPTOR_HEADER = (
("router", "caerSidi 71.35.133.197 9001 0 0"),
("published", "2012-03-01 17:15:27"),
("bandwidth", "153600 256000 104590"),
("reject", "*:*"),
("onion-key", "\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----" % CRYPTO_BLOB),
("signing-key", "\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----" % CRYPTO_BLOB),
+)
+
+SERVER_DESCRIPTOR_FOOTER = (
("router-signature", "\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB),
)
-BRIDGE_DESCRIPTOR_ATTR = (
+BRIDGE_DESCRIPTOR_HEADER = (
("router", "Unnamed 10.45.227.253 9001 0 0"),
("router-digest", "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4"),
("published", "2012-03-22 17:34:38"),
@@ -302,41 +307,95 @@ def get_protocolinfo_response(**attributes):
return protocolinfo_response
-def get_server_descriptor(attr = None, exclude = None, is_bridge = False):
+def _get_descriptor_content(attr = None, exclude = (), header_template = (), footer_template = ()):
"""
- Constructs a minimal server descriptor with the given attributes.
+ Constructs a minimal descriptor with the given attributes. The content we
+ provide back is of the form...
+
+ * header_template (with matching attr filled in)
+ * unused attr entries
+ * footer_template (with matching attr filled in)
+
+ So for instance...
+
+ ::
+
+ get_descriptor_content(
+ attr = {'nickname': 'caerSidi', 'contact': 'atagar'},
+ header_template = (
+ ('nickname', 'foobar'),
+ ('fingerprint', '12345'),
+ ),
+ )
+
+ ... would result in...
+
+ ::
+
+ nickname caerSidi
+ fingerprint 12345
+ contact atagar
:param dict attr: keyword/value mappings to be included in the descriptor
:param list exclude: mandatory keywords to exclude from the descriptor
- :param bool is_bridge: minimal descriptor is for a bridge if True, relay otherwise
+ :param tuple header_template: key/value pairs for mandatory fields before unrecognized content
+ :param tuple footer_template: key/value pairs for mandatory fields after unrecognized content
- :returns: str with customized descriptor content
+ :returns: str with the requested descriptor content
"""
- descriptor_lines = []
+ header_content, footer_content = [], []
if attr is None: attr = {}
- if exclude is None: exclude = []
- desc_attr = BRIDGE_DESCRIPTOR_ATTR if is_bridge else RELAY_DESCRIPTOR_ATTR
attr = dict(attr) # shallow copy since we're destructive
- for keyword, value in desc_attr:
- if keyword in exclude: continue
- elif keyword in attr:
- value = attr[keyword]
- del attr[keyword]
-
- # if this is the last entry then we should dump in any unused attributes
- if not is_bridge and keyword == "router-signature":
- for attr_keyword, attr_value in attr.items():
- descriptor_lines.append("%s %s" % (attr_keyword, attr_value))
-
- descriptor_lines.append("%s %s" % (keyword, value))
+ for content, template in ((header_content, header_template),
+ (footer_content, footer_template)):
+ for keyword, value in template:
+ if keyword in exclude: continue
+ elif keyword in attr:
+ value = attr[keyword]
+ del attr[keyword]
+
+ content.append("%s %s" % (keyword, value))
- # bridges don't have a router-signature so simply append any extra attributes
- # to the end
- if is_bridge:
- for attr_keyword, attr_value in attr.items():
- descriptor_lines.append("%s %s" % (attr_keyword, attr_value))
+ remainder = ["%s %s" % (k, v) for k, v in attr.items()]
+ return "\n".join(header_content + remainder + footer_content)
+
+def get_relay_server_descriptor(attr = None, exclude = (), content = False):
+ """
+ Provides the descriptor content for...
+ stem.descriptor.server_descriptor.RelayDescriptor
- return "\n".join(descriptor_lines)
+ :param dict attr: keyword/value mappings to be included in the descriptor
+ :param list exclude: mandatory keywords to exclude from the descriptor
+ :param bool content: provides the str content of the descriptor rather than the class if True
+
+ :returns: RelayDescriptor for the requested descriptor content
+ """
+
+ desc_content = _get_descriptor_content(attr, exclude, SERVER_DESCRIPTOR_HEADER, SERVER_DESCRIPTOR_FOOTER)
+
+ if content:
+ return desc_content
+ else:
+ return stem.descriptor.server_descriptor.RelayDescriptor(desc_content, validate = False)
+
+def get_bridge_server_descriptor(attr = None, exclude = (), content = False):
+ """
+ Provides the descriptor content for...
+ stem.descriptor.server_descriptor.BridgeDescriptor
+
+ :param dict attr: keyword/value mappings to be included in the descriptor
+ :param list exclude: mandatory keywords to exclude from the descriptor
+ :param bool content: provides the str content of the descriptor rather than the class if True
+
+ :returns: BridgeDescriptor for the requested descriptor content
+ """
+
+ desc_content = _get_descriptor_content(attr, exclude, BRIDGE_DESCRIPTOR_HEADER)
+
+ if content:
+ return desc_content
+ else:
+ return stem.descriptor.server_descriptor.BridgeDescriptor(desc_content, validate = False)
diff --git a/test/unit/descriptor/export.py b/test/unit/descriptor/export.py
index 6ef52cb..463171d 100644
--- a/test/unit/descriptor/export.py
+++ b/test/unit/descriptor/export.py
@@ -6,8 +6,7 @@ import StringIO
import unittest
from stem.descriptor.export import export_csv, export_csv_file
-from stem.descriptor.server_descriptor import RelayDescriptor, BridgeDescriptor
-from test.mocking import get_server_descriptor
+from test.mocking import get_relay_server_descriptor, get_bridge_server_descriptor
class TestExport(unittest.TestCase):
def test_minimal_descriptor(self):
@@ -15,7 +14,7 @@ class TestExport(unittest.TestCase):
Exports a single minimal tor server descriptor.
"""
- desc = RelayDescriptor(get_server_descriptor())
+ desc = get_relay_server_descriptor()
desc_csv = export_csv(desc, included_fields = ('nickname', 'address', 'published'), header = False)
expected = "caerSidi,71.35.133.197,2012-03-01 17:15:27\n"
@@ -36,7 +35,7 @@ class TestExport(unittest.TestCase):
for nickname in nicknames:
router_line = "%s 71.35.133.197 9001 0 0" % nickname
- descriptors.append(RelayDescriptor(get_server_descriptor({'router': router_line})))
+ descriptors.append(get_relay_server_descriptor({'router': router_line}))
expected = "\n".join(nicknames) + "\n"
self.assertEqual(expected, export_csv(descriptors, included_fields = ('nickname',), header = False))
@@ -47,7 +46,7 @@ class TestExport(unittest.TestCase):
the same output as export_csv().
"""
- desc = RelayDescriptor(get_server_descriptor())
+ desc = get_relay_server_descriptor()
desc_csv = export_csv(desc)
csv_buffer = StringIO.StringIO()
@@ -60,7 +59,7 @@ class TestExport(unittest.TestCase):
Checks that the default attributes for our csv output doesn't include private fields.
"""
- desc = RelayDescriptor(get_server_descriptor())
+ desc = get_relay_server_descriptor()
desc_csv = export_csv(desc)
self.assertTrue(',signature' in desc_csv)
@@ -79,7 +78,7 @@ class TestExport(unittest.TestCase):
Attempts to make a csv with attributes that don't exist.
"""
- desc = RelayDescriptor(get_server_descriptor())
+ desc = get_relay_server_descriptor()
self.assertRaises(ValueError, export_csv, desc, ('nickname', 'blarg!'))
def test_multiple_descriptor_types(self):
@@ -87,7 +86,7 @@ class TestExport(unittest.TestCase):
Attempts to make a csv with multiple descriptor types.
"""
- server_desc = RelayDescriptor(get_server_descriptor())
- bridge_desc = BridgeDescriptor(get_server_descriptor(is_bridge = True))
+ server_desc = get_relay_server_descriptor()
+ bridge_desc = get_bridge_server_descriptor()
self.assertRaises(ValueError, export_csv, (server_desc, bridge_desc))
diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py
index 902154d..39be157 100644
--- a/test/unit/descriptor/server_descriptor.py
+++ b/test/unit/descriptor/server_descriptor.py
@@ -10,7 +10,7 @@ import stem.prereq
import stem.descriptor.server_descriptor
from stem.descriptor.server_descriptor import RelayDescriptor, BridgeDescriptor
import test.runner
-from test.mocking import get_server_descriptor, CRYPTO_BLOB
+from test.mocking import get_relay_server_descriptor, get_bridge_server_descriptor, CRYPTO_BLOB
class TestServerDescriptor(unittest.TestCase):
def test_minimal_relay_descriptor(self):
@@ -19,8 +19,7 @@ class TestServerDescriptor(unittest.TestCase):
attributes.
"""
- desc_text = get_server_descriptor()
- desc = RelayDescriptor(desc_text)
+ desc = get_relay_server_descriptor()
self.assertEquals("caerSidi", desc.nickname)
self.assertEquals("71.35.133.197", desc.address)
@@ -34,8 +33,7 @@ class TestServerDescriptor(unittest.TestCase):
Includes an 'opt <keyword> <value>' entry.
"""
- desc_text = get_server_descriptor({"opt": "contact www.atagar.com/contact/"})
- desc = RelayDescriptor(desc_text)
+ desc = get_relay_server_descriptor({"opt": "contact www.atagar.com/contact/"})
self.assertEquals("www.atagar.com/contact/", desc.contact)
def test_unrecognized_line(self):
@@ -43,8 +41,7 @@ class TestServerDescriptor(unittest.TestCase):
Includes unrecognized content in the descriptor.
"""
- desc_text = get_server_descriptor({"pepperjack": "is oh so tasty!"})
- desc = RelayDescriptor(desc_text)
+ desc = get_relay_server_descriptor({"pepperjack": "is oh so tasty!"})
self.assertEquals(["pepperjack is oh so tasty!"], desc.get_unrecognized_lines())
def test_proceeding_line(self):
@@ -52,7 +49,7 @@ class TestServerDescriptor(unittest.TestCase):
Includes a line prior to the 'router' entry.
"""
- desc_text = "hibernate 1\n" + get_server_descriptor()
+ desc_text = "hibernate 1\n" + get_relay_server_descriptor(content = True)
self._expect_invalid_attr(desc_text)
def test_trailing_line(self):
@@ -60,7 +57,7 @@ class TestServerDescriptor(unittest.TestCase):
Includes a line after the 'router-signature' entry.
"""
- desc_text = get_server_descriptor() + "\nhibernate 1"
+ desc_text = get_relay_server_descriptor(content = True) + "\nhibernate 1"
self._expect_invalid_attr(desc_text)
def test_nickname_missing(self):
@@ -68,7 +65,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with a malformed router entry.
"""
- desc_text = get_server_descriptor({"router": " 71.35.133.197 9001 0 0"})
+ desc_text = get_relay_server_descriptor({"router": " 71.35.133.197 9001 0 0"}, content = True)
self._expect_invalid_attr(desc_text, "nickname")
def test_nickname_too_long(self):
@@ -76,7 +73,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with a nickname that is an invalid length.
"""
- desc_text = get_server_descriptor({"router": "saberrider2008ReallyLongNickname 71.35.133.197 9001 0 0"})
+ desc_text = get_relay_server_descriptor({"router": "saberrider2008ReallyLongNickname 71.35.133.197 9001 0 0"}, content = True)
self._expect_invalid_attr(desc_text, "nickname", "saberrider2008ReallyLongNickname")
def test_nickname_invalid_char(self):
@@ -84,7 +81,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with an invalid relay nickname.
"""
- desc_text = get_server_descriptor({"router": "$aberrider2008 71.35.133.197 9001 0 0"})
+ desc_text = get_relay_server_descriptor({"router": "$aberrider2008 71.35.133.197 9001 0 0"}, content = True)
self._expect_invalid_attr(desc_text, "nickname", "$aberrider2008")
def test_address_malformed(self):
@@ -92,7 +89,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with an invalid ip address.
"""
- desc_text = get_server_descriptor({"router": "caerSidi 371.35.133.197 9001 0 0"})
+ desc_text = get_relay_server_descriptor({"router": "caerSidi 371.35.133.197 9001 0 0"}, content = True)
self._expect_invalid_attr(desc_text, "address", "371.35.133.197")
def test_port_too_high(self):
@@ -100,7 +97,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with an ORPort that is too large.
"""
- desc_text = get_server_descriptor({"router": "caerSidi 71.35.133.197 900001 0 0"})
+ desc_text = get_relay_server_descriptor({"router": "caerSidi 71.35.133.197 900001 0 0"}, content = True)
self._expect_invalid_attr(desc_text, "or_port", 900001)
def test_port_malformed(self):
@@ -108,7 +105,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with an ORPort that isn't numeric.
"""
- desc_text = get_server_descriptor({"router": "caerSidi 71.35.133.197 900a1 0 0"})
+ desc_text = get_relay_server_descriptor({"router": "caerSidi 71.35.133.197 900a1 0 0"}, content = True)
self._expect_invalid_attr(desc_text, "or_port")
def test_port_newline(self):
@@ -116,7 +113,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with a newline replacing the ORPort.
"""
- desc_text = get_server_descriptor({"router": "caerSidi 71.35.133.197 \n 0 0"})
+ desc_text = get_relay_server_descriptor({"router": "caerSidi 71.35.133.197 \n 0 0"}, content = True)
self._expect_invalid_attr(desc_text, "or_port")
def test_platform_empty(self):
@@ -124,7 +121,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with an empty platform entry.
"""
- desc_text = get_server_descriptor({"platform": ""})
+ desc_text = get_relay_server_descriptor({"platform": ""}, content = True)
desc = RelayDescriptor(desc_text, validate = False)
self.assertEquals("", desc.platform)
@@ -138,7 +135,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with a protocols line without circuit versions.
"""
- desc_text = get_server_descriptor({"opt": "protocols Link 1 2"})
+ desc_text = get_relay_server_descriptor({"opt": "protocols Link 1 2"}, content = True)
self._expect_invalid_attr(desc_text, "circuit_protocols")
def test_published_leap_year(self):
@@ -147,10 +144,10 @@ class TestServerDescriptor(unittest.TestCase):
invalid.
"""
- desc_text = get_server_descriptor({"published": "2011-02-29 04:03:19"})
+ desc_text = get_relay_server_descriptor({"published": "2011-02-29 04:03:19"}, content = True)
self._expect_invalid_attr(desc_text, "published")
- desc_text = get_server_descriptor({"published": "2012-02-29 04:03:19"})
+ desc_text = get_relay_server_descriptor({"published": "2012-02-29 04:03:19"}, content = True)
expected_published = datetime.datetime(2012, 2, 29, 4, 3, 19)
self.assertEquals(expected_published, RelayDescriptor(desc_text).published)
@@ -159,7 +156,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with a published entry without a time component.
"""
- desc_text = get_server_descriptor({"published": "2012-01-01"})
+ desc_text = get_relay_server_descriptor({"published": "2012-01-01"}, content = True)
self._expect_invalid_attr(desc_text, "published")
def test_read_and_write_history(self):
@@ -171,8 +168,7 @@ class TestServerDescriptor(unittest.TestCase):
for field in ("read-history", "write-history"):
value = "2005-12-16 18:00:48 (900 s) 81,8848,8927,8927,83,8848"
- desc_text = get_server_descriptor({"opt %s" % field: value})
- desc = RelayDescriptor(desc_text)
+ desc = get_relay_server_descriptor({"opt %s" % field: value})
if field == "read-history":
attr = (desc.read_history_end, desc.read_history_interval, desc.read_history_values)
@@ -192,8 +188,7 @@ class TestServerDescriptor(unittest.TestCase):
"""
value = "2005-12-17 01:23:11 (900 s) "
- desc_text = get_server_descriptor({"opt read-history": value})
- desc = RelayDescriptor(desc_text)
+ desc = get_relay_server_descriptor({"opt read-history": value})
self.assertEquals(datetime.datetime(2005, 12, 17, 1, 23, 11), desc.read_history_end)
self.assertEquals(900, desc.read_history_interval)
self.assertEquals([], desc.read_history_values)
@@ -204,7 +199,7 @@ class TestServerDescriptor(unittest.TestCase):
"""
desc_text = "@pepperjack very tasty\n@mushrooms not so much\n"
- desc_text += get_server_descriptor()
+ desc_text += get_relay_server_descriptor(content = True)
desc_text += "\ntrailing text that should be ignored, ho hum"
# running parse_file should provide an iterator with a single descriptor
@@ -224,7 +219,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs with a field appearing twice.
"""
- desc_text = get_server_descriptor({"<replace>": ""})
+ desc_text = get_relay_server_descriptor({"<replace>": ""}, content = True)
desc_text = desc_text.replace("<replace>", "contact foo\ncontact bar")
self._expect_invalid_attr(desc_text, "contact", "foo")
@@ -234,7 +229,7 @@ class TestServerDescriptor(unittest.TestCase):
"""
for attr in stem.descriptor.server_descriptor.REQUIRED_FIELDS:
- desc_text = get_server_descriptor(exclude = [attr])
+ desc_text = get_relay_server_descriptor(exclude = [attr], content = True)
self.assertRaises(ValueError, RelayDescriptor, desc_text)
# check that we can still construct it without validation
@@ -258,8 +253,7 @@ class TestServerDescriptor(unittest.TestCase):
return
fingerprint = "4F0C 867D F0EF 6816 0568 C826 838F 482C EA7C FE44"
- desc_text = get_server_descriptor({"opt fingerprint": fingerprint})
- desc = RelayDescriptor(desc_text)
+ desc = get_relay_server_descriptor({"opt fingerprint": fingerprint})
self.assertEquals(fingerprint.replace(" ", ""), desc.fingerprint)
def test_fingerprint_invalid(self):
@@ -273,7 +267,7 @@ class TestServerDescriptor(unittest.TestCase):
return
fingerprint = "4F0C 867D F0EF 6816 0568 C826 838F 482C EA7C FE45"
- desc_text = get_server_descriptor({"opt fingerprint": fingerprint})
+ desc_text = get_relay_server_descriptor({"opt fingerprint": fingerprint}, content = True)
self._expect_invalid_attr(desc_text, "fingerprint", fingerprint.replace(" ", ""))
def test_minimal_bridge_descriptor(self):
@@ -281,8 +275,7 @@ class TestServerDescriptor(unittest.TestCase):
Basic sanity check that we can parse a descriptor with minimal attributes.
"""
- desc_text = get_server_descriptor(is_bridge = True)
- desc = BridgeDescriptor(desc_text)
+ desc = get_bridge_server_descriptor()
self.assertEquals("Unnamed", desc.nickname)
self.assertEquals("10.45.227.253", desc.address)
@@ -310,8 +303,7 @@ class TestServerDescriptor(unittest.TestCase):
]
for attr in unsanitized_attr:
- desc_text = get_server_descriptor(attr, is_bridge = True)
- desc = BridgeDescriptor(desc_text)
+ desc = get_bridge_server_descriptor(attr)
self.assertFalse(desc.is_scrubbed())
def test_bridge_unsanitized_relay(self):
@@ -320,7 +312,7 @@ class TestServerDescriptor(unittest.TestCase):
its unsanatized content.
"""
- desc_text = get_server_descriptor({"router-digest": "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4"})
+ desc_text = get_relay_server_descriptor({"router-digest": "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4"}, content = True)
desc = BridgeDescriptor(desc_text)
self.assertFalse(desc.is_scrubbed())
@@ -332,13 +324,12 @@ class TestServerDescriptor(unittest.TestCase):
# checks with valid content
router_digest = "068A2E28D4C934D9490303B7A645BA068DCA0504"
- desc_text = get_server_descriptor({"router-digest": router_digest}, is_bridge = True)
- desc = BridgeDescriptor(desc_text)
+ desc = get_bridge_server_descriptor({"router-digest": router_digest})
self.assertEquals(router_digest, desc.digest())
# checks when missing
- desc_text = get_server_descriptor(exclude = ["router-digest"], is_bridge = True)
+ desc_text = get_bridge_server_descriptor(exclude = ["router-digest"], content = True)
self.assertRaises(ValueError, BridgeDescriptor, desc_text)
# check that we can still construct it without validation
@@ -355,7 +346,7 @@ class TestServerDescriptor(unittest.TestCase):
)
for value in test_values:
- desc_text = get_server_descriptor({"router-digest": value}, is_bridge = True)
+ desc_text = get_bridge_server_descriptor({"router-digest": value}, content = True)
self.assertRaises(ValueError, BridgeDescriptor, desc_text)
desc = BridgeDescriptor(desc_text, validate = False)
@@ -366,8 +357,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs a bridge descriptor with a sanatized IPv4 or-address entry.
"""
- desc_text = get_server_descriptor({"or-address": "10.45.227.253:9001"}, is_bridge = True)
- desc = BridgeDescriptor(desc_text)
+ desc = get_bridge_server_descriptor({"or-address": "10.45.227.253:9001"})
self.assertEquals([("10.45.227.253", 9001, False)], desc.address_alt)
def test_or_address_v6(self):
@@ -375,8 +365,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs a bridge descriptor with a sanatized IPv6 or-address entry.
"""
- desc_text = get_server_descriptor({"or-address": "[fd9f:2e19:3bcf::02:9970]:9001"}, is_bridge = True)
- desc = BridgeDescriptor(desc_text)
+ desc = get_bridge_server_descriptor({"or-address": "[fd9f:2e19:3bcf::02:9970]:9001"})
self.assertEquals([("fd9f:2e19:3bcf::02:9970", 9001, True)], desc.address_alt)
def test_or_address_multiple(self):
@@ -384,7 +373,7 @@ class TestServerDescriptor(unittest.TestCase):
Constructs a bridge descriptor with multiple or-address entries and multiple ports.
"""
- desc_text = "\n".join((get_server_descriptor(is_bridge = True),
+ desc_text = "\n".join((get_bridge_server_descriptor(content = True),
"or-address 10.45.227.253:9001,9005,80",
"or-address [fd9f:2e19:3bcf::02:9970]:443"))
1
0

13 Oct '12
commit 3923916993535c87d95c223bbf9fd0479fc0dc68
Author: Damian Johnson <atagar(a)torproject.org>
Date: Thu Sep 27 09:10:27 2012 -0700
Moving extrainfo descriptor mocking into util
The extrainfo descriptor unit tests had its own helper function for getting
mock descriptor content. This is the same thing as what the server descriptor
unit tests use so moving it to the mocking module.
---
test/mocking.py | 77 +++++++++++++--
test/unit/descriptor/extrainfo_descriptor.py | 137 ++++++-------------------
2 files changed, 101 insertions(+), 113 deletions(-)
diff --git a/test/mocking.py b/test/mocking.py
index d0e6148..c867664 100644
--- a/test/mocking.py
+++ b/test/mocking.py
@@ -21,10 +21,12 @@ calling :func:`test.mocking.revert_mocking`.
raise_exception - raises an exception when called
Instance Constructors
- get_message - stem.socket.ControlMessage
- get_protocolinfo_response - stem.response.protocolinfo.ProtocolInfoResponse
- get_relay_server_descriptor - stem.descriptor.server_descriptor.RelayDescriptor
- get_bridge_server_descriptor - stem.descriptor.server_descriptor.BridgeDescriptor
+ get_message - stem.socket.ControlMessage
+ get_protocolinfo_response - stem.response.protocolinfo.ProtocolInfoResponse
+ get_relay_server_descriptor - stem.descriptor.server_descriptor.RelayDescriptor
+ get_bridge_server_descriptor - stem.descriptor.server_descriptor.BridgeDescriptor
+ get_relay_extrainfo_descriptor - stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor
+ get_bridge_extrainfo_descriptor - stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor
"""
import inspect
@@ -35,6 +37,7 @@ import __builtin__
import stem.response
import stem.socket
import stem.descriptor.server_descriptor
+import stem.descriptor.extrainfo_descriptor
# Once we've mocked a function we can't rely on its __module__ or __name__
# attributes, so instead we associate a unique 'mock_id' attribute that maps
@@ -54,7 +57,7 @@ skFtXhOHHqTRN4GPPrZsAIUOQGzQtGb66IQgT4tO/pj+P6QmSCCdTfhvGfgTCsC+
WPi4Fl2qryzTb3QO5r5x7T8OsG2IBUET1bLQzmtbC560SYR49IvVAgMBAAE=
"""
-SERVER_DESCRIPTOR_HEADER = (
+RELAY_SERVER_HEADER = (
("router", "caerSidi 71.35.133.197 9001 0 0"),
("published", "2012-03-01 17:15:27"),
("bandwidth", "153600 256000 104590"),
@@ -63,11 +66,11 @@ SERVER_DESCRIPTOR_HEADER = (
("signing-key", "\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----" % CRYPTO_BLOB),
)
-SERVER_DESCRIPTOR_FOOTER = (
+RELAY_SERVER_FOOTER = (
("router-signature", "\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB),
)
-BRIDGE_DESCRIPTOR_HEADER = (
+BRIDGE_SERVER_HEADER = (
("router", "Unnamed 10.45.227.253 9001 0 0"),
("router-digest", "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4"),
("published", "2012-03-22 17:34:38"),
@@ -75,6 +78,24 @@ BRIDGE_DESCRIPTOR_HEADER = (
("reject", "*:*"),
)
+RELAY_EXTRAINFO_HEADER = (
+ ("extra-info", "ninja B2289C3EAB83ECD6EB916A2F481A02E6B76A0A48"),
+ ("published", "2012-05-05 17:03:50"),
+)
+
+RELAY_EXTRAINFO_FOOTER = (
+ ("router-signature", "\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB),
+)
+
+BRIDGE_EXTRAINFO_HEADER = (
+ ("extra-info", "ec2bridgereaac65a3 1EC248422B57D9C0BD751892FE787585407479A4"),
+ ("published", "2012-05-05 17:03:50"),
+)
+
+BRIDGE_EXTRAINFO_FOOTER = (
+ ("router-digest", "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4"),
+)
+
def no_op():
def _no_op(*args): pass
return _no_op
@@ -373,7 +394,7 @@ def get_relay_server_descriptor(attr = None, exclude = (), content = False):
:returns: RelayDescriptor for the requested descriptor content
"""
- desc_content = _get_descriptor_content(attr, exclude, SERVER_DESCRIPTOR_HEADER, SERVER_DESCRIPTOR_FOOTER)
+ desc_content = _get_descriptor_content(attr, exclude, RELAY_SERVER_HEADER, RELAY_SERVER_FOOTER)
if content:
return desc_content
@@ -392,10 +413,48 @@ def get_bridge_server_descriptor(attr = None, exclude = (), content = False):
:returns: BridgeDescriptor for the requested descriptor content
"""
- desc_content = _get_descriptor_content(attr, exclude, BRIDGE_DESCRIPTOR_HEADER)
+ desc_content = _get_descriptor_content(attr, exclude, BRIDGE_SERVER_HEADER)
if content:
return desc_content
else:
return stem.descriptor.server_descriptor.BridgeDescriptor(desc_content, validate = False)
+def get_relay_extrainfo_descriptor(attr = None, exclude = (), content = False):
+ """
+ Provides the descriptor content for...
+ stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor
+
+ :param dict attr: keyword/value mappings to be included in the descriptor
+ :param list exclude: mandatory keywords to exclude from the descriptor
+ :param bool content: provides the str content of the descriptor rather than the class if True
+
+ :returns: RelayExtraInfoDescriptor for the requested descriptor content
+ """
+
+ desc_content = _get_descriptor_content(attr, exclude, RELAY_EXTRAINFO_HEADER, RELAY_EXTRAINFO_FOOTER)
+
+ if content:
+ return desc_content
+ else:
+ return stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor(desc_content, validate = False)
+
+def get_bridge_extrainfo_descriptor(attr = None, exclude = (), content = False):
+ """
+ Provides the descriptor content for...
+ stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor
+
+ :param dict attr: keyword/value mappings to be included in the descriptor
+ :param list exclude: mandatory keywords to exclude from the descriptor
+ :param bool content: provides the str content of the descriptor rather than the class if True
+
+ :returns: BridgeExtraInfoDescriptor for the requested descriptor content
+ """
+
+ desc_content = _get_descriptor_content(attr, exclude, BRIDGE_EXTRAINFO_HEADER, BRIDGE_EXTRAINFO_FOOTER)
+
+ if content:
+ return desc_content
+ else:
+ return stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor(desc_content, validate = False)
+
diff --git a/test/unit/descriptor/extrainfo_descriptor.py b/test/unit/descriptor/extrainfo_descriptor.py
index 54d6bf8..e8d4cac 100644
--- a/test/unit/descriptor/extrainfo_descriptor.py
+++ b/test/unit/descriptor/extrainfo_descriptor.py
@@ -5,61 +5,7 @@ Unit tests for stem.descriptor.extrainfo_descriptor.
import datetime
import unittest
from stem.descriptor.extrainfo_descriptor import RelayExtraInfoDescriptor, BridgeExtraInfoDescriptor, DirResponses, DirStats
-
-CRYPTO_BLOB = """
-K5FSywk7qvw/boA4DQcqkls6Ize5vcBYfhQ8JnOeRQC9+uDxbnpm3qaYN9jZ8myj
-k0d2aofcVbHr4fPQOSST0LXDrhFl5Fqo5um296zpJGvRUeO6S44U/EfJAGShtqWw
-7LZqklu+gVvhMKREpchVqlAwXkWR44VENm24Hs+mT3M=
-"""
-
-RELAY_EXTRAINFO_ATTR = (
- ("extra-info", "ninja B2289C3EAB83ECD6EB916A2F481A02E6B76A0A48"),
- ("published", "2012-05-05 17:03:50"),
- ("router-signature", "\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB),
-)
-
-BRIDGE_EXTRAINFO_ATTR = (
- ("extra-info", "ec2bridgereaac65a3 1EC248422B57D9C0BD751892FE787585407479A4"),
- ("published", "2012-05-05 17:03:50"),
- ("router-digest", "006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4"),
-)
-
-def _make_descriptor(attr = None, exclude = None, is_bridge = False):
- """
- Constructs a minimal extrainfo descriptor with the given attributes.
-
- :param dict attr: keyword/value mappings to be included in the descriptor
- :param list exclude: mandatory keywords to exclude from the descriptor
-
- :returns: str with customized descriptor content
- """
-
- descriptor_lines = []
- if attr is None: attr = {}
- if exclude is None: exclude = []
- desc_attr = BRIDGE_EXTRAINFO_ATTR if is_bridge else RELAY_EXTRAINFO_ATTR
- attr = dict(attr) # shallow copy since we're destructive
-
- for keyword, value in desc_attr:
- if keyword in exclude: continue
- elif keyword in attr:
- value = attr[keyword]
- del attr[keyword]
-
- # if this is the last entry then we should dump in any unused attributes
- if not is_bridge and keyword == "router-signature":
- for attr_keyword, attr_value in attr.items():
- descriptor_lines.append("%s %s" % (attr_keyword, attr_value))
-
- descriptor_lines.append("%s %s" % (keyword, value))
-
- # bridges don't have a router-signature so simply append any extra attributes
- # to the end
- if is_bridge:
- for attr_keyword, attr_value in attr.items():
- descriptor_lines.append("%s %s" % (attr_keyword, attr_value))
-
- return "\n".join(descriptor_lines)
+from test.mocking import get_relay_extrainfo_descriptor, get_bridge_extrainfo_descriptor, CRYPTO_BLOB
class TestExtraInfoDescriptor(unittest.TestCase):
def test_minimal_extrainfo_descriptor(self):
@@ -68,8 +14,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
attributes.
"""
- desc_text = _make_descriptor()
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor()
self.assertEquals("ninja", desc.nickname)
self.assertEquals("B2289C3EAB83ECD6EB916A2F481A02E6B76A0A48", desc.fingerprint)
@@ -80,8 +25,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
Includes unrecognized content in the descriptor.
"""
- desc_text = _make_descriptor({"pepperjack": "is oh so tasty!"})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({"pepperjack": "is oh so tasty!"})
self.assertEquals(["pepperjack is oh so tasty!"], desc.get_unrecognized_lines())
def test_proceeding_line(self):
@@ -89,7 +33,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
Includes a line prior to the 'extra-info' entry.
"""
- desc_text = "exit-streams-opened port=80\n" + _make_descriptor()
+ desc_text = "exit-streams-opened port=80\n" + get_relay_extrainfo_descriptor(content = True)
self._expect_invalid_attr(desc_text)
def test_trailing_line(self):
@@ -97,7 +41,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
Includes a line after the 'router-signature' entry.
"""
- desc_text = _make_descriptor() + "\nexit-streams-opened port=80"
+ desc_text = get_relay_extrainfo_descriptor(content = True) + "\nexit-streams-opened port=80"
self._expect_invalid_attr(desc_text)
def test_extrainfo_line_missing_fields(self):
@@ -115,7 +59,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({"extra-info": entry})
+ desc_text = get_relay_extrainfo_descriptor({"extra-info": entry}, content = True)
desc = self._expect_invalid_attr(desc_text, "nickname")
self.assertEquals(None, desc.nickname)
self.assertEquals(None, desc.fingerprint)
@@ -126,8 +70,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
"""
geoip_db_digest = "916A3CA8B7DF61473D5AE5B21711F35F301CE9E8"
- desc_text = _make_descriptor({"geoip-db-digest": geoip_db_digest})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({"geoip-db-digest": geoip_db_digest})
self.assertEquals(geoip_db_digest, desc.geoip_db_digest)
test_entries = (
@@ -139,7 +82,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({"geoip-db-digest": entry})
+ desc_text = get_relay_extrainfo_descriptor({"geoip-db-digest": entry}, content = True)
self._expect_invalid_attr(desc_text, "geoip_db_digest", entry)
def test_cell_circuits_per_decile(self):
@@ -153,8 +96,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in ("0", "11", "25"):
- desc_text = _make_descriptor({"cell-circuits-per-decile": entry})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({"cell-circuits-per-decile": entry})
self.assertEquals(int(entry), desc.cell_circuits_per_decile)
test_entries = (
@@ -165,7 +107,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({"cell-circuits-per-decile": entry})
+ desc_text = get_relay_extrainfo_descriptor({"cell-circuits-per-decile": entry}, content = True)
self._expect_invalid_attr(desc_text, "cell_circuits_per_decile")
def test_dir_response_lines(self):
@@ -179,8 +121,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
unknown_attr = attr + "_unknown"
test_value = "ok=0,unavailable=0,not-found=984,not-modified=0,something-new=7"
- desc_text = _make_descriptor({keyword: test_value})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({keyword: test_value})
self.assertEquals(0, getattr(desc, attr)[DirResponses.OK])
self.assertEquals(0, getattr(desc, attr)[DirResponses.UNAVAILABLE])
self.assertEquals(984, getattr(desc, attr)[DirResponses.NOT_FOUND])
@@ -194,7 +135,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({keyword: entry})
+ desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
desc = self._expect_invalid_attr(desc_text)
self.assertEqual({}, getattr(desc, attr))
self.assertEqual({}, getattr(desc, unknown_attr))
@@ -210,8 +151,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
unknown_attr = attr + "_unknown"
test_value = "complete=2712,timeout=32,running=4,min=741,d1=14507,d2=22702,q1=28881,d3=38277,d4=73729,md=111455,d6=168231,d7=257218,q3=319833,d8=390507,d9=616301,something-new=11,max=29917857"
- desc_text = _make_descriptor({keyword: test_value})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({keyword: test_value})
self.assertEquals(2712, getattr(desc, attr)[DirStats.COMPLETE])
self.assertEquals(32, getattr(desc, attr)[DirStats.TIMEOUT])
self.assertEquals(4, getattr(desc, attr)[DirStats.RUNNING])
@@ -237,7 +177,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({keyword: entry})
+ desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
desc = self._expect_invalid_attr(desc_text)
self.assertEqual({}, getattr(desc, attr))
self.assertEqual({}, getattr(desc, unknown_attr))
@@ -247,8 +187,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
Parses the conn-bi-direct line with valid and invalid data.
"""
- desc_text = _make_descriptor({"conn-bi-direct": "2012-05-03 12:07:50 (500 s) 277431,12089,0,2134"})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({"conn-bi-direct": "2012-05-03 12:07:50 (500 s) 277431,12089,0,2134"})
self.assertEquals(datetime.datetime(2012, 5, 3, 12, 7, 50), desc.conn_bi_direct_end)
self.assertEquals(500, desc.conn_bi_direct_interval)
self.assertEquals(277431, desc.conn_bi_direct_below)
@@ -271,7 +210,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({"conn-bi-direct": entry})
+ desc_text = get_relay_extrainfo_descriptor({"conn-bi-direct": entry}, content = True)
desc = self._expect_invalid_attr(desc_text)
self.assertEquals(None, desc.conn_bi_direct_end)
self.assertEquals(None, desc.conn_bi_direct_interval)
@@ -297,8 +236,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for test_value, expected_value in test_entries:
- desc_text = _make_descriptor({keyword: test_value})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({keyword: test_value})
self.assertEquals(expected_value, getattr(desc, attr))
test_entries = (
@@ -309,7 +247,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry, expected in test_entries:
- desc_text = _make_descriptor({keyword: entry})
+ desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
self._expect_invalid_attr(desc_text, attr, expected)
def test_number_list_lines(self):
@@ -329,8 +267,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for test_value, expected_value in test_entries:
- desc_text = _make_descriptor({keyword: test_value})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({keyword: test_value})
self.assertEquals(expected_value, getattr(desc, attr))
test_entries = (
@@ -340,7 +277,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry, expected in test_entries:
- desc_text = _make_descriptor({keyword: entry})
+ desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
self._expect_invalid_attr(desc_text, attr, expected)
def test_timestamp_lines(self):
@@ -352,8 +289,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
for keyword in ('published', 'geoip-start-time'):
attr = keyword.replace('-', '_')
- desc_text = _make_descriptor({keyword: "2012-05-03 12:07:50"})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({keyword: "2012-05-03 12:07:50"})
self.assertEquals(datetime.datetime(2012, 5, 3, 12, 7, 50), getattr(desc, attr))
test_entries = (
@@ -364,7 +300,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({keyword: entry})
+ desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
self._expect_invalid_attr(desc_text, attr)
def test_timestamp_and_interval_lines(self):
@@ -377,8 +313,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
end_attr = keyword.replace('-', '_').replace('dirreq', 'dir')
interval_attr = end_attr[:-4] + "_interval"
- desc_text = _make_descriptor({keyword: "2012-05-03 12:07:50 (500 s)"})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({keyword: "2012-05-03 12:07:50 (500 s)"})
self.assertEquals(datetime.datetime(2012, 5, 3, 12, 7, 50), getattr(desc, end_attr))
self.assertEquals(500, getattr(desc, interval_attr))
@@ -393,7 +328,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({keyword: entry})
+ desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
desc = self._expect_invalid_attr(desc_text)
self.assertEquals(None, getattr(desc, end_attr))
self.assertEquals(None, getattr(desc, interval_attr))
@@ -417,8 +352,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for test_values, expected_values in test_entries:
- desc_text = _make_descriptor({keyword: "2012-05-03 12:07:50 (500 s)%s" % test_values})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({keyword: "2012-05-03 12:07:50 (500 s)%s" % test_values})
self.assertEquals(datetime.datetime(2012, 5, 3, 12, 7, 50), getattr(desc, end_attr))
self.assertEquals(500, getattr(desc, interval_attr))
self.assertEquals(expected_values, getattr(desc, values_attr))
@@ -435,7 +369,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({keyword: entry})
+ desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
desc = self._expect_invalid_attr(desc_text)
self.assertEquals(None, getattr(desc, end_attr))
self.assertEquals(None, getattr(desc, interval_attr))
@@ -457,8 +391,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for test_value, expected_value in test_entries:
- desc_text = _make_descriptor({keyword: test_value})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({keyword: test_value})
self.assertEquals(expected_value, getattr(desc, attr))
test_entries = (
@@ -471,7 +404,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({keyword: entry})
+ desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
self._expect_invalid_attr(desc_text, attr, {})
def test_locale_mapping_lines(self):
@@ -489,8 +422,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for test_value, expected_value in test_entries:
- desc_text = _make_descriptor({keyword: test_value})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({keyword: test_value})
self.assertEquals(expected_value, getattr(desc, attr))
test_entries = (
@@ -501,7 +433,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
)
for entry in test_entries:
- desc_text = _make_descriptor({keyword: entry})
+ desc_text = get_relay_extrainfo_descriptor({keyword: entry}, content = True)
self._expect_invalid_attr(desc_text, attr, {})
def test_minimal_bridge_descriptor(self):
@@ -509,8 +441,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
Basic sanity check that we can parse a descriptor with minimal attributes.
"""
- desc_text = _make_descriptor(is_bridge = True)
- desc = BridgeExtraInfoDescriptor(desc_text)
+ desc = get_bridge_extrainfo_descriptor()
self.assertEquals("ec2bridgereaac65a3", desc.nickname)
self.assertEquals("1EC248422B57D9C0BD751892FE787585407479A4", desc.fingerprint)
@@ -525,13 +456,11 @@ class TestExtraInfoDescriptor(unittest.TestCase):
Basic exercise for both a bridge and relay's transport entry.
"""
- desc_text = _make_descriptor({"transport": "obfs3"}, is_bridge = True)
- desc = BridgeExtraInfoDescriptor(desc_text)
+ desc = get_bridge_extrainfo_descriptor({"transport": "obfs3"})
self.assertEquals({"obfs3": (None, None, None)}, desc.transport)
self.assertEquals([], desc.get_unrecognized_lines())
- desc_text = _make_descriptor({"transport": "obfs2 83.212.96.201:33570"})
- desc = RelayExtraInfoDescriptor(desc_text)
+ desc = get_relay_extrainfo_descriptor({"transport": "obfs2 83.212.96.201:33570"})
self.assertEquals({"obfs2": ("83.212.96.201", 33570, [])}, desc.transport)
self.assertEquals([], desc.get_unrecognized_lines())
1
0

13 Oct '12
commit 6f5d91666562e562fd773636ffcd292abff0d939
Author: Damian Johnson <atagar(a)torproject.org>
Date: Fri Sep 28 08:03:56 2012 -0700
Moving key certificate mocking into util
---
test/mocking.py | 33 +++++++++++
.../descriptor/networkstatus/key_certificate.py | 59 ++------------------
2 files changed, 38 insertions(+), 54 deletions(-)
diff --git a/test/mocking.py b/test/mocking.py
index 2dd3319..3d4a615 100644
--- a/test/mocking.py
+++ b/test/mocking.py
@@ -28,6 +28,7 @@ calling :func:`test.mocking.revert_mocking`.
get_relay_extrainfo_descriptor - stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor
get_bridge_extrainfo_descriptor - stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor
get_router_status_entry - stem.descriptor.networkstatus.RouterStatusEntry
+ get_key_certificate - stem.descriptor.networkstatus.KeyCertificate
"""
import inspect
@@ -102,6 +103,19 @@ ROUTER_STATUS_ENTRY_HEADER = (
("s", "Fast Named Running Stable Valid"),
)
+KEY_CERTIFICATE_HEADER = (
+ ("dir-key-certificate-version", "3"),
+ ("fingerprint", "27B6B5996C426270A5C95488AA5BCEB6BCC86956"),
+ ("dir-key-published", "2011-11-28 21:51:04"),
+ ("dir-key-expires", "2012-11-28 21:51:04"),
+ ("dir-identity-key", "\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----" % CRYPTO_BLOB),
+ ("dir-signing-key", "\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----" % CRYPTO_BLOB),
+)
+
+KEY_CERTIFICATE_FOOTER = (
+ ("dir-key-certification", "\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----" % CRYPTO_BLOB),
+)
+
def no_op():
def _no_op(*args): pass
return _no_op
@@ -483,3 +497,22 @@ def get_router_status_entry(attr = None, exclude = (), content = False):
else:
return stem.descriptor.networkstatus.RouterStatusEntry(desc_content, validate = True)
+def get_key_certificate(attr = None, exclude = (), content = False):
+ """
+ Provides the descriptor content for...
+ stem.descriptor.networkstatus.KeyCertificate
+
+ :param dict attr: keyword/value mappings to be included in the descriptor
+ :param list exclude: mandatory keywords to exclude from the descriptor
+ :param bool content: provides the str content of the descriptor rather than the class if True
+
+ :returns: KeyCertificate for the requested descriptor content
+ """
+
+ desc_content = _get_descriptor_content(attr, exclude, KEY_CERTIFICATE_HEADER, KEY_CERTIFICATE_FOOTER)
+
+ if content:
+ return desc_content
+ else:
+ return stem.descriptor.networkstatus.KeyCertificate(desc_content, validate = True)
+
diff --git a/test/unit/descriptor/networkstatus/key_certificate.py b/test/unit/descriptor/networkstatus/key_certificate.py
index 2d779e7..703b61d 100644
--- a/test/unit/descriptor/networkstatus/key_certificate.py
+++ b/test/unit/descriptor/networkstatus/key_certificate.py
@@ -6,56 +6,7 @@ import datetime
import unittest
from stem.descriptor.networkstatus import KeyCertificate
-
-sig_block = """\
------BEGIN %s-----
-MIGJAoGBAJ5itcJRYNEM3Qf1OVWLRkwjqf84oXPc2ZusaJ5zOe7TVvBMra9GNyc0
-NM9y6zVkHCAePAjr4KbW/8P1olA6FUE2LV9bozaU1jFf6K8B2OELKs5FUEW+n+ic
-GM0x6MhngyXonWOcKt5Gj+mAu5lrno9tpNbPkz2Utr/Pi0nsDhWlAgMBAAE=
------END %s-----\
-"""
-
-RSA_SIG = sig_block % ("RSA PUBLIC KEY", "RSA PUBLIC KEY")
-KEY_SIG = sig_block % ("SIGNATURE", "SIGNATURE")
-
-KEY_CERTIFICATE_ATTR = (
- ("dir-key-certificate-version", "3"),
- ("fingerprint", "27B6B5996C426270A5C95488AA5BCEB6BCC86956"),
- ("dir-key-published", "2011-11-28 21:51:04"),
- ("dir-key-expires", "2012-11-28 21:51:04"),
- ("dir-identity-key", "\n" + RSA_SIG),
- ("dir-signing-key", "\n" + RSA_SIG),
- ("dir-key-certification", "\n" + KEY_SIG),
-)
-
-def get_key_certificate(attr = None, exclude = None):
- """
- Constructs a minimal key certificate with the given attributes.
-
- :param dict attr: keyword/value mappings to be included in the entry
- :param list exclude: mandatory keywords to exclude from the entry
-
- :returns: str with customized key certificate content
- """
-
- descriptor_lines = []
- if attr is None: attr = {}
- if exclude is None: exclude = []
- attr = dict(attr) # shallow copy since we're destructive
-
- for keyword, value in KEY_CERTIFICATE_ATTR:
- if keyword in exclude: continue
- elif keyword in attr:
- value = attr[keyword]
- del attr[keyword]
-
- descriptor_lines.append("%s %s" % (keyword, value))
-
- # dump in any unused attributes
- for attr_keyword, attr_value in attr.items():
- descriptor_lines.append("%s %s" % (attr_keyword, attr_value))
-
- return "\n".join(descriptor_lines)
+from test.mocking import get_key_certificate, CRYPTO_BLOB
class TestKeyCertificate(unittest.TestCase):
def test_minimal(self):
@@ -63,17 +14,17 @@ class TestKeyCertificate(unittest.TestCase):
Parses a minimal key certificate.
"""
- certificate = KeyCertificate(get_key_certificate())
+ certificate = get_key_certificate()
self.assertEqual(3, certificate.version)
self.assertEqual(None, certificate.address)
self.assertEqual(None, certificate.dir_port)
self.assertEqual("27B6B5996C426270A5C95488AA5BCEB6BCC86956", certificate.fingerprint)
- self.assertEqual(RSA_SIG, certificate.identity_key)
+ self.assertTrue(CRYPTO_BLOB in certificate.identity_key)
self.assertEqual(datetime.datetime(2011, 11, 28, 21, 51, 4), certificate.published)
self.assertEqual(datetime.datetime(2012, 11, 28, 21, 51, 4), certificate.expires)
- self.assertEqual(RSA_SIG, certificate.signing_key)
+ self.assertTrue(CRYPTO_BLOB in certificate.signing_key)
self.assertEqual(None, certificate.crosscert)
- self.assertEqual(KEY_SIG, certificate.certification)
+ self.assertTrue(CRYPTO_BLOB in certificate.certification)
self.assertEqual([], certificate.get_unrecognized_lines())
1
0