[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[minion-cvs] Commit last nights path selection work. Better, but st...



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

Modified Files:
	ClientDirectory.py ClientMain.py test.py 
Log Message:
Commit last nights path selection work.  Better, but still not merged.

Index: ClientDirectory.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientDirectory.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- ClientDirectory.py	6 Oct 2003 20:55:06 -0000	1.2
+++ ClientDirectory.py	7 Oct 2003 21:57:46 -0000	1.3
@@ -5,12 +5,14 @@
    dealing with mixminion directories.  This includes:
      - downloading and caching directories
      - path generation
+   DOCDOC
      """
 
-__all__ = [ 'ClientDirectory', 'parsePath', 'parsePathLeg' ]
+__all__ = [ 'ClientDirectory', 'parsePath', 'parsePathLeg', 'parseAddress2' ]
 
 import cPickle
 import errno
+import operator
 import os
 import re
 import signal
@@ -28,7 +30,7 @@
 from mixminion.Common import LOG, MixError, MixFatalError, UIError, \
      ceilDiv, createPrivateDir, formatDate, formatFnameTime, openUnique, \
      previousMidnight, readPickled, readPossiblyGzippedFile, \
-     replaceFile, tryUnlink, writePickled 
+     replaceFile, tryUnlink, writePickled, floorDiv
 from mixminion.Packet import MBOX_TYPE, SMTP_TYPE, DROP_TYPE
 
 # FFFF This should be made configurable and adjustable.
@@ -437,6 +439,14 @@
 
         return u.values()
 
+    def getLiveServers(self, startAt=None, endAt=None):
+        """DOCDOC"""
+        if startAt is None:
+            startAt = time.time()
+        if endAt is None:
+            endAt = time.time()+self.DEFAULT_REQUIRED_LIFETIME
+        return self.__find(self.serverList, startAt, endAt)
+
     def findByExitTypeAndSize(self, exitType, size, nPackets):
         #XXXX006 remove this method.  It's not really a good interface,
         #XXXX006 and only gets used by the kludgy choose-a-new-last-hop logic
@@ -546,13 +556,18 @@
                       prng=None):
         """Return a list of pairs of lists of serverinfo DOCDOC."""
 
+        if prng is None:
+            prng = mixminion.Crypto.getCommonPRNG()
+
         paths = []
         lastHop = exitAddress.getLastHop()
-        fixedLastHop = pathSpec.getFixedLastServer(startAt,endAt)
-        if exitAddress.isSSFragmented:
-            # We _must_ have a single common last hop.
-            if not lastHop and not fixedLastHop:
-                fixedLastHop = exitAddress.pickExitServer(self,startAt,endAt)
+        if lastHop:
+            plausibleExits = []
+        else:
+            plausibleExits = exitAddress.getExitServers(self,startAt,endAt)
+            if exitAddress.isSSFragmented:
+                # We _must_ have a single common last hop.
+                plauxibleExits = [ prng.pick(plausibleExits) ]
 
         for _ in xrange(nPaths):
             p1 = []
@@ -561,17 +576,19 @@
                 p1.extend(p.getServerNames())
             for p in pathSpec.path2:
                 p2.extend(p.getServerNames())
-            n1 = len(p1)
+
             p = p1+p2
             # Make the exit hop _not_ be None; deal with getPath brokenness.
             #XXXX refactor this.
             if lastHop:
                 p.append(lastHop)
-            elif fixedLastHop:
-                assert p[-1] == None
-                p[-1] = fixedLastHop
             elif p[-1] == None and not exitAddress.isReply:
-                p[-1] = exitAddress.pickExitServer(self, startAt, endAt)
+                p[-1] = prng.pick(plausibleExits)
+ 
+            if pathSpec.lateSplit:
+                n1 = ceilDiv(len(p),2)
+            else:
+                n1 = len(p1)
 
             path = self.getPath(None, p, startAt=startAt, endAt=endAt)
             paths.append( (path[:n1], path[n1:]) )
@@ -687,14 +704,17 @@
         p = pathSpec.path1+pathSpec.path2
         for e in p:
             e.validate(self, startAt, endAt)
-        fs = p[-1].getFixedServer()
+        fs = p[-1].getFixedServer(self,startAt,endAt)
         lh = exitAddress.getLastHop()
         if lh is not None:
             fs = self.getServerInfo(lh, startAt, endAt)
             if fs is None:
                 raise UIError("No known server descriptor named %s",lh)
         if fs is not None:
-            exitAddress.checkSupportedBySever(fs)
+            exitAddress.checkSupportedByServer(fs)
+        elif exitAddress.isServerRelative():
+            raise UIError("%s exit type expects a fixed exit server.",
+                          exitAddress.getPrettyExitType())
             
     def checkClientVersion(self):
         """Check the current client's version against the stated version in
@@ -731,6 +751,26 @@
 def parsePath(directory, config, path, address, nHops=None,
               startAt=None, endAt=None, halfPath=0,
               defaultNHops=None):
+    """Wrap new path parsing methods to provide functionality of old parsePath
+       method.  The old methods has been (for now) renamed to 'parsePathOrig'.
+       """
+    isReply = halfPath and (address is None)
+    isSURB = halfPath and (address is not None)
+    if not isReply:
+        rt, ri, lastHop = address.getRouting()
+        exitAddress = ExitAddress(rt, ri, lastHop)
+    else:
+        exitAddress = ExitAddress(isReply=1)
+    pathSpec = parsePath2(config, path, nHops=nHops, isReply=isReply,
+                          isSURB=isSURB, defaultNHops=defaultNHops)
+    directory.validatePath2(pathSpec, exitAddress, startAt=startAt,endAt=endAt)
+    paths = directory.generatePaths(1, pathSpec, exitAddress, startAt,endAt)
+    assert len(paths) == 1
+    return paths[0]
+
+def parsePathOrig(directory, config, path, address, nHops=None,
+              startAt=None, endAt=None, halfPath=0,
+              defaultNHops=None):
     """Resolve a path as specified on the command line.  Returns a
        (path-leg-1, path-leg-2) tuple, where each leg is a list of ServerInfo.
 
@@ -962,8 +1002,6 @@
         else:
             assert exitType is not None
             assert exitAddress is not None
-        if exitType in (MBOX_TYPE, 'mbox'):
-            assert lastHop
         if type(exitType) == types.StringType:
             if exitType not in KNOWN_STRING_EXIT_TYPES:
                 raise UIError("Unknown exit type: %r"%exitType)
@@ -992,11 +1030,11 @@
         return self.lastHop
     def isSupportedByServer(self, desc):
         try:
-            self.checkSupportedByServer(desc)
+            self.checkSupportedByServer(desc,verbose=0)
             return 1
         except UIError:
             return 0
-    def checkSupportedByServer(self, desc):
+    def checkSupportedByServer(self, desc,verbose=1):
         if self.isReply:
             return
         nickname = desc.getNickname()
@@ -1019,7 +1057,7 @@
                 raise UIError("Message to long for server %s to deliver."%
                               nickname)
         elif self.exitType in ('mbox', MBOX_TYPE):
-            msec = desc['Delivery/SMTP']
+            msec = desc['Delivery/MBOX']
             if not msec.get("Version"):
                 raise UIError("Server %s doesn't support MBOX"%nickname)
             if self.headers.has_key("FROM") and not msec['Allow-From']:
@@ -1028,16 +1066,82 @@
             if floorDiv(self.exitSize,1024) > msec['Maximum-Size']:
                 raise UIError("Message to long for server %s to deliver."%
                               nickname)
-    def pickExitServer(self, directory, startAt, endAt):
-        """DOCDOC"""
+        elif self.exitType in ('drop', DROP_TYPE):
+            # everybody supports 'drop'.
+            pass
+        else:
+            if not verbose: return
+            LOG.warn("No way to tell if server %s supports exit type %s.",
+                     nickname, self.getPrettyExitType())
 
-        assert 0
+    def getPrettyExitType(self):
+        if type(self.exitType) == types.IntType:
+            prettyExit = "0x%04X"%self.exitType
+        else:
+            prettyExit = `self.exitType`
+
+    def isServerRelative(self):
+        return self.exitType in ('mbox', MBOX_TYPE)
+            
+    def getExitServers(self, directory, startAt=None, endAt=None):
+        """DOCDOC
+           Return a list of all exit servers that might work."""
+        assert self.lastHop is None
+        liveServers = directory.getLiveServers(startAt, endAt)
+        result = [ desc for desc in liveServers
+                   if self.isSupportedByServer(desc) ]
+        return result
 
     def getRouting(self, desc):
         """DOCDOC"""
         #XXXX
         assert 0
 
+def parseAddress2(s):
+    """Parse and validate an address; takes a string, and returns an
+       ExitAddress object.
+
+       Accepts strings of the format:
+              mbox:<mailboxname>@<server>
+           OR smtp:<email address>
+           OR <email address> (smtp is implicit)
+           OR drop
+           OR 0x<routing type>:<routing info>
+    """
+    # ???? Should this should get refactored into clientmodules, or someplace?
+    if s.lower() == 'drop':
+        return ExitAddress('drop',"")
+    elif s.lower() == 'test':
+        return ExitAddress(0xFFFE, "")
+    elif ':' not in s:
+        if isSMTPMailbox(s):
+            return ExitAddress('smtp', s)
+        else:
+            raise ParseError("Can't parse address %s"%s)
+    tp,val = s.split(':', 1)
+    tp = tp.lower()
+    if tp.startswith("0x"):
+        try:
+            tp = int(tp[2:], 16)
+        except ValueError:
+            raise ParseError("Invalid hexidecimal value %s"%tp)
+        if not (0x0000 <= tp <= 0xFFFF):
+            raise ParseError("Invalid type: 0x%04x"%tp)
+        return ExitAddress(tp, val)
+    elif tp == 'mbox':
+        if "@" in val:
+            mbox, server = val.split("@",1)
+            return ExitAddress('mbox', parseMBOXInfo(mbox).pack(), server)
+        else:
+            return ExitAddress('mbox', parseMBOXInfo(val).pack(), None)
+    elif tp == 'smtp':
+        # May raise ParseError
+        return ExitAddress('smtp', parseSMTPInfo(val).pack(), None)
+    elif tp == 'test':
+        return ExitAddress(0xFFFE, val, None)
+    else:
+        raise ParseError("Unrecognized address type: %s"%s)
+
 class PathElement:
     def validate(self, directory, start, end):
         raise NotImplemented
@@ -1047,17 +1151,23 @@
         raise NotImplemented
     def getServerNames(self):
         raise NotImplemented
+    def getMinLength(self):
+        raise NotImplemented
 
 class ServerPathElement(PathElement):
     def __init__(self, nickname):
         self.nickname = nickname
     def validate(self, directory, start, end):
         if None == directory.getServerInfo(self.nickname, start, end):
-            raise UIError("No valid server found with name %r",self.nickname)
+            raise UIError("No valid server found with name %r"%self.nickname)
     def getFixedServer(self, directory, start, end):
         return directory.getServerInfo(self.nickname, start, end)
     def getServerNames(self):
         return [ self.nickname ]
+    def getMinLength(self):
+        return 1
+    def __repr__(self):
+        return "ServerPathElement(%r)"%self.nickname
 
 class DescriptorPathElement(PathElement):
     def __init__(self, desc):
@@ -1070,6 +1180,10 @@
         return self.desc
     def getServerNames(self):
         return [ self.desc ]
+    def getMinLength(self):
+        return 1
+    def __repr__(self):
+        return "DescriptorPathElement(%r)"%self.desc
 
 class RandomServersPathElement(PathElement):
     def __init__(self, n=None, approx=None):
@@ -1086,25 +1200,36 @@
         else:
             prng = mixminion.Crypto.getCommonPRNG()
             n = int(prng.getNormal(self.approx,1.5)+0.5)
-        return [ None ] * self.n
+        return [ None ] * n
+    def getMinLength(self):
+        return max(self.n,self.approx)
+    def __repr__(self):
+        if self.n:
+            assert not self.approx
+            return "RandomServersPathElement(n=%r)"%self.n
+        else:
+            return "RandomServersPathElement(approx=%r)"%self.approx
 
 #----------------------------------------------------------------------
 class PathSpecifier:
-    def __init__(self, path1, path2, isReply, isSURB):
+    def __init__(self, path1, path2, isReply, isSURB, lateSplit):
         if isSURB:
             assert path2 and not path1
         elif isReply:
             assert path1 and not path2
-        else:
+        elif not lateSplit:
             assert path1 and path2
+        else:
+            assert path1 or path2
         self.path1=path1
         self.path2=path2
         self.isReply=isReply
         self.isSURB=isSURB
-    def getFixedLastServer(self,startAt,endAt):
+        self.lateSplit=lateSplit
+    def getFixedLastServer(self,directory,startAt,endAt):
         """DOCDOC"""
         if self.path2:
-            return self.path2[-1].getFixedServer(startAt,endAt)
+            return self.path2[-1].getFixedServer(directory,startAt,endAt)
         else:
             return None
 
@@ -1153,50 +1278,65 @@
             p.append("<swap>")
 
     pathEntries = []
-    approxHops = 0 # Not including star.
     for ent in p:
         if re.match(r'\*(\d+)', ent):
             pathEntries.append(RandomServersPathElement(n=int(ent[1:])))
-            nHops += int(ent[1:])
         elif re.match(r'\~(\d+)', ent):
             pathEntries.append(RandomServersPathElement(approx=int(ent[1:])))
-            nHops += int(ent[1:])
         elif ent == '*':
             pathEntries.append("*")
         elif ent == '<swap>':
             pathEntries.append("<swap>")
+        elif ent == '?':
+            pathEntries.append(RandomServersPathElement(n=1))
         else:
             pathEntries.append(ServerPathElement(ent))
-            nHops += 1
 
     # If there's a variable-length wildcard...
-    if "*" in path:
+    if "*" in pathEntries:
         # Find out how many hops we should have.
         starPos = pathEntries.index("*")
+        if "*" in pathEntries[starPos+1:]:
+            raise UIError("Only one '*' is permitted in a single path")
+        approxHops = reduce(operator.add,
+                            [ ent.getMinLength() for ent in pathEntries
+                              if ent not in ("*", "<swap>") ])
         myNHops = nHops or defaultNHops or 6
         extraHops = max(myNHops-approxHops, 0)
-        pathEntries[starPos:starPos+1] = RandomServersPathElement(n=extraHops)
+        pathEntries[starPos:starPos+1] =[RandomServersPathElement(n=extraHops)]
 
     # Figure out how long the first leg should be.
-    if "<swap>" in pathElements:
+    lateSplit = 0
+    if "<swap>" in pathEntries:
         # Calculate colon position
         if halfPath:
             raise UIError("Can't specify swap point with replies")
-        colonPos = pathElements.index("<swap>")
+        colonPos = pathEntries.index("<swap>")
+        if "<swap>" in pathEntries[colonPos+1:]:
+            raise UIError("Only one ':' is permitted in a single path")
         firstLegLen = colonPos
-        del pathElements[colonPos]
+        del pathEntries[colonPos]
     elif isReply:
-        firstLegLen = len(pathElements)
+        firstLegLen = len(pathEntries)
     elif isSURB:
         firstLegLen = 0
     else:
-        firstLegLen = ceilDiv(len(pathElements), 2)
+        firstLegLen = 0
+        lateSplit = 1
 
     # Split the path into 2 legs.
-    path1, path2 = pathElements[:firstLegLen], pathElements[firstLegLen:]
-    if not halfPath and len(path1)+len(path2) < 2:
-        raise UIError("Path is too short")
-    if not halfPath and (not path1 or not path2):
-        raise UIError("Each leg of the path must have at least 1 hop")
-
-    return PathSpecifier(path1, path2, isReply, isSURB)
+    path1, path2 = pathEntries[:firstLegLen], pathEntries[firstLegLen:]
+    if not lateSplit and not halfPath:
+        if len(path1)+len(path2) < 2:
+            raise UIError("The path must have at least 2 hops")
+        if not path1 or not path2:
+            raise UIError("Each leg of the path must have at least 1 hop")
+    else:
+        minLen = reduce(operator.add,
+                        [ ent.getMinLength() for ent in pathEntries ])
+        if halfPath and minLen < 1:
+            raise UIError("The path must have at least 1 hop")
+        if not halfPath and minLen < 2:
+            raise UIError("The path must have at least 2 hops")
+        
+    return PathSpecifier(path1, path2, isReply, isSURB, lateSplit=lateSplit)

Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.117
retrieving revision 1.118
diff -u -d -r1.117 -r1.118
--- ClientMain.py	28 Sep 2003 05:27:55 -0000	1.117
+++ ClientMain.py	7 Oct 2003 21:57:46 -0000	1.118
@@ -17,6 +17,7 @@
 
 import mixminion.BuildMessage
 import mixminion.ClientUtils
+import mixminion.ClientDirectory
 import mixminion.Config
 import mixminion.Crypto
 import mixminion.Filestore

Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.155
retrieving revision 1.156
diff -u -d -r1.155 -r1.156
--- test.py	28 Sep 2003 05:27:56 -0000	1.155
+++ test.py	7 Oct 2003 21:57:46 -0000	1.156
@@ -6205,7 +6205,7 @@
         p1,p2 = ppath(ks, None, "Alice,Fred,Bob,Lola", mboxWithoutServer)
         pathIs((p1,p2), ((alice,fred),(bob,lola)))
         p1,p2 = ppath(ks, None, "Alice,?,?,Bob", mboxWithServer)
-        eq((len(p1),len(p2)), (2,3))
+        eq((len(p1),len(p2)), (3,2))
         pathIs((p1[:1],p2[-2:]), ((alice,),(bob,lola)))
 
         # 1a'. Filename with internal commas and colons, where permitted.
@@ -6325,9 +6325,9 @@
         raises(MixError, ppath, ks, None, "Alice:Bob,Fred", mboxWithoutServer)
         # Two stars.
         raises(MixError, ppath, ks, None, "Alice,*,Bob,*,Joe", email)
-        # NHops mismatch
-        raises(MixError, ppath, ks, None, "Alice:Bob,Joe", email, nHops=2)
-        raises(MixError, ppath, ks, None, "Alice:Bob,Joe", email, nHops=4)
+        # NHops mismatch -- no longer an error.
+        ##raises(MixError, ppath, ks, None, "Alice:Bob,Joe", email, nHops=2)
+        ##raises(MixError, ppath, ks, None, "Alice:Bob,Joe", email, nHops=4)
         # Nonexistent file
         raises(MixError, ppath, ks, None, "./Pierre:Alice,*", email)