[tor-commits] [bridgedb/develop] Add bridgedb.distribute module with IDistribute and Distributor classes.

isis at torproject.org isis at torproject.org
Thu Jun 25 07:10:54 UTC 2015


commit fe0efec8b00250756d36c7fb2cd061fd8ef3d3d3
Author: Isis Lovecruft <isis at torproject.org>
Date:   Sun Apr 19 00:05:02 2015 +0000

    Add bridgedb.distribute module with IDistribute and Distributor classes.
    
     * ADD distribute.IDistibute interface.
     * ADD distribute.Distributor class, an implementation of IDistribute.
     * REMOVE bridgedb.Dist.Distributor.
     * RENAME HTTPSDistributor.getBridgesForIP() →
                                        HTTPSDistributor.getBridges().
     * RENAME EmailBasedDistributor.getBridgesForEmail() →
                                        EmailBasedDistributor.getBridges().
     * FIXES part of #12506: https://bugs.torproject.org/12506
     * FIXES part of #12029: https://bugs.torproject.org/12029
---
 lib/bridgedb/Dist.py                 |  104 +++----------
 lib/bridgedb/distribute.py           |  275 ++++++++++++++++++++++++++++++++++
 lib/bridgedb/email/autoresponder.py  |    5 +-
 lib/bridgedb/interfaces.py           |   56 ++++++-
 lib/bridgedb/test/email_helpers.py   |   10 +-
 lib/bridgedb/test/https_helpers.py   |    2 +-
 lib/bridgedb/test/legacy_Tests.py    |   64 ++++----
 lib/bridgedb/test/test_Dist.py       |   11 +-
 lib/bridgedb/test/test_distribute.py |   44 ++++++
 lib/bridgedb/test/test_interfaces.py |   55 +++++++
 10 files changed, 495 insertions(+), 131 deletions(-)

diff --git a/lib/bridgedb/Dist.py b/lib/bridgedb/Dist.py
index 93c08c1..fe81c04 100644
--- a/lib/bridgedb/Dist.py
+++ b/lib/bridgedb/Dist.py
@@ -17,20 +17,19 @@ import logging
 import re
 import time
 
-import bridgedb.Bridges
 import bridgedb.Storage
 
 from bridgedb import proxy
+from bridgedb.Bridges import BridgeRing
 from bridgedb.Bridges import FilteredBridgeSplitter
 from bridgedb.crypto import getHMAC
 from bridgedb.crypto import getHMACFunc
+from bridgedb.distribute import Distributor
 from bridgedb.Filters import filterAssignBridgesToRing
 from bridgedb.Filters import filterBridgesByRules
 from bridgedb.Filters import filterBridgesByIP4
 from bridgedb.Filters import filterBridgesByIP6
 from bridgedb.parse import addr
-from bridgedb.parse.addr import UnsupportedDomain
-from bridgedb.safelog import logSafely
 
 
 MAX_EMAIL_RATE = 3*3600
@@ -48,50 +47,6 @@ class EmailRequestedKey(Exception):
     """Raised when an incoming email requested a copy of our GnuPG keys."""
 
 
-class Distributor(object):
-    """Distributes bridges to clients."""
-
-    def __init__(self):
-        super(Distributor, self).__init__()
-        self.name = None
-        self.hashring = None
-
-    def setDistributorName(self, name):
-        """Set a **name** for identifying this distributor.
-
-        This is used to identify the distributor in the logs; the **name**
-        doesn't necessarily need to be unique. The hashrings created for this
-        distributor will be named after this distributor's name in
-        :meth:`propopulateRings`, and any sub hashrings of each of those
-        hashrings will also carry that name.
-
-        >>> from bridgedb import Dist
-        >>> dist = Dist.HTTPSDistributor(2, 'masterkey')
-        >>> dist.setDistributorName('Excellent Distributor')
-        >>> dist.name
-        'Excellent Distributor'
-
-        :param str name: A name for this distributor.
-        """
-        self.name = name
-        self.hashring.distributorName = name
-
-    def bridgesPerResponse(self, hashring=None, maximum=3):
-        if hashring is None:
-            hashring = self.hashring
-
-        if len(hashring) < 20:
-            n = 1
-        if 20 <= len(hashring) < 100:
-            n = min(2, maximum)
-        if len(hashring) >= 100:
-            n = maximum
-
-        logging.debug("Returning %d bridges from ring of len: %d" %
-                      (n, len(hashring)))
-        return n
-
-
 class HTTPSDistributor(Distributor):
     """A Distributor that hands out bridges based on the IP address of an
     incoming request and the current time period.
@@ -129,9 +84,7 @@ class HTTPSDistributor(Distributor):
             parameters, i.e. that an answer has "at least two obfsproxy
             bridges" or "at least one bridge on port 443", etc.
         """
-        super(HTTPSDistributor, self).__init__()
-
-        self.key = key
+        super(HTTPSDistributor, self).__init__(key)
         self.totalSubrings = totalSubrings
         self.answerParameters = answerParameters
 
@@ -154,13 +107,12 @@ class HTTPSDistributor(Distributor):
         self._clientToPositionHMAC = getHMACFunc(key3, hex=False)
         self._subnetToSubringHMAC = getHMACFunc(key4, hex=True)
         self.hashring = FilteredBridgeSplitter(key2, self.ringCacheSize)
