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

[minion-cvs] Backport; tests; hacking: Core code is clean, commented...



Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.seul.org:/tmp/cvs-serv23515/lib/mixminion

Modified Files:
	BuildMessage.py Common.py Crypto.py HashLog.py Modules.py 
	ServerInfo.py __init__.py benchmark.py test.py 
Added Files:
	.cvsignore Packet.py PacketHandler.py 
Removed Files:
	Formats.py ServerProcess.py 
Log Message:
Backport; tests; hacking: Core code is clean, commented, documented, and tested

- Backported to older versions of Python.  We now support 2.0 through 
  2.2 inclusive.  This included:
      - Removing uses of // operator.
      - Checking version properly in setup.py
      - Removing use of nested scopes.
      - Replace type(foo) == str with type(foo) == type("")
      - Bundling Python's unittest.py module so we can run tests on systems
        (Python 2.0) that don't include it.
      - In crypt.c, we can no longer go from openssl BIGNUMs to Python Longs
        using _PyLong_(To|From)ByteArray -- the latter method didn't exist
        until 2.2.  We have to to/from a hex representation instead.

- Better message on insane-endianness case.

- Rename 'Formats' module to 'Packet'.
- Rename 'ServerProcess' module to 'PacketHandler'.
- Document and comment all remaining code.
- Changes to Packet: (Formerly Formats)
     - Add ReplyBlock object
     - Fix bugs in header slicing
     - Freak out on overlong subheaders.

- Changes to BuildMessage:
     - Make BuildStatelessReplyBlock a bit more paranoid about missing userKey.
     - Rejoin _buildFoo and _buildFoo_impl
     - Grouse early if header will be too long.
     - Misc cleanups
  
     - New tests: Long intermediate info,  fail on overlong routing info

- Changes to Crypto: 
     - Use symbolic constants throughout
     - Add cache for TRNG

- Changes to Hashlog:
     - Make 'sync' method conditional on underlying sync: this allows us
       to support dumbdbm.

- Changes to PacketHandler: (formerly ServerProcess)
     - Remove processMessage intermediary; rename _processMessage.
     - Add support for multiple keys
     - Make setup more sane
     - Add note on timing attacks
     - New tests: Most failing cases 

- Changes to benchMark:
     - Restructure handling of primitives, loop overhead
     - Make cleanup process handle dumbdbm.
     - Add actual vs ideal comparisons for Lioness and PacketHandler

- Changes to crypt.c:
     - Write more idiomatic and paranoid C. 
     - Make failing SHA cases fail faster
     - Backport to py2.0->py2.2; see above



--- NEW FILE: .cvsignore ---
_unittest.py

--- NEW FILE: Packet.py ---
# Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
# $Id: Packet.py,v 1.1 2002/06/02 06:11:16 nickm Exp $
"""mixminion.Packet

   Functions, classes, and constants to parse and unparse Mixminion
   messages and related structures."""

__all__ = [ 'ParseError', 'Message', 'Header', 'Subheader',
            'parseMessage', 'parseHeader', 'parseSubheader',
            'getTotalBlocksForRoutingInfoLen', 'ReplyBlock',
            'IPV4Info', 'SMTPInfo', 'LocalInfo', 'parseIPV4Info',
            'parseSMTPInfo', 'parseLocalInfo', 'ENC_SUBHEADER_LEN',
            'HEADER_LEN', 'PAYLOAD_LEN', 'MAJOR_NO', 'MINOR_NO',
            'SECRET_LEN']

import types, struct
import mixminion.Common

# Major and minor number for the understood packet format.
# ???? The spec needs to specify this.
MAJOR_NO, MINOR_NO = 0,1

# Length of a Mixminion message
MESSAGE_LEN = 1 << 15
# Length of a header section
HEADER_LEN  = 128 * 16
# Length of a single payload
PAYLOAD_LEN = MESSAGE_LEN - HEADER_LEN*2

# Smallest possible size for a subheader
MIN_SUBHEADER_LEN = 42
# Most information we can fit into a subheader
MAX_SUBHEADER_LEN = 86
# Longest routing info that will fit in the main subheader
MAX_ROUTING_INFO_LEN = MAX_SUBHEADER_LEN - MIN_SUBHEADER_LEN

# Length of a subheader, once RSA-encoded.
ENC_SUBHEADER_LEN = 128
# Length of a digest
DIGEST_LEN = 20
# Length of a secret key
SECRET_LEN = 16

# Most info that fits in a single extened subheader
ROUTING_INFO_PER_EXTENDED_SUBHEADER = ENC_SUBHEADER_LEN

class ParseError(mixminion.Common.MixError):
    """Thrown when a message or portion thereof is incorrectly formatted."""
    pass

def parseMessage(s):
    """parseMessage(s) -> Message

       Given a 32K string, returns a Message object that breaks it into
       two headers and a payload."""
    if len(s) != MESSAGE_LEN:
        raise ParseError("Bad message length")
        
    return Message(s[:HEADER_LEN],
                   s[HEADER_LEN:HEADER_LEN*2],
                   s[HEADER_LEN*2:])

class Message:
    """Represents a complete Mixminion packet

       Fields: header1, header2, payload"""
    def __init__(self, header1, header2, payload):
        """Message(header1, header2, payload) -> msg

           Creates a new Message object from three strings."""
        self.header1 = header1
        self.header2 = header2
        self.payload = payload

    def pack(self):
        """Returns the 32K string value of this message."""
        return "".join([self.header1,self.header2,self.payload])

def parseHeader(s):
    """parseHeader(s) -> Header

       Converts a 2K string into a Header object"""
    if len(s) != HEADER_LEN:
        raise ParseError("Bad header length")

    return Header(s)

class Header:
    """Represents a 2K Mixminion header"""
    def __init__(self, contents):
        self.contents = contents

    def __getitem__(self, i):
        """header[i] -> str

           Returns the i'th encoded subheader of this header, for i in 0..15"""
        if i < 0: i = 16+i
        return self.contents[i*ENC_SUBHEADER_LEN:
                             (i+1)*ENC_SUBHEADER_LEN]

    def __getslice__(self, i, j):
        """header[i] -> str

           Returns a slice of the i-j'th subheaders of this header."""
        if j > 16: j = 16
        if i < 0: i=16+i
        if j < 0: j=16+j
        return self.contents[i*ENC_SUBHEADER_LEN:
                             j*ENC_SUBHEADER_LEN]

    def __len__(self):
        return 16

# A subheader begins with: a major byte, a minor byte, SECRET_LEN secret
# bytes, DIGEST_LEN digest bytes, a routing_len short, and a routing_type
# short.
SH_UNPACK_PATTERN = "!BB%ds%dsHH" % (SECRET_LEN, DIGEST_LEN)

def parseSubheader(s):
    """parseSubheader(s) -> Subheader

       Converts a decoded Mixminion subheader into a Subheader object"""
    if len(s) < MIN_SUBHEADER_LEN:
        raise ParseError("Header too short")
    if len(s) > MAX_SUBHEADER_LEN:
        raise ParseError("Header too long")

    try:
        major, minor, secret, digest, rlen, rt = \
               struct.unpack(SH_UNPACK_PATTERN, s[:MIN_SUBHEADER_LEN])
    except struct.error:
        raise ParseError("Misformatted subheader")
    ri = s[MIN_SUBHEADER_LEN:]
    if rlen < len(ri):
        ri = ri[:rlen]
    return Subheader(major,minor,secret,digest,rt,ri,rlen)

def getTotalBlocksForRoutingInfoLen(bytes):
    """Returns the number of subheraders that will be needed for a hop
       whose routinginfo is (bytes) long."""
    if bytes <= MAX_ROUTING_INFO_LEN:
        return 1
    else:
        extraBytes = bytes - MAX_ROUTING_INFO_LEN
        return 2 + divmod(extraBytes,ROUTING_INFO_PER_EXTENDED_SUBHEADER)[0]
    
class Subheader:
    """Represents a decoded Mixminion header

       Fields: major, minor, secret, digest, routinglen, routinginfo,
               routingtype.

       A Subheader can exist in a half-initialized state where routing
       info has been read from the first header, but not from the
       extened headers.  If this is so, routinglen will be > len(routinginfo).
       """
    
    def __init__(self, major, minor, secret, digest, routingtype,
                 routinginfo, routinglen=None):
        self.major = major
        self.minor = minor
        self.secret = secret
        self.digest = digest
        if routinglen == None:
            self.routinglen = len(routinginfo)
        else:
            self.routinglen = routinglen
        self.routingtype = routingtype
        self.routinginfo = routinginfo

    def __repr__(self):
        return ("Subheader(major=%(major)r, minor=%(minor)r, "+
                "secret=%(secret)r, digest=%(digest)r, "+
                "routingtype=%(routingtype)r, routinginfo=%(routinginfo)r, "+
                "routinglen=%(routinglen)r)")% self.__dict__
                
    def setRoutingInfo(self, info):
        """Changes the routinginfo, and the routinglength to correspond."""
        self.routinginfo = info
        self.routinglen = len(info)

    def isExtended(self):
        """Returns true iff the routinginfo is too long to fit in a single
           subheader."""
        return self.routinglen > MAX_ROUTING_INFO_LEN

    def getNExtraBlocks(self):
        """Returns the number of extra blocks that will be needed to fit
           the routinginfo."""
        return getTotalBlocksForRoutingInfoLen(self.routinglen)-1

    def appendExtraBlocks(self, data):
        """appendExtraBlocks(str)

           Given additional (decoded) blocks of routing info, adds them
           to the routinginfo of this object."""
        nBlocks = self.getNExtraBlocks()
        assert len(data) == nBlocks * ENC_SUBHEADER_LEN
        raw = [self.routinginfo]
        for i in range(nBlocks):
            block = data[i*ENC_SUBHEADER_LEN:(i+1)*ENC_SUBHEADER_LEN]
            raw.append(block)
        self.routinginfo = ("".join(raw))[:self.routinglen]
        
    def pack(self):
        """Returns the (unencrypted) string representation of this Subhead.

           Does not include extra blocks"""
        assert self.routinglen == len(self.routinginfo)
        assert len(self.digest) == DIGEST_LEN
        assert len(self.secret) == SECRET_LEN
        info = self.routinginfo[:MAX_ROUTING_INFO_LEN]

        return struct.pack(SH_UNPACK_PATTERN, 
                           self.major,self.minor,self.secret,self.digest,
                           self.routinglen, self.routingtype)+info
    
    def getExtraBlocks(self):
        """getExtraBlocks() -> [ str, ...]

           Returns a list of (unencrypted) blocks of extra routing info."""
        if not self.isExtended():
            return []
        else:
            info = self.routinginfo[MAX_ROUTING_INFO_LEN:]
            result = []
            for i in range(self.getNExtraBlocks()):
                content = info[i*ROUTING_INFO_PER_EXTENDED_SUBHEADER:
                               (i+1)*ROUTING_INFO_PER_EXTENDED_SUBHEADER]
                missing = ROUTING_INFO_PER_EXTENDED_SUBHEADER-len(content)
                if missing > 0:
                    content += '\000'*missing                
                result.append(content)
            return result


#FFFF We don't have an interchange format for these yet, so there's no way
#FFFF to parse or unparse 'em.
class ReplyBlock:
    """A mixminion reply block, including the address of the first hop
       on the path, and a ServerInfo for the server."""
    def __init__(self, header, addr):
        self.header = header
        self.addr = addr

def _packIP(s):
    """Helper method: converts a dotted-decimal IPv4 address into a
       four-byte encoding.  Raises ParseError if the input is not a
       dotted quad of 0..255"""
    addr = s.split(".")
    if len(addr) != 4:
        raise ParseError("Malformed IP address")
    try:
        addr = map(int, addr)
    except ValueError:
        raise ParseError("Malformed IP address")
    for i in addr:
        if not (0 <= i <= 255): raise ParseError("Malformed IP address")
    return struct.pack("!BBBB", *addr)

def _unpackIP(s):
    """Helper method: convers a four-byte IPv4 address into a dotted quad."""
    if len(s) != 4: raise ParseError("Malformed IP")
    return ".".join(map(str, struct.unpack("!BBBB", s)))

# An IPV4 address (Used by SWAP_FWD and FWD) is packed as: four bytes
# of IP address, a short for the portnum, and DIGEST_LEN bytes of keyid.
IPV4_PAT = "!4sH%ds" % DIGEST_LEN
    
