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

[minion-cvs] Renaming and testing



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

Modified Files:
	BuildMessage.py ClientMain.py test.py 
Log Message:
Renaming and testing

- Rename client pools to client queues.
- Rename mix queues to mix pools.

  (Now, we call something a "pool" iff we rely on it to batch and
   re-order messages.  "Queue" is still kind of a misnomer, since our
   "queues" aren't FIFO but rather FI-don't-care-O.)

- Test and debug client keyrings



Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.41
retrieving revision 1.42
diff -u -d -r1.41 -r1.42
--- BuildMessage.py	26 Mar 2003 16:36:46 -0000	1.41
+++ BuildMessage.py	27 Mar 2003 10:30:59 -0000	1.42
@@ -303,7 +303,7 @@
                 try:
                     p = _decodeStatelessReplyPayload(payload, tag, userKey)
                     if name:
-                        LOG.info("Decoded reply message to identity: %r", name)
+                        LOG.info("Decoded reply message to identity %r", name)
                     return p
                 except MixError:
                     pass

Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.67
retrieving revision 1.68
diff -u -d -r1.67 -r1.68
--- ClientMain.py	26 Mar 2003 16:36:46 -0000	1.67
+++ ClientMain.py	27 Mar 2003 10:30:59 -0000	1.68
@@ -43,7 +43,7 @@
 
 #----------------------------------------------------------------------
 # Global variable; holds an instance of Common.Lockfile used to prevent
-# concurrent access to the directory cache, message pool, or SURB log.
+# concurrent access to the directory cache, message queue, or SURB log.
 _CLIENT_LOCKFILE = None
 
 def clientLock():
@@ -933,9 +933,9 @@
         self.keyring = None
         self.keyringPassword = None
 
-    def getKey(self, keyid, create=0, createFn=None):
+    def getKey(self, keyid, create=0, createFn=None, password=None):
         if self.keyring is None:
-            self.getKeyring(create=create)
+            self.getKeyring(create=create,password=password)
             if self.keyring is None:
                 return None
         try:
@@ -950,7 +950,7 @@
                 self._saveKeyring()
                 return key
 
-    def getKeyring(self, create=0):
+    def getKeyring(self, create=0, password=None):
         if self.keyring is not None:
             return self.keyring
         fn = os.path.join(self.keyDir, "keyring")
@@ -965,7 +965,10 @@
                 pass
             # ...then ask the user for a password 'till it loads.
             while 1:
-                p = self._getPassword("Enter password for keyring:")
+                if password is not None:
+                    p = password
+                else:
+                    p = self._getPassword("Enter password for keyring:")
                 try:
                     data = self._load(fn, magic, p)
                     self.keyring = cPickle.loads(data)
@@ -973,10 +976,14 @@
                     return self.keyring
                 except (MixError, cPickle.UnpicklingError), e:
                     LOG.error("Cannot load keyring: %s", e)
+                    if password is not None: return None
         elif create:
             # If the key file doesn't exist, and 'create' is set, create it.
             LOG.warn("No keyring found; generating.")
-            self.keyringPassword = self._getNewPassword("keyring")
+            if password is not None:
+                self.keyringPassword = password
+            else:
+                self.keyringPassword = self._getNewPassword("keyring")
             self.keyring = {}
             self._saveKeyring()
             return self.keyring
@@ -987,19 +994,21 @@
         assert self.keyringPassword
         fn = os.path.join(self.keyDir, "keyring")
         LOG.trace("Saving keyring to %s", fn)
-        self._save(fn,
+        self._save(fn+"_tmp",
                    cPickle.dumps(self.keyring,1),
                    "KEYRING1", self.keyringPassword)
+        os.rename(fn+"_tmp", fn)
 
-    def getSURBKey(self, name="", create=0):
+    def getSURBKey(self, name="", create=0, password=None):
         k = self.getKey("SURB-"+name,
-                        create=create, createFn=lambda: trng(20))
-        if len(k) != 20:
+                        create=create, createFn=lambda: trng(20),
+                        password=password)
+        if k is not None and len(k) != 20:
             raise MixError("Bad length on SURB key")
         return k
 
