[tor-commits] [stem/master] Adding get_microdescriptor() method to the Controller

atagar at torproject.org atagar at torproject.org
Sun Mar 3 03:59:45 UTC 2013


commit f5c7c226321f801e448f759e2ba3617c0ef5814b
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat Mar 2 19:28:06 2013 -0800

    Adding get_microdescriptor() method to the Controller
    
    Adding a method to query individual microdescriptors. This is very similar to
    its server descriptor and network status counterparts.
---
 stem/control.py                    |   37 ++++++++++++++++++++++++++++++++++++
 stem/descriptor/microdescriptor.py |   15 ++++++++++++++
 test/integ/control/controller.py   |   37 ++++++++++++++++++++++++++++++++++++
 3 files changed, 89 insertions(+), 0 deletions(-)

diff --git a/stem/control.py b/stem/control.py
index 77e78c6..d1025b8 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -25,6 +25,7 @@ providing its own for interacting at a higher level.
     |- get_socks_listeners - provides where tor is listening for SOCKS connections
     |- get_protocolinfo - information about the controller interface
     |
+    |- get_microdescriptor - querying the microdescriptor for a relay
     |- get_server_descriptor - querying the server descriptor for a relay
     |- get_server_descriptors - provides all presently available server descriptors
     |- get_network_status - querying the router status entry for a relay
@@ -139,6 +140,7 @@ import StringIO
 import threading
 import time
 
+import stem.descriptor.microdescriptor
 import stem.descriptor.router_status_entry
 import stem.descriptor.server_descriptor
 import stem.exit_policy
@@ -962,6 +964,41 @@ class Controller(BaseController):
       else:
         return default
 
+  def get_microdescriptor(self, relay, default = UNDEFINED):
+    """
+    Provides the microdescriptor for the relay with the given fingerprint or
+    nickname. If the relay identifier could be either a fingerprint *or*
+    nickname then it's queried as a fingerprint.
+
+    :param str relay: fingerprint or nickname of the relay to be queried
+    :param object default: response if the query fails
+
+    :returns: :class:`~stem.descriptor.microdescriptor.Microdescriptor` for the given relay
+
+    :raises:
+      * :class:`stem.ControllerError` if unable to query the descriptor
+      * **ValueError** if **relay** doesn't conform with the pattern for being
+        a fingerprint or nickname
+
+      An exception is only raised if we weren't provided a default response.
+    """
+
+    try:
+      if stem.util.tor_tools.is_valid_fingerprint(relay):
+        query = "md/id/%s" % relay
+      elif stem.util.tor_tools.is_valid_nickname(relay):
+        query = "md/name/%s" % relay
+      else:
+        raise ValueError("'%s' isn't a valid fingerprint or nickname" % relay)
+
+      desc_content = self.get_info(query)
+      return stem.descriptor.microdescriptor.Microdescriptor(desc_content)
+    except Exception, exc:
+      if default == UNDEFINED:
+        raise exc
+      else:
+        return default
+
   def get_server_descriptor(self, relay, default = UNDEFINED):
     """
     Provides the server descriptor for the relay with the given fingerprint or
diff --git a/stem/descriptor/microdescriptor.py b/stem/descriptor/microdescriptor.py
index e06d254..dc818aa 100644
--- a/stem/descriptor/microdescriptor.py
+++ b/stem/descriptor/microdescriptor.py
@@ -224,3 +224,18 @@ class Microdescriptor(stem.descriptor.Descriptor):
 
     if "onion-key" != entries.keys()[0]:
       raise ValueError("Microdescriptor must start with a 'onion-key' entry")
+
+  def _compare(self, other, method):
+    if not isinstance(other, Microdescriptor):
+      return False
+
+    return method(str(self).strip(), str(other).strip())
+
+  def __eq__(self, other):
+    return self._compare(other, lambda s, o: s == o)
+
+  def __lt__(self, other):
+    return self._compare(other, lambda s, o: s < o)
+
+  def __le__(self, other):
+    return self._compare(other, lambda s, o: s <= o)
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 6d55b80..23966a9 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -795,6 +795,43 @@ class TestController(unittest.TestCase):
 
       self.assertTrue(stem.util.connection.is_valid_ip_address(ip_addr))
 
+  def test_get_microdescriptor(self):
+    """
+    Basic checks for get_microdescriptor().
+    """
+
+    if test.runner.require_control(self):
+      return
+
+    with test.runner.get_runner().get_tor_controller() as controller:
+      # we should balk at invalid content
+      self.assertRaises(ValueError, controller.get_microdescriptor, None)
+      self.assertRaises(ValueError, controller.get_microdescriptor, "")
+      self.assertRaises(ValueError, controller.get_microdescriptor, 5)
+      self.assertRaises(ValueError, controller.get_microdescriptor, "z" * 30)
+
+      # try with a relay that doesn't exist
+      self.assertRaises(stem.ControllerError, controller.get_microdescriptor, "blargg")
+      self.assertRaises(stem.ControllerError, controller.get_microdescriptor, "5" * 40)
+
+      # microdescriptors exclude the fingerprint and nickname so checking the
+      # consensus to get the nickname and fingerprint of a relay
+
+      test_router_status_entry = None
+
+      for desc in controller.get_network_statuses():
+        if desc.nickname != "Unnamed":
+          test_router_status_entry = desc
+          break
+
+      if test_router_status_entry is None:
+        self.fail("Unable to find any relays without a nickname of 'Unnamed'")
+
+      md_by_fingerprint = controller.get_microdescriptor(test_router_status_entry.fingerprint)
+      md_by_nickname = controller.get_microdescriptor(test_router_status_entry.nickname)
+
+      self.assertEqual(md_by_fingerprint, md_by_nickname)
+
   def test_get_server_descriptor(self):
     """
     Compares get_server_descriptor() against our cached descriptors.





More information about the tor-commits mailing list