commit 737018b7ee3da5b457689404376a7937de5a016f Author: Damian Johnson atagar@torproject.org Date: Sun Jun 10 13:33:21 2012 -0700
Making relay-signature field only apply to relay extra-info
Parsing and handling the relay-signature field as only an attribute of extra-info descriptors for relays. --- stem/descriptor/__init__.py | 4 +- stem/descriptor/extrainfo_descriptor.py | 78 ++++++++++++++++++------ stem/descriptor/server_descriptor.py | 31 ++++------ test/integ/descriptor/extrainfo_descriptor.py | 2 +- test/unit/descriptor/extrainfo_descriptor.py | 34 +++++----- 5 files changed, 91 insertions(+), 58 deletions(-)
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py index e4f23e5..929396c 100644 --- a/stem/descriptor/__init__.py +++ b/stem/descriptor/__init__.py @@ -70,9 +70,9 @@ def parse_file(path, descriptor_file): desc = stem.descriptor.server_descriptor.RelayDescriptor(descriptor_file.read()) elif first_line == "@type bridge-server-descriptor 1.0": desc = stem.descriptor.server_descriptor.BridgeDescriptor(descriptor_file.read()) - elif first_line in ("@type bridge-extra-info 1.0"): - desc = stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor(descriptor_file.read()) elif first_line in ("@type extra-info 1.0"): + desc = stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor(descriptor_file.read()) + elif first_line in ("@type bridge-extra-info 1.0"): desc = stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor(descriptor_file.read())
if desc: diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py index 03c7a37..0515099 100644 --- a/stem/descriptor/extrainfo_descriptor.py +++ b/stem/descriptor/extrainfo_descriptor.py @@ -114,9 +114,6 @@ SINGLE_FIELDS = ( "exit-streams-opened", )
-FIRST_FIELD = "extra-info" -LAST_FIELD = "router-signature" - def parse_file(descriptor_file, validate = True): """ Iterates over the extra-info descriptors in a file. @@ -139,7 +136,7 @@ def parse_file(descriptor_file, validate = True): extrainfo_content += stem.descriptor._read_until_keyword(block_end_prefix, descriptor_file, True)
if extrainfo_content: - yield ExtraInfoDescriptor("".join(extrainfo_content), validate) + yield RelayExtraInfoDescriptor("".join(extrainfo_content), validate) else: break # done parsing file
def _parse_timestamp_and_interval(keyword, content): @@ -180,7 +177,6 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor): :var str fingerprint: ***** identity key fingerprint :var datetime published: ***** time in GMT when this descriptor was made :var str geoip_db_digest: sha1 of geoIP database file - :var str signature: ***** signature for this extrainfo descriptor
**Bi-directional connection usage:**
@@ -289,7 +285,6 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor): self.fingerprint = None self.published = None self.geoip_db_digest = None - self.signature = None
self.conn_bi_direct_end = None self.conn_bi_direct_interval = None @@ -364,18 +359,21 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor): stem.descriptor._get_descriptor_components(raw_contents, validate, ())
if validate: - for keyword in REQUIRED_FIELDS: + for keyword in self._required_fields(): if not keyword in entries: raise ValueError("Extra-info descriptor must have a '%s' entry" % keyword)
- for keyword in REQUIRED_FIELDS + SINGLE_FIELDS: + for keyword in self._required_fields() + SINGLE_FIELDS: if keyword in entries and len(entries[keyword]) > 1: raise ValueError("The '%s' entry can only appear once in an extra-info descriptor" % keyword) - if not first_keyword == FIRST_FIELD: - raise ValueError("Extra-info descriptor must start with a '%s' entry" % FIRST_FIELD)
- if not last_keyword == LAST_FIELD: - raise ValueError("Descriptor must end with a '%s' entry" % LAST_FIELD) + expected_first_keyword = self._first_keyword() + if expected_first_keyword and not first_keyword == expected_first_keyword: + raise ValueError("Extra-info descriptor must start with a '%s' entry" % expected_first_keyword) + + expected_last_keyword = self._last_keyword() + if expected_last_keyword and not last_keyword == expected_last_keyword: + raise ValueError("Descriptor must end with a '%s' entry" % expected_last_keyword)
self._parse(entries, validate)
@@ -395,10 +393,8 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor):
for keyword, values in entries.items(): # most just work with the first (and only) value - value, block_contents = values[0] - + value, _ = values[0] line = "%s %s" % (keyword, value) # original line - if block_contents: line += "\n%s" % block_contents
if keyword == "extra-info": # "extra-info" Nickname Fingerprint @@ -674,23 +670,65 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor): self.entry_ips = locale_usage elif keyword == "bridge-ips": self.bridge_ips = locale_usage - elif keyword == "router-signature": - if validate and not block_contents: - raise ValueError("Router signature line must be followed by a signature block: %s" % line) - - self.signature = block_contents else: self._unrecognized_lines.append(line) + + def _required_fields(self): + return REQUIRED_FIELDS + + def _first_keyword(self): + return "extra-info" + + def _last_keyword(self): + return "router-signature"
class RelayExtraInfoDescriptor(ExtraInfoDescriptor): """ Relay extra-info descriptor, constructed from data such as that provided by "GETINFO extra-info/digest/*", cached descriptors, and metrics (`specification https://gitweb.torproject.org/torspec.git/blob/HEAD:/dir-spec.txt`_). + + :var str signature: ***** signature for this extrainfo descriptor + + ***** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined """ + + def __init__(self, raw_contents, validate = True): + self.signature = None + + ExtraInfoDescriptor.__init__(self, raw_contents, validate) + + def _parse(self, entries, validate): + entries = dict(entries) # shallow copy since we're destructive + + # handles fields only in server descriptors + for keyword, values in entries.items(): + value, block_contents = values[0] + + line = "%s %s" % (keyword, value) # original line + if block_contents: line += "\n%s" % block_contents + + if keyword == "router-signature": + if validate and not block_contents: + raise ValueError("Router signature line must be followed by a signature block: %s" % line) + + self.signature = block_contents + del entries["router-signature"] + + ExtraInfoDescriptor._parse(self, entries, validate)
class BridgeExtraInfoDescriptor(ExtraInfoDescriptor): """ Bridge extra-info descriptor (`specification https://metrics.torproject.org/formats.html#bridgedesc`_) """ + + def _required_fields(self): + excluded_fields = ( + "router-signature", + ) + + filter(lambda e: not e in excluded_fields, REQUIRED_FIELDS) + + def _last_keyword(self): + return None
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py index 6f8079b..af31f03 100644 --- a/stem/descriptor/server_descriptor.py +++ b/stem/descriptor/server_descriptor.py @@ -542,10 +542,17 @@ class ServerDescriptor(stem.descriptor.Descriptor): # Constraints that the descriptor must meet to be valid. These can be None if # not applicable.
- def _required_fields(self): return None - def _single_fields(self): return None - def _first_keyword(self): return None - def _last_keyword(self): return None + def _required_fields(self): + return REQUIRED_FIELDS + + def _single_fields(self): + return REQUIRED_FIELDS + SINGLE_FIELDS + + def _first_keyword(self): + return "router" + + def _last_keyword(self): + return "router-signature"
class RelayDescriptor(ServerDescriptor): """ @@ -640,18 +647,6 @@ class RelayDescriptor(ServerDescriptor): del entries["router-signature"]
ServerDescriptor._parse(self, entries, validate) - - def _required_fields(self): - return REQUIRED_FIELDS - - def _single_fields(self): - return REQUIRED_FIELDS + SINGLE_FIELDS - - def _first_keyword(self): - return "router" - - def _last_keyword(self): - return "router-signature"
class BridgeDescriptor(ServerDescriptor): """ @@ -780,6 +775,6 @@ class BridgeDescriptor(ServerDescriptor): def _single_fields(self): return self._required_fields() + SINGLE_FIELDS
- def _first_keyword(self): - return "router" + def _last_keyword(self): + return None
diff --git a/test/integ/descriptor/extrainfo_descriptor.py b/test/integ/descriptor/extrainfo_descriptor.py index a753d27..2739985 100644 --- a/test/integ/descriptor/extrainfo_descriptor.py +++ b/test/integ/descriptor/extrainfo_descriptor.py @@ -29,7 +29,7 @@ k0d2aofcVbHr4fPQOSST0LXDrhFl5Fqo5um296zpJGvRUeO6S44U/EfJAGShtqWw 7LZqklu+gVvhMKREpchVqlAwXkWR44VENm24Hs+mT3M= -----END SIGNATURE-----"""
- desc = stem.descriptor.extrainfo_descriptor.ExtraInfoDescriptor(descriptor_contents) + desc = stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor(descriptor_contents) self.assertEquals("NINJA", desc.nickname) self.assertEquals("B2289C3EAB83ECD6EB916A2F481A02E6B76A0A48", desc.fingerprint) self.assertEquals(datetime.datetime(2012, 5, 5, 17, 3, 50), desc.published) diff --git a/test/unit/descriptor/extrainfo_descriptor.py b/test/unit/descriptor/extrainfo_descriptor.py index 5d4c910..3bfff70 100644 --- a/test/unit/descriptor/extrainfo_descriptor.py +++ b/test/unit/descriptor/extrainfo_descriptor.py @@ -4,7 +4,7 @@ Unit tests for stem.descriptor.extrainfo_descriptor.
import datetime import unittest -from stem.descriptor.extrainfo_descriptor import ExtraInfoDescriptor, DirResponses, DirStats +from stem.descriptor.extrainfo_descriptor import RelayExtraInfoDescriptor, DirResponses, DirStats
CRYPTO_BLOB = """ K5FSywk7qvw/boA4DQcqkls6Ize5vcBYfhQ8JnOeRQC9+uDxbnpm3qaYN9jZ8myj @@ -58,7 +58,7 @@ class TestExtraInfoDescriptor(unittest.TestCase): """
desc_text = _make_descriptor() - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text)
self.assertEquals("ninja", desc.nickname) self.assertEquals("B2289C3EAB83ECD6EB916A2F481A02E6B76A0A48", desc.fingerprint) @@ -70,7 +70,7 @@ class TestExtraInfoDescriptor(unittest.TestCase): """
desc_text = _make_descriptor({"pepperjack": "is oh so tasty!"}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) self.assertEquals(["pepperjack is oh so tasty!"], desc.get_unrecognized_lines())
def test_proceeding_line(self): @@ -116,7 +116,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
geoip_db_digest = "916A3CA8B7DF61473D5AE5B21711F35F301CE9E8" desc_text = _make_descriptor({"geoip-db-digest": geoip_db_digest}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) self.assertEquals(geoip_db_digest, desc.geoip_db_digest)
test_entries = ( @@ -143,7 +143,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
for entry in ("0", "11", "25"): desc_text = _make_descriptor({"cell-circuits-per-decile": entry}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) self.assertEquals(int(entry), desc.cell_circuits_per_decile)
test_entries = ( @@ -169,7 +169,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
test_value = "ok=0,unavailable=0,not-found=984,not-modified=0,something-new=7" desc_text = _make_descriptor({keyword: test_value}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) 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]) @@ -200,7 +200,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
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 = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) 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 +237,7 @@ class TestExtraInfoDescriptor(unittest.TestCase): """
desc_text = _make_descriptor({"conn-bi-direct": "2012-05-03 12:07:50 (500 s) 277431,12089,0,2134"}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) 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) @@ -287,7 +287,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
for test_value, expected_value in test_entries: desc_text = _make_descriptor({keyword: test_value}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) self.assertEquals(expected_value, getattr(desc, attr))
test_entries = ( @@ -320,7 +320,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
for test_value, expected_value in test_entries: desc_text = _make_descriptor({keyword: test_value}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) self.assertEquals(expected_value, getattr(desc, attr))
test_entries = ( @@ -343,7 +343,7 @@ class TestExtraInfoDescriptor(unittest.TestCase): attr = keyword.replace('-', '_')
desc_text = _make_descriptor({keyword: "2012-05-03 12:07:50"}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) self.assertEquals(datetime.datetime(2012, 5, 3, 12, 7, 50), getattr(desc, attr))
test_entries = ( @@ -368,7 +368,7 @@ class TestExtraInfoDescriptor(unittest.TestCase): interval_attr = end_attr[:-4] + "_interval"
desc_text = _make_descriptor({keyword: "2012-05-03 12:07:50 (500 s)"}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) self.assertEquals(datetime.datetime(2012, 5, 3, 12, 7, 50), getattr(desc, end_attr)) self.assertEquals(500, getattr(desc, interval_attr))
@@ -408,7 +408,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 = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) 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)) @@ -448,7 +448,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
for test_value, expected_value in test_entries: desc_text = _make_descriptor({keyword: test_value}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) self.assertEquals(expected_value, getattr(desc, attr))
test_entries = ( @@ -480,7 +480,7 @@ class TestExtraInfoDescriptor(unittest.TestCase):
for test_value, expected_value in test_entries: desc_text = _make_descriptor({keyword: test_value}) - desc = ExtraInfoDescriptor(desc_text) + desc = RelayExtraInfoDescriptor(desc_text) self.assertEquals(expected_value, getattr(desc, attr))
test_entries = ( @@ -501,8 +501,8 @@ class TestExtraInfoDescriptor(unittest.TestCase): value when we're constructed without validation. """
- self.assertRaises(ValueError, ExtraInfoDescriptor, desc_text) - desc = ExtraInfoDescriptor(desc_text, validate = False) + self.assertRaises(ValueError, RelayExtraInfoDescriptor, desc_text) + desc = RelayExtraInfoDescriptor(desc_text, validate = False)
if attr: # check that the invalid attribute matches the expected value when
tor-commits@lists.torproject.org