-        logging.debug("Added %s to HTTPS distributor." %
-                      self.hashring.__class__.__name__)
+        self.name = 'HTTPS'
+        logging.debug("Added %s to %s distributor." %
+                      (self.hashring.__class__.__name__, self.name))
 
-        self.setDistributorName('HTTPS')
-
-    def bridgesPerResponse(self, hashring=None, maximum=3):
-        return super(HTTPSDistributor, self).bridgesPerResponse(hashring, maximum)
+    def bridgesPerResponse(self, hashring=None):
+        return super(HTTPSDistributor, self).bridgesPerResponse(hashring)
 
     @classmethod
     def getSubnet(cls, ip, usingProxy=False, proxySubnets=4):
@@ -311,7 +263,7 @@ class HTTPSDistributor(Distributor):
             for subring in range(1, self.totalSubrings + 1):
                 filters = self._buildHashringFilters([filterFn,], subring)
                 key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
-                ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
+                ring = BridgeRing(key1, self.answerParameters)
                 # For consistency with previous implementation of this method,
                 # only set the "name" for "clusters" which are for this
                 # distributor's proxies:
@@ -330,7 +282,7 @@ class HTTPSDistributor(Distributor):
         previousFilters.append(f)
         return frozenset(previousFilters)
 
-    def getBridges(self, bridgeRequest, interval, N=1):
+    def getBridges(self, bridgeRequest, interval):
         """Return a list of bridges to give to a user.
 
         :type bridgeRequest: :class:`bridgedb.https.request.HTTPSBridgeRequest`
@@ -339,15 +291,13 @@ class HTTPSDistributor(Distributor):
             attribute set to a string containing the client's IP address.
         :param str interval: The time period when we got this request.  This
             can be any string, so long as it changes with every period.
-        :param int N: The number of bridges to try to give back. (default: 1)
         :rtype: list
         :return: A list of :class:`~bridgedb.Bridges.Bridge`s to include in
             the response. See
             :meth:`bridgedb.https.server.WebResourceBridges.getBridgeRequestAnswer`
             for an example of how this is used.
         """
-        logging.info("Attempting to return %d bridges to client %s..."
-                     % (N, bridgeRequest.client))
+        logging.info("Attempting to get bridges for %s..." % bridgeRequest.client)
 
         if not len(self.hashring):
             logging.warn("Bailing! Hashring has zero bridges!")
@@ -386,12 +336,12 @@ class HTTPSDistributor(Distributor):
         else:
             logging.debug("Cache miss %s" % filters)
             key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
-            ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
+            ring = BridgeRing(key1, self.answerParameters)
             self.hashring.addRing(ring, filters, filterBridgesByRules(filters),
                                   populate_from=self.hashring.bridges)
 
         # Determine the appropriate number of bridges to give to the client:
-        returnNum = self.bridgesPerResponse(ring, maximum=N)
+        returnNum = self.bridgesPerResponse(ring)
         answer = ring.getBridges(position, returnNum)
 
         return answer
@@ -427,7 +377,7 @@ class EmailBasedDistributor(Distributor):
         :param whitelist: A dictionary that maps whitelisted email addresses
             to GnuPG fingerprints.
         """
-        self.key = key
+        super(EmailBasedDistributor, self).__init__(key)
 
         key1 = getHMAC(key, "Map-Addresses-To-Ring")
         self.emailHmac = getHMACFunc(key1, hex=False)
@@ -440,19 +390,17 @@ class EmailBasedDistributor(Distributor):
         self.answerParameters = answerParameters
 
         #XXX cache options not implemented
-        self.hashring = bridgedb.Bridges.FilteredBridgeSplitter(
-            key2, max_cached_rings=5)
-
-        self.setDistributorName('Email')
+        self.hashring = FilteredBridgeSplitter(key2, max_cached_rings=5)
+        self.name = "Email"
 
-    def bridgesPerResponse(self, hashring=None, maximum=3):
-        return super(EmailBasedDistributor, self).bridgesPerResponse(hashring, maximum)
+    def bridgesPerResponse(self, hashring=None):
+        return super(EmailBasedDistributor, self).bridgesPerResponse(hashring)
 
     def insert(self, bridge):
         """Assign a bridge to this distributor."""
         self.hashring.insert(bridge)
 
-    def getBridges(self, bridgeRequest, interval, N=1):
+    def getBridges(self, bridgeRequest, interval):
         """Return a list of bridges to give to a user.
 
         :type bridgeRequest: :class:`~bridgedb.email.request.EmailBridgeRequest`
@@ -462,7 +410,6 @@ class EmailBasedDistributor(Distributor):
             email address.
         :param interval: The time period when we got this request. This can be
             any string, so long as it changes with every period.