-    def getSURBKeys(self):
-        self.getKeyring(create=0)
+    def getSURBKeys(self,password=None):
+        self.getKeyring(create=0,password=password)
         if not self.keyring: return {}
         r = {}
         for k in self.keyring.keys():
@@ -1222,9 +1231,9 @@
             del self.log[hash]
         self.log['LAST_CLEANED'] = str(int(now))
 
-class ClientPool:
-    """A ClientPool holds packets that have been scheduled for delivery
-       but not yet delivered.  As a matter of policy, we pool messages if
+class ClientQueue:
+    """A ClientQueue holds packets that have been scheduled for delivery
+       but not yet delivered.  As a matter of policy, we queue messages if
        the user tells us to, or if deliver has failed and the user didn't
        tell us not to."""
     ## Fields:
@@ -1237,14 +1246,14 @@
     #             a 32K string (the packet),
     #             an instance of IPV4Info (the first hop),
     #             the latest midnight preceeding the time when this
-    #                 packet was inserted into the pool.
+    #                 packet was inserted into the queue
     #           )
     # XXXX004 change this to be OO; add nicknames.
     
     # XXXX004 write unit tests
 
     def __init__(self, directory, prng=None):
-        """Create a new ClientPool object, storing packets in 'directory'
+        """Create a new ClientQueue object, storing packets in 'directory'
            and generating random filenames using 'prng'."""
         self.dir = directory
         createPrivateDir(directory)
@@ -1253,9 +1262,9 @@
         else:
             self.prng = mixminion.Crypto.getCommonPRNG()
 
-    def poolPacket(self, message, routing):
+    def queuePacket(self, message, routing):
         """Insert the 32K packet 'message' (to be delivered to 'routing')
-           into the pool.  Return the handle of the newly inserted packet."""
+           into the queue.  Return the handle of the newly inserted packet."""
         clientLock()
         try:
             f, handle = self.prng.openNewFile(self.dir, "pkt_", 1)
@@ -1268,7 +1277,7 @@
     
     def getHandles(self):
         """Return a list of the handles of all messages currently in the
-           pool."""
+           queue."""
         clientLock()
         try:
             fnames = os.listdir(self.dir)
@@ -1282,7 +1291,7 @@
 
     def getPacket(self, handle):
         """Given a handle, return a 3-tuple of the corresponding
-           32K packet, IPV4Info, and time of first pooling.  (The time
+           32K packet, IPV4Info, and time of first queueing.  (The time
            is rounded down to the closest midnight GMT.)"""
         f = open(os.path.join(self.dir, "pkt_"+handle), 'rb')
         magic, message, routing, when = cPickle.load(f)
@@ -1293,7 +1302,7 @@
         return message, routing, when
 
     def packetExists(self, handle):
-        """Return true iff the pool contains a packet with the handle
+        """Return true iff the queue contains a packet with the handle
            'handle'."""
         fname = os.path.join(self.dir, "pkt_"+handle)
         return os.path.exists(fname)
@@ -1303,14 +1312,14 @@
         fname = os.path.join(self.dir, "pkt_"+handle)
         secureDelete(fname, blocking=1)
 
-    def inspectPool(self, now=None):
-        """Print a message describing how many messages in the pool are headed
+    def inspectQueue(self, now=None):
+        """Print a message describing how many messages in the queue are headed
            to which addresses."""
         if now is None:
             now = time.time()
         handles = self.getHandles()
         if not handles:
-            print "[Pool is empty.]"
+            print "[Queue is empty.]"
             return
         timesByServer = {}
         for h in handles:
@@ -1332,7 +1341,7 @@
     # config: The ClientConfig object with the current configuration
     # prng: A pseudo-random number generator for padding and path selection
     # keys: A ClientKeyring object.
-    # pool: A ClientPool object.
+    # queue: A ClientQueue object.
     # surbLogFilename: The filename used by the SURB log.
     def __init__(self, conf):
         """Create a new MixminionClient with a given configuration"""
@@ -1347,20 +1356,24 @@
 
         # Initialize PRNG
         self.prng = mixminion.Crypto.getCommonPRNG()
