[tor-commits] [bridgedb/develop] Refactor bridgedb.Filters and move it to bridgedb.filters.

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


commit 9c09ef7bdb335fd58d48d8f8ff6d79d41fbd088c
Author: Isis Lovecruft <isis at torproject.org>
Date:   Tue Apr 21 05:09:32 2015 +0000

    Refactor bridgedb.Filters and move it to bridgedb.filters.
---
 doc/sphinx/source/bridgedb.Filters.rst |    8 -
 doc/sphinx/source/bridgedb.filters.rst |    8 +
 doc/sphinx/source/bridgedb.rst         |    2 +-
 doc/sphinx/source/conf.py              |    2 +-
 lib/bridgedb/Bridges.py                |   12 +-
 lib/bridgedb/Dist.py                   |   25 ++-
 lib/bridgedb/Filters.py                |  175 -----------------
 lib/bridgedb/bridgerequest.py          |   39 ++--
 lib/bridgedb/distribute.py             |    2 +-
 lib/bridgedb/filters.py                |  238 +++++++++++++++++++++++
 lib/bridgedb/https/server.py           |    4 -
 lib/bridgedb/parse/addr.py             |    6 +-
 lib/bridgedb/persistent.py             |   12 +-
 lib/bridgedb/test/legacy_Tests.py      |    5 -
 lib/bridgedb/test/test_Dist.py         |   17 +-
 lib/bridgedb/test/test_filters.py      |  333 ++++++++++++++++++++++++++++++++
 16 files changed, 638 insertions(+), 250 deletions(-)

diff --git a/doc/sphinx/source/bridgedb.Filters.rst b/doc/sphinx/source/bridgedb.Filters.rst
deleted file mode 100644
index caf8dfc..0000000
--- a/doc/sphinx/source/bridgedb.Filters.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-bridgedb.Filters
-----------------
-
-.. automodule:: bridgedb.Filters
-    :members:
-    :undoc-members:
-    :private-members:
-    :show-inheritance:
diff --git a/doc/sphinx/source/bridgedb.filters.rst b/doc/sphinx/source/bridgedb.filters.rst
new file mode 100644
index 0000000..9c14ce5
--- /dev/null
+++ b/doc/sphinx/source/bridgedb.filters.rst
@@ -0,0 +1,8 @@
+bridgedb.filters
+----------------
+
+.. automodule:: bridgedb.filters
+    :members:
+    :undoc-members:
+    :private-members:
+    :show-inheritance:
diff --git a/doc/sphinx/source/bridgedb.rst b/doc/sphinx/source/bridgedb.rst
index 31e3a90..7b1fef7 100644
--- a/doc/sphinx/source/bridgedb.rst
+++ b/doc/sphinx/source/bridgedb.rst
@@ -15,7 +15,7 @@ BridgeDB Package and Module Documentation
     bridgedb.crypto
     bridgedb.Dist
     bridgedb.email
-    bridgedb.Filters
+    bridgedb.filters
     bridgedb.geo
     bridgedb.https
     bridgedb.interfaces
diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py
index b655c27..9b43d0f 100644
--- a/doc/sphinx/source/conf.py
+++ b/doc/sphinx/source/conf.py
@@ -40,7 +40,7 @@ import bridgedb.email.dkim
 import bridgedb.email.request
 import bridgedb.email.server
 import bridgedb.email.templates
-import bridgedb.Filters
+import bridgedb.filters
 import bridgedb.geo
 import bridgedb.https
 import bridgedb.https.request
diff --git a/lib/bridgedb/Bridges.py b/lib/bridgedb/Bridges.py
index 507013d..2b56884 100644
--- a/lib/bridgedb/Bridges.py
+++ b/lib/bridgedb/Bridges.py
@@ -569,7 +569,7 @@ class FilteredBridgeSplitter(object):
         filterNames = []
 
         for filterName in [x.func_name for x in list(ringname)]:
-            # Using `filterAssignBridgesToRing.func_name` gives us a messy
+            # Using `assignBridgesToSubring.func_name` gives us a messy
             # string which includes all parameters and memory addresses. Get
             # rid of this by partitioning at the first `(`:
             realFilterName = filterName.partition('(')[0]
@@ -599,8 +599,8 @@ class FilteredBridgeSplitter(object):
         # hashring '%s'!" % (inserted, ringname))`, this log message appears:
         #
         # Jan 04 23:18:37 [INFO] Inserted 12 bridges into hashring
-        # frozenset([<function filterBridgesByIP4 at 0x2d67cf8>, <function
-        # filterAssignBridgesToRing(<function hmac_fn at 0x3778398>, 4, 0) at
+        # frozenset([<function byIPv4 at 0x2d67cf8>, <function
+        # assignBridgesToSubring(<function hmac_fn at 0x3778398>, 4, 0) at
         # 0x37de578>])!
         #
         # I suppose since it contains memory addresses, it *is* technically
@@ -615,10 +615,10 @@ class FilteredBridgeSplitter(object):
         subringName = [self.distributorName]
         subringNumber = None
         for filterName in filterNames:
-            if filterName.startswith('filterAssignBridgesToRing'):
-                subringNumber = filterName.lstrip('filterAssignBridgesToRing')
+            if filterName.startswith('assignBridgesToSubring'):
+                subringNumber = filterName.lstrip('assignBridgesToSubring')
             else:
-                subringName.append(filterName.lstrip('filterBridgesBy'))
+                subringName.append(filterName.lstrip('by'))
         if subring.name and 'Proxy' in subring.name:
             subringName.append('Proxy')
         elif subringNumber:
diff --git a/lib/bridgedb/Dist.py b/lib/bridgedb/Dist.py
index fe6b8da..9b9e35c 100644
--- a/lib/bridgedb/Dist.py
+++ b/lib/bridgedb/Dist.py
@@ -25,10 +25,10 @@ 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.filters import byFilters
+from bridgedb.filters import byIPv4
+from bridgedb.filters import byIPv6
+from bridgedb.filters import bySubring
 from bridgedb.parse import addr
 
 