-        :param int N: The number of bridges to try to give back.
         """
         # All checks on the email address, such as checks for whitelisting and
         # canonicalization of domain name, are done in
@@ -473,15 +420,13 @@ class EmailBasedDistributor(Distributor):
                 ("%s distributor can't get bridges for invalid email email "
                  " address: %s") % (self.name, bridgeRequest.client))
 
+        logging.info("Attempting to get bridges for %s..." % bridgeRequest.client)
+
         now = time.time()
 
         with bridgedb.Storage.getDB() as db:
             wasWarned = db.getWarnedEmail(bridgeRequest.client)
             lastSaw = db.getEmailTime(bridgeRequest.client)
-
-            logging.info("Attempting to return for %d bridges for %s..."
-                         % (N, bridgeRequest.client))
-
             if lastSaw is not None:
                 if bridgeRequest.client in self.whitelist.keys():
                     logging.info(("Whitelisted email address %s was last seen "
@@ -500,7 +445,6 @@ class EmailBasedDistributor(Distributor):
                         db.commit()
                         raise TooSoonEmail("Must wait %d seconds" % wait,
                                            bridgeRequest.client)
-
             # warning period is over
             elif wasWarned:
                 db.setWarnedEmail(bridgeRequest.client, False)
@@ -518,12 +462,12 @@ class EmailBasedDistributor(Distributor):
 
                 # add new ring
                 key1 = getHMAC(self.key, "Order-Bridges-In-Ring")
-                ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
+                ring = BridgeRing(key1, self.answerParameters)
                 self.hashring.addRing(ring, ruleset,
                                       filterBridgesByRules(ruleset),
                                       populate_from=self.hashring.bridges)
 
-            returnNum = self.bridgesPerResponse(ring, maximum=N)
+            returnNum = self.bridgesPerResponse(ring)
             result = ring.getBridges(pos, returnNum)
 
             db.setEmailTime(bridgeRequest.client, now)
@@ -553,7 +497,7 @@ class EmailBasedDistributor(Distributor):
         for filterFn in [filterBridgesByIP4, filterBridgesByIP6]:
             ruleset = frozenset([filterFn])
             key1 = getHMAC(self.key, "Order-Bridges-In-Ring")
-            ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
+            ring = BridgeRing(key1, self.answerParameters)
             self.hashring.addRing(ring, ruleset,
                                   filterBridgesByRules([filterFn]),
                                   populate_from=self.hashring.bridges)
diff --git a/lib/bridgedb/distribute.py b/lib/bridgedb/distribute.py
new file mode 100644
index 0000000..c7c9044
--- /dev/null
+++ b/lib/bridgedb/distribute.py
@@ -0,0 +1,275 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_distribute ; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+#           please also see AUTHORS file
+# :copyright: (c) 2013-2015, Isis Lovecruft
+#             (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+
+"""Classes for creating bridge distribution systems.
+
+DEFINITELY
+----------
+
+Distributor {
+  name property
+  bridgesPerResponse() property      FORMERLY getNumBridgesPerAnswer()
+  hashring struct     FORMERLY KNOWN AS splitter
+      rotate bool
+      rotationGroups
+      rotationSchedule
+      key str
+      subrings list
+          - Subring
+      clear()
+      export()        FORMERLY KNOWN AS dumpAssignments()
+      insert()        
+  getBridges()        FORMERLY KNOWN AS getBridgesForEmail() and getBridgesForIP()
+  handleBridgeRequest()
+  handleIncomingBridges()
+}
+
+DistributionContext {  # should go in bridgedb.py
+    distributors {
+        name: DistributorContext
+    }
+}
+
+DistributorContext {   # should go in bridgedb.py
+    name str
+    allocationPercentage property
+    publicKey
+}
+
+Hashring {
+    assignBridgesToRings()   FORMERLY filterAssignBridgesToRing()
+        + filters bridges uniformly into subrings
+    clear() / __del__()
+    isEmpty property
+}
+
+MAYBE
+-----
+mapClientToHashring() FORMERLY KNOWN AS areaMapper AND
+mapClientToSubhashring()
+authenticateToBridgeDB()
+maintainACL() for proxylists
+
+- need a way for BridgeDB to decide global parameters to be followed
+  by all distributors.  
+      - BridgeAnswerParameters?
+        maybe call it DistributionContext?
+        then have DistributorContexts?
+
+  requiredFlags  AnswerParameters?
+  requireFlag()
+  requiredPorts
+  requirePorts()
+
+  THINGS NEEDED FOR COMMUNICATION BETWEEN DISTRIBUTORS AND BRIDGEDB
+  -----------------------------------------------------------------
+  * distributorCredential (for authenticating to the DB)
+  * metrics?
+      * total clients seen
+      * total clients served
+      - unique clients seen
+      - unique clients served
+      * total requests for TRANSPORT
+      * total times TRANSPORT was served
+
+  THINGS DISTRIBUTORS SHOULD KEEP TRACK OF, BUT NOT REPORT
+  --------------------------------------------------------
+  - approximate bridge bandwidth
+  - approximate bandwidth per client
+  - approximate bridge bandwidth already distributed
+
+  NAMES FOR CHOOSING "GET ME WHATEVER TRANSPORTS"
+  -----------------------------------------------
+  chocolate box, russian roulette
+
+  * How much of a bad idea would it be to store bridges allocated to a
+    distributor as diffs over the last time the Distributor asked?
+"""
+
+import logging
+import math
+
+from zope import interface
+from zope.interface import Attribute
+from zope.interface import implements
+
+# from bridgedb.hashring import IHashring
+from bridgedb.interfaces import IName
+from bridgedb.interfaces import Named
+
+
+class IDistribute(IName):
+    """An interface specification for a system which distributes bridges."""
+
+    _bridgesPerResponseMin = Attribute(
+        ("The minimum number of bridges to distribute (if possible), per "
+         "client request."))
+    _bridgesPerResponseMax = Attribute(
+        ("The maximum number of bridges to distribute (if possible), per "
+         "client request."))
+    _hashringLevelMin = Attribute(
+        ("The bare minimum number of bridges which should be in a hashring. "
+         "If there less bridges than this, then the implementer of "
+         "IDistribute should only distribute _bridgesPerResponseMin number "
+         "of bridges, per client request."))
+    _hashringLevelMax = Attribute(
+        ("The number of bridges which should be in a hashring for the "
+         "implementer of IDistribute to distribute _bridgesPerResponseMax "
+         "number of bridges, per client request."))
+
+    hashring = Attribute(
+        ("An implementer of ``bridgedb.hashring.IHashring`` which stores the "
+         "entirety of bridges allocated to this ``Distributor`` by the "
+         "BridgeDB.  This ``Distributor`` is only capable of distributing "
+         "these bridges to its clients, and these bridges are only "
+         "distributable by this ``Distributor``."))
+
+    key = Attribute(
+        ("A master key which is used to HMAC bridge and client data into "
+         "this Distributor's **hashring** and its subhashrings."))
+
+    def __str__():
+        """Get a string representation of this Distributor's ``name``."""
+
+    def bridgesPerResponse(hashring):
+        """Get the current number of bridges to return in a response."""
+
+    def getBridges(bridgeRequest):
+        """Get bridges based on a client's **bridgeRequest**."""
+
+
+class Distributor(Named):
+    """A :class:`Distributor` distributes bridges to clients.
+
+    Inherit from me to create a new type of ``Distributor``.
+    """
+    implements(IDistribute)
+
+    _bridgesPerResponseMin = 1
+    _bridgesPerResponseMax = 3
+    _hashringLevelMin = 20
+    _hashringLevelMax = 100
+
+    def __init__(self, key=None):
+        """Create a new bridge Distributor.
+
+        :param key: A master key for this Distributor. This is used to HMAC
+            bridge and client data in order to arrange them into hashring
+            structures.
+        """
+        super(Distributor, self).__init__()
+        self._hashring = None
+        self.key = key
+
+    def __str__(self):
+        """Get a string representation of this ``Distributor``'s ``name``.
+
+        :rtype: str
+        :returns: This ``Distributor``'s ``name`` attribute.
+        """
+        return self.name
+
+    @property
+    def hashring(self):
+        """Get this Distributor's main hashring, which holds all bridges
+        allocated to this Distributor.
+
+        :rtype: :class:`~bridgedb.hashring.Hashring`.
+        :returns: An implementer of :interface:`~bridgedb.hashring.IHashring`.
+        """
+        return self._hashring
+
+    @hashring.setter
+    def hashring(self, ring):
+        """Set this Distributor's main hashring.
+
+        :type ring: :class:`~bridgedb.hashring.Hashring`
+        :param ring: An implementer of :interface:`~bridgedb.hashring.IHashring`.
+        :raises TypeError: if the **ring** does not implement the
+            :interface:`~bridgedb.hashring.IHashring` interface.
+        """
+        # if not IHashring.providedBy(ring):
+        #     raise TypeError("%r doesn't implement the IHashring interface." % ring)
+
+        self._hashring = ring
+
+    @hashring.deleter
+    def hashring(self):
+        """Clear this Distributor's hashring."""
+        if self.hashring:
+            self.hashring.clear()
+
+    @property
+    def name(self):
+        """Get the name of this Distributor.
+
+        :rtype: str
+        :returns: A string which identifies this :class:`Distributor`.
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Set a **name** for identifying this Distributor.
+
+        This is used to identify the distributor in the logs; the **name**
+        doesn't necessarily need to be unique. The hashrings created for this
+        distributor will be named after this distributor's name, and any
+        subhashrings of each of those hashrings will also carry that name.
+
+        >>> from bridgedb.distribute import Distributor
+        >>> dist = Distributor()
+        >>> dist.name = 'Excellent Distributor'
+        >>> dist.name
+        'Excellent Distributor'
+
+        :param str name: A name for this distributor.
+        """
+        self._name = name
+
+        try:
+            self.hashring.distributor = name
+        except AttributeError:
+            logging.debug(("Couldn't set distributor attribute for %s "
+                           "Distributor's hashring." % name))
+
+    def bridgesPerResponse(self, hashring=None):
+        """Get the current number of bridge to distribute in response to a
+        client's request for bridges.
+        """
+        if hashring is None:
+            hashring = self.hashring
+
+        if len(hashring) < self._hashringLevelMin:
+            n = self._bridgesPerResponseMin
+        elif self._hashringLevelMin <= len(hashring) < self._hashringLevelMax:
+            n = int(math.ceil(
+                (self._bridgesPerResponseMin + self._bridgesPerResponseMax) / 2.0))
+        elif self._hashringLevelMax <= len(hashring):
+            n = self._bridgesPerResponseMax
+
+        logging.debug("Returning %d bridges from ring of len: %d" %
+                      (n, len(hashring)))
+
+        return n
+
+    def getBridges(self, bridgeRequest):
+        """Get some bridges in response to a client's **bridgeRequest**.
+
+        :type bridgeRequest: :class:`~bridgedb.bridgerequest.BridgeRequestBase`
+        :param bridgeRequest: A client's request for bridges, including some
+            information on the client making the request, whether they asked
+            for IPv4 or IPv6 bridges, which type of
+            :class:`~bridgedb.bridges.PluggableTransport` they wanted, etc.
+        """
+        # XXX generalise the getBridges() method
diff --git a/lib/bridgedb/email/autoresponder.py b/lib/bridgedb/email/autoresponder.py
index 0ab04fa..b98717f 100644
--- a/lib/bridgedb/email/autoresponder.py
+++ b/lib/bridgedb/email/autoresponder.py
@@ -97,10 +97,7 @@ def createResponseBody(lines, context, client, lang='en'):
 
         # Otherwise they must have requested bridges:
         interval = context.schedule.intervalStart(time.time())