def parseIPV4Info(s):
    """parseIP4VInfo(s) -> IPV4Info

       Converts routing info for an IPV4 address into an IPV4Info object,
       suitable for use by FWD or SWAP_FWD modules."""
    if len(s) != 4+2+DIGEST_LEN:
        raise ParseError("IPV4 information with wrong length")
    try:
        ip, port, keyinfo = struct.unpack(IPV4_PAT, s)
    except struct.error:
        raise ParseError("Misformatted IPV4 routing info")
    ip = _unpackIP(ip)
    return IPV4Info(ip, port, keyinfo)

class IPV4Info:
    """An IPV4Info object represents the routinginfo for a FWD or SWAP_FWD hop.

       Fields: ip (a dotted quad), port (an int from 0..65535), and keyinfo
       (a digest)."""
    def __init__(self, ip, port, keyinfo):
        assert 0<=port<=65535
        self.ip = ip
        self.port = port
        self.keyinfo = keyinfo

    def pack(self):
        assert len(self.keyinfo) == DIGEST_LEN
        return struct.pack(IPV4_PAT, _packIP(self.ip), self.port, self.keyinfo)

def parseSMTPInfo(s):
    """Converts the encoding of an SMTP routinginfo into an SMTPInfo object."""
    lst = s.split("\000",1)
    if len(lst) == 1:
        return SMTPInfo(s,None)
    else:
        return SMTPInfo(lst[0], lst[1])

class SMTPInfo:
    """Represents the routinginfo for an SMTP hop.

       Fields: email (an email address), tag (an arbitrary tag, optional)."""
    def __init__(self, email, tag):
        self.email = email
        self.tag = tag

    def pack(self):
        if self.tag != None:
            return self.email+"\000"+self.tag
        else:
            return self.email
        
def parseLocalInfo(s):
    """Converts the encoding of an LOCAL routinginfo into an LocalInfo
       object."""
    lst = s.split("\000",1)
    if len(lst) == 1:
        return LocalInfo(s,None)
    else:
        return LocalInfo(lst[0], lst[1])
    
class LocalInfo:
    """Represents the routinginfo for a LOCAL hop.

       Fields: user (a user identifier), tag (an arbitrary tag, optional)."""
    def __init__(self, user, tag):
        self.user = user
        assert user.find('\000') == -1
        self.tag = tag

    def pack(self):
        if self.tag:
            return self.user+"\000"+self.tag
        else:
            return self.user

--- NEW FILE: PacketHandler.py ---
# Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
# $Id: PacketHandler.py,v 1.1 2002/06/02 06:11:16 nickm Exp $

"""mixminion.PacketHandler: Code to process mixminion packets"""

import mixminion.Crypto as Crypto
import mixminion.Packet as Packet
from mixminion.Packet import MAJOR_NO, MINOR_NO
import mixminion.Modules as Modules
import mixminion.Common as Common

__all__ = [ 'PacketHandler', 'ContentError' ]

class ContentError(Common.MixError):
    """Exception raised when a packed is malformatted or unacceptable."""
    pass

class PacketHandler:
    """Class to handle processing packets.  Given an incoming packet,
       it removes one layer of encryption, does all necessary integrity
       checks, swaps headers if necessary, re-pads, and decides whether
       to drop the message, relay the message, or send the message to
       an exist handler."""
    
    def __init__(self, privatekey, hashlog):
        """PacketHandler(privatekey, hashlog)

           Constructs a new packet handler, given a private key object for
           header encryption, and a hashlog object to prevent replays.

           A sequence of private keys may be provided, if you'd like the
           server to accept messages encrypted with any of them.  Beware,
           though: this slows down the packet handler a lot.
        """
        # ???? Any way to support multiple keys in protocol?
        if type(privatekey) in (type(()), type([])):
            self.privatekey = privatekey
        else:
            self.privatekey = (privatekey, )
        self.hashlog = hashlog

    # Raises ParseError, ContentError, SSLError.
    #  Returns oneof (None), (EXIT, argl), ("QUEUE", (ipv4info, msg))
    def processMessage(self, msg):
        """ph.processMessage(msg)

           Given a 32K mixminion message, processes it completely.

           Returns one of:
                    None [if the mesesage should be dropped.
                    ("EXIT",
                       (routing_type, routing_info, application_key,
                        payload)) [if this is the exit node]
                    ("QUEUE", (ipv4info, message_out))
                        [if this is a forwarding node]

           May raise SSLError, ParseError, or ContentError if the packet
           is malformatted, misencrypted, unparseable, repeated, or otherwise
           unhandleable.

           WARNING: This implementation does nothing to prevent timing
           attacks: dropped messages, messages with digests, replayed
           messages, and exit messages are all processed faster than
           forwarded messages.  You must prevent timing attacks elsewhere."""

        # Break into headers and payload
        msg = Packet.parseMessage(msg)
        header1 = Packet.parseHeader(msg.header1)

        # Try to decrypt the first subheader.
        enc_subh = header1[0]
        subh = None
        err = None
        for pk in self.privatekey:
            try:
                subh = Crypto.pk_decrypt(enc_subh, pk)
            except Crypto.SSLError, e:
                err = e
        if not subh:
            raise err
        subh = Packet.parseSubheader(subh)

        # Check the version: can we read it?
        if subh.major != MAJOR_NO or subh.minor != MINOR_NO:
            raise ContentError("Invalid protocol version")

        # Check the digest: is it correct?
        digest = Crypto.sha1(header1[1:])
        if digest != subh.digest:
            raise ContentError("Invalid digest")

        # Get ready to generate message keys.
        keys = Crypto.Keyset(subh.secret)

        # Replay prevention
        replayhash = keys.get(Crypto.REPLAY_PREVENTION_MODE, 20)
        if self.hashlog.seenHash(replayhash):
            raise ContentError("Duplicate message detected.")
        else:
            self.hashlog.logHash(replayhash)

        # If we're meant to drop, drop now.
        rt = subh.routingtype
        if rt == Modules.DROP_TYPE:
            return None

        # Prepare the key to decrypt the header in counter mode.  We'll be
        # using this more than once.
        header_sec_key = Crypto.aes_key(keys.get(Crypto.HEADER_SECRET_MODE))

        # If the subheader says that we have extra blocks of routing info,
        # decrypt and parse them now.
        if subh.isExtended():
            nExtra = subh.getNExtraBlocks() 
            if (rt < Modules.MIN_EXIT_TYPE) or (nExtra > 15):
                # None of the native methods allow multiple blocks; no
                # size can be longer than the number of bytes in the rest
                # of the header.
                raise ContentError("Impossibly long routing info length")
                
            extra = Crypto.ctr_crypt(header1[1:1+nExtra], header_sec_key)
            subh.appendExtraBlocks(extra)
            remainingHeader = header1[1+nExtra:]
        else:
            nExtra = 0
            remainingHeader = header1[1:]

        # Decrypt the payload.
        payload = Crypto.lioness_decrypt(msg.payload,
                              keys.getLionessKeys(Crypto.PAYLOAD_ENCRYPT_MODE))

        # If we're an exit node, there's no need to process the headers
        # further.
        if rt >= Modules.MIN_EXIT_TYPE:
            return ("EXIT",
                    (rt, subh.routinginfo,
                     keys.get(Crypto.APPLICATION_KEY_MODE),
                     payload))

        # If we're not an exit node, make sure that what we recognize our
        # routing type.
        if rt not in (Modules.SWAP_FWD_TYPE, Modules.FWD_TYPE):
            raise ContentError("Unrecognized Mixminion routing type")

        # Pad the rest of header 1
        remainingHeader = remainingHeader +\
                          Crypto.prng(keys.get(Crypto.PRNG_MODE),
                                      Packet.HEADER_LEN-len(remainingHeader))

        # Decrypt the rest of header 1, encrypting the padding.
        header1 = Crypto.ctr_crypt(remainingHeader, header_sec_key, nExtra*128)

        # Decrypt header 2.
        header2 = Crypto.lioness_decrypt(msg.header2,
                           keys.getLionessKeys(Crypto.HEADER_ENCRYPT_MODE))

        # If we're the swap node, decrypt header2 with a hash of the
        # payload, and swap the headers.
        if rt == Modules.SWAP_FWD_TYPE:
            hkey = Crypto.lioness_keys_from_payload(payload)
            header2 = Crypto.lioness_decrypt(header2, hkey)
            header1, header2 = header2, header1

        # Build the address object for the next hop
        address = Packet.parseIPV4Info(subh.routinginfo)

        # Construct the message for the next hop.
        msg = Packet.Message(header1, header2, payload).pack()

        return ("QUEUE", (address, msg))

Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- BuildMessage.py	31 May 2002 12:47:58 -0000	1.4
+++ BuildMessage.py	2 Jun 2002 06:11:16 -0000	1.5
@@ -1,7 +1,12 @@
 # Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
 # $Id$
 
-from mixminion.Formats import *
+"""mixminion.BuildMessage
+
+   Code to construct messages and reply blocks."""
+
+from mixminion.Packet import *
+from mixminion.Common import MixError
 import mixminion.Crypto as Crypto
 import mixminion.Modules as Modules
 import operator
@@ -10,38 +15,78 @@
             'buildStatelessReplyBlock' ]
 
 def buildForwardMessage(payload, exitType, exitInfo, path1, path2):
-    "XXXX"
+    """buildForwardMessage(payload, exitType, exitInfo, path1, path2) ->str
+
+       Constructs a forward message.
+            payload: The payload to deliver.
+            exitType: The routing type for the final node
+            exitType: The routing info for the final node
+            path1: Sequence of ServerInfo objects for the first leg of the path
+            path1: Sequence of ServerInfo objects for the 2nd leg of the path
+        """
     return _buildMessage(payload, exitType, exitInfo, path1, path2)
 
 def buildReplyMessage(payload, path1, replyBlock):
-    # Bad iface; shouldn't take a tuple
-    "XXXX"
+    """buildReplyMessage(payload, path1, replyBlock) ->str
+
+       Builds a message using a reply block.  'path1' is a sequence of
+       ServerInfo for the nodes on the first leg of the path."""
     return _buildMessage(payload, None, None,
                          path1=path1,
                          reply=replyBlock)
 
-# Bad interface: this shouldn't return a tuple. 
 def buildReplyBlock(path, exitType, exitInfo, secretPRNG=None):
-    "XXXX"
+    """buildReplyBlock(path, exitType, exitInfo, secretPRNG=None) 
+                                                  -> (Reply block, secret list)
+
+       Returns a newly-constructed reply block and a list of secrets used
+       to make it.
+       
+              path: A list of ServerInfo
+              exitType: Routing type to use for the final node
+              exitInfo: Routing info for the final node
+              secretPRNG: A PRNG to use for generating secrets.  If not
+                 provided, uses an AES counter-mode stream seeded from our
+                 entropy source.
+       """
     if secretPRNG == None:
         secretPRNG = Crypto.AESCounterPRNG()
     secrets = [ secretPRNG.getBytes(SECRET_LEN) for node in path ]
     header = _buildHeader(path, secrets, exitType, exitInfo, 
                           paddingPRNG=Crypto.AESCounterPRNG())
-    return (header, path[0]), secrets
+    return ReplyBlock(header, path[0]), secrets
+
+# Maybe we shouldn't even allow this to be called with userKey==None.
+def buildStatelessReplyBlock(path, user, userKey, email=0):
+    """buildStatelessReplyBlock(path, user, userKey, email=0) -> ReplyBlock
+
+       Constructs a 'stateless' reply block that does not require the
+       reply-message recipient to remember a list of secrets.
+       Instead, all secrets are generated from an AES counter-mode
+       stream, and the seed for the stream is stored in the 'tag'
+       field of the final block's routing info.
+
+       If the user provides a 'userkey', that key is used to encrypt
+       the seed before storing it in the tag field.  Otherwise, the
+       seed is stored in the clear.  USERS SHOULD ALWAYS SET 'userkey'
+       IF THE EXIT INFORMATION WILL BE TRAVELING OVER THE NETWORK, OR
+       IF THEY DO NOT PERSONALLY CONTROL THE EXIT NODE.  Otherwise,
+       their anonymity can be completely broken.
+
+                  path: a list of ServerInfo objects
+                  user: the users username/email address
+                  userKey: an AES key to encrypt the seed, or None.
+                  email: If true, delivers via SMTP; else delivers via LOCAL.
+       """
+    if email and userKey:
+        raise MixError("Requested EMail delivery without password-protection")
 