@@ -259,7 +259,7 @@ class HTTPSDistributor(Distributor):
         """
         logging.info("Prepopulating %s distributor hashrings..." % self.name)
 
-        for filterFn in [filterBridgesByIP4, filterBridgesByIP6]:
+        for filterFn in [byIPv4, byIPv6]:
             for subring in range(1, self.totalSubrings + 1):
                 filters = self._buildHashringFilters([filterFn,], subring)
                 key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
@@ -269,8 +269,7 @@ class HTTPSDistributor(Distributor):
                 # distributor's proxies:
                 if subring == self.proxySubring:
                     ring.setName('{0} Proxy Ring'.format(self.name))
-                self.hashring.addRing(ring, filters,
-                                      filterBridgesByRules(filters),
+                self.hashring.addRing(ring, filters, byFilters(filters),
                                       populate_from=self.hashring.bridges)
 
     def insert(self, bridge):
@@ -278,7 +277,7 @@ class HTTPSDistributor(Distributor):
         self.hashring.insert(bridge)
 
     def _buildHashringFilters(self, previousFilters, subring):
-        f = filterAssignBridgesToRing(self.hashring.hmac, self.totalSubrings, subring)
+        f = bySubring(self.hashring.hmac, subring, self.totalSubrings)
         previousFilters.append(f)
         return frozenset(previousFilters)
 
@@ -337,7 +336,7 @@ class HTTPSDistributor(Distributor):
             logging.debug("Cache miss %s" % filters)
             key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
             ring = BridgeRing(key1, self.answerParameters)
-            self.hashring.addRing(ring, filters, filterBridgesByRules(filters),
+            self.hashring.addRing(ring, filters, byFilters(filters),
                                   populate_from=self.hashring.bridges)
 
         # Determine the appropriate number of bridges to give to the client:
@@ -457,8 +456,7 @@ class EmailBasedDistributor(Distributor):
                 # add new ring
                 key1 = getHMAC(self.key, "Order-Bridges-In-Ring")
                 ring = BridgeRing(key1, self.answerParameters)
-                self.hashring.addRing(ring, ruleset,
-                                      filterBridgesByRules(ruleset),
+                self.hashring.addRing(ring, ruleset, byFilters(ruleset),
                                       populate_from=self.hashring.bridges)
 
             returnNum = self.bridgesPerResponse(ring)
@@ -482,10 +480,9 @@ class EmailBasedDistributor(Distributor):
 
     def prepopulateRings(self):
         # populate all rings (for dumping assignments and testing)
-        for filterFn in [filterBridgesByIP4, filterBridgesByIP6]:
+        for filterFn in [byIPv4, byIPv6]:
             ruleset = frozenset([filterFn])
             key1 = getHMAC(self.key, "Order-Bridges-In-Ring")
             ring = BridgeRing(key1, self.answerParameters)
-            self.hashring.addRing(ring, ruleset,
-                                  filterBridgesByRules([filterFn]),
+            self.hashring.addRing(ring, ruleset, byFilters([filterFn]),
                                   populate_from=self.hashring.bridges)
diff --git a/lib/bridgedb/Filters.py b/lib/bridgedb/Filters.py
deleted file mode 100644
index d0d65e8..0000000
--- a/lib/bridgedb/Filters.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# BridgeDB by Nick Mathewson.
-# Copyright (c) 2007-2012, The Tor Project, Inc.
-# See LICENSE for licensing information 
-
-from ipaddr import IPv6Address, IPv4Address
-import logging
-
-funcs = {}
-
-def filterAssignBridgesToRing(hmac, numRings, assignedRing):
-    logging.debug(("Creating a filter for assigning bridges to subhashring "
-                   "%s-of-%s...") % (assignedRing, numRings))
-    ruleset = frozenset([hmac, numRings, assignedRing]) 
-    try: 
-        return funcs[ruleset]
-    except KeyError:
-        def _assignBridgesToRing(bridge):
-            digest = hmac(bridge.identity)
-            pos = long( digest[:8], 16 )
-            which = pos % numRings + 1
-
-            if which == assignedRing:
-                return True
-            return False
-        _assignBridgesToRing.__name__ = ("filterAssignBridgesToRing%sof%s"
-                                         % (assignedRing, numRings))
-        # XXX The `description` attribute must contain an `=`, or else
-        # dumpAssignments() will not work correctly.
-        setattr(_assignBridgesToRing, "description", "ring=%d" % assignedRing)
-        funcs[ruleset] = _assignBridgesToRing
-        return _assignBridgesToRing
-
-def filterBridgesByRules(rules):
-    ruleset = frozenset(rules)
-    try: 
-        return funcs[ruleset] 
-    except KeyError:
-        def g(x):
-            r = [f(x) for f in rules]
-            if False in r: return False
-            return True
-        setattr(g, "description", " ".join([getattr(f,'description','') for f in rules]))
-        funcs[ruleset] = g
-        return g  
-
-def filterBridgesByIP4(bridge):
-    try:
-        if IPv4Address(bridge.address): return True
-    except ValueError:
-        pass
-
-    for address, port, version in bridge.allVanillaAddresses:
-        if version == 4:
-            return True
-    return False
-setattr(filterBridgesByIP4, "description", "ip=4")
-
-def filterBridgesByIP6(bridge):
-    try:
-        if IPv6Address(bridge.address): return True
-    except ValueError:
-        pass
-
-    for address, port, version in bridge.allVanillaAddresses:
-        if version == 6:
-            return True
-    return False
-setattr(filterBridgesByIP6, "description", "ip=6")
-
-def filterBridgesByTransport(methodname, addressClass=None):
-    if not ((addressClass is IPv4Address) or (addressClass is IPv6Address)):
-        addressClass = IPv4Address
-
-    # Ignore case
-    methodname = methodname.lower()
-
-    ruleset = frozenset([methodname, addressClass])
-    try:
-        return funcs[ruleset]
-    except KeyError:
-        def _filterByTransport(bridge):
-            for transport in bridge.transports:
-                if (transport.methodname == methodname and
-                    isinstance(transport.address, addressClass)):
-                    return True
-            return False
-        _filterByTransport.__name__ = ("filterBridgesByTransport(%s,%s)"
-                                       % (methodname, addressClass))
-        setattr(_filterByTransport, "description", "transport=%s" % methodname)
-        funcs[ruleset] = _filterByTransport
-        return _filterByTransport
-
-def filterBridgesByUnblockedTransport(methodname, countryCode=None, addressClass=None):
-    """Return a filter function for :class:`~bridgedb.bridges.Bridge`s.
-
-    The returned filter function should be called on a
-    :class:`~bridgedb.bridges.Bridge`.  It returns ``True`` if the ``Bridge``
-    has a :class:`~bridgedb.bridges.PluggableTransport` such that:
-
-      1. The :data:`~bridge.bridges.PluggableTransport.methodname` matches
-         **methodname**,
-
-      2. The :data:`~bridgedb.bridges.PluggableTransport.address`` is an
-         instance of **addressClass**, and isn't known to be blocked in
-         **countryCode**.
-
-    :param str methodname: A Pluggable Transport
-        :data:`~bridge.bridges.PluggableTransport.methodname`.
-    :type countryCode: str or ``None``
-    :param countryCode: A two-letter country code which the filtered
-        :class:`PluggableTransport`s should not be blocked in.
-    :type addressClass: ``ipaddr.IPAddress``
-    :param addressClass: The IP version that the ``Bridge``'s
-        ``PluggableTransport``
-        :data:`~bridgedb.bridges.PluggableTransport.address`` should have.
-    :rtype: callable
-    :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
-    """
-    if not countryCode:
-        return filterBridgesByTransport(methodname, addressClass)
-
-    if not ((addressClass is IPv4Address) or (addressClass is IPv6Address)):
-        addressClass = IPv4Address
-
-    # Ignore case
-    methodname = methodname.lower()
-    countryCode = countryCode.lower()
-
-    ruleset = frozenset([methodname, countryCode, addressClass.__name__])
-    try:
-        return funcs[ruleset]
-    except KeyError:
-        def _filterByUnblockedTransport(bridge):
-            # Since bridge.transportIsBlockedIn() will return True if the
-            # bridge has that type of transport AND that transport is blocked,
-            # we can "fail fast" here by doing this faster check before
-            # iterating over all the transports testing for the other
-            # conditions.
-            if bridge.transportIsBlockedIn(countryCode, methodname):
-                return False
-            else:
-                for transport in bridge.transports:
-                    if (transport.methodname == methodname and
-                        isinstance(transport.address, addressClass)):
-                        return True
-            return False
-        _filterByUnblockedTransport.__name__ = ("filterBridgesByUnblockedTransport(%s,%s,%s)"
-                                                % (methodname, countryCode, addressClass))
-        setattr(_filterByUnblockedTransport, "description",
-                "transport=%s unblocked=%s" % (methodname, countryCode))
-        funcs[ruleset] = _filterByUnblockedTransport
-        return _filterByUnblockedTransport
-
-def filterBridgesByNotBlockedIn(countryCode):
-    """Return ``True`` if at least one of a bridge's (transport) bridgelines isn't
-    known to be blocked in **countryCode**.
-
-    :param str countryCode: A two-letter country code.
-    :rtype: bool
-    :returns: ``True`` if at least one address of the bridge isn't blocked.
-        ``False`` otherwise.
-    """
-    countryCode = countryCode.lower()
-    ruleset = frozenset([countryCode])
-    try:
-        return funcs[ruleset]
-    except KeyError:
-        def _filterByNotBlockedIn(bridge):
-            if bridge.isBlockedIn(countryCode):
-                return False
-            return True
-        _filterByNotBlockedIn.__name__ = "filterBridgesByNotBlockedIn(%s)" % countryCode
-        setattr(_filterByNotBlockedIn, "description", "unblocked=%s" % countryCode)
-        funcs[ruleset] = _filterByNotBlockedIn
-        return _filterByNotBlockedIn
diff --git a/lib/bridgedb/bridgerequest.py b/lib/bridgedb/bridgerequest.py
index b699896..2ded7b3 100644
--- a/lib/bridgedb/bridgerequest.py
+++ b/lib/bridgedb/bridgerequest.py
@@ -11,16 +11,17 @@
 #_____________________________________________________________________________
 
 
-import logging
-
 import ipaddr
+import logging
 
 from zope.interface import implements
 from zope.interface import Attribute
 from zope.interface import Interface
 
-from bridgedb import Filters
 from bridgedb.crypto import getHMACFunc
+from bridgedb.filters import byIPv
+from bridgedb.filters import byNotBlockedIn
+from bridgedb.filters import byTransport
 
 
 class IRequestBridges(Interface):
@@ -173,20 +174,22 @@ class BridgeRequestBase(object):
     def generateFilters(self):
         self.clearFilters()
 
-        transport = self.justOnePTType()
+        pt = self.justOnePTType()
+        msg = ("Adding a filter to %s for %s for IPv%s"
+               % (self.__class__.__name__, self.client, self.addressClass))
 
-        if transport:
-            if self.notBlockedIn:
-                for country in self.notBlockedIn:
-                    self.addFilter(Filters.filterBridgesByUnblockedTransport(
-                        transport, country, self.addressClass))
-            else:
-                self.addFilter(Filters.filterBridgesByTransport(
-                    transport, self.addressClass))
-        else:
-            if self.addressClass is ipaddr.IPv6Address:
-                self.addFilter(Filters.filterBridgesByIP6)
-            else:
-                self.addFilter(Filters.filterBridgesByIP4)
+        self.ipVersion = 4
+        if self.addressClass is ipaddr.IPv6Address:
+            self.ipVersion = 6
+
+        if self.notBlockedIn:
             for country in self.notBlockedIn:
-                self.addFilter(Filters.filterBridgesByNotBlockedIn(country.lower()))
+                logging.info("%s %s bridges not blocked in %s..." %
+                             (msg, pt or "vanilla", country))
+                self.addFilter(byNotBlockedIn(country, pt, self.addressClass))
+        elif pt:
+            logging.info("%s %s bridges..." % (msg, pt))
+            self.addFilter(byTransport(pt, self.addressClass))
+        else:
+            logging.info("%s bridges..." % msg)
+            self.addFilter(byIPv(self.addressClass))
diff --git a/lib/bridgedb/distribute.py b/lib/bridgedb/distribute.py
index c7c9044..f48cbb6 100644
--- a/lib/bridgedb/distribute.py
+++ b/lib/bridgedb/distribute.py
@@ -47,7 +47,7 @@ DistributorContext {   # should go in bridgedb.py
 }
 
 Hashring {
-    assignBridgesToRings()   FORMERLY filterAssignBridgesToRing()
+    assignBridgesToSubrings()   FORMERLY bridgedb.filters.assignBridgesToSubring()
         + filters bridges uniformly into subrings
     clear() / __del__()
     isEmpty property
diff --git a/lib/bridgedb/filters.py b/lib/bridgedb/filters.py
new file mode 100644
index 0000000..8843937
--- /dev/null
+++ b/lib/bridgedb/filters.py
@@ -0,0 +1,238 @@
+# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_filters ; -*-
+#_____________________________________________________________________________
+#
+# This file is part of BridgeDB, a Tor bridge distribution system.
+#
+# :authors: Nick Mathewson <nickm at torproject.org>
+#           Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
+#           please also see AUTHORS file
+# :copyright: (c) 2007-2015, The Tor Project, Inc.
+#             (c) 2013-2015, Isis Lovecruft
+# :license: see LICENSE for licensing information
+#_____________________________________________________________________________
+
+import logging
+
+from ipaddr import IPv4Address
+from ipaddr import IPv6Address
+
+from bridgedb.parse.addr import isIPv
+
+
+_cache = {}
+
+
+def bySubring(hmac, assigned, total):
+    """Create a filter function which filters for only the bridges which fall
+    into the same **assigned** subhashring (based on the results of an **hmac**
+    function).
+
+    :type hmac: callable
+    :param hmac: An HMAC function, i.e. as returned from
+        :func:`bridgedb.crypto.getHMACFunc`.
+    :param int assigned: The subring number that we wish to draw bridges from.
+        For example, if a user is assigned to subring 2of3 based on their IP
+        address, then this function should only return bridges which would
+        also be assigned to subring 2of3.
+    :param int total: The total number of subrings.
+    :rtype: callable
+    :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
+    """
+    logging.debug(("Creating a filter for assigning bridges to subhashring "
+                   "%s-of-%s...") % (assigned, total))
+
+    name = "-".join([str(hmac("")[:8]).encode('hex'),
+                     str(assigned), "of", str(total)])
+    try:
+        return _cache[name]
+    except KeyError:
+        def _bySubring(bridge):
+            position = int(hmac(bridge.identity)[:8], 16)
+            which = (position % total) + 1
+            return True if which == assigned else False
+        # The `description` attribute must contain an `=`, or else
+        # dumpAssignments() will not work correctly.
+        setattr(_bySubring, "description", "ring=%d" % assigned)
+        _bySubring.__name__ = ("bySubring%sof%s" % (assigned, total))
+        _bySubring.name = name
+        _cache[name] = _bySubring
+        return _bySubring
+
+def byFilters(filtres):
+    """Returns a filter which filters by multiple **filtres**.
+
+    :type filtres: list
+    :param filtres: A list (or other iterable) of callables which some
+        :class:`~bridgedb.bridges.Bridge`s should be filtered according to.
+    :rtype: callable
+    :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
+    """
+    name = []
+    for filtre in filtres:
+        name.extend(filtre.name.split(" "))
+    name = " ".join(set(name))
+
+    try:
+        return _cache[name]
+    except KeyError:
+        def _byFilters(bridge):
+            results = [f(bridge) for f in filtres]
+            if False in results:
+                return False
+            return True
+        setattr(_byFilters, "description",
+                " ".join([getattr(f, "description", "") for f in filtres]))
+        _byFilters.name = name
+        _cache[name] = _byFilters
+        return _byFilters
+
+def byIPv(ipVersion=None):
+    """Return ``True`` if at least one of the **bridge**'s addresses has the
+    specified **ipVersion**.
+
+    :param int ipVersion: Either ``4`` or ``6``.
+    """
+    if not ipVersion in (4, 6):
+        ipVersion = 4
+
+    name = "ipv%d" % ipVersion
+    try:
+        return _cache[name]
+    except KeyError:
+        def _byIPv(bridge):
+            if isIPv(ipVersion, bridge.address):
+                return True
+            else:
+                for address, port, version in bridge.allVanillaAddresses:
+                    if version == ipVersion or isIPv(ipVersion, address):
+                        return True
+            return False
+        setattr(_byIPv, "description", "ip=%d" % ipVersion)
+        _byIPv.__name__ = "byIPv%d()" % ipVersion
+        _byIPv.name = name
+        _cache[name] = _byIPv
+        return _byIPv
+
+byIPv4 = byIPv(4)
+byIPv6 = byIPv(6)
+
+def byTransport(methodname=None, ipVersion=None):
+    """Returns a filter function for :class:`~bridgedb.bridges.Bridge`s.
+
+    The returned filter function should be called on a
+    :class:`~bridgedb.bridges.Bridge`.  It returns ``True`` if the ``Bridge``
+    has a :class:`~bridgedb.bridges.PluggableTransport` such that:
+
+      1. The :data:`~bridge.bridges.PluggableTransport.methodname` matches
+         **methodname**, and
+
+      2. The :data:`~bridgedb.bridges.PluggableTransport.address`` is an
+         instance of **addressClass**.
+
+    :param str methodname: A Pluggable Transport
+        :data:`~bridge.bridges.PluggableTransport.methodname`.
+    :param int ipVersion: Either ``4`` or ``6``. The IP version that the
+        ``Bridge``'s ``PluggableTransport``
+        :data:`~bridgedb.bridges.PluggableTransport.address`` should have.
+    :rtype: callable
+    :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
+    """
+    if not ipVersion in (4, 6):
+        ipVersion = 4
+    if not methodname:
+        return byIPv(ipVersion)
+
+    methodname = methodname.lower()
+    name = "transport-%s ipv%d" % (methodname, ipVersion)
+
+    try:
+        return _cache[name]
+    except KeyError:
+        def _byTransport(bridge):
+            for transport in bridge.transports:
+                if transport.methodname == methodname:
+                    if transport.address.version == ipVersion:
+                        return True
+            return False
+        setattr(_byTransport, "description", "transport=%s" % methodname)
+        _byTransport.__name__ = "byTransport(%s,%s)" % (methodname, ipVersion)
+        _byTransport.name = name
+        _cache[name] = _byTransport
+        return _byTransport
+
+def byNotBlockedIn(countryCode=None, methodname=None, ipVersion=4):
+    """Returns a filter function for :class:`~bridgedb.bridges.Bridge`s.
+
+    If a Pluggable Transport **methodname** was not specified, the returned
+    filter function returns ``True`` if any of the ``Bridge``'s addresses or
+    :class:`~bridgedb.bridges.PluggableTransport` addresses aren't blocked in
+    **countryCode**.  See :meth:`~bridgedb.bridges.Bridge.isBlockedIn`.
+
+    Otherwise, if a Pluggable Transport **methodname** was specified, it
+    returns ``True`` if the ``Bridge`` has a
+    :class:`~bridgedb.bridges.PluggableTransport` such that:
+
+      1. The :data:`~bridge.bridges.PluggableTransport.methodname` matches
+         **methodname**,
+
+      2. The :data:`~bridgedb.bridges.PluggableTransport.address.version``
+         equals the **ipVersion**, and isn't known to be blocked in
+         **countryCode**.
+
+    :type countryCode: str or ``None``
+    :param countryCode: A two-letter country code which the filtered
+        :class:`PluggableTransport`s should not be blocked in.
+    :param str methodname: A Pluggable Transport
+        :data:`~bridge.bridges.PluggableTransport.methodname`.
+    :param int ipVersion: Either ``4`` or ``6``. The IP version that the
+        ``Bridge``'s addresses should have.
+    :rtype: callable
+    :returns: A filter function for :class:`~bridgedb.bridges.Bridge`s.
+    """
+    if not ipVersion in (4, 6):
+        ipVersion = 4
+    if not countryCode:
+        return byTransport(methodname, ipVersion)
+
+    methodname = methodname.lower() if methodname else methodname
+    countryCode = countryCode.lower()
+
+    name = []
+    if methodname:
+        name.append("transport-%s" % methodname)
+    name.append("ipv%d" % ipVersion)
+    name.append("not-blocked-in-%s" % countryCode)
+    name = " ".join(name)
+
+    try:
+        return _cache[name]
+    except KeyError:
+        def _byNotBlockedIn(bridge):
+            if not methodname:
+                return not bridge.isBlockedIn(countryCode)
+            elif methodname == "vanilla":
+                if bridge.address.version == ipVersion:
+                    if not bridge.addressIsBlockedIn(countryCode,
+                                                     bridge.address,
+                                                     bridge.orPort):
+                        return True
+            else:
+                # Since bridge.transportIsBlockedIn() will return True if the
+                # bridge has that type of transport AND that transport is
+                # blocked, we can "fail fast" here by doing this faster check
+                # before iterating over all the transports testing for the
+                # other conditions.
+                if bridge.transportIsBlockedIn(countryCode, methodname):
+                    return False
+                else:
+                    for transport in bridge.transports:
+                        if transport.methodname == methodname:
+                            if transport.address.version == ipVersion:
+                                return True
+            return False
+        setattr(_byNotBlockedIn, "description", "unblocked=%s" % countryCode)
+        _byNotBlockedIn.__name__ = ("byTransportNotBlockedIn(%s,%s,%s)"
+                                    % (methodname, countryCode, ipVersion))
+        _byNotBlockedIn.name = name
+        _cache[name] = _byNotBlockedIn
+        return _byNotBlockedIn
diff --git a/lib/bridgedb/https/server.py b/lib/bridgedb/https/server.py
index e8cdd62..2a1d510 100644
--- a/lib/bridgedb/https/server.py
+++ b/lib/bridgedb/https/server.py
@@ -47,10 +47,6 @@ from bridgedb import crypto
 from bridgedb import strings
 from bridgedb import translations
 from bridgedb import txrecaptcha
-from bridgedb.Filters import filterBridgesByIP4
-from bridgedb.Filters import filterBridgesByIP6
-from bridgedb.Filters import filterBridgesByTransport
-from bridgedb.Filters import filterBridgesByNotBlockedIn
 from bridgedb.https.request import HTTPSBridgeRequest
 from bridgedb.parse import headers
 from bridgedb.parse.addr import isIPAddress
diff --git a/lib/bridgedb/parse/addr.py b/lib/bridgedb/parse/addr.py
index 96dc21e..b3ad680 100644
--- a/lib/bridgedb/parse/addr.py
+++ b/lib/bridgedb/parse/addr.py
@@ -318,7 +318,7 @@ def isIPAddress(ip, compressed=True):
                 return ip
     return False
 
-def _isIPv(version, ip):
+def isIPv(version, ip):
     """Check if **ip** is a certain **version** (IPv4 or IPv6).
 
     .. warning: Do *not* put any calls to the logging module in this function,
@@ -352,7 +352,7 @@ def isIPv4(ip):
     :rtype: boolean
     :returns: True if the address is an IPv4 address.
     """
-    return _isIPv(4, ip)
+    return isIPv(4, ip)
 
 def isIPv6(ip):
     """Check if an address is IPv6.
@@ -364,7 +364,7 @@ def isIPv6(ip):
     :rtype: boolean
     :returns: True if the address is an IPv6 address.
     """
-    return _isIPv(6, ip)
+    return isIPv(6, ip)
 
 def isValidIP(ip):
     """Check that an IP (v4 or v6) is valid.
diff --git a/lib/bridgedb/persistent.py b/lib/bridgedb/persistent.py
index c996daa..00726f6 100644
--- a/lib/bridgedb/persistent.py
+++ b/lib/bridgedb/persistent.py
@@ -4,9 +4,9 @@
 #
 # :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis at torproject.org>
 #           please also see AUTHORS file
-# :copyright: (c) 2013 Isis Lovecruft
-#             (c) 2007-2013, The Tor Project, Inc.
-#             (c) 2007-2013, all entities within the AUTHORS file
+# :copyright: (c) 2013-2015 Isis Lovecruft
+#             (c) 2007-2015, The Tor Project, Inc.
+#             (c) 2007-2015, all entities within the AUTHORS file
 # :license: 3-clause BSD, see included LICENSE for information
 
 """Module for functionality to persistently store state."""
@@ -23,7 +23,9 @@ except (ImportError, NameError):  # pragma: no cover
 from twisted.python.reflect import safe_repr
 from twisted.spread import jelly
 
-from bridgedb import Filters, Bridges, Dist
+from bridgedb import Bridges
+from bridgedb import Dist
+from bridgedb import filters
 from bridgedb.configure import Conf
 #from bridgedb.proxy import ProxySet
 
@@ -32,7 +34,7 @@ _state = None
 #: Types and classes which are allowed to be jellied:
 _security = jelly.SecurityOptions()
 #_security.allowInstancesOf(ProxySet)
-_security.allowModules(Filters, Bridges, Dist)
+_security.allowModules(filters, Bridges, Dist)
 
 
 class MissingState(Exception):
diff --git a/lib/bridgedb/test/legacy_Tests.py b/lib/bridgedb/test/legacy_Tests.py
index 95dc34e..3ec634c 100644
--- a/lib/bridgedb/test/legacy_Tests.py
+++ b/lib/bridgedb/test/legacy_Tests.py
@@ -25,11 +25,6 @@ import bridgedb.Storage
 import re
 import ipaddr
 
-from bridgedb.Filters import filterBridgesByIP4
-from bridgedb.Filters import filterBridgesByIP6
-from bridgedb.Filters import filterBridgesByTransport
-from bridgedb.Filters import filterBridgesByNotBlockedIn
-
 from bridgedb.Stability import BridgeHistory
 
 from bridgedb.parse import addr
diff --git a/lib/bridgedb/test/test_Dist.py b/lib/bridgedb/test/test_Dist.py
index 85c1b87..46a11b0 100644
--- a/lib/bridgedb/test/test_Dist.py
+++ b/lib/bridgedb/test/test_Dist.py
@@ -24,9 +24,8 @@ from bridgedb.bridges import Bridge
 from bridgedb.bridges import PluggableTransport
 from bridgedb.Bridges import BridgeRing
 from bridgedb.Bridges import BridgeRingParameters
-from bridgedb.Filters import filterBridgesByNotBlockedIn
-from bridgedb.Filters import filterBridgesByIP4
-from bridgedb.Filters import filterBridgesByIP6
+from bridgedb.filters import byIPv4
+from bridgedb.filters import byIPv6
 from bridgedb.https.request import HTTPSBridgeRequest
 from bridgedb.proxy import ProxySet
 from bridgedb.test.util import randomHighPort
@@ -355,7 +354,7 @@ class HTTPSDistributorTests(unittest.TestCase):
 
         bridgeRequest = self.randomClientRequest()
         bridgeRequest.withIPv4()
-        bridgeRequest.filters.append(filterBridgesByIP6)
+        bridgeRequest.filters.append(byIPv6)
         bridgeRequest.generateFilters()
 
         bridges = dist.getBridges(bridgeRequest, 1)
@@ -367,7 +366,7 @@ class HTTPSDistributorTests(unittest.TestCase):
         address, port = addrport.rsplit(':', 1)
         address = address.strip('[]')
         self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv4Address)
-        self.assertIsNotNone(filterBridgesByIP4(random.choice(bridges)))
+        self.assertIsNotNone(byIPv4(random.choice(bridges)))
 
     def test_HTTPSDistributor_getBridges_ipv6_ipv4(self):
         """Asking for bridge addresses which are simultaneously IPv6 and IPv4
@@ -379,7 +378,7 @@ class HTTPSDistributorTests(unittest.TestCase):
         bridgeRequest = self.randomClientRequest()
         bridgeRequest.withIPv6()
         bridgeRequest.generateFilters()
-        bridgeRequest.filters.append(filterBridgesByIP4)
+        bridgeRequest.filters.append(byIPv4)
 
         bridges = dist.getBridges(bridgeRequest, 1)
         self.assertEqual(len(bridges), 3)
@@ -390,7 +389,7 @@ class HTTPSDistributorTests(unittest.TestCase):
         address, port = addrport.rsplit(':', 1)
         address = address.strip('[]')
         self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv6Address)
-        self.assertIsNotNone(filterBridgesByIP6(random.choice(bridges)))
+        self.assertIsNotNone(byIPv6(random.choice(bridges)))
 
     def test_HTTPSDistributor_getBridges_ipv6(self):
         """A request for IPv6 bridges should return IPv6 bridges."""
@@ -412,7 +411,7 @@ class HTTPSDistributorTests(unittest.TestCase):
             address, port = addrport.rsplit(':', 1)
             address = address.strip('[]')
             self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv6Address)
-            self.assertIsNotNone(filterBridgesByIP6(random.choice(bridges)))
+            self.assertIsNotNone(byIPv6(random.choice(bridges)))
 
     def test_HTTPSDistributor_getBridges_ipv4(self):
         """A request for IPv4 bridges should return IPv4 bridges."""
@@ -432,4 +431,4 @@ class HTTPSDistributorTests(unittest.TestCase):
             addrport, fingerprint = bridgeLine.split()
             address, port = addrport.rsplit(':', 1)
             self.assertIsInstance(ipaddr.IPAddress(address), ipaddr.IPv4Address)
-            self.assertIsNotNone(filterBridgesByIP4(random.choice(bridges)))
+            self.assertIsNotNone(byIPv4(random.choice(bridges)))
diff --git a/lib/bridgedb/test/test_filters.py b/lib/bridgedb/test/test_filters.py
new file mode 100644
index 0000000..73e5685
--- /dev/null
+++ b/lib/bridgedb/test/test_filters.py
@@ -0,0 +1,333 @@
+# -*- 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 included LICENSE for information
+
+"""Tests for :mod:`bridgedb.filters`."""
+
+from __future__ import print_function
+
+import ipaddr
+
+from twisted.trial import unittest
+
+from bridgedb import filters
+from bridgedb.bridges import Bridge
+from bridgedb.bridges import PluggableTransport
+from bridgedb.crypto import getHMACFunc
+
+
+class FiltersTests(unittest.TestCase):
+    """Tests for :mod:`bridgedb.filters`."""
+
+    def setUp(self):
+        """Create a Bridge whose address is 1.1.1.1, orPort is 1111, and
+        fingerprint is 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'.  Also,
+        create an HMAC function whose key is 'plasma'.
+        """
+        self.bridge = Bridge()
+        self.bridge.address = '1.1.1.1'
+        self.bridge.orPort = 1111
+        self.bridge.fingerprint = 'a' * 40
+
+        self.hmac = getHMACFunc('plasma')
+
+    def addIPv4VoltronPT(self):
+        pt = PluggableTransport('a' * 40, 'voltron', '1.1.1.1', 1111, {})
+        self.bridge.transports.append(pt)
+
+    def addIPv6VoltronPT(self):
+        pt = PluggableTransport('a' * 40, 'voltron', '2006:2222::2222', 1111, {})
+        self.bridge.transports.append(pt)
+
+    def test_bySubring_1_of_2(self):
+        """A Bridge with fingerprint 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+        should be assigned to sub-hashring 1-of-2 (in this case, using a
+        particular HMAC key), and therefore filters.bySubring(HMAC, 1, 2)
+        should return that Bridge (because it is in the sub-hashring we asked
+        for).
+        """
+        filtre = filters.bySubring(self.hmac, 1, 2)
+        self.assertTrue(filtre(self.bridge))
+
+    def test_bySubring_2_of_2(self):
+        """A Bridge with fingerprint 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+        should be assigned to sub-hashring 1-of-2 (in this case, using a
+        particular HMAC key), and therefore filters.bySubring(HMAC, 2, 2)
+        should *not* return that Bridge (because it is in sub-hashring 1-of-2
+        and we asked for Bridges which are in sub-hashring 2-of-2).
+        """
+        filtre = filters.bySubring(self.hmac, 2, 2)
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byFilters_bySubring_byTransport_correct_subhashring_with_transport(self):
+        """Filtering byTransport('voltron') and bySubring(HMAC, 1, 2) when the
+        Bridge has a voltron transport and is assigned to sub-hashring 1-of-2
+        should return True.
+        """
+        self.addIPv4VoltronPT()
+        filtre = filters.byFilters([filters.bySubring(self.hmac, 1, 2),
+                                    filters.byTransport('voltron')])
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byFilters_bySubring_byTransport_wrong_subhashring_with_transport(self):
+        """Filtering byTransport('voltron') and bySubring(HMAC, 2, 2) when the
+        Bridge has a voltron transport and is assigned to sub-hashring 1-of-2
+        should return False.
+        """
+        self.addIPv4VoltronPT()
+        filtre = filters.byFilters([filters.bySubring(self.hmac, 2, 2),
+                                    filters.byTransport('voltron')])
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byFilters_bySubring_byTransport_correct_subhashring_no_transport(self):
+        """Filtering byTransport('voltron') and bySubring(HMAC, 1, 2) when the
+        Bridge has no transports and is assigned to sub-hashring 1-of-2
+        should return False.
+        """
+        filtre = filters.byFilters([filters.bySubring(self.hmac, 1, 2),
+                                    filters.byTransport('voltron')])
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byFilters_bySubring_byTransport_wrong_subhashring_no_transport(self):
+        """Filtering byTransport('voltron') and bySubring(HMAC, 2, 2) when the
+        Bridge has no transports and is assigned to sub-hashring 1-of-2
+        should return False.
+        """
+        filtre = filters.byFilters([filters.bySubring(self.hmac, 2, 2),
+                                    filters.byTransport('voltron')])
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byFilters_no_filters(self):
+        self.addIPv4VoltronPT()
+        filtre = filters.byFilters([])
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byIPv_ipv5(self):
+        """Calling byIPv(ipVersion=5) should default to filterint by IPv4."""
+        filtre = filters.byIPv(5)
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byIPv4_address(self):
+        """A bridge with an IPv4 address for its main orPort address should
+        cause filters.byIPv4() to return True.
+        """
+        self.assertTrue(filters.byIPv4(self.bridge))
+
+    def test_byIPv4_orAddress(self):
+        """A bridge with an IPv4 address in its orAddresses address should
+        cause filters.byIPv4() to return True.
+        """
+        self.bridge.address = '2006:2222::2222'
+        self.bridge.orAddresses = [(ipaddr.IPv4Address('2.2.2.2'), 2222, 4)]
+        self.assertTrue(filters.byIPv4(self.bridge))
+
+    def test_byIPv4_none(self):
+        """A bridge with no IPv4 addresses should cause filters.byIPv4() to
+        return False.
+        """
+        self.bridge.address = ipaddr.IPv6Address('2006:2222::2222')
+        self.bridge.orAddresses = [(ipaddr.IPv6Address('2006:3333::3333'), 3333, 6)]
+        self.assertFalse(filters.byIPv4(self.bridge))
+
+    def test_byIPv6_address(self):
+        """A bridge with an IPv6 address for its main orPort address should
+        cause filters.byIPv6() to return True.
+        """
+        self.bridge.address = '2006:2222::2222'
+        self.assertTrue(filters.byIPv6(self.bridge))
+
+    def test_byIPv6_orAddress(self):
+        """A bridge with an IPv6 address in its orAddresses address should
+        cause filters.byIPv6() to return True.
+        """
+        self.bridge.orAddresses = [(ipaddr.IPv6Address('2006:3333::3333'), 3333, 6)]
+        self.assertTrue(filters.byIPv6(self.bridge))
+
+    def test_byIPv6_none(self):
+        """A bridge with no IPv6 addresses should cause filters.byIPv6() to
+        return False.
+        """
+        self.assertFalse(filters.byIPv6(self.bridge))
+
+    def test_byTransport_with_transport_ipv4(self):
+        """A bridge with an IPv4 voltron transport should cause
+        byTransport('voltron') to return True.
+        """
+        self.addIPv4VoltronPT()
+        filtre = filters.byTransport('voltron')
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byTransport_with_transport_ipv6(self):
+        """A bridge with an IPv6 voltron transport should cause
+        byTransport('voltron', ipVersion=6) to return True.
+        """
+        self.addIPv6VoltronPT()
+        filtre = filters.byTransport('voltron', ipVersion=6)
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byTransport_with_transport_ipv6_filtering_by_ipv4(self):
+        """A bridge with an IPv6 voltron transport should cause
+        byTransport('voltron') to return True.
+        """
+        self.addIPv6VoltronPT()
+        filtre = filters.byTransport('voltron')
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byTransport_no_transports(self):
+        """A bridge without any transports should cause
+        byTransport('voltron') to return False.
+        """
+        filtre = filters.byTransport('voltron')
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byTransport_vanilla_ipv4(self):
+        """byTransport() without namimg a transport to filter by should just
+        return the bridge's IPv4 address.
+        """
+        filtre = filters.byTransport()
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byTransport_vanilla_ipv6(self):
+        """byTranspfort(ipVersion=6) without namimg a transport to filter by
+        should just return the bridge's IPv4 address.
+        """
+        self.bridge.orAddresses = [(ipaddr.IPv6Address('2006:3333::3333'), 3333, 6)]
+        filtre = filters.byTransport(ipVersion=6)
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byTransport_wrong_transport(self):
+        """A bridge with only a Voltron transport should cause
+        byTransport('obfs3') to return False.
+        """
+        self.addIPv4VoltronPT()
+        filtre = filters.byTransport('obfs3')
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byNotBlockedIn_no_countryCode_with_transport_ipv4(self):
+        """A bridge with an IPv4 voltron transport should cause
+        byNotBlockedIn('voltron') to return True (because it calls
+        filters.byTransport).
+        """
+        self.addIPv4VoltronPT()
+        filtre = filters.byNotBlockedIn(None, methodname='voltron')
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byNotBlockedIn_no_countryCode_with_transport_ipv6(self):
+        """A bridge with an IPv6 voltron transport should cause
+        byNotBlockedIn('voltron') to return True (because it calls
+        filters.byTransport).
+        """
+        self.addIPv6VoltronPT()
+        filtre = filters.byNotBlockedIn(None, methodname='voltron', ipVersion=6)
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byNotBlockedIn_with_transport_ipv4(self):
+        """A bridge with an IPv4 voltron transport should cause
+        byNotBlockedIn('voltron') to return True.
+        """
+        self.addIPv4VoltronPT()
+        filtre = filters.byNotBlockedIn('CN', methodname='voltron')
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byNotBlockedIn_with_transport_ipv4_blocked(self):
+        """A bridge with an IPv4 voltron transport which is blocked should
+        cause byNotBlockedIn('voltron') to return False.
+        """
+        self.addIPv4VoltronPT()
+        self.bridge.setBlockedIn('CN')
+        filtre = filters.byNotBlockedIn('CN', methodname='voltron')
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byNotBlockedIn_with_transport_ipv6(self):
+        """A bridge with an IPv6 voltron transport should cause
+        byNotBlockedIn('voltron') to return True.
+        """
+        self.addIPv6VoltronPT()
+        filtre = filters.byNotBlockedIn('cn', 'voltron', ipVersion=6)
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byNotBlockedIn_with_transport_ipv4_not_blocked_ipv4(self):
+        """A bridge with an IPv6 voltron transport which is not blocked in China
+        should cause byNotBlockedIn('cn', 'voltron') to return False, because
+        the IP version is wrong.
+        """
+        self.addIPv6VoltronPT()
+        filtre = filters.byNotBlockedIn('cn', 'voltron')
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byNotBlockedIn_with_transport_ipv6_blocked(self):
+        """A bridge with an IPv6 voltron transport which is blocked should
+        cause byNotBlockedIn('voltron') to return False.
+        """
+        self.addIPv6VoltronPT()
+        self.bridge.setBlockedIn('CN')
+        filtre = filters.byNotBlockedIn('cn', 'voltron', ipVersion=6)
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byNotBlockedIn_no_countryCode_no_transports(self):
+        """A bridge without any transports should cause
+        byNotBlockedIn('voltron') to return False (because it calls
+        filters.byTransport('voltron')).
+        """
+        filtre = filters.byNotBlockedIn(None, methodname='voltron')
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byNotBlockedIn_no_transports(self):
+        """A bridge without any transports should cause
+        byNotBlockedIn('cn', 'voltron') to return False.
+        """
+        filtre = filters.byNotBlockedIn('cn', methodname='voltron')
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byNotBlockedIn_no_transports_blocked(self):
+        """A bridge without any transports which is also blocked should cause
+        byNotBlockedIn('voltron') to return False.
+        """
+        self.bridge.setBlockedIn('cn')
+        filtre = filters.byNotBlockedIn('cn', methodname='voltron')
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byNotBlockedIn_wrong_transport(self):
+        """A bridge with only a Voltron transport should cause
+        byNotBlockedIn('obfs3') to return False.
+        """
+        self.addIPv4VoltronPT()
+        filtre = filters.byNotBlockedIn('cn', methodname='obfs3')
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byNotBlockedIn_ipv5(self):
+        """Calling byNotBlockedIn([…], ipVersion=5) should default to IPv4."""
+        self.bridge.setBlockedIn('ru')
+        filtre = filters.byNotBlockedIn('cn', ipVersion=5)
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byNotBlockedIn_vanilla_not_blocked(self):
+        """Calling byNotBlockedIn('vanilla') should return the IPv4 vanilla
+        address, if it is not blocked.
+        """
+        self.bridge.setBlockedIn('ru')
+        filtre = filters.byNotBlockedIn('cn', methodname='vanilla')
+        self.assertTrue(filtre(self.bridge))
+
+    def test_byNotBlockedIn_vanilla_not_blocked_ipv6(self):
+        """Calling byNotBlockedIn('vanilla', ipVersion=6) should not return the
+        IPv4 vanilla address, even if it is not blocked, because it has the
+        wrong IP version.
+        """
+        self.bridge.setBlockedIn('ru')
+        filtre = filters.byNotBlockedIn('cn', methodname='vanilla', ipVersion=6)
+        self.assertFalse(filtre(self.bridge))
+
+    def test_byNotBlockedIn_vanilla_blocked(self):
+        """Calling byNotBlockedIn('vanilla') should not return the IPv4 vanilla
+        address, if it is blocked.
+        """
+        self.bridge.setBlockedIn('ru')
+        filtre = filters.byNotBlockedIn('ru', methodname='vanilla')
+        self.assertFalse(filtre(self.bridge))





More information about the tor-commits mailing list