commit 92419a54b83d80d7b124482df9bdf42fb931c7a0 Author: Damian Johnson atagar@torproject.org Date: Sun Dec 2 17:23:02 2012 -0800
Support for CLIENTS_SEEN events
Implementation and testing for CLINETS_SEEN events. This would be a difficult event to get examples for, but luckily the spec has one. --- stem/control.py | 1 + stem/response/events.py | 58 ++++++++++++++++++++++++++++++++++++++++++ test/unit/response/events.py | 16 +++++++++++ 3 files changed, 75 insertions(+), 0 deletions(-)
diff --git a/stem/control.py b/stem/control.py index 5fd69c7..ec727ce 100644 --- a/stem/control.py +++ b/stem/control.py @@ -82,6 +82,7 @@ providing its own for interacting at a higher level. **AUTHDIR_NEWDESCS** :class:`stem.response.events.AuthDirNewDescEvent` **BW** :class:`stem.response.events.BandwidthEvent` **CIRC** :class:`stem.response.events.CircuitEvent` + **CLIENTS_SEEN** :class:`stem.response.events.ClientsSeenEvent` **DEBUG** :class:`stem.response.events.LogEvent` **DESCCHANGED** :class:`stem.response.events.DescChangedEvent` **ERR** :class:`stem.response.events.LogEvent` diff --git a/stem/response/events.py b/stem/response/events.py index 1bdfd02..49a8169 100644 --- a/stem/response/events.py +++ b/stem/response/events.py @@ -311,6 +311,63 @@ class CircuitEvent(Event): log_id = "event.circ.unknown_remote_reason.%s" % self.remote_reason log.log_once(log_id, log.INFO, unrecognized_msg % ('remote reason', self.remote_reason))
+class ClientsSeenEvent(Event): + """ + Periodic event on bridge relays that provides a summary of our users. + + :var datetime start_time: time in UTC that we started collecting these stats + :var dict locales: mapping of country codes to a rounded count for the number of users + :var dict ip_versions: mapping of ip protocols to a rounded count for the number of users + """ + + _KEYWORD_ARGS = { + "TimeStarted": "start_time", + "CountrySummary": "locales", + "IPVersions": "ip_versions", + } + + _QUOTED = ("TimeStarted",) + + def _parse(self): + if self.start_time != None: + self.start_time = datetime.datetime.strptime(self.start_time, "%Y-%m-%d %H:%M:%S") + + if self.locales != None: + locale_to_count = {} + + for entry in self.locales.split(','): + if not '=' in entry: + raise stem.ProtocolError("The CLIENTS_SEEN's CountrySummary should be a comma separated listing of '<locale>=<count>' mappings: %s" % self) + + locale, count = entry.split('=', 1) + + if len(locale) != 2: + raise stem.ProtocolError("Locales should be a two character code, got '%s': %s" % (locale, self)) + elif not count.isdigit(): + raise stem.ProtocolError("Locale count was non-numeric (%s): %s" % (count, self)) + elif locale in locale_to_count: + raise stem.ProtocolError("CountrySummary had multiple mappings for '%s': %s" % (locale, self)) + + locale_to_count[locale] = int(count) + + self.locales = locale_to_count + + if self.ip_versions != None: + protocol_to_count = {} + + for entry in self.ip_versions.split(','): + if not '=' in entry: + raise stem.ProtocolError("The CLIENTS_SEEN's IPVersions should be a comma separated listing of '<protocol>=<count>' mappings: %s" % self) + + protocol, count = entry.split('=', 1) + + if not count.isdigit(): + raise stem.ProtocolError("IP protocol count was non-numeric (%s): %s" % (count, self)) + + protocol_to_count[protocol] = int(count) + + self.ip_versions = protocol_to_count + class DescChangedEvent(Event): """ Event that indicates that our descriptor has changed. This was first added in @@ -581,6 +638,7 @@ EVENT_TYPE_TO_CLASS = { "AUTHDIR_NEWDESCS": AuthDirNewDescEvent, "BW": BandwidthEvent, "CIRC": CircuitEvent, + "CLIENTS_SEEN": ClientsSeenEvent, "DEBUG": LogEvent, "DESCCHANGED": DescChangedEvent, "ERR": LogEvent, diff --git a/test/unit/response/events.py b/test/unit/response/events.py index 84a1e7b..0b225d9 100644 --- a/test/unit/response/events.py +++ b/test/unit/response/events.py @@ -41,6 +41,13 @@ $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone" CIRC_BUILT_OLD = "650 CIRC 1 BUILT \ $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14"
+# CLIENTS_SEEN example from the spec + +CLIENTS_SEEN_EVENT = '650 CLIENTS_SEEN \ +TimeStarted="2008-12-25 23:50:43" \ +CountrySummary=us=16,de=8,uk=8 \ +IPVersions=v4=16,v6=40' + # GUARD events from tor v0.2.1.30.
GUARD_NEW = "650 GUARD ENTRY $36B5DBA788246E8369DBAF58577C6BC044A9A374 NEW" @@ -343,6 +350,15 @@ class TestEvents(unittest.TestCase): self.assertEqual(None, event.reason) self.assertEqual(None, event.remote_reason)
+ def test_clients_seen_event(self): + event = _get_event(CLIENTS_SEEN_EVENT) + + self.assertTrue(isinstance(event, stem.response.events.ClientsSeenEvent)) + self.assertEqual(CLIENTS_SEEN_EVENT.lstrip("650 "), str(event)) + self.assertEqual(datetime.datetime(2008, 12, 25, 23, 50, 43), event.start_time) + self.assertEqual({'us': 16, 'de': 8, 'uk': 8}, event.locales) + self.assertEqual({'v4': 16, 'v6': 40}, event.ip_versions) + def test_descchanged_event(self): # all we can check for is that the event is properly parsed as a # DescChangedEvent instance