[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[minion-cvs] Support functionality for directory server generation.



Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv20744/lib/mixminion

Modified Files:
	Common.py Crypto.py ServerInfo.py benchmark.py test.py 
Log Message:
Support functionality for directory server generation.

Common:
	- Add method to format time for use in a filename.
	- Add an IntervalSet class to keep track of server liveness

Crypto:
	- Add pk_same_public_key so we can tell whether identity keys
	  are the same.

ServerInfo:
	- Keep track of whether we've validated the desc or not.
	- Make digest-and-sign logic generic.

test:
	- Add tests for IntervalSet



Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.37
retrieving revision 1.38
diff -u -d -r1.37 -r1.38
--- Common.py	29 Dec 2002 20:25:32 -0000	1.37
+++ Common.py	31 Dec 2002 04:48:46 -0000	1.38
@@ -5,14 +5,15 @@
 
    Common functionality and utility code for Mixminion"""
 
-__all__ = [ 'LOG', 'LogStream', 'MixError', 'MixFatalError',
+__all__ = [ 'IntervalSet', 'LOG', 'LogStream', 'MixError', 'MixFatalError',
             'MixProtocolError', 'ceilDiv', 'checkPrivateDir',
             'createPrivateDir', 'floorDiv', 'formatBase64', 'formatDate',
-            'formatTime', 'installSignalHandlers', 'isSMTPMailbox',
-            'onReset', 'onTerminate', 'previousMidnight', 'secureDelete',
-            'stringContains', 'waitForChildren' ]
+            'formatFnameTime', 'formatTime', 'installSignalHandlers',
+            'isSMTPMailbox', 'onReset', 'onTerminate', 'previousMidnight',
+            'secureDelete', 'stringContains', 'waitForChildren' ]
 
 import base64
+import bisect
 import calendar
 import os
 import re
@@ -536,6 +537,158 @@
     gmt = time.gmtime(when+1) # Add 1 to make sure we round down.
     return "%04d/%02d/%02d" % (gmt[0],gmt[1],gmt[2])
 
+def formatFnameTime(when=None):
+    """Given a time in seconds since the epoch, returns a date value suitable
+       for use as part of a fileame.  Defaults to the current time."""
+    # XXXX002 test
+    if when is None:
+        when = time.time()
+    return time.strftime("%Y%m%d%H%M%S", time.localtime(when))
+
+#----------------------------------------------------------------------
+# InteralSet
+
+class IntervalSet:
+    """An IntervalSet is a mutable set of numeric intervals, closed below and
+       open above.  Not very optimized for now.  Supports "+" for union, "-"
+       for disjunction, and "*" for intersection."""
+    ## Fields:
+    # edges: an ordered list of boundary points between interior and
+    #     exterior points, of the form (x, '+') for an 'entry' and
+    #     (x, '-') for an 'exit' boundary.
+    #
+    # FFFF There must be a more efficient algorithm for this, but we're so
+    # FFFF far from the critical path here that I'm not going to look for it
+    # FFFF for quite a while.
+    def __init__(self, intervals=None):
+        """Given a list of (start,end) tuples, construct a new IntervalSet.
+           Tuples are ignored if start>=end."""
+        self.edges = []
+        if intervals:
+            for start, end in intervals:
+                if start < end:
+                    self.edges.append((start, '+'))
+                    self.edges.append((end, '-'))
+    def copy(self):
+        """Create a new IntervalSet with the same intervals as this one."""
+        r = IntervalSet()
+        r.edges = self.edges[:]
+        return r
+    def __iadd__(self, other):
+        """self += b : Causes this set to contain all points in itself but not
+           in b."""
+        self.edges += other.edges
+        self._cleanEdges()
+        return self
+    def __isub__(self, other):
+        """self -= b : Causes this set to contain all points in itself but not
+           in b"""
+        for t, e in other.edges:
+            if e == '+':
+                self.edges.append((t, '-'))
+            else:
+                self.edges.append((t, '+'))
+        self._cleanEdges()
+        return self
+    def __imul__(self, other):
+        """self *= b : Causes this set to contain all points in both itself and
+           b."""
+        self.edges += other.edges
+        self._cleanEdges(2)
+        return self
+
+    def _cleanEdges(self, nMin=1):
+        """Internal helper method: to be called when 'edges' is in a dirty
+           state, containing entry and exit points that don't create a
+           well-defined set of intervals.  Only those points that are 'entered'
+           nMin times or more are retained.
+           """
+        edges = self.edges
+        edges.sort()
+        depth = 0
+        newEdges = [ ('X', 'X') ] #marker value; will be removed.
+        for t, e in edges:
+            # Traverse the edges in order; keep track of how many more 
+            # +'s we have seen than -'s.  Whenever that number increases
+            # above nMin, add a +.  Whenever that number drops below nMin,
+            # add a - ... but if the new edge would cancel out the most
+            # recently added one, then delete the most recently added one.
+            if e == '+':
+                depth += 1
+                if depth == nMin:
+                    if newEdges[-1] == (t, '-'):
+                        del newEdges[-1]
+                    else:
+                        newEdges.append((t, '+'))
+            if e == '-':
+                if depth == nMin:
+                    if newEdges[-1] == (t, '+'):
+                        del newEdges[-1]
+                    else:
+                        newEdges.append((t, '-'))
+                depth -= 1
+        assert depth == 0
+        del newEdges[0]
+        self.edges = newEdges
+
+    def __add__(self, other):
+        r = self.copy()
+        r += other
+        return r
+
+    def __sub__(self, other):
+        r = self.copy()
+        r -= other
+        return r
+
+    def __mul__(self, other):
+        r = self.copy()
+        r *= other
+        return r
+
+    def __contains__(self, other):
+        """'a in self' is true when 'a' is a number contained in some interval
+            in this set, or when 'a' is an IntervalSet that is a subset of
+            this set."""
+        if isinstance(other, IntervalSet):
+            return self*other == other
+        idx = bisect.bisect_right(self.edges, (other, '-'))
+        return idx < len(self.edges) and self.edges[idx][1] == '-'
+
+    def isEmpty(self):
+        """Return true iff this set contains no points"""
+        return len(self.edges) == 0
+    
+    def __nonzero__(self):
+        """Return true iff this set contains some points"""
+        return len(self.edges) != 0
+
+    def __repr__(self):
+        s = [ "(%s,%s)"%(start,end) for start, end in self.getIntervals() ]
+        return "IntervalSet([%s])"%",".join(s)
+    
+    def getIntervals(self):
+        """Returns a list of (start,end) tuples for a the intervals in this
+           set."""
+        s = []
+        for i in range(0, len(self.edges), 2):
+            s.append((self.edges[i][0], self.edges[i+1][0]))
+        return s
+            
+    def _checkRep(self):
+        """Helper function: raises AssertionError if this set's data is 
+           corrupted."""
+        assert (len(self.edges) % 2) == 0
+        for i in range(0, len(self.edges), 2):
+            assert self.edges[i][0] < self.edges[i+1][0]
+            assert self.edges[i][1] == '+'
+            assert self.edges[i+1][1] == '-'
+            assert i == 0 or self.edges[i-1][0] < self.edges[i][0]
+
+    def __cmp__(self, other):
+        """A == B iff A and B contain exactly the same intervals."""
+        return cmp(self.edges, other.edges)
+        
 #----------------------------------------------------------------------
 # SMTP address functionality
 

Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.30
retrieving revision 1.31
diff -u -d -r1.30 -r1.31
--- Crypto.py	29 Dec 2002 20:28:01 -0000	1.30
+++ Crypto.py	31 Dec 2002 04:48:46 -0000	1.31
@@ -256,6 +256,11 @@
     """Reads an ASN1 representation of a public key from external storage."""
     return _ml.rsa_decode_key(s,1)
 
+def pk_same_public_key(key1, key2):
+    """Return true iff key1 and key2 are the same key."""
+    #XXXX TEST
+    return key1.encode_key(1) == key2.encode_key(1)
+
 def pk_PEM_save(rsa, filename, password=None):
     """Save a PEM-encoded private key to a file.  If <password> is provided,
        encrypt the key using the password."""

Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.27
retrieving revision 1.28
diff -u -d -r1.27 -r1.28
--- ServerInfo.py	29 Dec 2002 20:46:54 -0000	1.27
+++ ServerInfo.py	31 Dec 2002 04:48:46 -0000	1.28
@@ -11,6 +11,7 @@
 __all__ = [ 'ServerInfo' ]
 
 import re
+import time
 
 import mixminion.Config
 import mixminion.Crypto
@@ -37,6 +38,8 @@
 # tmp alias to make this easier to spell.
 C = mixminion.Config
 class ServerInfo(mixminion.Config._ConfigFile):
+    ## Fields
+    # isValidated: DOCDOC
     """A ServerInfo object holds a parsed server descriptor."""
     _restrictFormat = 1
     _syntax = {
@@ -77,6 +80,7 @@
         }
 
     def __init__(self, fname=None, string=None, assumeValid=0):
+        self._isValidated = 0
         mixminion.Config._ConfigFile.__init__(self, fname, string, assumeValid)
         LOG.trace("Reading server descriptor %s from %s",
                        self['Server']['Nickname'],
@@ -95,6 +99,8 @@
         identityBytes = identityKey.get_modulus_bytes()
         if not (MIN_IDENTITY_BYTES <= identityBytes <= MAX_IDENTITY_BYTES):
             raise ConfigError("Invalid length on identity key")
+        if server['Published'] > time.time() + 600:
+            raise ConfigError("Server published in the future")
         if server['Valid-Until'] <= server['Valid-After']:
             raise ConfigError("Server is never valid")
         if server['Contact'] and len(server['Contact']) > MAX_CONTACT:
@@ -136,6 +142,8 @@
         # FFFF When a better client module system exists, check the
         # FFFF module descriptors.
 
+        self._isValidated = 1
+
     def getNickname(self):
         """Returns this server's nickname"""
         return self['Server']['Nickname']
@@ -161,6 +169,10 @@
            to this server."""
         return IPV4Info(self.getAddr(), self.getPort(), self.getKeyID())
 
+    def isValidated(self):
+        "DOCDOC"
+        return self._isValidated
+
 #----------------------------------------------------------------------
 def getServerInfoDigest(info):
     """Calculate the digest of a server descriptor"""
@@ -172,38 +184,55 @@
        no values."""
     return _getServerInfoDigestImpl(info, rsa)
 
+_leading_whitespace_re = re.compile(r'^[ \t]+', re.M)
 _trailing_whitespace_re = re.compile(r'[ \t]+$', re.M)
-_special_line_re = re.compile(r'^(?:Digest|Signature):.*$', re.M)
-def _getServerInfoDigestImpl(info, rsa=None):
+_abnormal_line_ending_re = re.compile(r'\r\n?')
+def _cleanForDigest(s):
+    "DOCDOC"
+    # should be shared with config, serverinfo.
+    s = _abnormal_line_ending_re.sub("\n", s)
+    s = _trailing_whitespace_re.sub("", s)
+    s = _leading_whitespace_re.sub("", s)
+    if s[-1] != "\n":
+        s += "\n"
+    return s
+
+def _getDigestImpl(info, regex, digestField=None, sigField=None, rsa=None):
     """Helper method.  Calculates the correct digest of a server descriptor
        (as provided in a string).  If rsa is provided, signs the digest and
-       creates a new descriptor.  Otherwise just returns the digest."""
+       creates a new descriptor.  Otherwise just returns the digest.
 
-    # The algorithm's pretty easy.  We just find the Digest and Signature
-    # lines, replace each with an 'Empty' version, and calculate the digest.
-    info = _trailing_whitespace_re.sub("", info)
-    if not info.startswith("[Server]"):
-        raise ConfigError("Must begin with server section")
-    def replaceFn(s):
-        if s.group(0)[0] == 'D':
-            return "Digest:"
-        else:
-            return "Signature:"
-    info = _special_line_re.sub(replaceFn, info)
+       DOCDOC: NO LONGER QUITE TRUE
+       """
+    info = _cleanForDigest(info)
+    def replaceFn(m):
+        s = m.group(0)
+        return s[:s.index(':')+1]
+    info = regex.sub(replaceFn, info, 2)
     digest = mixminion.Crypto.sha1(info)
 
     if rsa is None:
         return digest
-    # If we got an RSA key, we need to add the digest and signature.
-
+    
     signature = mixminion.Crypto.pk_sign(digest,rsa)
     digest = formatBase64(digest)
     signature = formatBase64(signature)
-    def replaceFn2(s, digest=digest, signature=signature):
-        if s.group(0)[0] == 'D':
-            return "Digest: "+digest
+    def replaceFn2(s, digest=digest, signature=signature,
+                   digestField=digestField, sigField=sigField):
+        if s.group(0).startswith(digestField):
+            return "%s: %s" % (digestField, digest)
         else:
-            return "Signature: "+signature
+            assert s.group(0).startswith(sigField)
+            return "%s: %s" % (sigField, signature)
 
-    info = _special_line_re.sub(replaceFn2, info)
+    info = regex.sub(replaceFn2, info, 2)
     return info
+
+_special_line_re = re.compile(r'^(?:Digest|Signature):.*$', re.M)
+def _getServerInfoDigestImpl(info, rsa=None):
+    return _getDigestImpl(info, _special_line_re, "Digest", "Signature", rsa)
+
+_dir_special_line_re = re.compile(r'^Directory(?:Digest|Signature):.*$', re.M)
+def _getDirectoryDigestImpl(directory, rsa=None):
+    return _getDigestImpl(directory, _dir_special_line_re, 
+                          "DirectoryDigest", "DirectorySignature", rsa)

Index: benchmark.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/benchmark.py,v
retrieving revision 1.20
retrieving revision 1.21
diff -u -d -r1.20 -r1.21
--- benchmark.py	29 Dec 2002 20:46:54 -0000	1.20
+++ benchmark.py	31 Dec 2002 04:48:47 -0000	1.21
@@ -359,10 +359,7 @@
 #----------------------------------------------------------------------
 def directoryTiming():
     print "#========== DESCRIPTORS AND DIRECTORIES =============="
-    from mixminion.server.ServerKeys import ServerKeyring, \
-         generateServerDescriptorAndKeys
-    gen = generateServerDescriptorAndKeys
-    homedir = mix_mktemp()
+    from mixminion.server.ServerKeys import ServerKeyring
     confStr = """
 [Server]
 EncryptIdentityKey: no
@@ -648,8 +645,6 @@
             _ml.rsa_make_public_key(n,e)
 
 #----------------------------------------------------------------------
-import base64
-import binascii
 
 def timeAll(name, args):
     cryptoTiming()

Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.51
retrieving revision 1.52
diff -u -d -r1.51 -r1.52
--- test.py	29 Dec 2002 20:34:36 -0000	1.51
+++ test.py	31 Dec 2002 04:48:47 -0000	1.52
@@ -51,6 +51,7 @@
 import mixminion.server.ServerConfig
 import mixminion.server.ServerKeys
 import mixminion.server.ServerMain
+import mixminion.directory.ServerList
 from mixminion.Common import *
 from mixminion.Common import Log, _FileLogHandler, _ConsoleLogHandler
 from mixminion.Config import _ConfigFile, ConfigError, _parseInt
@@ -214,6 +215,192 @@
                      "foo@bar;cat /etc/shadow;echo ","foo bar@baz.com",
                      "a@b@c"):
             self.assert_(not isSMTPMailbox(addr))