-        bridges = context.distributor.getBridges(
-            bridgeRequest,
-            interval,
-            context.nBridges)
+        bridges = context.distributor.getBridges(bridgeRequest, interval)
     except EmailRequestedHelp as error:
         logging.info(error)
         return templates.buildWelcomeText(translator, client)
diff --git a/lib/bridgedb/interfaces.py b/lib/bridgedb/interfaces.py
index 38c3541..89ddb12 100644
--- a/lib/bridgedb/interfaces.py
+++ b/lib/bridgedb/interfaces.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_interfaces ; -*-
 #_____________________________________________________________________________
 #
 # This file is part of BridgeDB, a Tor bridge distribution system.
@@ -13,7 +13,55 @@
 
 """All available ``zope.interface``s in BridgeDB."""
 
+from zope.interface import Interface
+from zope.interface import Attribute
+from zope.interface import implementer
 
-from bridgedb.bridgerequest import IRequestBridges
-from bridgedb.captcha import ICaptcha
-from bridgedb.schedule import ISchedule
+
+class IName(Interface):
+    """An interface specification for a named object."""
+
+    name = Attribute("A string which identifies this object.")
+
+
+ at implementer(IName)
+class Named(object):
+    """A named object"""
+
+    #: The characters used to join child Named object's names with our name.
+    separator = ' '
+
+    def __init__(self):
+        self._name = str()
+
+    @property
+    def name(self):
+        """Get the name of this object.
+
+        :rtype: str
+        :returns: A string which identifies this object.
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Set a **name** for identifying this object.
+
+        This is used to identify the object in log messages; the **name**
+        doesn't necessarily need to be unique. Other :class:`Named` objects
+        which are properties of a :class:`Named` object may inherit their
+        parents' **name**s.
+
+        >>> from bridgedb.distribute import Named
+        >>> named = Named()
+        >>> named.name = 'Excellent Super-Awesome Thing'
+        >>> named.name
+        'Excellent Super-Awesome Thing'
+
+        :param str name: A name for this object.
+        """
+        self._name = name
+
+        for attr in self.__dict__.values():
+            if IName.providedBy(attr):
+                attr.name = self.separator.join([name, attr.name])
diff --git a/lib/bridgedb/test/email_helpers.py b/lib/bridgedb/test/email_helpers.py
index 0eac35e..20aee57 100644
--- a/lib/bridgedb/test/email_helpers.py
+++ b/lib/bridgedb/test/email_helpers.py
@@ -124,6 +124,8 @@ class DummyEmailDistributor(object):
     test :class:`bridgedb.EmailServer`.
     """
 
+    _bridgesPerResponseMin = 3
+
     def __init__(self, key=None, domainmap=None, domainrules=None,
                  answerParameters=None):
         """None of the parameters are really used, ― they are just there to retain an
