[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