[tor-commits] [stem/master] Parsing vote-status attribute
atagar at torproject.org
atagar at torproject.org
Sat Oct 13 18:35:45 UTC 2012
commit b931e980b62600d5cc9cd60069102c2ffa26cb84
Author: Damian Johnson <atagar at torproject.org>
Date: Sat Sep 8 12:35:01 2012 -0700
Parsing vote-status attribute
Changing our 'vote_status' string attribute to 'is_vote' and 'is_consensus'
boolean attributes. The spec specifically says that anything else is invalid so
there's little reason to allow arbitrary content in the field.
---
stem/descriptor/networkstatus.py | 61 ++++++++++++------------
test/integ/descriptor/networkstatus.py | 6 ++-
test/unit/descriptor/networkstatus/document.py | 27 +++++++++-
test/unit/descriptor/networkstatus/entry.py | 3 +-
4 files changed, 60 insertions(+), 37 deletions(-)
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index d554d42..f4c3b40 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -199,7 +199,8 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
:var tuple routers: RouterStatusEntry contained in the document
:var str version: **\*** document version
- :var str vote_status: **\*** status of the vote (is either "vote" or "consensus")
+ :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
@@ -237,7 +238,8 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
self.directory_signatures = []
self.version = None
- self.vote_status = None
+ self.is_consensus = True
+ self.is_vote = False
self.consensus_methods = []
self.published = None
self.consensus_method = None
@@ -293,19 +295,6 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
all_entries.update(header_entries)
all_entries.update(footer_entries)
- if 'vote-status' in header_entries:
- is_consensus = header_entries['vote-status'][0][0] == "consensus"
- is_vote = not is_consensus
- else:
- if validate:
- raise ValueError("Network status documents must have a 'vote-status' line to say if they're a vote or consensus")
-
- is_consensus, is_vote = True, False
-
- if validate:
- self._check_for_missing_and_disallowed_fields(is_consensus, header_entries, footer_entries)
- self._check_for_misordered_fields(is_consensus, header_entries, footer_entries)
-
known_fields = [attr[0] for attr in HEADER_STATUS_DOCUMENT_FIELDS + FOOTER_STATUS_DOCUMENT_FIELDS]
content = header + '\n' + footer
@@ -330,6 +319,22 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
if validate and self.version != "3":
raise ValueError("Expected a version 3 network status documents, got version '%s' instead" % self.version)
+ elif keyword == 'vote-status':
+ # "vote-status" type
+
+ if value == 'consensus':
+ self.is_consensus, self.is_vote = True, False
+ elif value == 'vote':
+ self.is_consensus, self.is_vote = False, True
+ elif validate:
+ raise ValueError("A network status document's vote-status line can only be 'consensus' or 'vote', got '%s' instead" % value)
+
+ # doing this validation afterward so we know our 'is_consensus' and
+ # 'is_vote' attributes
+
+ if validate:
+ self._check_for_missing_and_disallowed_fields(header_entries, footer_entries)
+ self._check_for_misordered_fields(header_entries, footer_entries)
def _parse_old(self, raw_content, validate):
# preamble
@@ -338,14 +343,9 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
# ignore things the parse() method handles
_read_keyword_line("network-status-version", content, False, True)
+ _read_keyword_line("vote-status", content, False, True)
-
- map(read_keyword_line, ["vote-status"])
-
- vote = False
- if self.vote_status == "vote": vote = True
- elif self.vote_status == "consensus": vote = False
- elif validate: raise ValueError("Unrecognized vote-status")
+ vote = self.is_vote
if vote:
read_keyword_line("consensus-methods", True)
@@ -431,30 +431,28 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
else:
self.unrecognized_lines = []
- def _check_for_missing_and_disallowed_fields(self, is_consensus, header_entries, footer_entries):
+ def _check_for_missing_and_disallowed_fields(self, header_entries, footer_entries):
"""
Checks that we have mandatory fields for our type, and that we don't have
any fields exclusive to the other (ie, no vote-only fields appear in a
consensus or vice versa).
- :param bool is_consensus: true if we're a conensus and false if we're a vote
:param dict header_entries: ordered keyword/value mappings of the header
:param dict footer_entries: ordered keyword/value mappings of the footer
:raises: ValueError if we're missing mandatory fields or have fiels we shouldn't
"""
- is_vote = not is_consensus # aliasing inverse for readability
missing_fields, disallowed_fields = [], []
for entries, fields in ((header_entries, HEADER_STATUS_DOCUMENT_FIELDS),\
(footer_entries, FOOTER_STATUS_DOCUMENT_FIELDS)):
for field, in_votes, in_consensus, mandatory in fields:
- if mandatory and ((is_consensus and in_consensus) or (is_vote and in_votes)):
+ if mandatory and ((self.is_consensus and in_consensus) or (self.is_vote and in_votes)):
# mandatory field, check that we have it
if not field in entries.keys():
missing_fields.append(field)
- elif (is_consensus and not in_consensus) or (is_vote and not in_votes):
+ elif (self.is_consensus and not in_consensus) or (self.is_vote and not in_votes):
# field we shouldn't have, check that we don't
if field in entries.keys():
disallowed_fields.append(field)
@@ -465,7 +463,7 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
if disallowed_fields:
raise ValueError("Network status document has fields that shouldn't appear in this document type: %s" % ', '.join(disallowed_fields))
- def _check_for_misordered_fields(self, is_consensus, header_entries, footer_entries):
+ def _check_for_misordered_fields(self, header_entries, footer_entries):
"""
To be valid a network status document's fiends need to appear in a specific
order. Checks that known fields appear in that order (unrecognized fields
@@ -824,10 +822,10 @@ class RouterStatusEntry(stem.descriptor.Descriptor):
m_comp = value.split(" ")
- if not (self.document and self.document.vote_status == "vote"):
+ if not (self.document and self.document.is_vote):
if not validate: continue
- vote_status = self.document.vote_status if self.document else "<undefined document>"
+ vote_status = "vote" if self.document else "<undefined document>"
raise ValueError("Router status entry's 'm' line should only appear in votes (appeared in a %s): %s" % (vote_status, line))
elif len(m_comp) < 1:
if not validate: continue
@@ -870,7 +868,8 @@ class MicrodescriptorConsensus(NetworkStatusDocument):
A v3 microdescriptor consensus.
:var str version: **\*** a document format version. For v3 microdescriptor consensuses this is "3 microdesc"
- :var str vote_status: **\*** status of the vote (is "consensus")
+ :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 datetime valid_after: **\*** time when the consensus becomes valid
:var datetime fresh_until: **\*** time until when the consensus is considered to be fresh
diff --git a/test/integ/descriptor/networkstatus.py b/test/integ/descriptor/networkstatus.py
index 1777828..9087b22 100644
--- a/test/integ/descriptor/networkstatus.py
+++ b/test/integ/descriptor/networkstatus.py
@@ -86,7 +86,8 @@ class TestNetworkStatus(unittest.TestCase):
descriptor_file.close()
self.assertEquals("3", desc.version)
- self.assertEquals("consensus", desc.vote_status)
+ self.assertEquals(True, desc.is_consensus)
+ self.assertEquals(False, desc.is_vote)
self.assertEquals([], desc.consensus_methods)
self.assertEquals(None, desc.published)
self.assertEquals(12, desc.consensus_method)
@@ -178,7 +179,8 @@ I/TJmV928na7RLZe2mGHCAW3VQOvV+QkCfj05VZ8CsY=
descriptor_file.close()
self.assertEquals("3", desc.version)
- self.assertEquals("vote", desc.vote_status)
+ self.assertEquals(False, desc.is_consensus)
+ self.assertEquals(True, desc.is_vote)
self.assertEquals(range(1, 13), desc.consensus_methods)
self.assertEquals(_strptime("2012-07-11 23:50:01"), desc.published)
self.assertEquals(None, desc.consensus_method)
diff --git a/test/unit/descriptor/networkstatus/document.py b/test/unit/descriptor/networkstatus/document.py
index a099fa9..9b24a00 100644
--- a/test/unit/descriptor/networkstatus/document.py
+++ b/test/unit/descriptor/networkstatus/document.py
@@ -101,7 +101,8 @@ class TestNetworkStatusDocument(unittest.TestCase):
self.assertEqual((), document.routers)
self.assertEqual("3", document.version)
- self.assertEqual("consensus", document.vote_status)
+ self.assertEqual(True, document.is_consensus)
+ self.assertEqual(False, document.is_vote)
self.assertEqual(9, document.consensus_method)
self.assertEqual([], document.consensus_methods)
self.assertEqual(None, document.published)
@@ -134,7 +135,8 @@ class TestNetworkStatusDocument(unittest.TestCase):
self.assertEqual((), document.routers)
self.assertEqual("3", document.version)
- self.assertEqual("vote", document.vote_status)
+ self.assertEqual(False, document.is_consensus)
+ self.assertEqual(True, document.is_vote)
self.assertEqual(None, document.consensus_method)
self.assertEqual([9], document.consensus_methods)
self.assertEqual(datetime.datetime(2012, 9, 2, 22, 0, 0), document.published)
@@ -216,7 +218,7 @@ class TestNetworkStatusDocument(unittest.TestCase):
def test_invalid_version(self):
"""
- Try parsing a different document version with the v3 parser.
+ Parses a different document version with the v3 parser.
"""
content = get_network_status_document({"network-status-version": "4"})
@@ -224,4 +226,23 @@ class TestNetworkStatusDocument(unittest.TestCase):
document = NetworkStatusDocument(content, False)
self.assertEquals("4", document.version)
+
+ def test_invalid_vote_status(self):
+ """
+ Parses an invalid vote-status field.
+ """
+
+ test_values = (
+ "",
+ " ",
+ "votee",
+ )
+
+ for test_value in test_values:
+ content = get_network_status_document({"vote-status": test_value})
+ self.assertRaises(ValueError, NetworkStatusDocument, content)
+
+ document = NetworkStatusDocument(content, False)
+ self.assertEquals(True, document.is_consensus)
+ self.assertEquals(False, document.is_vote)
diff --git a/test/unit/descriptor/networkstatus/entry.py b/test/unit/descriptor/networkstatus/entry.py
index 7eb0cb8..195600c 100644
--- a/test/unit/descriptor/networkstatus/entry.py
+++ b/test/unit/descriptor/networkstatus/entry.py
@@ -408,7 +408,8 @@ class TestRouterStatusEntry(unittest.TestCase):
# we need a document that's a vote
mock_document = lambda x: x # just need anything with a __dict__
- mock_document.__dict__["vote_status"] = "vote"
+ mock_document.__dict__["is_vote"] = True
+ mock_document.__dict__["is_consensus"] = False
for m_line, expected in test_values.items():
content = get_router_status_entry({'m': m_line})
More information about the tor-commits
mailing list