@@ -134,8 +136,8 @@ class DummyEmailDistributor(object):
         self.domainrules = domainrules
         self.answerParameters = answerParameters
 
-    def getBridges(self, bridgeRequest, epoch, N=1):
-        return [util.DummyBridge() for _ in xrange(N)]
+    def getBridges(self, bridgeRequest, epoch):
+        return [util.DummyBridge() for _ in xrange(self._bridgesPerResponseMin)]
 
     def cleanDatabase(self):
         pass
@@ -157,14 +159,14 @@ class DummyEmailDistributorWithState(DummyEmailDistributor):
         super(DummyEmailDistributorWithState, self).__init__()
         self.alreadySeen = {}
 
-    def getBridges(self, bridgeRequest, epoch, N=1):
+    def getBridges(self, bridgeRequest, epoch):
         # Keep track of the number of times we've seen a client.
         if not bridgeRequest.client in self.alreadySeen.keys():
             self.alreadySeen[bridgeRequest.client] = 0
         self.alreadySeen[bridgeRequest.client] += 1
 
         if self.alreadySeen[bridgeRequest.client] <= 1:
-            return [util.DummyBridge() for _ in xrange(N)]
+            return [util.DummyBridge() for _ in xrange(self._bridgesPerResponseMin)]
         elif self.alreadySeen[bridgeRequest.client] == 2:
             raise TooSoonEmail(
                 "Seen client '%s' %d times"
diff --git a/lib/bridgedb/test/https_helpers.py b/lib/bridgedb/test/https_helpers.py
index 00ccbd6..3d8ec19 100644
--- a/lib/bridgedb/test/https_helpers.py
+++ b/lib/bridgedb/test/https_helpers.py
@@ -105,7 +105,7 @@ class DummyHTTPSDistributor(object):
     _bridge_class = util.DummyBridge
     _bridgesPerResponseMin = 3
 
-    def getBridges(self, bridgeRequest=None, epoch=None, N=1):
+    def getBridges(self, bridgeRequest=None, epoch=None):
         """Needed because it's called in
         :meth:`BridgesResource.getBridgeRequestAnswer`."""
         return [self._bridge_class() for _ in range(self._bridgesPerResponseMin)]
diff --git a/lib/bridgedb/test/legacy_Tests.py b/lib/bridgedb/test/legacy_Tests.py
index 5621515..4cdcc03 100644
--- a/lib/bridgedb/test/legacy_Tests.py
+++ b/lib/bridgedb/test/legacy_Tests.py
@@ -169,11 +169,11 @@ class EmailBridgeDistTests(unittest.TestCase):
                 {'example.com': [], 'dkim.example.com': ['dkim']})
         for _ in xrange(256):
             d.insert(fakeBridge())