-        self.pool = ClientPool(os.path.join(userdir, "pool"))
+        # XXXX005 remove.
+        if os.path.exists(os.path.join(userdir, "pool")):
+            os.rename(os.path.join(userdir, "pool"),
+                      os.path.join(userdir, "queue"))
+        self.queue = ClientQueue(os.path.join(userdir, "queue"))
 
     def sendForwardMessage(self, address, payload, servers1, servers2,
-                           forcePool=0, forceNoPool=0):
+                           forceQueue=0, forceNoQueue=0):
         """Generate and send a forward message.
             address -- the results of a parseAddress call
             payload -- the contents of the message to send
             servers1,servers2 -- lists of ServerInfos for the first and second
                legs the path, respectively.
-            forcePool -- if true, do not try to send the message; simply
-               pool it and exit.
-            forceNoPool -- if true, do not pool the message even if delivery
+            forceQueue -- if true, do not try to send the message; simply
+               quque it and exit.
+            forceNoQueue -- if true, do not queue the message even if delivery
                fails."""
-        assert not (forcePool and forceNoPool)
+        assert not (forceQueue and forceNoQueue)
 
         message, firstHop = \
                  self.generateForwardMessage(address, payload,
@@ -1368,22 +1381,22 @@
 
         routing = firstHop.getRoutingInfo()
 
-        if forcePool:
-            self.poolMessages([message], routing)
+        if forceQueue:
+            self.queueMessages([message], routing)
         else:
-            self.sendMessages([message], routing, noPool=forceNoPool)
+            self.sendMessages([message], routing, noQueue=forceNoQueue)
 
-    def sendReplyMessage(self, payload, servers, surbList, forcePool=0,
-                         forceNoPool=0):
+    def sendReplyMessage(self, payload, servers, surbList, forceQueue=0,
+                         forceNoQueue=0):
         """Generate and send a reply message.
             payload -- the contents of the message to send
             servers -- a list of ServerInfos for the first leg of the path.
             surbList -- a list of SURBs to consider for the second leg of
                the path.  We use the first one that is neither expired nor
                used, and mark it used.
-            forcePool -- if true, do not try to send the message; simply
-               pool it and exit.
-            forceNoPool -- if true, do not pool the message even if delivery
+            forceQueue -- if true, do not try to send the message; simply
+               queue it and exit.
+            forceNoQueue -- if true, do not queue the message even if delivery
                fails."""
         #XXXX004 write unit tests
         message, firstHop = \
@@ -1391,11 +1404,10 @@
 
         routing = firstHop.getRoutingInfo()
         
-        if forcePool:
-            self.poolMessages([message], routing)
+        if forceQueue:
+            self.queueMessages([message], routing)
         else:
-            self.sendMessages([message], routing, noPool=forceNoPool)
-
+            self.sendMessages([message], routing, noQueue=forceNoQueue)
 
     def generateReplyBlock(self, address, servers, name="", expiryTime=0):
         """Generate an return a new ReplyBlock object.
@@ -1466,18 +1478,18 @@
         """
         return SURBLog(self.surbLogFilename)
 
-    def sendMessages(self, msgList, routingInfo, noPool=0, lazyPool=0,
+    def sendMessages(self, msgList, routingInfo, noQueue=0, lazyQueue=0,
                      warnIfLost=1):
         """Given a list of packets and an IPV4Info object, sends the
            packets to the server via MMTP.
 
-           If noPool is true, do not pool the message even on failure.
-           If lazyPool is true, only pool the message on failure.
-           Otherwise, insert the message in the pool, and remove it on
+           If noQueue is true, do not queue the message even on failure.
+           If lazyQueue is true, only queue the message on failure.
+           Otherwise, insert the message in the queue, and remove it on
            success.
 
            If warnIfLost is true, log a warning if we fail to deliver
-           the message, and we don't pool it.
+           the message, and we don't queue it.
            """
         #XXXX004 write unit tests
         timeout = self.config['Network'].get('ConnectionTimeout')
@@ -1486,10 +1498,10 @@
         else:
             timeout = 60
 
-        if noPool or lazyPool:
+        if noQueue or lazyQueue:
             handles = []
         else:
-            handles = self.poolMessages(msgList, routingInfo)
+            handles = self.queueMessages(msgList, routingInfo)
 
         if len(msgList) > 1:
             mword = "messages"
@@ -1505,40 +1517,40 @@
                                                   timeout)
                 LOG.info("... %s sent", mword)
             except:
-                if noPool and warnIfLost:
-                    LOG.error("Error with pooling disabled: %s lost", mword)
-                elif lazyPool:
-                    LOG.info("Error while delivering %s; %s pooled",
+                if noQueue and warnIfLost:
+                    LOG.error("Error with queueing disabled: %s lost", mword)
+                elif lazyQueue:
+                    LOG.info("Error while delivering %s; %s queued",
                              mword,mword)
-                    self.poolMessages(msgList, routingInfo)
+                    self.queueMessages(msgList, routingInfo)
                 else:
-                    LOG.info("Error while delivering %s; leaving in pool",
+                    LOG.info("Error while delivering %s; leaving in queue",
                              mword)
                 raise
             try:
                 clientLock()
                 for h in handles:
-                    if self.pool.packetExists(h):
-                        self.pool.removePacket(h)
+                    if self.queue.packetExists(h):
+                        self.queue.removePacket(h)
             finally:
                 clientUnlock()
         except MixProtocolError, e:
             raise UIError(str(e))
             
-    def flushPool(self):
+    def flushQueue(self):
         """Try to send end all messages in the queue to their destinations.
         """
         #XXXX004 write unit tests
 
-        LOG.info("Flushing message pool")
+        LOG.info("Flushing message queue")
         # XXXX This is inefficient in space!
         clientLock()
         try:
-            handles = self.pool.getHandles()
+            handles = self.queue.getHandles()
             LOG.info("Found %s pending messages", len(handles))
             messagesByServer = {}
             for h in handles:
-                message, routing, _ = self.pool.getPacket(h)
+                message, routing, _ = self.queue.getPacket(h)
                 messagesByServer.setdefault(routing, []).append((message, h))
         finally:
             clientUnlock()
@@ -1549,37 +1561,37 @@
             msgs = [ m for m, _ in messagesByServer[routing] ]
             handles = [ h for _, h in messagesByServer[routing] ]
             try:
-                self.sendMessages(msgs, routing, noPool=1, warnIfLost=0)
+                self.sendMessages(msgs, routing, noQueue=1, warnIfLost=0)
                 try:
                     clientLock()
                     for h in handles:
-                        if self.pool.packetExists(h):
-                            self.pool.removePacket(h)
+                        if self.queue.packetExists(h):
+                            self.queue.removePacket(h)
                 finally:
                     clientUnlock()
             except MixError:
-                LOG.error("Can't deliver messages to %s:%s; leaving in pool",
+                LOG.error("Can't deliver messages to %s:%s; leaving in queue",
                           routing.ip, routing.port)
-        LOG.info("Pool flushed")
+        LOG.info("Queue flushed")
 
-    def poolMessages(self, msgList, routing):
-        """Insert all the messages in msgList into the pool, to be sent
+    def queueMessages(self, msgList, routing):
+        """Insert all the messages in msgList into the queue, to be sent
            to the server identified by the IPV4Info object 'routing'.
         """
         #XXXX004 write unit tests
-        LOG.trace("Pooling messages")
+        LOG.trace("Queueing messages")
         handles = []
         try:
             clientLock()
             for msg in msgList:
-                h = self.pool.poolPacket(msg, routing)
+                h = self.queue.queuePacket(msg, routing)
                 handles.append(h)
         finally:
             clientUnlock()
         if len(msgList) > 1:
-            LOG.info("Messages pooled")
+            LOG.info("Messages queued")
         else:
-            LOG.info("Message pooled")
+            LOG.info("Message queued")
         return handles
 
     def decodeMessage(self, s, force=0, isatty=0):
@@ -1724,7 +1736,7 @@
           REPLY PATH ONLY
              --lifetime : Required lifetime of new reply blocks.
           MESSAGE-SENDING ONLY:
-             --pool | --no-pool : force/disable pooling.
+             --queue | --no-queue : force/disable queueing.
 
          The class's constructor parses command line options, as required.
          The .init() method initializes a config file, logging, a
@@ -1746,8 +1758,8 @@
     #  lifetime: SURB lifetime, or None.
     #  replyBlockFiles: list of SURB filenames.
     #  configFile: Filename of configuration file, or None.
-    #  forcePool: true if "--pool" is set.
-    #  forceNoPool: true if "--no-pool" is set.
+    #  forceQueue: true if "--queue" is set.
+    #  forceNoQueue: true if "--no-queue" is set.
     #  verbose: true if verbose mode is set.
     #  download: 1 if the user told us to download the directory, 0 if
     #    they told us not to download it, and None if they didn't say.
@@ -1807,8 +1819,8 @@
         self.lifetime = None
         self.replyBlockFiles = []
 
-        self.forcePool = None
-        self.forceNoPool = None
+        self.forceQueue = None
+        self.forceNoQueue = None
 
         for o,v in opts:
             if o in ('-h', '--help'):
@@ -1875,9 +1887,17 @@
                 except ValueError:
                     raise UsageError("%s expects an integer"%o)
             elif o in ('--pool',):
-                self.forcePool = 1
+                LOG.warn(
+                    "The --pool option is deprecated; use --queue instead")
+                self.forceQueue = 1
             elif o in ('--no-pool',):
-                self.forceNoPool = 1
+                LOG.warn(
+                 "The --no-pool option is deprecated; use --no-queue instead")
+                self.forceNoQueue = 1
+            elif o in ('--queue',):
+                self.forceQueue = 1
+            elif o in ('--no-queue',):
+                self.forceNoQueue = 1
 
     def init(self):
         """Configure objects and initialize subsystems as specified by the
@@ -2071,14 +2091,14 @@
         print >>sys.stderr, "ERROR: %s"%error
         print >>sys.stderr, "For usage, run 'mixminion send --help'"
         sys.exit(1)
-    if cmd.endswith(" pool"):
+    if cmd.endswith(" pool") or cmd.endswith(" queue"):
         print _SEND_USAGE % { 'cmd' : cmd, 'send' : 'pool', 'Send': 'Pool',
                               'extra' : '' }
     else:
         print _SEND_USAGE % { 'cmd' : cmd, 'send' : 'send', 'Send': 'Send',
                               'extra' : """\
-  --pool                     Pool the message; don't send it.
-  --no-pool                  Do not attempt to pool the message.""" }
+  --queue                    Pool the message; don't send it.
+  --no-queue                 Do not attempt to pool the message.""" }
     sys.exit(0)
 
 # NOTE: This isn't the final client interface.  Many or all options will
@@ -2087,14 +2107,17 @@
     #DOCDOC Comment this function
     if cmd.endswith(" client"): #XXXX004 remove this.
         print "The 'client' command is deprecated.  Use 'send' instead."
-    poolMode = 0
-    if cmd.endswith(" pool"):
-        poolMode = 1
+    queueMode = 0
+    if cmd.endswith(" queue"):
+        queueMode = 1
+    elif cmd.endswith(" pool"):
+        LOG.warn("The 'pool' command is deprecated.  Use 'queue' instead.")
+        queueMode = 1
 
     options, args = getopt.getopt(args, "hvf:D:t:H:P:R:i:",
              ["help", "verbose", "config=", "download-directory=",
               "to=", "hops=", "swap-at=", "path=", "reply-block=",
-              "input=", "pool", "no-pool" ])
+              "input=", "pool", "no-pool", "queue", "no-queue" ])
               
     if not options:
         usageAndExit(cmd)
@@ -2111,10 +2134,10 @@
         parser = CLIArgumentParser(options, wantConfig=1,wantClientDirectory=1,
                                    wantClient=1, wantLog=1, wantDownload=1,
                                    wantForwardPath=1)
-        if poolMode and parser.forceNoPool:
-            raise UsageError("Can't use --no-pool option with pool command")
-        if parser.forcePool and parser.forceNoPool:
-            raise UsageError("Can't use both --pool and --no-pool")
+        if queueMode and parser.forceNoQueue:
+            raise UsageError("Can't use --no-queue option with queue command")
+        if parser.forceQueue and parser.forceNoQueue:
+            raise UsageError("Can't use both --queue and --no-queue")
     except UsageError, e:
         e.dump()
         usageAndExit(cmd)
@@ -2123,9 +2146,9 @@
         raise UIError(
             "Can't read both message and reply block from stdin")
 
-    # FFFF Make pooling configurable from .mixminionrc
-    forcePool = poolMode or parser.forcePool
-    forceNoPool = parser.forceNoPool
+    # FFFF Make queueing configurable from .mixminionrc
+    forceQueue = queueMode or parser.forceQueue
+    forceNoQueue = parser.forceNoQueue
 
     parser.init()
     client = parser.client
@@ -2173,10 +2196,10 @@
     if parser.usingSURBList:
         assert isinstance(path2, ListType)
         client.sendReplyMessage(payload, path1, path2,
-                                forcePool, forceNoPool)
+                                forceQueue, forceNoQueue)
     else:
         client.sendForwardMessage(address, payload, path1, path2,
-                                  forcePool, forceNoPool)
+                                  forceQueue, forceNoQueue)
 
 _IMPORT_SERVER_USAGE = """\
 Usage: %(cmd)s [options] <filename> ...
@@ -2525,7 +2548,7 @@
     finally:
         surblog.close()
 
-_FLUSH_POOL_USAGE = """\
+_FLUSH_QUEUE_USAGE = """\
 Usage: %(cmd)s [options]
   -h, --help                 Print this usage message and exit.
   -v, --verbose              Display extra debugging messages.
@@ -2533,11 +2556,11 @@
                                (You can also use MIXMINIONRC=FILE)
 
 EXAMPLES:
-  Try to send all currently pooled messages.
+  Try to send all currently queued messages.
       %(cmd)s
 """.strip()
 
-def flushPool(cmd, args):
+def flushQueue(cmd, args):
     options, args = getopt.getopt(args, "hvf:",
              ["help", "verbose", "config=", ])
     try:
@@ -2545,16 +2568,16 @@
                                    wantClient=1)
     except UsageError, e:
         e.dump()
-        print _FLUSH_POOL_USAGE % { 'cmd' : cmd }
+        print _FLUSH_QUEUE_USAGE % { 'cmd' : cmd }
         sys.exit(1)
 
     parser.init()
     client = parser.client
 
-    client.flushPool()
+    client.flushQueue()
 
 
-_LIST_POOL_USAGE = """\
+_LIST_QUEUE_USAGE = """\
 Usage: %(cmd)s [options]
   -h, --help                 Print this usage message and exit.
   -v, --verbose              Display extra debugging messages.
@@ -2562,11 +2585,11 @@
                                (You can also use MIXMINIONRC=FILE)
 
 EXAMPLES:
-  Describe the current contents of the pool.
+  Describe the current contents of the queue.
       %(cmd)s
 """.strip()
 
-def listPool(cmd, args):
+def listQueue(cmd, args):
     options, args = getopt.getopt(args, "hvf:",
              ["help", "verbose", "config=", ])
     try:
@@ -2574,7 +2597,7 @@
                                    wantClient=1)
     except UsageError, e:
         e.dump()
-        print _LIST_POOL_USAGE % { 'cmd' : cmd }
+        print _LIST_QUEUE_USAGE % { 'cmd' : cmd }
         sys.exit(1)
 
     parser.init()
@@ -2582,6 +2605,6 @@
 
     try:
         clientLock()
-        client.pool.inspectPool()
+        client.queue.inspectQueue()
     finally:
         clientUnlock()

Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.95
retrieving revision 1.96
diff -u -d -r1.95 -r1.96
--- test.py	26 Mar 2003 16:36:46 -0000	1.95
+++ test.py	27 Mar 2003 10:30:59 -0000	1.96
@@ -2039,30 +2039,28 @@
 
         # Stateful replies are disabled.
 
-##      # repl (stateful)
-##      sdict2 = { 'tag2'*5 : [secrets] + [ '\x00\xFF'*8] }
-##      for pk in (self.pk1, None):
-##          for p in (passwd, None):
-##              sd = sdict.copy()
-##              self.assertEquals(payload,
-##                     decodePayload(repl1, "tag1"*5, pk, sd, p))
-##              self.assert_(not sd)
-##              self.assertEquals(None,
-##                     decodePayload(repl1, "tag1"*5, pk, None, p))
-##              self.assertEquals(None,
-##                     decodePayload(repl1, "tag1"*5, pk, sdict2, p))
-
         # repl (stateless)
         for pk in (self.pk1, None):
             #for sd in (sdict, None): #Stateful replies are disabled
                 self.assertEquals(payload,
                             decodePayload(repl2, repl2tag, pk, passwd))
+                try:
+                    suspendLog("INFO")
+                    self.assertEquals(payload,
+                                      decodePayload(repl2, repl2tag, pk,
+                                     userKeys={ "Fred": passwd, "": "z"*20 }))
+                finally:
+                    s = resumeLog()
+                self.assert_(stringContains(s,
+                               "Decoded reply message to identity 'Fred'"))
                 self.assertEquals(None,
                             decodePayload(repl2, repl2tag, pk, "Bliznerty"))
                 self.assertEquals(None,
+                            decodePayload(repl2, repl2tag, pk,
+                                   userKeys={ "Fred":"Bliznerty", "":"z"*20}))
+                self.assertEquals(None,
                             decodePayload(repl2, repl2tag, pk, None))
 
-
         # Try decoding a payload that looks like a zlib bomb.  An easy way to
         # get such a payload is to compress 25K of zeroes.
         nils = "\x00"*(25*1024)
@@ -2074,7 +2072,7 @@
         # And now the cases that fail hard.  This can only happen on:
         #   1) *: Hash checks out, but zlib or size is wrong.  Already tested.
         #   2) EFWD: OAEP checks out, but hash is wrong.
-        #   3) REPLY: Tag matches; hash doesn't.
+        #   3) <omitted; stateful replies are gone>
         #   4) SREPLY: ---.
 
         # Bad efwd
@@ -2082,26 +2080,14 @@
         self.failUnlessRaises(MixError,
                               BuildMessage._decodeEncryptedForwardPayload,
                               efwd_pbad, efwd_t, self.pk1)
-        #for d in (sdict, None):
-        if 1:
-            for p in (passwd, None):
-                self.failUnlessRaises(MixError, decodePayload,
-                                      efwd_pbad, efwd_t, self.pk1, p)
-                self.assertEquals(None,
-                          decodePayload(efwd_pbad, efwd_t, self.pk2, p))
 
-##      # Bad repl
-##      repl2_bad = repl2[:-1] + chr(ord(repl1[-1])^0xaa)
-##      for pk in (self.pk1, None):
-##          for p in (passwd, None):
-##              #sd = sdict.copy()
-##              self.failUnlessRaises(MixError,
-##                       decodePayload, repl1_bad, "tag1"*5, pk, p)
-##              #sd = sdict.copy()
-##              self.failUnlessRaises(MixError,
-##                       BuildMessage._decodeReplyPayload, repl1_bad,
-##                                    sd["tag1"*5])
-        # Bad srepl
+        for p in (passwd, None):
+            self.failUnlessRaises(MixError, decodePayload,
+                                  efwd_pbad, efwd_t, self.pk1, p)
+            self.assertEquals(None,
+                      decodePayload(efwd_pbad, efwd_t, self.pk2, p))
+
+        # Bad repl
         repl2_bad = repl2[:-1] + chr(ord(repl2[-1])^0xaa)
         self.assertEquals(None,
                   decodePayload(repl2_bad, repl2tag, None, passwd))
@@ -2621,11 +2607,11 @@
         queue.removeAll()
         queue.cleanQueue()
 
-    def testMixQueues(self):
+    def testMixPools(self):
         d_m = mix_mktemp("qm")
 
-        # Trivial 'TimedMixQueue'
-        queue = TimedMixQueue(d_m)
+        # Trivial 'TimedMixPool'
+        queue = TimedMixPool(d_m)
         h1 = queue.queueMessage("Hello1")
         h2 = queue.queueMessage("Hello2")
         h3 = queue.queueMessage("Hello3")
@@ -2635,8 +2621,8 @@
         b.sort()
         self.assertEquals(msgs,b)
 
-        # Now, test the CottrellMixQueue.
-        cmq = CottrellMixQueue(d_m, 600, 6, sendRate=.3)
+        # Now, test the CottrellMixPool.
+        cmq = CottrellMixPool(d_m, 600, 6, sendRate=.3)
         # Not enough messages (<= 6) -- none will be sent.
         self.assertEquals([], cmq.getBatch())
         self.assertEquals([], cmq.getBatch())
@@ -2664,7 +2650,7 @@
             self.assertEquals(30, len(cmq.getBatch()))
 
         # Binomial Cottrell pool
-        bcmq = BinomialCottrellMixQueue(d_m, 600, 6, sendRate=.3)
+        bcmq = BinomialCottrellMixPool(d_m, 600, 6, sendRate=.3)
         # (Just make sure that we don't always return the same number of
         #  messages each time.)
         messageLens = []
@@ -3393,10 +3379,10 @@
         self.assert_(floatEq(SC._parseFraction("100%"), 1))
         self.assert_(floatEq(SC._parseFraction("0%"), 0))
         # Mix algorithms
-        self.assertEquals(SC._parseMixRule(" Cottrell"), "CottrellMixQueue")
+        self.assertEquals(SC._parseMixRule(" Cottrell"), "CottrellMixPool")
         self.assertEquals(SC._parseMixRule("binomialCottrell"),
-                          "BinomialCottrellMixQueue")
-        self.assertEquals(SC._parseMixRule("TIMED"), "TimedMixQueue")
+                          "BinomialCottrellMixPool")
+        self.assertEquals(SC._parseMixRule("TIMED"), "TimedMixPool")
 
         ##
         # Now, try the failing cases.
@@ -4637,12 +4623,12 @@
         # Test pool configuration
         pool = MixPool(configTimed, mixDir)
         self.assert_(isinstance(pool.queue,
-                                TimedMixQueue))
+                                TimedMixPool))
         self.assertEquals(pool.getNextMixTime(100), 100+2*60*60)
 
         pool = MixPool(configCottrell, mixDir)
         self.assert_(isinstance(pool.queue,
-                                CottrellMixQueue))
+                                CottrellMixPool))
         self.assertEquals(pool.getNextMixTime(100), 100+12*60*60)
         self.assertEquals(pool.queue.minPool, 10)
         self.assertEquals(pool.queue.minSend, 1)