-# Bad interface: userkey should only be None if we trust the final node
-# a lot.
-def buildStatelessReplyBlock(path, user, userKey=None, email=0):
-    "XXXX"
-    # COMMENT
-    if email:
-        assert userKey
     seed = Crypto.trng(16)
     if userKey:
         tag = Crypto.ctr_crypt(seed,userKey)
     else:
         tag = seed
+        
     if email:
         exitType = Modules.SMTP_TYPE
         exitInfo = SMTPInfo(user, "RTRN"+tag).pack()
@@ -53,87 +98,106 @@
     return buildReplyBlock(path, exitType, exitInfo, prng)[0]
 
 #----------------------------------------------------------------------
-# needs a more informative name
 def _buildMessage(payload, exitType, exitInfo,
                   path1, path2=None, reply=None, paddingPRNG=None, paranoia=0):
-    "XXXX"
+    """_buildMessage(payload, exitType, exitInfo, path1, path2=None,
+                     reply=None, paddingPRNG=None, paranoia=0) -> str
+    
+    Helper method to create a message.
+
+    The following fields must be set:
+       payload: the intended exit payload.
+       (exitType, exitInfo): the routing type and info for the final node.
+              (Ignored for reply messages)
+       path1: a sequence of ServerInfo objects, one for each of the nodes
+          on the first leg of the path.
+
+    The following fields must be set for a forward message:
+       path2: a sequence of ServerInfo objects, one for each of the nodes
+          on the second leg of the path.
+
+    The following fields must be set for a reply message:
+       reply: a ReplyBlock object
+
+    The following fields are optional:
+       paddingPRNG: A pseudo-random number generator used to pad the headers
+         and the payload.  If not provided, we use a counter-mode AES stream
+         seeded from our entropy source.
+       paranoia: If this is false, we use the padding PRNG to generate
+         header secrets too.  Otherwise, we read all of our header secrets
+         from the true entropy source. 
+    """
     assert path2 or reply
     assert not (path2 and reply)
-    if paddingPRNG == None:
-        paddingPRNG = Crypto.AESCounterPRNG()
 
-    # ???? Payload padding/sizing must be handled in spec.
-    if len(payload) < PAYLOAD_LEN:
-        payload += paddingPRNG.getBytes(PAYLOAD_LEN-len(payload))
-
-    nHops = len(path1)
-    if path2: nHops += len(path2)
+    ### SETUP CODE: let's handle all the variant cases.
 
+    # Set up the random number generators.
+    if paddingPRNG == None:
+        paddingPRNG = Crypto.AESCounterPRNG()
     if paranoia:
+        nHops = len(path1)
+        if path2: nHops += len(path2)
         secretRNG = Crypto.TrueRNG(SECRET_LEN*len(nHops))
     else:
         secretRNG = paddingPRNG
 
+    # Determine exit routing for path1 and path2.
     if reply:
-        path1exitnode = reply[1]
-        path2exit = None
-        reply = reply[0]
+        path1exitinfo = reply.addr.getRoutingInfo().pack()
     else:
-        path1exitnode = path2[0]
-        path2exit = ( exitType, exitInfo )
-    
-    path1exit = ( Modules.SWAP_FWD_TYPE,
-                  path1exitnode.getRoutingInfo().pack() )
-    
-    return _buildMessage_impl(payload, path1, path1exit, path2, path2exit,
-                              reply, nHops, secretRNG, paddingPRNG)
+        path1exitinfo = path2[0].getRoutingInfo().pack()
 
-#needs a more informative name
-def _buildMessage_impl(payload, path1, path1exit, path2, path2exit,
-                       reply, nHops, secretRNG, paddingRNG):
-    "XXXX"
-    # XXXX ???? Payload padding/sizing must be handled in spec.
+    # Pad the payload, as needed.
     if len(payload) < PAYLOAD_LEN:
-        payload += paddingRNG.getBytes(PAYLOAD_LEN-len(payload))
+        # ???? Payload padding/sizing must be handled in spec.
+        payload += paddingPRNG.getBytes(PAYLOAD_LEN-len(payload))
 
+    # Generate secrets for path1.
     secrets1 = [ secretRNG.getBytes(SECRET_LEN) for x in range(len(path1)) ]
     
     if path2:
-        # Make secrets2 before header1 so we don't use the RNG to pad 
-        # the first header yet.
+        # Make secrets for header 2, and construct header 2.  We do the before
+        # making header1 so that our rng won't be used for padding yet.
         secrets2 = [ secretRNG.getBytes(SECRET_LEN) for x in range(len(path2))]
-        header2 = _buildHeader(path2,secrets2,path2exit[0],path2exit[1],
-                                 paddingRNG)
+        header2 = _buildHeader(path2,secrets2,exitType,exitInfo,paddingPRNG)
     else:
         secrets2 = None
-        header2 = reply
+        header2 = reply.header
+
+    # Construct header1.
+    header1 = _buildHeader(path1,secrets1,Modules.SWAP_FWD_TYPE,path1exitinfo,
+                           paddingPRNG)
 
-    header1 = _buildHeader(path1,secrets1,path1exit[0],path1exit[1],
-                             paddingRNG)
     return _constructMessage(secrets1, secrets2, header1, header2, payload)
 
 def _buildHeader(path,secrets,exitType,exitInfo,paddingPRNG):
-    "XXXX"
-    # XXXX insist on sane parameters, path lengths.
-    routing = []
-    for i in range(len(path)-1):
-        nextNode = path[i+1]
-        info = nextNode.getRoutingInfo()
-        routing.append( (Modules.FWD_TYPE, info.pack() ) )
-        
-    routing.append( (exitType, exitInfo) )
-    
-    return _buildHeader_impl(path,secrets,routing,paddingPRNG)
+    """_buildHeader(path, secrets, exitType, exitInfo, paddingPRNG) -> str
 
-#routing is list of (type,info)
-def _buildHeader_impl(path, secrets, routing, paddingPRNG):
-    "XXXX"
-    hops = len(path)
+       Helper method to construct a single header.
+           path: A sequence of serverinfo objects.
+           secrets: A list of 16-byte strings to use as master-secrets for
+               each of the subeaders.
+           exitType: The routing for the last node in the header
+           exitInfo: The routing info for the last node in the header
+           paddingPRNG: A pseudo-random number generator to generate padding"""
+
+    assert len(path) == len(secrets)
+    if len(path) * ENC_SUBHEADER_LEN > HEADER_LEN:
+        raise MixError("Too many nodes in path")
 
+    # Construct a list 'routing' of exitType, exitInfo.  
+    routing = [ (Modules.FWD_TYPE, node.getRoutingInfo().pack()) for
+                node in path[1:] ]
+    routing.append( (exitType, exitInfo) )
+    
     # sizes[i] is size, in blocks, of subheaders for i.
     sizes =[ getTotalBlocksForRoutingInfoLen(len(info)) for t, info in routing]
+    
     # totalSize is number total number of blocks.
     totalSize = reduce(operator.add, sizes)