-        d.getBridges('abc at example.com', 1, 3)
+        d.getBridges('abc at example.com', 1)
         self.assertRaises(bridgedb.Dist.TooSoonEmail,
-                d.getBridges, 'abc at example.com', 1, 3)
+                d.getBridges, 'abc at example.com', 1)
         self.assertRaises(bridgedb.Dist.IgnoreEmail,
-                d.getBridges, 'abc at example.com', 1, 3)
+                d.getBridges, 'abc at example.com', 1)
 
     def testUnsupportedDomain(self):
         db = self.db
@@ -184,19 +184,17 @@ class EmailBridgeDistTests(unittest.TestCase):
                           {'example.com':[]})
 
 class IPBridgeDistTests(unittest.TestCase):
-    def dumbAreaMapper(self, ip):
-        return ip
+
     def testBasicDist(self):
-        d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo")
+        d = bridgedb.Dist.HTTPSDistributor(3, "Foo")
         for _ in xrange(256):
             d.insert(fakeBridge())
-        n = d.getBridges("1.2.3.4", "x", 2)
-        n2 = d.getBridges("1.2.3.4", "x", 2)
+        n = d.getBridges("1.2.3.4", "x")
+        n2 = d.getBridges("1.2.3.4", "x")
         self.assertEquals(n, n2)
 
     def testDistWithProxies(self):
-        d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo",
-                                             [RhymesWith255ProxySet()])
+        d = bridgedb.Dist.HTTPSDistributor(3, "Foo", [RhymesWith255ProxySet()])
         for _ in xrange(256):
             d.insert(fakeBridge())
 
@@ -204,8 +202,8 @@ class IPBridgeDistTests(unittest.TestCase):
             # Make sure that the ProxySets do not overlap
             f = lambda: ".".join([str(random.randrange(1,255)) for _ in xrange(4)])
             g = lambda: ".".join([str(random.randrange(1,255)) for _ in xrange(3)] + ['255'])
-            n = d.getBridges(g(), "x", 10)
-            n2 = d.getBridges(f(), "x", 10)
+            n = d.getBridges(g(), "x")
+            n2 = d.getBridges(f(), "x")
 
             assert(len(n) > 0)
             assert(len(n2) > 0)
@@ -219,7 +217,7 @@ class IPBridgeDistTests(unittest.TestCase):
     #XXX: #6175 breaks this test!
     #def testDistWithPortRestrictions(self):
     #    param = bridgedb.Bridges.BridgeRingParameters(needPorts=[(443, 1)])
-    #    d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Baz",
+    #    d = bridgedb.Dist.HTTPSDistributor(3, "Baz",
     #                                       answerParameters=param)
     #    for _ in xrange(32):
     #        d.insert(fakeBridge(443))
@@ -227,7 +225,7 @@ class IPBridgeDistTests(unittest.TestCase):
     #        d.insert(fakeBridge())
     #    for _ in xrange(32):
     #        i = randomIP()
-    #        n = d.getBridges(i, "x", 5)
+    #        n = d.getBridges(i, "x")
     #        count = 0
     #        fps = {}
     #        for b in n:
@@ -239,15 +237,15 @@ class IPBridgeDistTests(unittest.TestCase):
     #        self.assertTrue(count >= 1)
 
     def testDistWithFilterIP6(self):
-        d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo")
+        d = bridgedb.Dist.HTTPSDistributor(3, "Foo")
         for _ in xrange(250):
             d.insert(fakeBridge6(or_addresses=True))
             d.insert(fakeBridge(or_addresses=True))
 
         for i in xrange(500):
             bridges = d.getBridges(randomIPv4String(),
-                                        "faketimestamp",
-                                        bridgeFilterRules=[filterBridgesByIP6])
+                                   "faketimestamp",
+                                   bridgeFilterRules=[filterBridgesByIP6])
             bridge = random.choice(bridges)
             bridge_line = bridge.getConfigLine(addressClass=ipaddr.IPv6Address)
             address, portlist = networkstatus.parseALine(bridge_line)
