[tor-commits] [stem/master] Support for CLIENTS_SEEN events

atagar at torproject.org atagar at torproject.org
Mon Dec 3 02:35:44 UTC 2012


commit 92419a54b83d80d7b124482df9bdf42fb931c7a0
Author: Damian Johnson <atagar at 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





More information about the tor-commits mailing list