@@ -4650,7 +4636,7 @@
 
         pool = MixPool(configBCottrell, mixDir)
         self.assert_(isinstance(pool.queue,
-                                BinomialCottrellMixQueue))
+                                BinomialCottrellMixPool))
         self.assertEquals(pool.getNextMixTime(100), 100+6*60*60)
         self.assertEquals(pool.queue.minPool, 10)
         self.assertEquals(pool.queue.minSend, 1)
@@ -5263,8 +5249,43 @@
 
     def testClientKeyring(self):
         keydir = mix_mktemp()
-        keyring = mixminion.ClientMain.ClientKeyring(keyring)
+        keyring = mixminion.ClientMain.ClientKeyring(keydir)
+        # Check for some nonexistant keys.
+        self.assertEquals({}, keyring.getSURBKeys(password="pwd"))
+        
+        self.assertEquals(None, keyring.getSURBKey(create=0))
+        # Reload, try again:
+        kfirst = None
+        for _ in 1, 2:
+            keyring = mixminion.ClientMain.ClientKeyring(keydir)
+            self.assertEquals(kfirst, keyring.getSURBKey(
+                create=0,password="pwd"))
+            try:
+                suspendLog()
+                k1 = keyring.getSURBKey(create=1,password="pwd")
+            finally:
+                s = resumeLog()
+            if kfirst:
+                self.assertEquals(s, "")
+                self.assertEquals(k1, kfirst)
+            else:
+                self.assert_(stringContains(s, "No keyring found"))
+                kfirst = k1
+            self.assertEquals(20, len(k1))
+            k2 = keyring.getSURBKey(name="Bob",create=1)
+            self.assertEquals(20, len(k2))
+            self.assertNotEquals(k1,k2)
+            self.assertEquals({"":k1,"Bob":k2}, keyring.getSURBKeys())
 
+        # Incorrect password
+        keyring = mixminion.ClientMain.ClientKeyring(keydir)
+        try:
+            suspendLog()
+            self.assertEquals(None,keyring.getSURBKey(password="incorrect"))
+        finally:
+            s = resumeLog()
+        self.assert_(stringContains(s, "Incorrect password"))
+        
     def testMixminionClient(self):
         # Create and configure a MixminionClient object...
         parseAddress = mixminion.ClientMain.parseAddress