@@ -255,15 +253,15 @@ class IPBridgeDistTests(unittest.TestCase):
             assert filterBridgesByIP6(random.choice(bridges))
 
     def testDistWithFilterIP4(self):
-        d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo")
+        d = bridgedb.Dist.HTTPSDistributor(3, "Foo")
         for _ in xrange(250):
             d.insert(fakeBridge6(or_addresses=True))
             d.insert(fakeBridge(or_addresses=True))
 
         for i in xrange(500):
             bridges = d.getBridges(randomIPv4String(),
-                                        "faketimestamp",
-                                        bridgeFilterRules=[filterBridgesByIP4])
+                                   "faketimestamp",
+                                   bridgeFilterRules=[filterBridgesByIP4])
             bridge = random.choice(bridges)
             bridge_line = bridge.getConfigLine(addressClass=ipaddr.IPv4Address)
             address, portlist = networkstatus.parseALine(bridge_line)
@@ -271,17 +269,17 @@ class IPBridgeDistTests(unittest.TestCase):
             assert filterBridgesByIP4(random.choice(bridges))
 
     def testDistWithFilterBoth(self):
-        d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo")
+        d = bridgedb.Dist.HTTPSDistributor(3, "Foo")
         for _ in xrange(250):
             d.insert(fakeBridge6(or_addresses=True))
             d.insert(fakeBridge(or_addresses=True))
 
         for i in xrange(50):
             bridges = d.getBridges(randomIPv4String(),
-                                        "faketimestamp", 1,
-                                        bridgeFilterRules=[
-                                            filterBridgesByIP4,
-                                            filterBridgesByIP6])
+                                   "faketimestamp",
+                                   bridgeFilterRules=[
+                                       filterBridgesByIP4,
+                                       filterBridgesByIP6])
             if bridges:
                 t = bridges.pop()
                 assert filterBridgesByIP4(t)
@@ -295,18 +293,18 @@ class IPBridgeDistTests(unittest.TestCase):
 
 
     def testDistWithFilterAll(self):
-        d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo")
+        d = bridgedb.Dist.HTTPSDistributor(3, "Foo")
         for _ in xrange(250):
             d.insert(fakeBridge6(or_addresses=True))
             d.insert(fakeBridge(or_addresses=True))
 
         for i in xrange(5):