+
+    def test_intervalset(self):
+        eq = self.assertEquals
+        nil = IntervalSet()
+        nil2 = IntervalSet()
+        nil._checkRep()
+        self.assert_(nil.isEmpty())
+        self.assert_(nil == nil2)
+        eq(repr(nil), "IntervalSet([])")
+        eq([], nil.getIntervals())
+        nil3 = IntervalSet([(10, 0)])
+        eq([], nil3.getIntervals())
+
+        oneToTen = IntervalSet([(1,10)])
+        fourToFive = IntervalSet([(4,5)])
+        zeroToTen = IntervalSet([(0,10)])
+        zeroToTwenty = IntervalSet([(0,20)])
+        tenToTwenty = IntervalSet([(10,20)])
+        oneToTwenty = IntervalSet([(1,20)])
+        fifteenToFifty = IntervalSet([(15,50)])
+
+        eq(zeroToTen.getIntervals(), [(0, 10)])
+        for iset in oneToTen, fourToFive, zeroToTen, zeroToTwenty, oneToTwenty:
+            iset._checkRep()
+
+        checkEq = self._intervalEq
+
+        # Tests for addition: A + B, where...
+        #   1. A and B are empty.
+        checkEq(nil+nil, nil, [])
+        #   2. Just A or B is empty.
+        checkEq(nil+oneToTen, oneToTen+nil, oneToTen, [(1,10)])
+        #   3. A contains B, or vice versa.
+        checkEq(oneToTen+fourToFive, fourToFive+oneToTen, oneToTen)
+        checkEq(oneToTen+zeroToTwenty, zeroToTwenty)
+        #   4. A == B
+        checkEq(oneToTen+oneToTen, oneToTen)
+        #   5. A and B are disjoint and don't touch.
+        checkEq(oneToTen+fifteenToFifty, fifteenToFifty+oneToTen,
+                [(1,10),(15,50)])
+        #   6. A and B are disjoint and touch
+        checkEq(oneToTen+tenToTwenty, tenToTwenty+oneToTen, oneToTwenty)
+        #   7. A and B overlap on one side only.
+        checkEq(oneToTwenty+fifteenToFifty,
+                fifteenToFifty+oneToTwenty,
+                "IntervalSet([(1,50)])")
+        #   8. A nice complex situation.
+        fromPrimeToPrime = IntervalSet([(2,3),(5,7),(11,13),(17,19),(23,29)])
+        fromSquareToSquare = IntervalSet([(1,4),(9,16),(25,36)])
+        fromFibToFib = IntervalSet([(1,1),(2,3),(5,8),(13,21),(34,55)])
+        x = fromPrimeToPrime.copy()
+        x += fromSquareToSquare
+        x += fromSquareToSquare
+        checkEq(fromPrimeToPrime+fromSquareToSquare,
+                fromSquareToSquare+fromPrimeToPrime,
+                x,
+                [(1,4),(5,7),(9,16),(17,19),(23,36)])
+        checkEq(fromSquareToSquare+fromFibToFib,
+                [(1,4),(5,8),(9,21),(25,55)])
+        checkEq(fromPrimeToPrime+fromFibToFib,
+                [(2,3),(5,8),(11,21),(23,29),(34,55)])
+        
+        # Now, subtraction!
+        #  1. Involving nil.
+        checkEq(nil-nil, nil, [])
+        checkEq(fromSquareToSquare-nil, fromSquareToSquare)
+        checkEq(nil-fromSquareToSquare, nil)
+        #  2. Disjoint ranges.
+        checkEq(fourToFive-tenToTwenty, fourToFive)
+        checkEq(tenToTwenty-fourToFive, tenToTwenty)
+        #  3. Matching on one side
+        checkEq(oneToTwenty-oneToTen, tenToTwenty)
+        checkEq(oneToTwenty-tenToTwenty, oneToTen)
+        checkEq(oneToTen-oneToTwenty, nil)
+        checkEq(tenToTwenty-oneToTwenty, nil)
+        #  4. Overlapping on one side
+        checkEq(fifteenToFifty-oneToTwenty, [(20,50)])
+        checkEq(oneToTwenty-fifteenToFifty, [(1,15)])
+        #  5. Overlapping in the middle
+        checkEq(oneToTen-fourToFive, [(1,4),(5,10)])
+        checkEq(fourToFive-oneToTen, nil)
+        #  6. Complicated
+        checkEq(fromPrimeToPrime-fromSquareToSquare,
+                [(5,7),(17,19),(23,25)])
+        checkEq(fromSquareToSquare-fromPrimeToPrime,
+                [(1,2),(3,4),(9,11),(13,16),(29,36)])
+        checkEq(fromSquareToSquare-fromFibToFib,
+                [(1,2),(3,4),(9,13),(25,34)])
+        checkEq(fromFibToFib-fromSquareToSquare,
+                [(5,8),(16,21),(36,55)])
+        #  7. Identities
+        for a in (fromPrimeToPrime, fromSquareToSquare, fromFibToFib, nil):
+            for b in (fromPrimeToPrime, fromSquareToSquare, fromFibToFib, nil):
+                checkEq(a-b+b, a+b)
+                checkEq(a+b-b, a-b)
+        
+        ## Test intersection
+        # 1. With nil
+        checkEq(nil*nil, nil*fromFibToFib, oneToTen*nil, nil, [])
+        # 2. Self
+        for iset in oneToTen, fromSquareToSquare, fourToFive:
+            checkEq(iset, iset*iset)
+        # 3. A disjoint from B
+        checkEq(oneToTen*fifteenToFifty, fifteenToFifty*oneToTen, nil)
+        # 4. A disjoint from B but touching.
+        checkEq(oneToTen*tenToTwenty, tenToTwenty*oneToTen, nil)
+        # 5. A contains B at the middle.
+        checkEq(oneToTen*fourToFive, fourToFive*oneToTen, fourToFive)
+        # 6. A contains B at one end
+        checkEq(oneToTen*oneToTwenty, oneToTwenty*oneToTen, oneToTen)
+        checkEq(tenToTwenty*oneToTwenty, oneToTwenty*tenToTwenty, tenToTwenty)
+        # 7. A and B overlap without containment.
+        checkEq(fifteenToFifty*oneToTwenty, oneToTwenty*fifteenToFifty, 
+                [(15,20)])
+        # 8. Tricky cases
+        checkEq(fromPrimeToPrime*fromSquareToSquare,
+                fromSquareToSquare*fromPrimeToPrime,
+                [(2,3),(11,13),(25,29)])
+        checkEq(fromPrimeToPrime*fromFibToFib,
+                fromFibToFib*fromPrimeToPrime,
+                [(2,3),(5,7),(17,19)])
+        checkEq(fromSquareToSquare*fromFibToFib,
+                fromFibToFib*fromSquareToSquare,
+                [(2,3),(13,16),(34,36)])
+        # 9. Identities
+        for a in (fromPrimeToPrime, fromSquareToSquare, fromFibToFib, oneToTen,
+                  fifteenToFifty, nil):
+            self.assert_((not a) == a.isEmpty() == (a == nil))
+            for b in (fromPrimeToPrime, fromSquareToSquare, fromFibToFib, 
+                      oneToTen, fifteenToFifty, nil):
+                checkEq(a*b,b*a)
+                checkEq(a-b, a*(a-b), (a-b)*a)
+                checkEq(b*(a-b), (a-b)*b, nil)
+                checkEq(a-b, a-a*b)
+                checkEq((a-b)+a*b, a)
+                checkEq((a-b)*(b-a), nil)
+                checkEq((a-b)+(b-a)+a*b, a+b)
+
+        ## Contains
+        t = self.assert_
+        # 1. With nil
+        t(5 not in nil)
+        t(oneToTen not in nil)
+        t(fromFibToFib not in nil)
+        # 2. Self in self
+        for iset in nil, oneToTen, tenToTwenty, fromSquareToSquare:
+            t(iset in iset)
+        # 3. Simple sets: closed below, open above.
+        t(1 in oneToTen)
+        t(2 in oneToTen)
+        t(9.9 in oneToTen)
+        t(10 not in oneToTen)
+        t(0 not in oneToTen)
+        t(11 not in oneToTen)
+        # 4. Simple sets: A contains B.
+        t(fourToFive in oneToTen) # contained wholly
+        t(oneToTen in zeroToTen) #contains on one side.
+        t(oneToTwenty not in oneToTen) #disjoint on one side
+        t(oneToTen not in tenToTwenty) #disjoint but touching
+        t(fourToFive not in tenToTwenty) #disjoint, not touching
+        # 5. Complex sets: closed below, open above
+        t(0 not in fromSquareToSquare)
+        t(1 in fromSquareToSquare)
+        t(2 in fromSquareToSquare)
+        t(4 not in fromSquareToSquare)
+        t(8 not in fromSquareToSquare)
+        t(9 in fromSquareToSquare)
+        t(15 in fromSquareToSquare)
+        t(16 not in fromSquareToSquare)
+        t(35 in fromSquareToSquare)
+        t(36 not in fromSquareToSquare)
+        t(100 not in fromSquareToSquare)
+
+    def _intervalEq(self, a, *others):
+        eq = self.assertEquals
+        for b in others:
+            if isinstance(b, IntervalSet):
+                eq(a,b)
+                b._checkRep()
+            elif isinstance(b, types.StringType):
+                eq(repr(a), b)
+            elif isinstance(b, types.ListType):
+                eq(a.getIntervals(), b)
+            else:
+                raise MixError()
+            a._checkRep()
 
 #----------------------------------------------------------------------