commit f5c7c226321f801e448f759e2ba3617c0ef5814b Author: Damian Johnson atagar@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.
tor-commits@lists.torproject.org