-            b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[
+            b = d.getBridges(randomIPv4String(), "x", bridgeFilterRules=[
                 filterBridgesByIP4, filterBridgesByIP6])
             assert len(b) == 0
 
     def testDistWithFilterBlockedCountries(self):
-        d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo")
+        d = bridgedb.Dist.HTTPSDistributor(3, "Foo")
         for _ in xrange(250):
             d.insert(fakeBridge6(or_addresses=True))
             d.insert(fakeBridge(or_addresses=True))
@@ -324,15 +322,15 @@ class IPBridgeDistTests(unittest.TestCase):
             b.blockingCountries[key] = set(['cn'])
 
         for i in xrange(5):
-            b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[
+            b = d.getBridges(randomIPv4String(), "x", bridgeFilterRules=[
                 filterBridgesByNotBlockedIn("cn")])
             assert len(b) == 0
-            b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[
+            b = d.getBridges(randomIPv4String(), "x", bridgeFilterRules=[
                 filterBridgesByNotBlockedIn("us")])
             assert len(b) > 0
 
     def testDistWithFilterBlockedCountriesAdvanced(self):
-        d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo")
+        d = bridgedb.Dist.HTTPSDistributor(3, "Foo")
         for _ in xrange(250):
             d.insert(fakeBridge6(or_addresses=True, transports=True))
             d.insert(fakeBridge(or_addresses=True, transports=True))
@@ -355,7 +353,7 @@ class IPBridgeDistTests(unittest.TestCase):
         # we probably will get at least one bridge back!
         # it's pretty unlikely to lose a coin flip 250 times in a row
         for i in xrange(5):
-            b = d.getBridges(randomIPString(), "x", 1,
+            b = d.getBridges(randomIPString(), "x",
                     bridgeFilterRules=[
                         filterBridgesByNotBlockedIn("cn"),
                         filterBridgesByTransport('obfs2'),
@@ -363,7 +361,7 @@ class IPBridgeDistTests(unittest.TestCase):
             try: assert len(b) > 0
             except AssertionError:
                 print("epic fail")
-            b = d.getBridges(randomIPString(), "x", 1, bridgeFilterRules=[
+            b = d.getBridges(randomIPString(), "x", bridgeFilterRules=[
                 filterBridgesByNotBlockedIn("us")])
             assert len(b) > 0
 
diff --git a/lib/bridgedb/test/test_Dist.py b/lib/bridgedb/test/test_Dist.py
index f47449c..3de7945 100644
--- a/lib/bridgedb/test/test_Dist.py
+++ b/lib/bridgedb/test/test_Dist.py
@@ -125,8 +125,9 @@ class HTTPSDistributorTests(unittest.TestCase):
 
     def test_HTTPSDistributor_bridgesPerResponse_100_max_5(self):
         dist = Dist.HTTPSDistributor(3, self.key)
+        dist._bridgesPerResponseMax = 5
         [dist.insert(bridge) for bridge in self.bridges[:100]]
-        self.assertEqual(dist.bridgesPerResponse(maximum=5), 5)
+        self.assertEqual(dist.bridgesPerResponse(), 5)
 
     def test_HTTPSDistributor_getSubnet_usingProxy(self):
         """HTTPSDistributor.getSubnet(usingProxy=True) should return a proxy
@@ -199,11 +200,11 @@ class HTTPSDistributorTests(unittest.TestCase):
 
         for _ in range(5):
             clientRequest1 = self.randomClientRequestForNotBlockedIn('cn')
-            b = dist.getBridges(clientRequest1, 1, 3)
+            b = dist.getBridges(clientRequest1, 1)
             self.assertEqual(len(b), 0)
 
             clientRequest2 = self.randomClientRequestForNotBlockedIn('ir')
-            b = dist.getBridges(clientRequest2, 1, 3)
+            b = dist.getBridges(clientRequest2, 1)
             self.assertEqual(len(b), 3)
 
     def test_HTTPSDistributor_getBridges_with_some_blocked_bridges(self):
@@ -226,14 +227,14 @@ class HTTPSDistributorTests(unittest.TestCase):
 
         for _ in range(5):
             clientRequest1 = self.randomClientRequestForNotBlockedIn('cn')
-            bridges = dist.getBridges(clientRequest1, 1, 3)
+            bridges = dist.getBridges(clientRequest1, 1)
             for b in bridges:
                 self.assertFalse(b.isBlockedIn('cn'))
             # The client *should* have gotten some bridges still.
             self.assertGreater(len(bridges), 0)
 
             clientRequest2 = self.randomClientRequestForNotBlockedIn('ir')
-            bridges = dist.getBridges(clientRequest2, 1, 3)
+            bridges = dist.getBridges(clientRequest2, 1)
             for b in bridges:
                 self.assertFalse(b.isBlockedIn('ir'))
             self.assertGreater(len(bridges), 0)
diff --git a/lib/bridgedb/test/test_distribute.py b/lib/bridgedb/test/test_distribute.py
new file mode 100644
index 0000000..95fda31
--- /dev/null
+++ b/lib/bridgedb/test/test_distribute.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8  -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+#           please also see AUTHORS file
+# :copyright: (c) 2013-2015, Isis Lovecruft
+#             (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+"""Unittests for :mod:`bridgedb.distribute`."""
+
+from __future__ import print_function
+
+from twisted.trial import unittest
+
+from zope.interface.verify import verifyObject
+
+from bridgedb.distribute import IDistribute
+from bridgedb.distribute import Distributor
+
+
+class DistributorTests(unittest.TestCase):
+    """Tests for :class:`bridgedb.distribute.Distributor`."""
+
+    def test_Distributor_implements_IDistribute(self):
+        IDistribute.namesAndDescriptions()
+        IDistribute.providedBy(Distributor)
+        self.assertTrue(verifyObject(IDistribute, Distributor()))
+
+    def test_Distributor_str_no_name(self):
+        """str(dist) when the distributor doesn't have a name should return a
+        blank string.
+        """
+        dist = Distributor()
+        self.assertEqual(str(dist), "")
+
+    def test_Distributor_str_with_name(self):
+        """str(dist) when the distributor has a name should return the name."""
+        dist = Distributor()
+        dist.name = "foo"
+        self.assertEqual(str(dist), "foo")
diff --git a/lib/bridgedb/test/test_interfaces.py b/lib/bridgedb/test/test_interfaces.py
new file mode 100644
index 0000000..8a78291
--- /dev/null
+++ b/lib/bridgedb/test/test_interfaces.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8  -*-
+# ____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+#           please also see AUTHORS file
+# :copyright: (c) 2013-2015, Isis Lovecruft
+#             (c) 2007-2015, The Tor Project, Inc.
+# :license: see LICENSE for licensing information
+# ____________________________________________________________________________
+
+"""Unittests for :mod:`bridgedb.interfaces`."""
+
+from twisted.trial import unittest
+
+from bridgedb import interfaces
+
+
+class DummyNamedOtherThing(interfaces.Named):
+    def __init__(self):
+        self.name = "hipster"
+
+
+class DummyNamedThing(interfaces.Named):
+    def __init__(self):
+        self.whatever = DummyNamedOtherThing()
+        self.name = "original"
+
+
+class NamedTests(unittest.TestCase):
+    """Tests for :class:`bridgedb.interfaces.Named`."""
+
+    def test_Named_init(self):
+        """Initializing a Named() object should set its name to ''."""
+        named = interfaces.Named()
+        self.assertEqual(named.name, '')
+
+    def test_Named_name(self):
+        """For a Named object A without any other Named objects which have
+        object A as an attribute, should just have its name set to whatever
+        it was set to.
+        """
+        named = DummyNamedOtherThing()
+        self.assertEqual(named.name, "hipster")
+
+    def test_Named_with_named_object_for_attribute(self):
+        """For a Named object A which has another Named object B as an
+        attribute, object A should just have its name set to whatever
+        it was set to, and object B should have its name set to object A's
+        name plus whatever object B's name was set to.
+        """
+        named = DummyNamedThing()
+        self.assertEqual(named.name, "original")
+        self.assertEqual(named.whatever.name, "original hipster")





More information about the tor-commits mailing list