commit c33c4a0f0e6b98898622d0929e531d12abdce72a Author: Damian Johnson atagar@torproject.org Date: Thu May 10 09:29:04 2012 -0700
Parsing bridge specific extra-info params
Parsing, validation, and tests for bridge specific lines... - geoip-start-time - geoip-client-origins - bridge-stats-end - bridge-stats-ips
I haven't yet seen an actual bridge descriptor, and I'd like to add one as an integ test but that can come later. --- stem/descriptor/extrainfo_descriptor.py | 58 ++++++++++++++++++- stem/descriptor/server_descriptor.py | 3 +- test/unit/descriptor/extrainfo_descriptor.py | 81 ++++++++++++++++++++++++-- 3 files changed, 132 insertions(+), 10 deletions(-)
diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py index d2ee780..380d022 100644 --- a/stem/descriptor/extrainfo_descriptor.py +++ b/stem/descriptor/extrainfo_descriptor.py @@ -165,6 +165,16 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor): dir_write_history_interval (int) - seconds per interval dir_write_history_values (list) - bytes read during each interval (*)
+ Bridge-only Attributes: + bridge_stats_end (datetime.datetime) - end of the period when geoip + statistics were gathered + bridge_stats_end_interval (int) - length in seconds of th interval where + stats were gathered + bridge_ips (dict) - mapping of country codes to a rounded number of unique + ips from that region + geoip_start_time (datetime.datetime) - replaced by bridge_stats_end + geoip_client_origins (dict) - replaced by bridge_ips + (*) required fields, others are left as None if undefined """
@@ -215,6 +225,12 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor): self.dir_write_history_interval = None self.dir_write_history_values = []
+ self.bridge_stats_end = None + self.bridge_stats_end_interval = None + self.bridge_ips = None + self.geoip_start_time = None + self.geoip_client_origins = None + self._unrecognized_lines = []
entries, first_keyword, last_keyword, _ = \ @@ -290,6 +306,45 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor): raise ValueError("Geoip digest line had an invalid sha1 digest: %s" % line)
self.geoip_db_digest = value + elif keyword == "geoip-start-time": + # "geoip-start-time" YYYY-MM-DD HH:MM:SS + + try: + self.geoip_start_time = datetime.datetime.strptime(value, "%Y-%m-%d %H:%M:%S") + except ValueError: + if validate: + raise ValueError("Geoip start time line's time wasn't parseable: %s" % line) + elif keyword == "bridge-stats-end": + # "bridge-stats-end" YYYY-MM-DD HH:MM:SS (NSEC s) + + try: + timestamp, interval, _ = _parse_timestamp_and_interval(keyword, value) + self.bridge_stats_end = timestamp + self.bridge_stats_end_interval = interval + except ValueError, exc: + if validate: raise exc + elif keyword in ("geoip-client-origins", "bridge-ips"): + # "geoip-client-origins" CC=N,CC=N,... + + locale_usage = {} + error_msg = "Entries in %s line should only be CC=N entries: %s" % (keyword, line) + + for entry in value.split(","): + if not "=" in entry: + if validate: raise ValueError(error_msg) + else: continue + + locale, count = entry.split("=", 1) + + if re.match("^[a-zA-Z]{2}$", locale) and count.isdigit(): + locale_usage[locale] = int(count) + elif validate: + raise ValueError(error_msg) + + if keyword == "geoip-client-origins": + self.geoip_client_origins = locale_usage + elif keyword == "bridge-ips": + self.bridge_ips = locale_usage elif keyword in ("read-history", "write-history", "dirreq-read-history", "dirreq-write-history"): try: timestamp, interval, remainder = _parse_timestamp_and_interval(keyword, value) @@ -327,8 +382,7 @@ class ExtraInfoDescriptor(stem.descriptor.Descriptor): # without fixing this one raise ValueError("BUG: unrecognized keyword '%s'" % keyword) except ValueError, exc: - if not validate: continue - else: raise exc + if validate: raise exc elif keyword == "router-signature": if validate and not block_contents: raise ValueError("Router signature line must be followed by a signature block: %s" % line) diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py index 80f1538..2963a53 100644 --- a/stem/descriptor/server_descriptor.py +++ b/stem/descriptor/server_descriptor.py @@ -491,8 +491,7 @@ class ServerDescriptor(stem.descriptor.Descriptor): self.write_history_interval = interval self.write_history_values = history_values except ValueError, exc: - if not validate: continue - else: raise exc + if validate: raise exc else: self._unrecognized_lines.append(line)
diff --git a/test/unit/descriptor/extrainfo_descriptor.py b/test/unit/descriptor/extrainfo_descriptor.py index 5a4a450..32732e3 100644 --- a/test/unit/descriptor/extrainfo_descriptor.py +++ b/test/unit/descriptor/extrainfo_descriptor.py @@ -2,6 +2,7 @@ Unit tests for stem.descriptor.extrainfo_descriptor. """
+import datetime import unittest from stem.descriptor.extrainfo_descriptor import ExtraInfoDescriptor
@@ -110,18 +111,13 @@ class TestExtraInfoDescriptor(unittest.TestCase):
def test_geoip_db_digest(self): """ - Parses a geoip-db-digest line with valid data. + Parses the geoip-db-digest line with valid and invalid data. """
geoip_db_digest = "916A3CA8B7DF61473D5AE5B21711F35F301CE9E8" desc_text = _make_descriptor({"geoip-db-digest": geoip_db_digest}) desc = ExtraInfoDescriptor(desc_text) self.assertEquals(geoip_db_digest, desc.geoip_db_digest) - - def test_geoip_db_digest_invalid(self): - """ - Parses the geoip-db-digest line with a variety of bad input. - """
test_entry = ( "", @@ -135,6 +131,79 @@ class TestExtraInfoDescriptor(unittest.TestCase): desc_text = _make_descriptor({"geoip-db-digest": entry}) desc = self._expect_invalid_attr(desc_text, "geoip_db_digest", entry)
+ def test_geoip_start_time(self): + """ + Parses the geoip-start-time line with valid and invalid data. + """ + + desc_text = _make_descriptor({"geoip-start-time": "2012-05-03 12:07:50"}) + desc = ExtraInfoDescriptor(desc_text) + self.assertEquals(datetime.datetime(2012, 5, 3, 12, 7, 50), desc.geoip_start_time) + + test_entry = ( + "", + "2012-05-03 12:07:60", + "2012-05-03 ", + "2012-05-03", + ) + + for entry in test_entry: + desc_text = _make_descriptor({"geoip-start-time": entry}) + desc = self._expect_invalid_attr(desc_text, "geoip_start_time") + + def test_bridge_stats_end(self): + """ + Parses the bridge-stats-end line with valid and invalid data. + """ + + desc_text = _make_descriptor({"bridge-stats-end": "2012-05-03 12:07:50 (500 s)"}) + desc = ExtraInfoDescriptor(desc_text) + self.assertEquals(datetime.datetime(2012, 5, 3, 12, 7, 50), desc.bridge_stats_end) + self.assertEquals(500, desc.bridge_stats_end_interval) + + test_entry = ( + "", + "2012-05-03 12:07:60 (500 s)", + "2012-05-03 12:07:50 (500s)", + "2012-05-03 12:07:50 (500 s", + "2012-05-03 12:07:50 (500 )", + "2012-05-03 ", + "2012-05-03", + ) + + for entry in test_entry: + desc_text = _make_descriptor({"bridge-stats-end": entry}) + desc = self._expect_invalid_attr(desc_text, "bridge_stats_end") + + def test_bridge_ips(self): + """ + Parses both the bridge-ips and geoip-client-origins lines with valid and + invalid data. + """ + + # Testing both attributes since they contain the exact same data, + # geoip-client-origins was simply replaced by bridge-ips while adding an + # interval value for the period. + + for keyword in ('bridge-ips', 'geoip-client-origins'): + attr = keyword.replace('-', '_') + + desc_text = _make_descriptor({keyword: "uk=5,de=3,jp=2"}) + desc = ExtraInfoDescriptor(desc_text) + self.assertEquals({'uk': 5, 'de': 3, 'jp': 2}, getattr(desc, attr)) + + test_entry = ( + "", + "uk=-4", + "uki=4", + "uk:4", + "uk=4.de=3", + ) + + for entry in test_entry: + desc_text = _make_descriptor({keyword: entry}) + desc = self._expect_invalid_attr(desc_text, attr, {}) + def _expect_invalid_attr(self, desc_text, attr = None, expected_value = None): """ Asserts that construction will fail due to desc_text having a malformed
tor-commits@lists.torproject.org