+    if totalSize * ENC_SUBHEADER_LEN >  HEADER_LEN:
+        raise MixError("Routing info won't fit in header")
 
     # headerKey[i]==the AES key object node i will use to decrypt the header
     headerKeys = [ Crypto.Keyset(secret).get(Crypto.HEADER_SECRET_MODE)
@@ -153,17 +217,16 @@
         # NewJunk is the junk that node i will append. (It's as long as
         #   the subheaders that i removes.)
         newJunk = Crypto.prng(prngKey,size*128)
-        nextJunk = junkSeen[-1] + newJunk
+        lastJunk = junkSeen[-1]
+        nextJunk = lastJunk + newJunk
         # Node i encrypts starting with its first extended subheader.  By
         #   the time it reaches the junk, it's traversed:
-        #          All of its extended subheaders            [(size-1)*128]
-        #          The parts of the header that aren't junk.
-        #                                           [HEADER_LEN-len(nextJunk)]
+        #          All of its extended subheaders    [(size-1)*128]
+        #          Non-junk parts of the header      [HEADER_LEN-len(nextJunk)]
         #
         # Simplifying, we find that the PRNG index for the junk is
-        #      HEADER_LEN-len(junkSeen[-1])-128*size+128*size-128
-        #    = HEADER_LEN-len(junkSeen[-1])-128
-        startIdx = HEADER_LEN-len(junkSeen[-1])-128
+        #    HEADER_LEN-len(lastJunk)-128.
+        startIdx = HEADER_LEN-len(lastJunk)-128
         nextJunk = Crypto.ctr_crypt(nextJunk, headerKey, startIdx)
         junkSeen.append(nextJunk)
 
@@ -171,15 +234,17 @@
     header = paddingPRNG.getBytes(HEADER_LEN - totalSize*128)
 
     # Now, we build the subheaders, iterating through the nodes backwards.
-    for i in range(hops-1, -1, -1):
+    for i in range(len(path)-1, -1, -1):
         rt, ri = routing[i]
 
+        # Create a subheader object for this node, but don't fill in the
+        # digest until we've constructed the rest of the header.
         subhead = Subheader(MAJOR_NO, MINOR_NO,
                             secrets[i], " "*20,
                             rt, ri)
 
         extHeaders = "".join(subhead.getExtraBlocks())
-        rest = Crypto.ctr_crypt("".join((extHeaders,header)), headerKeys[i])
+        rest = Crypto.ctr_crypt(extHeaders+header, headerKeys[i])
         subhead.digest = Crypto.sha1(rest+junkSeen[i])
         pubkey = Crypto.pk_from_modulus(path[i].getModulus())
         esh = Crypto.pk_encrypt(subhead.pack(), pubkey)
@@ -187,27 +252,36 @@
 
     return header
 
-# For a reply, secrets2==None
 def _constructMessage(secrets1, secrets2, header1, header2, payload):
-    "XXXX"
-    #XXXX comment    
+    """Helper method: Builds a message, given both headers, all known
+       secrets, and the padded payload.
+
+       If using a reply block, secrets2 should be null."""
     assert len(payload) == PAYLOAD_LEN
     assert len(header1) == len(header2) == HEADER_LEN
-    secrets1 = secrets1[:]
-    if secrets2:
-        secrets2 = secrets2[:]
+
     
     if secrets2:
+        # (Copy secrets2 so we don't reverse the original)
+        secrets2 = secrets2[:]
+
+        # If we're not using a reply block, encrypt the payload for
+        # each key in the second path, in reverse order.
         secrets2.reverse()
         for secret in secrets2:
             ks = Crypto.Keyset(secret)
             key = ks.getLionessKeys(Crypto.PAYLOAD_ENCRYPT_MODE)
             payload = Crypto.lioness_encrypt(payload, key)
 
+    # Encrypt header2 with a hash of the payload.
     key = Crypto.lioness_keys_from_payload(payload)
     header2 = Crypto.lioness_encrypt(header2, key)
 
-    secrets1.reverse()
+    # Copy secrets1 so we don't reverse the original.
+    secrets1 = secrets1[:]
+
+    # Now, encrypt header2 and the payload for each node in path1, reversed.
+    secrets1.reverse()    
     for secret in secrets1:
         ks = Crypto.Keyset(secret)
         hkey = ks.getLionessKeys(Crypto.HEADER_ENCRYPT_MODE)

Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- Common.py	31 May 2002 12:47:58 -0000	1.2
+++ Common.py	2 Jun 2002 06:11:16 -0000	1.3
@@ -1,11 +1,16 @@
 # Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
 # $Id$
 
+"""mixminion.Common
+
+   Common functionality and utility code for Mixminion"""
+
+__all__ = [ 'MixError', 'MixFatalError' ]
+
 class MixError(Exception):
+    """Base exception class for all Mixminion errors"""
     pass
 
 class MixFatalError(MixError):
+    """Exception class for unrecoverable Mixminion errors."""
     pass
-
-
-

Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- Crypto.py	31 May 2002 12:47:58 -0000	1.5
+++ Crypto.py	2 Jun 2002 06:11:16 -0000	1.6
@@ -11,7 +11,8 @@
 import sys
 import mixminion._minionlib as _ml
 
-__all__ = [ 'init_crypto', 'sha1',  'ctr_crypt', 'prng', 'strxor',
+__all__ = [ 'SSLError',
+            'init_crypto', 'sha1',  'ctr_crypt', 'prng', 'strxor',
             'lioness_encrypt', 'lioness_decrypt', 'trng', 'pk_encrypt',
             'pk_decrypt', 'pk_generate', 'openssl_seed',
             'pk_get_modulus', 'pk_from_modulus',
@@ -21,6 +22,8 @@
             'HEADER_ENCRYPT_MODE', 'APPLICATION_KEY_MODE',
             'PAYLOAD_ENCRYPT_MODE', 'HIDE_HEADER_MODE' ]
 
+SSLError = _ml.SSLError
+
 AES_KEY_LEN = 128/8
 DIGEST_LEN = 160/8
 
@@ -60,13 +63,13 @@
 
        Given a string s and a 16-byte key key, computes the AES counter-mode
        encryption of s using k.  The counter begins at idx."""
-    if type(key) == str:
+    if type(key) == type(""):
         key = _ml.aes_key(key)
     return _ml.aes_ctr128_crypt(key,s,idx)
 
 def prng(key,count,idx=0):
     """Returns the bytestream 0x00000000...., encrypted in counter mode."""
-    if type(key) == str:
+    if type(key) == type(""):
         key = _ml.aes_key(key)
     return _ml.aes_ctr128_crypt(key,"",idx,count)
 
@@ -78,19 +81,19 @@
 
     assert len(key) == 4
     key1,key2,key3,key4 = key
-    assert len(key1)==len(key3)==20
-    assert len(key2)==len(key4)==20
-    assert len(s) > 20
+    assert len(key1)==len(key3)==DIGEST_LEN
+    assert len(key2)==len(key4)==DIGEST_LEN
+    assert len(s) > DIGEST_LEN
 
-    left = s[:20]
-    right = s[20:]
+    left = s[:DIGEST_LEN]
+    right = s[DIGEST_LEN:]
     del s
     # Performance note: This business with sha1("".join([key,right,key]))
     # may look slow, but it contributes only a 6% to the hashing step,
     # which in turn contributes under 11% of the time for LIONESS.
-    right = ctr_crypt(right, _ml.sha1("".join([key1,left,key1]))[:16])
+    right = ctr_crypt(right, _ml.sha1("".join([key1,left,key1]))[:AES_KEY_LEN])
     left = _ml.strxor(left,  _ml.sha1("".join([key2,right,key2])))
-    right = ctr_crypt(right, _ml.sha1("".join([key3,left,key3]))[:16])
+    right = ctr_crypt(right, _ml.sha1("".join([key3,left,key3]))[:AES_KEY_LEN])
     left = _ml.strxor(left,  _ml.sha1("".join([key4,right,key4])))
     return left + right
 
@@ -102,17 +105,17 @@
 
     assert len(key) == 4
     key1,key2,key3,key4 = key
-    assert len(key1)==len(key3)==20
-    assert len(key2)==len(key4)==20
-    assert len(s) > 20
+    assert len(key1)==len(key3)==DIGEST_LEN
+    assert len(key2)==len(key4)==DIGEST_LEN
+    assert len(s) > DIGEST_LEN
 
-    left = s[:20]
-    right = s[20:]
+    left = s[:DIGEST_LEN]
+    right = s[DIGEST_LEN:]
     del s
     left = _ml.strxor(left,  _ml.sha1("".join([key4,right,key4])))
-    right = ctr_crypt(right, _ml.sha1("".join([key3,left,key3]))[:16])
+    right = ctr_crypt(right, _ml.sha1("".join([key3,left,key3]))[:AES_KEY_LEN])
     left = _ml.strxor(left,  _ml.sha1("".join([key2,right,key2])))
-    right = ctr_crypt(right, _ml.sha1("".join([key1,left,key1]))[:16])
+    right = ctr_crypt(right, _ml.sha1("".join([key1,left,key1]))[:AES_KEY_LEN])
     return left + right
 
 def openssl_seed(count):
@@ -125,7 +128,7 @@
     """trng(count) -> str
 
     Returns (count) bytes of true random data from a true source of
-    entropy (/dev/urandom)"""
+    entropy (/dev/urandom).  May read ahead and cache values."""
     return _theTrueRNG.getBytes(count)
 
 OAEP_PARAMETER = "He who would make his own liberty secure, "+\
@@ -228,6 +231,9 @@
 #---------------------------------------------------------------------
 
 class RNG:
+    '''Base implementation class for random number generators.  Works
+       by requesting a bunch of bytes via self._prng, and doling them
+       out piecemeal via self.getBytes.'''
     def __init__(self, chunksize):
         self.bytes=""
         self.chunksize=chunksize
@@ -246,27 +252,32 @@
         assert 0
 
 class AESCounterPRNG(RNG):
+    '''Pseudorandom number generator that yields an AES counter-mode cipher
+       stram.'''
     def __init__(self, seed=None):
         RNG.__init__(self, 16*1024)
         self.counter = 0
         if seed == None:
-            seed = trng(16)
+            seed = trng(AES_KEY_LEN)
         self.key = aes_key(seed)
     def _prng(self, n):
         c = self.counter
         self.counter+=n
         return prng(self.key,n,c)
 
-def trng_uncached(n):
+def _trng_uncached(n):
+    '''Underlying access to our true entropy source.'''
     f = open('/dev/urandom')
     d = f.read(n)
     f.close()
     return d
 
 class _TrueRNG(RNG):
+    '''Random number generator that yields pieces of entropy from
+       our true rng.'''
     def __init__(self,n):
         RNG.__init__(self,n)
     def _prng(self,n):
-        return trng_uncached(n)
+        return _trng_uncached(n)
 
-_theTrueRNG = _TrueRNG(128)
+_theTrueRNG = _TrueRNG(1024)

Index: HashLog.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/HashLog.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- HashLog.py	31 May 2002 12:47:58 -0000	1.2
+++ HashLog.py	2 Jun 2002 06:11:16 -0000	1.3
@@ -1,11 +1,17 @@
 # Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
 # $Id$
 
+"""mixminion.HashLog
+
+   Persistant memory for the hashed secrets we've seen."""
+
 import anydbm
 from mixminion.Common import MixFatalError
 
 __all__ = [ 'HashLog' ]
 
+# FFFF Mechanism to force a different default db module.
+
 class HashLog:
     """A HashLog is a file containing a list of message digests that we've
        already processed.
@@ -29,6 +35,7 @@
            Creates a new HashLog to store data in 'filename' for the key
            'keyid'."""
         self.log = anydbm.open(filename, 'c')
+        #FFFF Warn if we're using dumbdbm
         try:
             if self.log["KEYID"] != keyid:
                 raise MixFatalError("Log KEYID does not match current KEYID")
@@ -55,10 +62,13 @@
         """sync()
 
            Flushes changes to this log to the filesystem."""
-        self.log.sync()
+        if hasattr(self.log, "sync"):
+            self.log.sync()
         
     def close(self):
         """close()
 
            Closes this log."""
+        self.sync()
         self.log.close()
+    

Index: Modules.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Modules.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- Modules.py	29 May 2002 03:52:13 -0000	1.1
+++ Modules.py	2 Jun 2002 06:11:16 -0000	1.2
@@ -1,12 +1,18 @@
 # Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
 # $Id$
 
-DROP_TYPE      = 0x0000
-FWD_TYPE       = 0x0001
-SWAP_FWD_TYPE  = 0x0002
+"""mixminion.Modules
 
+   Type codes and dispatch functions for routing functionality."""
+
+# Numerically first exit type.
 MIN_EXIT_TYPE  = 0x0100
-SMTP_TYPE      = 0x0100
-LOCAL_TYPE     = 0x0101
 
+# Mixminion types
+DROP_TYPE      = 0x0000  # Drop the current message
+FWD_TYPE       = 0x0001  # Forward the msg to an IPV4 addr via MMTP
+SWAP_FWD_TYPE  = 0x0002  # SWAP, then forward the msg to an IPV4 addr via MMTP
 
+# Exit types
+SMTP_TYPE      = 0x0100  # Mail the message
+LOCAL_TYPE     = 0x0101  # Store the message for local delivery.

Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- ServerInfo.py	31 May 2002 12:47:58 -0000	1.3
+++ ServerInfo.py	2 Jun 2002 06:11:16 -0000	1.4
@@ -1,20 +1,26 @@
 # Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
 # $Id$
 
-"XXXX"
+"""mixminion.ServerInfo
+
+   Data structures to represent a server's information, and functions to
+   martial and unmarshal it.
+
+   ???? Since we don't have an interchange format yet, we only have
+   an object with the minimal info."""
 
 __all__ = [ 'ServerInfo' ]
 
 from mixminion.Modules import SWAP_FWD_TYPE, FWD_TYPE
-from mixminion.Formats import IPV4Info
+from mixminion.Packet import IPV4Info
 
 #
 # Stub class till we have the real thing
 #
 class ServerInfo:
-    "XXXX"
+    """Represents a Mixminion server, and the information needed to send
+       messages to it."""
     def __init__(self, addr, port, modulus, keyid):
-        "XXXX"
         self.addr = addr
         self.port = port
         self.modulus = modulus
@@ -26,5 +32,6 @@
     def getKeyID(self): return self.keyid
     
     def getRoutingInfo(self, swap=0):
+        """Returns a mixminion.Packet.IPV4Info object for routing messages
+           to this server."""
         return IPV4Info(self.addr, self.port, self.keyid)
-    

Index: __init__.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/__init__.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- __init__.py	29 May 2002 03:52:13 -0000	1.1
+++ __init__.py	2 Jun 2002 06:11:16 -0000	1.2
@@ -1,4 +1,12 @@
 # Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
 # $Id$
 
+"""mixminion
+
+   Client and server code for type III anonymous remailers.
+
+   XXXX write more on principal interfaces"""
+
 __version__ = "0.1"
+
+# XXXX __all__

Index: benchmark.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/benchmark.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- benchmark.py	31 May 2002 12:47:58 -0000	1.4
+++ benchmark.py	2 Jun 2002 06:11:16 -0000	1.5
@@ -1,35 +1,53 @@
 # Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
 # $Id$
+
+"""mixminion.benchmark
+
+   Performance tests for Mixminion functionality.
+
+   Usage:
+   >>> import mixminion.benchmark
+   >>> mixminion.benchmark.timeAll()
+
+   """
+
+
 from time import time
 
 __all__ = [ 'timeAll', 'testLeaks1', 'testLeaks2' ]
 
+# If PRECISION_FACTOR is >1, we time everything for PRECISION_FACTOR times
+# more iterations than ususal.
+#
+# If PRESISION_FACTOR is 0, we only try stuff once.  Good for testing this
+# file in a hurry.
 PRECISION_FACTOR = 1
 
-primitives = {}#XXXXDOC
+# Dictionary holds cached values of time for no-op loops.
 loop_overhead = {}
-def timeit_(fn, iters, ov=1, p=None):
-    """XXXX"""
+def timeit_(fn, iters, ov=1):
+    """timeit_(fn, iters)
+
+       returns the execution time for fn(), measures with iters sample
+       iterations."""
     iters *= PRECISION_FACTOR
+    if iters < 1: iters = 1
     nones = [None]*iters
-    overhead = [0, loop_overhead.get(iters, 0)][ov]
+    if ov:
+        overhead = loop_overhead.get(iters, None)
+        if overhead == None:
+            overhead = loop_overhead[iters] = timeit_((
+                lambda:(lambda:None)()), iters, 0)
+    else:
+        overhead = 0
     t = time()
     for n in nones: fn()
     t2 = time()-t
-    t_each = (t2-overhead) / float(iters)
-    if p: primitives[p]=t_each
+    t_each = ((t2) / float(iters))-overhead
     return t_each
 
-min_o = 1.0
-max_o = 0.0
-for iters in [10**n for n in range(2,7)]:
-    overhead = timeit_((lambda:(lambda:None)()), iters)
-    loop_overhead[iters] = overhead
-    min_o = min(min_o, overhead/float(iters))
-    max_o = max(max_o, overhead/float(iters))
-
 def timestr(t):
-    """XXXX"""
+    """Given a time in seconds, returns a readable representation"""
     if abs(t) >= 1.0:
         return "%.3f sec" % t
     elif abs(t) >= .001:
@@ -39,50 +57,51 @@
     else:
         return "%f psec" % (t*1000000000L)
 
-def timeit(fn,times, p=None):
-    """XXXX"""
-    return timestr(timeit_(fn,times,p=p))
+def timeit(fn,times):
+    """Same as timeit_, but returns a readable representation"""
+    return timestr(timeit_(fn,times))
 
 def spacestr(n):
+    """Converts number of bytes to readable representation)"""
     if abs(n) < 1e4:
         return "%d bytes" %n
     elif abs(n) < 1e7:
-        return "%d KB" % (n//1024)
+        return "%d KB" % (n >> 10)
     elif abs(n) < 1e10:
-        return "%d MB" % (n//(1024*1024))
+        return "%d MB" % (n >> 20)
     else:
-        return "%d GB" % (n//(1024*1024*1024))
+        return "%d GB" % (n >> 30)
 
 #----------------------------------------------------------------------
 import mixminion._minionlib as _ml
 from Crypto import *
 from Crypto import OAEP_PARAMETER
 
+short = "Hello, Dali!"
+s20b = "ABCDEFGHIJKLMNOPQRST"
+s64b = "8charstr"*8
+s128b = s64b*2
+s70b = "10character"*7
+s1K = "8charstr"*128
+s2K = s1K*2
+s4K = s2K*2
+s8K = s4K*2
+s28K = s1K*28
+s32K = s8K*4
+
 def cryptoTiming():
-    short = "Hello, Dali!"
-    s1K = "8charstr"*128
-    s2K = s1K*2
-    s4K = s2K*2
-    s8K = s4K*2
-    s28K = s1K*28
-    s32K = s8K*4
 
     print "#==================== CRYPTO ======================="
-    print "Timing overhead: %s...%s" % (timestr(min_o),timestr(max_o))
-
     print "SHA1 (short)", timeit((lambda : sha1(short)), 100000)
-    print "SHA1 (2K)", timeit((lambda : sha1(s8K)), 10000, p="SHA2")
+    print "SHA1 (64b)", timeit((lambda : sha1(s64b)), 100000)
+    print "SHA1 (2K)", timeit((lambda : sha1(s2K)), 10000)
     print "SHA1 (8K)", timeit((lambda : sha1(s8K)), 10000)
-    print "SHA1 (28K)", timeit((lambda : sha1(s28K)), 1000, p="SHA28")
+    print "SHA1 (28K)", timeit((lambda : sha1(s28K)), 1000)
     print "SHA1 (32K)", timeit((lambda : sha1(s32K)), 1000)
 
     shakey = "8charstr"*2
-    #print "Keyed SHA1 (short)",
-    #print timeit((lambda : _ml.sha1(short,shakey)), 100000)
-    #print "Keyed SHA1 (8K)", timeit((lambda : _ml.sha1(s8K, shakey)), 10000)
-    #print "Keyed SHA1 (32K)", timeit((lambda : _ml.sha1(s32K, shakey)), 1000)
     print "Keyed SHA1 for lioness (28K, unoptimized)", timeit(
-        (lambda : _ml.sha1("".join([shakey,s28K,shakey]))), 1000)
+        (lambda shakey=shakey: _ml.sha1("".join([shakey,s28K,shakey]))), 1000)
 
     print "TRNG (20 byte)", timeit((lambda: trng(20)), 100)
     print "TRNG (128 byte)", timeit((lambda: trng(128)), 100)
@@ -92,58 +111,75 @@
     print "xor (32K)", timeit((lambda: _ml.strxor(s32K,s32K)), 1000)
 
     key = "8charstr"*2
-    print "aes (short)", timeit((lambda: ctr_crypt(short,key)), 100000)
-    print "aes (1K)", timeit((lambda: ctr_crypt(s1K,key)), 10000)
-    print "aes (2K)", timeit((lambda: ctr_crypt(s1K,key)), 10000, p="AES2")
-    print "aes (28K)", timeit((lambda: ctr_crypt(s28K,key)), 100, p="AES28")
-    print "aes (32K)", timeit((lambda: ctr_crypt(s32K,key)), 100)
+    print "aes (short)", timeit((lambda key=key: ctr_crypt(short,key)), 100000)
+    print "aes (1K)", timeit((lambda key=key: ctr_crypt(s1K,key)), 10000)
+    print "aes (2K)", timeit((lambda key=key: ctr_crypt(s2K,key)), 10000)
+    print "aes (28K)", timeit((lambda key=key: ctr_crypt(s28K,key)), 100)
+    print "aes (32K)", timeit((lambda key=key: ctr_crypt(s32K,key)), 100)
 
     key = _ml.aes_key(key)
-    print "aes (short,pre-key)", timeit((lambda: ctr_crypt(short,key)), 100000)
-    print "aes (1K,pre-key)", timeit((lambda: ctr_crypt(s1K,key)), 10000)
-    print "aes (28K,pre-key)", timeit((lambda: ctr_crypt(s28K,key)), 100)
-    print "aes (32K,pre-key)", timeit((lambda: ctr_crypt(s32K,key)), 100)
+    print "aes (short,pre-key)", \
+          timeit((lambda key=key: ctr_crypt(short,key)), 100000)
+    print "aes (1K,pre-key)", \
+          timeit((lambda key=key: ctr_crypt(s1K,key)), 10000)
+    print "aes (28K,pre-key)", \
+          timeit((lambda key=key: ctr_crypt(s28K,key)), 100)
+    print "aes (32K,pre-key)", \
+          timeit((lambda key=key: ctr_crypt(s32K,key)), 100)
 
     print "aes (32K,pre-key,unoptimized)", timeit(
-        (lambda: _ml.strxor(prng(key,32768),s32K)), 100)
+        (lambda key=key: _ml.strxor(prng(key,32768),s32K)), 100)
 
-    print "prng (short)", timeit((lambda: prng(key,8)), 100000)
-    print "prng (128b)", timeit((lambda: prng(key,18)), 10000, p="PRNG128b")
-    print "prng (1K)", timeit((lambda: prng(key,1024)), 10000)
-    print "prng (2K)", timeit((lambda: prng(key,1024)), 10000, p="PRNG2")
-    print "prng (28K)", timeit((lambda: prng(key,28678)), 100, p="PRNG28")
-    print "prng (32K)", timeit((lambda: prng(key,32768)), 100)
+    print "prng (short)", timeit((lambda key=key: prng(key,8)), 100000)
+    print "prng (128b)", timeit((
+        lambda key=key: prng(key,18)), 10000)
+    print "prng (1K)", timeit((
+        lambda key=key: prng(key,1024)), 10000)
+    print "prng (2K)", timeit((
+        lambda key=key: prng(key,2048)), 10000)
+    print "prng (28K)", timeit((
+        lambda key=key: prng(key,28678)), 100)
+    print "prng (32K)", timeit((lambda key=key: prng(key,32768)), 100)
     print "prng (32K, unoptimized)", timeit(
-        (lambda: ctr_crypt('\x00'*32768, key)), 100)
+        (lambda key=key: ctr_crypt('\x00'*32768, key)), 100)
 
     lkey = Keyset("keymaterial foo bar baz").getLionessKeys("T")
-    print "lioness E (1K)", timeit((lambda: lioness_encrypt(s1K, lkey)), 1000)
-    print "lioness E (2K)", timeit((lambda: lioness_encrypt(s1K, lkey)), 1000,
-                                   p="LIONESS2")
-    print "lioness E (4K)", timeit((lambda: lioness_encrypt(s4K, lkey)), 1000)
-    print "lioness E (28K)", timeit((lambda: lioness_encrypt(s28K, lkey)), 100,
-                                    p="LIONESS28")
-    print "lioness E (32K)", timeit((lambda: lioness_encrypt(s32K, lkey)), 100)
-    print "lioness D (1K)", timeit((lambda: lioness_decrypt(s1K, lkey)), 1000)
-    print "lioness D (2K)", timeit((lambda: lioness_decrypt(s1K, lkey)), 1000)
-    print "lioness D (4K)", timeit((lambda: lioness_decrypt(s4K, lkey)), 1000)
-    print "lioness D (28K)", timeit((lambda: lioness_decrypt(s28K, lkey)), 100)
-    print "lioness D (32K)", timeit((lambda: lioness_decrypt(s32K, lkey)), 100)
+    print "lioness E (1K)", timeit((
+        lambda lkey=lkey: lioness_encrypt(s1K, lkey)), 1000)
+    print "lioness E (2K)", timeit((
+        lambda lkey=lkey: lioness_encrypt(s1K, lkey)), 1000)
+    print "lioness E (4K)", timeit((
+        lambda lkey=lkey: lioness_encrypt(s4K, lkey)), 1000)
+    print "lioness E (28K)", timeit((
+        lambda lkey=lkey: lioness_encrypt(s28K, lkey)), 100)
+    print "lioness E (32K)", timeit((
+        lambda lkey=lkey: lioness_encrypt(s32K, lkey)), 100)
+    print "lioness D (1K)", timeit((
+        lambda lkey=lkey: lioness_decrypt(s1K, lkey)), 1000)
+    print "lioness D (2K)", timeit((
+        lambda lkey=lkey: lioness_decrypt(s1K, lkey)), 1000)
+    print "lioness D (4K)", timeit((
+        lambda lkey=lkey: lioness_decrypt(s4K, lkey)), 1000)
+    print "lioness D (28K)", timeit((
+        lambda lkey=lkey: lioness_decrypt(s28K, lkey)), 100)
+    print "lioness D (32K)", timeit((
+        lambda lkey=lkey: lioness_decrypt(s32K, lkey)), 100)
 
-    s70b = "10character"*7
     print "OAEP_add (70->128B)",
     print timeit((lambda: _ml.add_oaep_padding(s70b,OAEP_PARAMETER,128)),10000)
     r = _ml.add_oaep_padding(s70b, OAEP_PARAMETER,128)
     print "OAEP_check (128B->70B)",
-    print timeit((lambda: _ml.check_oaep_padding(r,OAEP_PARAMETER,128)),10000)
+    print timeit((lambda r=r:
+                  _ml.check_oaep_padding(r,OAEP_PARAMETER,128)),10000)
 
     print "RSA generate (1024 bit)", timeit((lambda: pk_generate()),10)
     rsa = pk_generate()
     print "Pad+RSA public encrypt",
-    print timeit((lambda: pk_encrypt(s70b, rsa)),1000,p="RSAENC")
+    print timeit((lambda rsa=rsa: pk_encrypt(s70b, rsa)),1000)
     
     enc = pk_encrypt(s70b, rsa)
-    print "Pad+RSA private decrypt", timeit((lambda: pk_decrypt(enc, rsa)),100)
+    print "Pad+RSA private decrypt", \
+          timeit((lambda enc=enc,rsa=rsa: pk_decrypt(enc, rsa)),100)
 
     for (bits,it) in ((2048,10),(4096,10)):
         t = time()
@@ -151,10 +187,16 @@
         t = time()-t
         print "RSA genrate (%d bit)"%bits, timestr(t)
         enc = pk_encrypt(s70b, rsa2)
+        print "Pad+RSA public encrypt (%d bit)"%bits,
+        print timeit((lambda rsa2=rsa2: pk_encrypt("zzz", rsa2)),it)
         print "Pad+RSA private decrypt (%d bit)"%bits,
-        print timeit((lambda: pk_decrypt(enc, rsa2)),it)
+        print timeit((lambda enc=enc,rsa2=rsa2: pk_decrypt(enc, rsa2)),it)
+
+    o = loop_overhead.values()
+    print "Timing overhead: %s...%s" % (timestr(min(o)),timestr(max(o)))
 
 #----------------------------------------------------------------------
+
 def hashlogTiming():
     import tempfile, os
     print "#==================== HASH LOGS ======================="
@@ -163,17 +205,18 @@
         try:
             _hashlogTiming(fname,load)
         finally:
-            try:
-                os.unlink(fname)
-            except:
-                pass
+            for suffix in ("", ".dat", ".bak", ".dir"):
+                try:
+                    os.unlink(fname+suffix)
+                except OSError:
+                    pass
 
 def _hashlogTiming(fname, load):
-    import os
+    import os, stat
     from mixminion.Crypto import AESCounterPRNG
     from mixminion.HashLog import HashLog
     prng = AESCounterPRNG("a"*16)
-        
+    
     h = HashLog(fname, "A")
     hashes = [ prng.getBytes(20) for i in xrange(load) ]
 
@@ -196,24 +239,39 @@
     t = time()-t   
     print "Check entry [miss] (%s entries)" %load, timestr( t/1000.0 )
 
+    hashes =[ prng.getBytes(20) for i in xrange(1000) ]
+    t = time()
+    for hash in hashes:
+        h.seenHash(hash)
+        h.logHash(hash)        
+    t = time()-t   
+    print "Check entry [miss+add] (%s entries)" %load, timestr( t/1000.0 )
+    
     h.close()
-    print "File size (%s entries)"%load, spacestr(os.stat(fname).st_size)
+    size = 0
+    for suffix in ("", ".dat", ".bak", ".dir"):
+        if not os.path.exists(fname+suffix):
+            continue
+        size += os.stat(fname+suffix)[stat.ST_SIZE]
+        
+    print "File size (%s entries)"%load, spacestr(size)
 
 #----------------------------------------------------------------------
+import mixminion.BuildMessage as BMsg
 
 def buildMessageTiming():
-    import mixminion.BuildMessage as BMsg
     from mixminion.ServerInfo import ServerInfo
     print "#================= BUILD MESSAGE ====================="
     pk = pk_generate()
     payload = ("Junky qoph flags vext crwd zimb."*1024)[:22*1024]
     serverinfo = [ ServerInfo("127.0.0.1", 48099, pk_get_modulus(pk),
                               "x"*20) ] * 16
-    def bh(np,it):
+    def bh(np,it, serverinfo=serverinfo):
         ctr = AESCounterPRNG()
         
-        tm = timeit_( \
-              lambda: BMsg._buildHeader(serverinfo[:np], ["Z"*16]*np,
+        tm = timeit_( 
+              lambda np=np,it=it,serverinfo=serverinfo,ctr=ctr: 
+                         BMsg._buildHeader(serverinfo[:np], ["Z"*16]*np,
                                         99, "Hello", ctr), it )
         
         print "Build header (%s)" %(np), timestr(tm)
@@ -223,9 +281,10 @@
     bh(8,20)
     bh(16,10)
     
-    def bm(np1,np2,it):
+    def bm(np1,np2,it,serverinfo=serverinfo,payload=payload):
         tm = timeit_( \
-              lambda: BMsg.buildForwardMessage(payload,
+              lambda np1=np1,np2=np2,it=it,serverinfo=serverinfo,
+                      payload=payload: BMsg.buildForwardMessage(payload,
                                                1,
                                                "Hello",
                                                serverinfo[:np1],
@@ -238,33 +297,124 @@
     bm(8,8,20)
     bm(16,16,10)
 #----------------------------------------------------------------------
+class DummyLog:
+    def seenHash(self,h): return 0
+    def logHash(self,h): pass
+
+from mixminion.PacketHandler import PacketHandler
+from mixminion.ServerInfo import ServerInfo
+from mixminion.BuildMessage import buildForwardMessage
+from mixminion.Modules import SMTP_TYPE
 
 def serverProcessTiming():
     print "#================= SERVER PROCESS ====================="
-    from mixminion.ServerProcess import ServerProcess
-    from mixminion.ServerInfo import ServerInfo
-    from mixminion.BuildMessage import buildForwardMessage
-    from mixminion.Modules import SMTP_TYPE
-    class DummyLog:
-        def seenHash(self,h): return 0
-        def logHash(self,h): pass
 
     pk = pk_generate()
     n = pk_get_modulus(pk)
     server = ServerInfo("127.0.0.1", 1, n, "X"*20)
-    sp = ServerProcess(pk, DummyLog(), None, None)
+    sp = PacketHandler(pk, DummyLog())
 
     m_noswap = buildForwardMessage("Hello world", SMTP_TYPE, "f@invalid",
                                    [server, server], [server, server])
 
     print "Server process (no swap, no log)", timeit(
-        lambda : sp._processMessage(m_noswap), 100)
+        lambda sp=sp, m_noswap=m_noswap : sp.processMessage(m_noswap), 100)
 
     m_swap = buildForwardMessage("Hello world", SMTP_TYPE, "f@invalid",
                                  [server], [server, server])
 
     print "Server process (swap, no log)", timeit(
-        lambda : sp._processMessage(m_swap), 100)
+        lambda sp=sp, m_swap=m_swap : sp.processMessage(m_swap), 100)
+
+#----------------------------------------------------------------------
+def timeEfficiency():
+    print "#================= ACTUAL v. IDEAL ====================="
+
+    ##### LIONESS
+
+    shakey = "z"*20
+    aeskey = "p"*16
+    # Lioness_encrypt is:
+    # 2 28K SHA1's (keyed)
+    # 2 20b SHA1's (keyed)
+    # 2 20b string xors.
+    # 2 28K aes_crypts.
+    shastr = shakey+s28K+shakey
+    sha1_keyed_28k = timeit_((lambda shastr=shastr: _ml.sha1(shastr)), 1000)
+    shastr = shakey+s20b+shakey
+    sha1_keyed_20b = timeit_((lambda shastr=shastr: _ml.sha1(shastr)), 100000)
+    strxor_20b = timeit_((lambda s=s20b: _ml.strxor(s,s)), 100000)
+    aes_28k = timeit_((lambda s=s28K,k=aeskey: ctr_crypt(s,k)), 100)
+
+    lionesskey = ("p"*20,)*4
+    lioness_e = timeit_((lambda s=s28K,k=lionesskey: lioness_encrypt(s,k)),100)
+
+    expected = 2*(strxor_20b+aes_28k+sha1_keyed_28k+sha1_keyed_20b)
+    print "LIONESS TOOK:", timestr(lioness_e)
+    print "    expected:", timestr(expected)
+    print "  difference:", timestr(lioness_e-expected)
+    print "    goodness: %3.2f%%" % (100*expected/lioness_e)
+    print "   breakdown:       aes: %3.1f%%" % (100*2*aes_28k/lioness_e)
+    print "              long sha1: %3.1f%%" % (100*2*sha1_keyed_28k/lioness_e)
+    print "             short sha1: %3.1f%%" % (100*2*sha1_keyed_20b/lioness_e)
+    print "              short xor: %3.1f%%" % (100*2*strxor_20b/lioness_e)
+    
+    ##### SERVER PROCESS
+    pk = pk_generate(1024)
+
+    # Typical (no swap) server process is:
+    #  pk_decrypt (128b)
+    #  sha1       (2K-128b)
+    #5*sha1       (16b+~16b) [HEADER_SEC,HEADER_ENC,PRNG,PAYLOAD_ENC,REPLAY]
+    #  hashlog.seen **omit
+    #  hashlog.log  **omit
+    #  ctr_crypt  (2K)
+    #  lioness_D  (28K)
+    #  prng       (128b)
+    #  lioness_D  (2K)
+
+    # With swap, add:
+    #  keys_from_payload=HASH(28K)
+    #  lioness_D  (2K)
+
+    enc = pk_encrypt(s70b, pk)
+    rsa_128b = timeit_((lambda pk=pk,enc=enc: pk_decrypt(enc,pk)), 100)
+    shastr = s2K[2048-128]
+    sha1_hdr = timeit_((lambda shastr=shastr: sha1(shastr)), 10000)
+    shastr = s64b[:32]
+    sha1_key = timeit_((lambda shastr=shastr: sha1(shastr)), 10000)
+    aes_2k = timeit_((lambda k=aeskey: ctr_crypt(s2K,k)), 1000)
+    lioness_28k = lioness_e
+    lioness_2k = timeit_((
+        lambda s=s2K,k=lionesskey: lioness_encrypt(s,k)),1000)
+    prng_128b = timeit_((lambda k=aeskey: prng(k,128)),10000)
+
+    n = pk_get_modulus(pk)
+    server = ServerInfo("127.0.0.1", 1, n, "X"*20)
+    sp = PacketHandler(pk, DummyLog())
+
+    m_noswap = buildForwardMessage("Hello world", SMTP_TYPE, "f@invalid",
+                                   [server, server], [server, server])
+
+    sp_ns = timeit_(
+        lambda sp=sp, m_noswap=m_noswap : sp.processMessage(m_noswap), 100)
+
+    expected = rsa_128b+sha1_hdr+sha1_key*5+aes_2k+lioness_28k+prng_128b
+    expected += lioness_2k
+    print "SERVERPROCESS TOOK:", timestr(sp_ns)
+    print "          expected:", timestr(expected)
+    print "        difference:", timestr(sp_ns-expected)
+    print "          goodness: %3.2f%%" % (100*expected/sp_ns)
+    print "         breakdown:         rsa: %3.1f%%" % (100*rsa_128b/sp_ns)
+    print "                    28K lioness: %3.1f%%" % (100*lioness_28k/sp_ns)
+    print "                     2K lioness: %3.1f%%" % (100*lioness_2k/sp_ns)
+    print "                     header aes: %3.1f%%" % (100*aes_2k/sp_ns)
+    print "                    header sha1: %3.1f%%" % (100*sha1_hdr/sp_ns)
+    print "                    keygen sha1: %3.1f%%" % (500*sha1_key/sp_ns)
+    print " (logs not included)"
+    # FFFF Time the logs too..?  
+    
+    # FFFF It would be nice to time BuildMessage, but less critical.
     
 #----------------------------------------------------------------------
 def testLeaks1():
@@ -329,6 +479,7 @@
     buildMessageTiming()
     hashlogTiming()
     serverProcessTiming()
+    timeEfficiency()
 
 if __name__ == '__main__':
     timeAll()

Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- test.py	31 May 2002 12:47:58 -0000	1.5
+++ test.py	2 Jun 2002 06:11:16 -0000	1.6
@@ -1,12 +1,29 @@
 # Copyright 2002 Nick Mathewson.  See LICENSE for licensing information.
 # $Id$
 
-import unittest
+"""mixminion.tests
+
+   Unit tests for all Mixminion functionality.
+
+   Usage:
+   >>> import mixminion.tests
+   >>> mixminion.tests.testAll()
+
+   """
+
+import os
+from mixminion.Common import MixError
+
+try:
+    import unittest
+except ImportError:
+    import mixminion._unittest as unittest
 
 def hexread(s):
+    assert (len(s) % 2) == 0
     r = []
     hexvals = "0123456789ABCDEF"
-    for i in range(len(s) // 2):
+    for i in range(len(s) / 2):
         v1 = hexvals.index(s[i*2])
         v2 = hexvals.index(s[i*2+1])
         c = (v1 << 4) + v2
@@ -14,6 +31,16 @@
         r.append(chr(c))
     return "".join(r)
 
+def try_unlink(fname):
+    try:
+        os.unlink(fname)
+    except OSError:
+        pass
+
+def try_unlink_db(fname):
+    for suffix in ("", ".bak", ".dat", ".dir"):
+        try_unlink(fname+suffix)
+
 #----------------------------------------------------------------------
 import mixminion._minionlib as _ml
 
@@ -127,7 +154,6 @@
 
     def test_rsa(self):
         p = _ml.rsa_generate(1024, 65535)
-        def sslerr(*args): self.failUnlessRaises(_ml.SSLError, *args)
 
         #For all of SIGN, CHECK_SIG, ENCRYPT, DECRYPT...
         for pub1 in (0,1):
@@ -214,6 +240,8 @@
         # Check pk_get_modulus sanity
         self.failUnless((1L<<511) < pk_get_modulus(k512) < (1L<<513))
         self.failUnless((1L<<1023) < pk_get_modulus(k1024) < (1L<<1024))
+        self.assertEquals(pk_get_modulus(k512),
+                        pk_get_modulus(pk_from_modulus(pk_get_modulus(k512))))
 
         # Make sure that public keys can be made from moduli, and used to
         # encrypt and decrypt.
@@ -299,8 +327,8 @@
                           PRNG.getBytes(34764)))
 
 #----------------------------------------------------------------------
-import mixminion.Formats
-from mixminion.Formats import *
+import mixminion.Packet
+from mixminion.Packet import *
 
 class FormatTests(unittest.TestCase):
     def test_subheader(self):
@@ -310,11 +338,13 @@
         
         expected = "\003\000abcdeabcdeabcdef"+\
                    "ABCDEFGHIJABCDEFGHIJ\000\005\000\001Hello"
+        # test packing
         self.assertEquals(s.pack(), expected)
         self.failUnless(not s.isExtended())
         self.assertEquals(s.getNExtraBlocks(), 0)
         self.assertEquals(s.getExtraBlocks(), [])
 
+        # test unpacking,
         s = parseSubheader(s.pack())
         self.assertEquals(s.major, 3)
         self.assertEquals(s.minor, 0)
@@ -337,8 +367,9 @@
 
         self.assertEquals(len(ts_eliot), 186)
 
+        # test extended subeaders
         expected = "\003\011abcdeabcdeabcdefABCDEFGHIJABCDEFGHIJ\000\272\000\076Who is the third who walks always beside you"
-        self.assertEquals(len(expected), mixminion.Formats.MAX_SUBHEADER_LEN)
+        self.assertEquals(len(expected), mixminion.Packet.MAX_SUBHEADER_LEN)
         self.assertEquals(s.pack(), expected)
 
         extra = s.getExtraBlocks()
@@ -348,6 +379,7 @@
                           "road / There is always another one walk")
         self.assertEquals(extra[1], "ing beside you"+(114*'\000'))
 
+        # test parsing extended subheaders
         s = parseSubheader(expected)
         self.assertEquals(s.major, 3)
         self.assertEquals(s.minor, 9)
@@ -363,8 +395,12 @@
         self.assertEquals(s.pack(), expected)
         self.assertEquals(s.getExtraBlocks(), extra)
 
+        # Underlong subheaders must fail
         self.failUnlessRaises(ParseError,
                               parseSubheader, "a"*(41))
+        # overlong subheaders must fail
+        self.failUnlessRaises(ParseError,
+                              parseSubheader, "a"*(99))
 
     def test_headers(self):
         header = ("abcdefghi"*(256))[:2048]
@@ -375,6 +411,8 @@
         self.failUnless(h[1:] == header[128:])
         self.failUnless(h[1:4] == header[128:128*4])
         self.failUnless(h[15] == header[-128:])
+        self.failUnless(h[15] == h[-1])
+        self.failUnless(h[14:] == h[-2:])
 
     def test_message(self):
         # 9 is relatively prime to all pwrs of 2.
@@ -435,17 +473,14 @@
         try:
             self.hashlogTestImpl(fname)
         finally:
-            try:
-                os.unlink(fname)
-            except:
-                pass
+            try_unlink_db(fname)
         
     def hashlogTestImpl(self,fname):
-        h = HashLog(fname, "Xyzzy")
+        h = [HashLog(fname, "Xyzzy")]
         
-        notseen = lambda hash: self.assert_(not h.seenHash(hash))
-        seen = lambda hash: self.assert_(h.seenHash(hash))
-        log = lambda hash: h.logHash(hash)
+        notseen = lambda hash,self=self,h=h: self.assert_(not h[0].seenHash(hash))
+        seen = lambda hash,self=self,h=h: self.assert_(h[0].seenHash(hash))
+        log = lambda hash,h=h: h[0].logHash(hash)
         
         notseen("a")
         notseen("a*20")
@@ -475,8 +510,8 @@
         log("abcdef"*4)
         seen("abcdef"*4)
         
-        h.close()
-        h = HashLog(fname, "Xyzzy")
+        h[0].close()
+        h[0] = HashLog(fname, "Xyzzy")
         seen("a")
         seen("b")
         seen("\277"*20)
@@ -490,12 +525,11 @@
         log("ddddd")
         seen("ddddd")
         
-        h.close()
-        h = HashLog(fname, "Xyzzy")
+        h[0].close()
+        h[0] = HashLog(fname, "Xyzzy")
         seen("ddddd")
 
-        h.close()
-
+        h[0].close()
 
 #----------------------------------------------------------------------
 import mixminion.BuildMessage
@@ -515,9 +549,10 @@
 
 BMTSupport = _BMTSupport()
 
+from mixminion.ServerInfo import ServerInfo
+
 class BuildMessageTests(unittest.TestCase):
     def setUp(self):
-        from mixminion.ServerInfo import ServerInfo
         self.pk1 = BMTSupport.pk1
         self.pk2 = BMTSupport.pk2
         self.pk3 = BMTSupport.pk3
@@ -544,7 +579,7 @@
         head = bhead([self.server1, self.server2],
                      ["9"*16, "1"*16], 99, "Hi mom", AESCounterPRNG()) 
 
-        ipv4 = mixminion.Formats.IPV4Info
+        ipv4 = mixminion.Packet.IPV4Info
         self.do_header_test(head,
                             (self.pk1, self.pk2),
                             ["9"*16, "1"*16],
@@ -560,26 +595,39 @@
                       99, "Hi mom", AESCounterPRNG())
         pks = (self.pk1,self.pk2,self.pk3)
         rtypes = (FWD_TYPE, FWD_TYPE, 99)
-        rinfo = (mixminion.Formats.IPV4Info("127.0.0.2", 3, "Z"*20).pack(),
-                 mixminion.Formats.IPV4Info("127.0.0.3", 5, "Q"*20).pack(),
+        rinfo = (mixminion.Packet.IPV4Info("127.0.0.2", 3, "Z"*20).pack(),
+                 mixminion.Packet.IPV4Info("127.0.0.3", 5, "Q"*20).pack(),
                  "Hi mom")
         self.do_header_test(head, pks, secrets, rtypes, rinfo)
 
     def do_header_test(self, head, pks, secrets, rtypes, rinfo):
+        """Unwraps and checks the layers of a single header.
+                    head: the header to check
+                    pks: sequence of public keys for hops in the path
+                    secrets: sequence of master secrets for the subheaders
+                    rtypes: sequenece of expected routing types
+                    rinfo: sequenece of expected routing info's.
+
+              If secrets is None, takes secrets from the headers without
+                checking.
+
+              Returns a list of the secrets encountered.
+              If rinfo is None, also returns a list of the routinginfo objects.
+            """
         retsecrets = []
         retinfo = []
         if secrets == None:
             secrets = [ None ] * len(pks)
-        self.assertEquals(len(head), mixminion.Formats.HEADER_LEN)
+        self.assertEquals(len(head), mixminion.Packet.HEADER_LEN)
         for pk, secret, rt, ri in zip(pks, secrets,rtypes,rinfo):
-            subh = mixminion.Formats.parseSubheader(pk_decrypt(head[:128], pk))
+            subh = mixminion.Packet.parseSubheader(pk_decrypt(head[:128], pk))
             if secret:
                 self.assertEquals(subh.secret, secret)
             else:
                 secret = subh.secret
                 retsecrets.append(secret)
-            self.assertEquals(subh.major, mixminion.Formats.MAJOR_NO)
-            self.assertEquals(subh.minor, mixminion.Formats.MINOR_NO)
+            self.assertEquals(subh.major, mixminion.Packet.MAJOR_NO)
+            self.assertEquals(subh.minor, mixminion.Packet.MINOR_NO)
             
             self.assertEquals(subh.digest, sha1(head[128:]))
             self.assertEquals(subh.routingtype, rt)
@@ -595,7 +643,7 @@
                 size = 128
                 n = 0
             else:
-                self.assert_(len(ri) > mixminion.Formats.MAX_ROUTING_INFO_LEN)
+                self.assert_(len(ri) > mixminion.Packet.MAX_ROUTING_INFO_LEN)
                 n = subh.getNExtraBlocks()
                 size = (1+n)*128
                 more = ctr_crypt(head[128:128+128*n], key)
@@ -615,7 +663,7 @@
 
     def test_extended_routinginfo(self):
         bhead = mixminion.BuildMessage._buildHeader
-        bhead_impl = mixminion.BuildMessage._buildHeader_impl
+
         secrets = ["9"*16 ]
         longStr = "Foo"*50
         head = bhead([self.server1 ], secrets, 99, longStr, AESCounterPRNG())
@@ -625,22 +673,34 @@
 
         self.do_header_test(head, pks, secrets, rtypes, rinfo)
 
-        secrets.append("1"*16)
-        longStr2 = longStr*2
+        # Now try a header with extended **intermediate** routing info.
+        # Since this never happens in the wild, we fake it.
+        longStr2 = longStr * 2
 
-        head = bhead_impl([self.server1,self.server2], secrets,
-                          [ (99,longStr2) , (99,longStr) ], AESCounterPRNG())
+        def getLongRoutingInfo(longStr2=longStr2):
+            return LocalInfo("fred",longStr2)
 
-        pks = (self.pk1,self.pk2)
-        rtypes = (99,99)
-        rinfo = (longStr2,longStr)
+        server4 = ServerInfo("127.0.0.1", 1, pk_get_modulus(self.pk1), "X"*20)
+        server4.getRoutingInfo = getLongRoutingInfo
+
+        secrets.append("1"*16)
+        head = bhead([self.server2, server4], secrets, 99, longStr,
+                     AESCounterPRNG())
+        pks = (self.pk2,self.pk1)
+        rtypes = (FWD_TYPE,99)
+        rinfo = ("fred\000"+longStr2,longStr)
         self.do_header_test(head, pks, secrets, rtypes, rinfo)
 
+        # Now we make sure that overlong routing info fails.
+        self.failUnlessRaises(MixError, bhead,
+                              [self.server2, server4], secrets, 99, "Z"*2048,
+                              AESCounterPRNG())
+
     def test_constructmessage(self):
         consMsg = mixminion.BuildMessage._constructMessage
         
-        h1 = "abcdefgh"*(2048//8)
-        h2 = "aBcDeFgH"*(2048//8)
+        h1 = "abcdefgh"*(2048/8)
+        h2 = "aBcDeFgH"*(2048/8)
 
         ######
         ### non-reply case
@@ -656,8 +716,8 @@
         
         message = consMsg(secrets1, secrets2, h1, h2, pld)
 
-        self.assertEquals(len(message), mixminion.Formats.MESSAGE_LEN)
-        msg = mixminion.Formats.parseMessage(message)
+        self.assertEquals(len(message), mixminion.Packet.MESSAGE_LEN)
+        msg = mixminion.Packet.parseMessage(message)
         head1, head2, payload = msg.header1, msg.header2, msg.payload
         self.assert_(h1 == head1)
 
@@ -680,8 +740,8 @@
         ######
         ### Reply case
         message = consMsg(secrets1, None, h1, h2, pld)
-        self.assertEquals(len(message), mixminion.Formats.MESSAGE_LEN)
-        msg = mixminion.Formats.parseMessage(message)
+        self.assertEquals(len(message), mixminion.Packet.MESSAGE_LEN)
+        msg = mixminion.Packet.parseMessage(message)
         head1, head2, payload = msg.header1, msg.header2, msg.payload
         self.assert_(h1 == head1)
 
@@ -698,9 +758,18 @@
         self.assert_(payload == pld)
 
     def do_message_test(self, msg,
-                        header_info_1, #XXXX doc
+                        header_info_1,
                         header_info_2,
                         payload):
+        """Decrypts the layers of a message one by one, checking them for
+           correctness.
+                      msg: the message to examine
+                      header_info_1: a tuple of (pks,secrets,rtypes,rinfo)
+                            as used by do_header_test for the first header.
+                      header_info_2: a tuple of (pks,secrets,rtypes,rinfo)
+                            as used by do_header_test for the second header.
+                      payload: The beginning of the expected decrypted payload.
+           """
         # Check header 1, and get secrets
         sec = self.do_header_test(msg[:2048], *header_info_1)
         h2 = msg[2048:4096]
@@ -717,7 +786,7 @@
             ks = Keyset(s)
             p = lioness_decrypt(p,ks.getLionessKeys(PAYLOAD_ENCRYPT_MODE))
 
-        # XXXX Need to do something about size encoding.
+        # ???? Need to do something about size encoding.
         self.assertEquals(payload, p[:len(payload)])
 
         
@@ -759,7 +828,7 @@
         brm = mixminion.BuildMessage.buildReplyMessage
 
         ## Stateful reply blocks.
-        (rb, node1), secrets = \
+        reply, secrets = \
              brb([ self.server3, self.server1, self.server2,
                    self.server1, self.server3 ],
                  SMTP_TYPE,
@@ -770,11 +839,11 @@
                  self.server1.getRoutingInfo().pack(),
                  self.server3.getRoutingInfo().pack())
 
-        self.assert_(node1 is self.server3)
+        self.assert_(reply.addr == self.server3)
                 
         m = brm("Information?",
                 [self.server3, self.server1],
-                (rb,node1))
+                reply)
 
         self.do_message_test(m,
                              ((self.pk3, self.pk1), None,
@@ -789,12 +858,12 @@
                              "Information?")
                                    
         ## Stateless replies
-        rb,node1 = bsrb([ self.server3, self.server1, self.server2,
-                          self.server1, self.server3 ],
-                        "fred", "Galaxy Far Away.", 1)
+        reply = bsrb([ self.server3, self.server1, self.server2,
+                       self.server1, self.server3 ],
+                     "fred", "Galaxy Far Away.", 0)
 
-        sec,(loc,) = self.do_header_test(rb, pks_1, None,
-                            (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,SMTP_TYPE),
+        sec,(loc,) = self.do_header_test(reply.header, pks_1, None,
+                            (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,LOCAL_TYPE),
                             infos+(None,))
         s = "fred\x00RTRN"
         self.assert_(loc.startswith(s))
@@ -803,10 +872,10 @@
         self.assert_(sec == [ prng.getBytes(16) for i in range(5)])
 
         ## Stateless reply, no user key (trusted server)
-        rb,node1 = bsrb([ self.server3, self.server1, self.server2,
-                          self.server1, self.server3 ],
-                        "fred" )
-        sec,(loc,) = self.do_header_test(rb, pks_1, None,
+        reply = bsrb([ self.server3, self.server1, self.server2,
+                       self.server1, self.server3 ],
+                     "fred", None)
+        sec,(loc,) = self.do_header_test(reply.header, pks_1, None,
                             (FWD_TYPE,FWD_TYPE,FWD_TYPE,FWD_TYPE,LOCAL_TYPE),
                                          infos+(None,))
         self.assert_(loc.startswith(s))
@@ -815,15 +884,14 @@
         self.assert_(sec == [ prng.getBytes(16) for i in range(5)])
             
 #----------------------------------------------------------------------
-# Having tested BuildMessage without using ServerProcess, we can now use
-# BuildMessage to see whether ServerProcess is doing the right thing.
+# Having tested BuildMessage without using PacketHandler, we can now use
+# BuildMessage to see whether PacketHandler is doing the right thing.
 #
 # (of course, we still need to build failing messages by hand)
 
-class ServerProcessTests(unittest.TestCase):
+class PacketHandlerTests(unittest.TestCase):
     def setUp(self):
-        from mixminion.ServerProcess import ServerProcess
-        from mixminion.ServerInfo import ServerInfo
+        from mixminion.PacketHandler import PacketHandler
         from tempfile import mktemp
         self.pk1 = BMTSupport.pk1
         self.pk2 = BMTSupport.pk2
@@ -836,20 +904,27 @@
         self.server1 = ServerInfo("127.0.0.1", 1, n_1, "X"*20)
         self.server2 = ServerInfo("127.0.0.2", 3, n_2, "Z"*20)
         self.server3 = ServerInfo("127.0.0.3", 5, n_3, "Q"*20)
-        self.sp1 = ServerProcess(self.pk1, h, None, None)
-        self.sp2 = ServerProcess(self.pk2, h, None, None)
-        self.sp3 = ServerProcess(self.pk3, h, None, None)
+        self.sp1 = PacketHandler(self.pk1, h)
+        self.sp2 = PacketHandler(self.pk2, h)
+        self.sp3 = PacketHandler(self.pk3, h)
+        self.sp2_3 = PacketHandler((self.pk2,self.pk3), h)
 
     def tearDown(self):
-        import os
-        try:
-            os.unlink(self.tmpfile)
-        except:
-            pass
+        self.hlog.close()
+        try_unlink_db(self.tmpfile)
 
-    def do_test_chain(self, m, sps, routingtypes, routinginfo, payload):
+    def do_test_chain(self, m, sps, routingtypes, routinginfo, payload,
+                      appkey=None):
+        """Routes a message through a series of servers, making sure that
+           each one decrypts it properly and routes it correctly to the
+           next.
+                    m: the message to test
+                    sps: sequence of PacketHandler objects for m's path
+                    routingtypes: sequence of expected routingtype
+                    routinginfo: sequence of expected routinginfo
+                    payload: beginning of expected final payload."""
         for sp, rt, ri in zip(sps,routingtypes,routinginfo):
-            res = sp._processMessage(m)
+            res = sp.processMessage(m)
             self.assertEquals(len(res), 2)
             if rt in (FWD_TYPE, SWAP_FWD_TYPE):
                 self.assertEquals(res[0], "QUEUE")
@@ -860,7 +935,8 @@
                 self.assertEquals(res[0], "EXIT")
                 self.assertEquals(res[1][0], rt)
                 self.assertEquals(res[1][1], ri)
-                # XXXX TEST application key
+                if appkey:
+                    self.assertEquals(appkey, res[1][2])
                 self.assert_(res[1][3].startswith(payload))
                 break
 
@@ -880,9 +956,8 @@
                            p)
 
         # A one-hop/one-hop message.
-        m = bfm(p, SMTP_TYPE, "nobody@invalid",
-                [self.server1], [self.server3])
-
+        m = bfm(p, SMTP_TYPE, "nobody@invalid", [self.server1], [self.server3])
+                
         self.do_test_chain(m,
                            [self.sp1,self.sp3],
                            [FWD_TYPE, SMTP_TYPE],
@@ -890,6 +965,12 @@
                             "nobody@invalid"],
                            p)
 
+        # Try servers with multiple keys
+        m = bfm(p, SMTP_TYPE, "nobody@invalid", [self.server2], [self.server3])
+        self.do_test_chain(m, [self.sp2_3, self.sp2_3], [FWD_TYPE, SMTP_TYPE],
+                           [self.server3.getRoutingInfo().pack(),
+                            "nobody@invalid"], p)
+
         # A 3/3 message with a long exit header.
         for i in (100,300):
             longemail = "f"*i+"@invalid"
@@ -910,63 +991,118 @@
                                 longemail],
                                p)
 
-
     def test_rejected(self):
         bfm = mixminion.BuildMessage.buildForwardMessage
         brm = mixminion.BuildMessage.buildReplyMessage
         brb = mixminion.BuildMessage.buildReplyBlock
-        from mixminion.ServerProcess import ContentError
+        from mixminion.PacketHandler import ContentError
 
         # A long intermediate header needs to fail.
-        from mixminion.ServerInfo import ServerInfo
         server1X = ServerInfo("127.0.0.1", 1, pk_get_modulus(self.pk1), "X"*20)
         class _packable:
             def pack(self): return "x"*200
-        server1X.getRoutingInfo = lambda : _packable()
+        server1X.getRoutingInfo = lambda _packable=_packable : _packable()
         
         m = bfm("Z", LOCAL_TYPE, "hello\000bye",
                 [self.server2, server1X, self.server3],
                 [server1X, self.server2, self.server3])
-        self.failUnlessRaises(ContentError, self.sp2._processMessage, m)
+        self.failUnlessRaises(ContentError, self.sp2.processMessage, m)
         
         # Duplicate messages need to fail.
         m = bfm("Z", SMTP_TYPE, "nobody@invalid",
                 [self.server1, self.server2], [self.server3])
-        self.sp1._processMessage(m)
-        self.failUnlessRaises(ContentError, self.sp1._processMessage, m)
+        self.sp1.processMessage(m)
+        self.failUnlessRaises(ContentError, self.sp1.processMessage, m)
 
         # Duplicate reply blocks need to fail
-        (r,n),s = brb([self.server3], SMTP_TYPE, "fred@invalid")
-        m = brm("Y", [self.server2], (r,n))
-        m2 = brm("Y", [self.server1], (r,n))
-        q, (a,m) = self.sp2._processMessage(m)
-        self.sp3._processMessage(m)
-        q, (a,m2) = self.sp1._processMessage(m2)
-        self.failUnlessRaises(ContentError, self.sp3._processMessage, m2)
+        reply,s = brb([self.server3], SMTP_TYPE, "fred@invalid")
+        m = brm("Y", [self.server2], reply)
+        m2 = brm("Y", [self.server1], reply)
+        q, (a,m) = self.sp2.processMessage(m)
+        self.sp3.processMessage(m)
+        q, (a,m2) = self.sp1.processMessage(m2)
+        self.failUnlessRaises(ContentError, self.sp3.processMessage, m2)
 
         # Even duplicate secrets need to go.
         prng = AESCounterPRNG(" "*16)
-        (r1,n),s = brb([self.server1], SMTP_TYPE, "fred@invalid",prng)
+        reply1,s = brb([self.server1], SMTP_TYPE, "fred@invalid",prng)
         prng = AESCounterPRNG(" "*16)
-        (r2,n),s = brb([self.server2], LOCAL_TYPE, "foo",prng)
-        m = brm("Y", [self.server3], (r1,n))
-        m2 = brm("Y", [self.server3], (r2,n))
-        q, (a,m) = self.sp3._processMessage(m)
-        self.sp1._processMessage(m)
-        q, (a,m2) = self.sp3._processMessage(m2)
-        self.failUnlessRaises(ContentError, self.sp2._processMessage, m2)
+        reply2,s = brb([self.server2], LOCAL_TYPE, "foo",prng)
+        m = brm("Y", [self.server3], reply1)
+        m2 = brm("Y", [self.server3], reply2)
+        q, (a,m) = self.sp3.processMessage(m)
+        self.sp1.processMessage(m)
+        q, (a,m2) = self.sp3.processMessage(m2)
+        self.failUnlessRaises(ContentError, self.sp2.processMessage, m2)
 
         # Drop gets dropped.
         m = bfm("Z", DROP_TYPE, "", [self.server2], [self.server2])
-        q, (a,m) = self.sp2._processMessage(m)
-        res = self.sp2._processMessage(m)
+        q, (a,m) = self.sp2.processMessage(m)
+        res = self.sp2.processMessage(m)
         self.assertEquals(res,None)
 
-        # XXXX Bogus types
-        # XXXX Non-parsing information
-        # XXXX Bad Major or Minor
-        # XXXX Bad digest
-        # XXXX Bad payload
+        # Wrong server.
+        m = bfm("Z", DROP_TYPE, "", [self.server1], [self.server2])
+        self.failUnlessRaises(SSLError, self.sp2.processMessage, m)
+        self.failUnlessRaises(SSLError, self.sp2_3.processMessage, m)
+
+        # Plain junk in header
+        m_x = ("XY"*64)+m[128:]
+        self.failUnlessRaises(SSLError, self.sp1.processMessage, m_x)
+
+        # Bad message length
+        m_x = m+"Z"
+        self.failUnlessRaises(ParseError, self.sp1.processMessage, m_x)
+
+        # Bad internal type
+        m_x = bfm("Z", 50, "", [self.server1], [self.server2])
+        q, (a,m_x) = self.sp1.processMessage(m_x)
+        self.failUnlessRaises(ContentError, self.sp2.processMessage, m_x)
+
+        # Subhead we can't parse
+        m_x = pk_encrypt("foo", self.pk1)+m[128:]
+        self.failUnlessRaises(ParseError, self.sp1.processMessage, m_x)
+
+        # Bad IPV4 info
+        subh_real = pk_decrypt(m[:128], self.pk1)
+        subh = parseSubheader(subh_real)
+        subh.setRoutingInfo(subh.routinginfo + "X")
+        m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
+        self.failUnlessRaises(ParseError, self.sp1.processMessage, m_x)
+
+        # Subhead that claims to be impossibly long: FWD case
+        subh = parseSubheader(subh_real)
+        subh.setRoutingInfo("X"*100)
+        m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
+        self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
+        
+        # Subhead that claims to be impossibly long: exit case
+        subh = parseSubheader(subh_real)
+        subh.routingtype = LOCAL_TYPE
+        subh.setRoutingInfo("X"*10000)
+        m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
+        self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
+
+        # Bad Major or Minor
+        subh = parseSubheader(subh_real)
+        subh.major = 255
+        m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
+        self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
+        
+        # Bad digest
+        subh = parseSubheader(subh_real)
+        subh.digest = " "*20
+        m_x = pk_encrypt(subh.pack(), self.pk1)+m[128:]
+        self.failUnlessRaises(ContentError, self.sp1.processMessage, m_x)
+
+        # Corrupt payload
+        m = bfm("Z", LOCAL_TYPE, "Z", [self.server1, self.server2],
+                [self.server3])
+        m_x = m[:-30] + " "*30
+        assert len(m_x) == len(m)
+        q, (a, m_x) = self.sp1.processMessage(m_x)
+        q, (a, m_x) = self.sp2.processMessage(m_x)
+        self.failUnlessRaises(SSLError, self.sp3.processMessage, m_x)
 
 #----------------------------------------------------------------------
 
@@ -979,7 +1115,7 @@
     suite.addTest(tc(FormatTests))
     suite.addTest(tc(HashLogTests))
     suite.addTest(tc(BuildMessageTests))
-    suite.addTest(tc(ServerProcessTests))
+    suite.addTest(tc(PacketHandlerTests))
     return suite
 
 def testAll():

--- Formats.py DELETED ---

--- ServerProcess.py DELETED ---