[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Flesh out inline documentation
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv14411/lib/mixminion
Modified Files:
BuildMessage.py Common.py Config.py Crypto.py Filestore.py
Fragments.py Packet.py ServerInfo.py test.py
Log Message:
Flesh out inline documentation
Index: BuildMessage.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/BuildMessage.py,v
retrieving revision 1.55
retrieving revision 1.56
diff -u -d -r1.55 -r1.56
--- BuildMessage.py 21 Aug 2003 21:34:02 -0000 1.55
+++ BuildMessage.py 25 Aug 2003 21:05:33 -0000 1.56
@@ -34,7 +34,9 @@
overhead: number of bytes to omit from each payload,
given the type ofthe message encoding.
(0 or ENC_FWD_OVERHEAD)
- uncompressedFragmentPrefix: DOCDOC
+ uncompressedFragmentPrefix: If we fragment the message,
+ we add this string to the message after compression but
+ before whitening and fragmentation.
paddingPRNG: generator for padding.
Note: If multiple strings are returned, be sure to shuffle them
@@ -61,14 +63,17 @@
p.computeHash()
return [ p.pack() ]
+ # Okay, we need fo fragment the message. First, add the prefix if needed.
if uncompressedFragmentPrefix:
payload = uncompressedFragmentPrefix+payload
-
- # DOCDOC
+ # Now generate a message ID
messageid = Crypto.getCommonPRNG().getBytes(20)
+ # Figure out how many chunks to divide it into...
p = mixminion.Fragments.FragmentationParams(len(payload), overhead)
+ # ... fragment the payload into chunks...
rawFragments = p.getFragments(payload)
fragments = []
+ # ... and annotate each chunk with appropriate payload header info.
for i in xrange(len(rawFragments)):
pyld = FragmentPayload(i, None, messageid, p.length, rawFragments[i])
pyld.computeHash()
Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.105
retrieving revision 1.106
diff -u -d -r1.105 -r1.106
--- Common.py 21 Aug 2003 21:34:02 -0000 1.105
+++ Common.py 25 Aug 2003 21:05:33 -0000 1.106
@@ -486,14 +486,22 @@
# File helpers
-#DOCDOC
+# On windows, rename(f1,f2) fails if f2 already exists. These wrappers
+# handle replacing files.
if sys.platform == 'win32':
def replaceFile(f1, f2):
+ """Move the file named 'f1' to a new name 'f2'. Replace any file
+ already named 'f2'."""
+ # WWWW This isn't atomic. Later versions of the windows API add
+ # WWWW functions named MoveFileEx and ReplaceFile that may do the
+ # WWWW right thing, but those don't exist in Windows Me/98/95.
if os.path.exists(f2):
os.unlink(f2)
os.rename(f1, f2)
else:
def replaceFile(f1, f2):
+ """Move the file named 'f1' to a new name 'f2'. Replace any file
+ already named 'f2'."""
os.rename(f1, f2)
class AtomicFile:
Index: Config.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Config.py,v
retrieving revision 1.54
retrieving revision 1.55
diff -u -d -r1.54 -r1.55
--- Config.py 21 Aug 2003 21:34:02 -0000 1.54
+++ Config.py 25 Aug 2003 21:05:33 -0000 1.55
@@ -552,6 +552,8 @@
# we insist on a tight data format?
# _restrictKeys is 1/0: do we raise a ConfigError when we see an
# unrecognized key, or do we simply generate a warning?
+ # _restrictSections is 1/0: do we raise a ConfigError when we see an
+ # unrecognized section, or do we simply generate a warning?
## Validation rules:
# A key without a corresponding entry in _syntax gives an error.
@@ -568,7 +570,6 @@
_syntax = None
_restrictFormat = 0
_restrictKeys = 1
- #DOCDOC
_restrictSections = 1
def __init__(self, filename=None, string=None, assumeValid=0):
@@ -739,7 +740,8 @@
return self._sections[sec]
def get(self, sec, val="---"):
- """DOCDOC"""
+ """Return a section named sec, if any such section exists. Otherwise
+ return an empty dict, or 'val' if provided."""
if val == "---": val = {}
return self._sections.get(sec, val)
Index: Crypto.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Crypto.py,v
retrieving revision 1.53
retrieving revision 1.54
diff -u -d -r1.53 -r1.54
--- Crypto.py 21 Aug 2003 21:34:02 -0000 1.53
+++ Crypto.py 25 Aug 2003 21:05:34 -0000 1.54
@@ -611,7 +611,8 @@
Random portions are generated by choosing 8 random characters
from the set 'A-Za-z0-9+-'.
- DOCDOC conflictPrefix
+ If 'conflictPrefix' is set, do not return any file named
+ prefix+H if a file named conflictPrefix+H already exists.
"""
flags = os.O_WRONLY|os.O_CREAT|os.O_EXCL
mode = "w"
@@ -783,9 +784,8 @@
return b
if hasattr(_ml, "win32_get_random_bytes"):
- print "WAHOO!"
class _WinTrueRNG(RNG):
- """DOCDOC"""
+ """A random number generator using the windows crypto API."""
def __init__(self):
RNG.__init__(self, 1024)
self.getBytes(1)
@@ -793,15 +793,15 @@
return _ml.win32_get_random_bytes(n)
class _OpensslRNG(RNG):
- """DOCDOC"""
+ """Random number generator that falls back to openssl's implementation."""
def __init__(self):
- """DOCDOC"""
RNG.__init__(self, 1024)
def _prng(self,n):
return _ml.openssl_rand(n)
class _XorRNG(RNG):
- """DOCDOC"""
+ """A random number generator that takes random data from two sources
+ and XORs the two streams together."""
def __init__(self, base1, base2):
RNG.__init__(self, 1024)
self.base1 = base1
Index: Filestore.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Filestore.py,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- Filestore.py 21 Aug 2003 21:34:02 -0000 1.7
+++ Filestore.py 25 Aug 2003 21:05:34 -0000 1.8
@@ -157,7 +157,8 @@
return hs
def messageExists(self, handle):
- """DOCDOC"""
+ """Return true iff this filestore contains a message with the handle
+ 'handle'."""
return os.path.exists(os.path.join(self.dir, "msg_"+handle))
def removeMessage(self, handle):
Index: Fragments.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Fragments.py,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- Fragments.py 21 Aug 2003 21:34:02 -0000 1.5
+++ Fragments.py 25 Aug 2003 21:05:34 -0000 1.6
@@ -401,7 +401,7 @@
# inserted -- the midnight (GMT) of the day on the first packet
# associated with this message was inserted.
# nym -- the name of the identity receiving this message. Used to
- # prevent linkage attacks. XXXX005 specify this need!
+ # prevent linkage attacks.
#
# params -- an instance of FragmentationParams for this message.
# chunks -- a map from chunk number to tuples of (handle within the pool,
@@ -412,7 +412,8 @@
# are ready for reconstruction, but haven't been reconstructed
# yet.
def __init__(self, messageid, length, overhead, inserted, nym):
- """Create a new MessageState."""
+ """Create a new MessageState.
+ """
self.messageid = messageid
self.overhead = overhead
self.inserted = inserted
Index: Packet.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Packet.py,v
retrieving revision 1.56
retrieving revision 1.57
diff -u -d -r1.56 -r1.57
--- Packet.py 21 Aug 2003 21:34:02 -0000 1.56
+++ Packet.py 25 Aug 2003 21:05:34 -0000 1.57
@@ -86,7 +86,7 @@
FRAGMENT_TYPE = 0x0103 # Find the actual deliver info in the message payload
MAX_EXIT_TYPE = 0xFFFF
-#DOCDOC
+# Set of exit types that don't get tag fields.
_TYPES_WITHOUT_TAGS = { FRAGMENT_TYPE : 1 }
class ParseError(MixError):
@@ -312,7 +312,8 @@
self.data = data
def computeHash(self):
- """DOCDOC"""
+ """Update the hash field of this payload to correspond to the hash
+ of the data."""
self.hash = sha1(self.data)
def isSingleton(self):
@@ -359,7 +360,8 @@
self.data = data
def computeHash(self):
- """DOCDOC"""
+ """Update the hash field of this payload to correspond to the hash
+ of the data."""
self.hash = "X"*DIGEST_LEN
p = self.pack()
self.hash = sha1(p[23:])
@@ -386,7 +388,12 @@
return PAYLOAD_LEN - FRAGMENT_PAYLOAD_OVERHEAD - len(self.data)
#----------------------------------------------------------------------
-#DOCDOC
+# Encoding of messages fragmented for reassembly by the exit server.
+#
+# Such messages are encoded by adding routing-type, routing-len, and
+# routing-info fields at the start of the payload before fragmentation,
+# so that the server doesn't recover the delivery address before it's time
+# to deliver the message.
SSF_UNPACK_PATTERN = "!HH"
SSF_PREFIX_LEN = 4
@@ -403,7 +410,6 @@
return ServerSideFragmentedMessage(rt, ri, comp)
class ServerSideFragmentedMessage:
- """DOCDOC"""
def __init__(self, routingtype, routinginfo, compressedContents):
self.routingtype = routingtype
self.routinginfo = routinginfo
@@ -694,9 +700,8 @@
over a text-based medium."""
def __init__(self, contents, messageType, tag=None):
"""Create a new TextEncodedMessage given a set of contents, a
- messageType ('TXT', 'ENC', 'LONG', or 'BIN'), and optionally
+ messageType ('TXT', 'ENC', 'LONG', 'FRAG' or 'BIN'), and optionally
a tag.
- DOCDOC FRAG
"""
assert messageType in ('TXT', 'ENC', 'LONG', 'BIN', 'FRAG')
assert tag is None or (messageType == 'ENC' and len(tag) == 20)
@@ -716,7 +721,7 @@
"""Return true iff this is an overcompressed plaintext packet."""
return self.messageType == 'LONG'
def isFragment(self):
- """DOCDOC"""
+ """Return true iff this is a fragment packet."""
return self.messageType == 'FRAG'
def getContents(self):
"""Return the (unencoded) contents of this packet."""
@@ -744,12 +749,14 @@
#----------------------------------------------------------------------
# Header encoding
-#DOCDOC
+# Longest allowed length of a single header.
MAX_HEADER_LEN = 900
def encodeMailHeaders(subject=None, fromAddr=None, inReplyTo=None,
references=None):
- """DOCDOC. Raise MixError on failure."""
+ """Given (optionally) any of the headers permissible for email
+ messages, return a string to be prepended to a message before
+ encoding. Raise MixError on failure."""
headers = {}
if subject:
headers['SUBJECT'] = subject
@@ -766,9 +773,10 @@
return encodeMessageHeaders(headers)
def encodeMessageHeaders(headers):
- """DOCDOC dict, max size
-
- Requires that headers are in acceptable format.
+ """Given a dictionary of (header,header-value) entries, encode the
+ entries and return a string to be prepended to a message before
+ encoding. Requires that headers are in acceptable format.
+ Raises MixError on failure.
"""
items = []
hitems = headers.items()
@@ -786,7 +794,9 @@
HEADER_RE = re.compile(r'^([!-9;-~]+):([ -~]*)\n')
def parseMessageAndHeaders(message):
- """DOCDOC -> message, {header : value}"""
+ """Given a message with encoded headers, return a 2-tuple containing
+ the message, and a dictionary mapping header names to header values.
+ Skips improperly formatted headers."""
headers = {}
msg = message
while 1:
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.53
retrieving revision 1.54
diff -u -d -r1.53 -r1.54
--- ServerInfo.py 21 Aug 2003 21:34:02 -0000 1.53
+++ ServerInfo.py 25 Aug 2003 21:05:34 -0000 1.54
@@ -105,6 +105,14 @@
"Configuration": ("ALLOW", None, None),
},
}
+ expected_versions = {
+ "Server" : ( "Descriptor-Version", "0.2"),
+ "Incoming/MMTP" : ("Version", "0.1"),
+ "Outgoing/MMTP" : ("Version", "0.1"),
+ "Delivery/Fragmented" : ("Version", "0.1"),
+ "Delivery/MBOX" : ("Version", "0.1"),
+ "Delivery/SMTP" : ("Version", "0.1"),
+ }
def __init__(self, fname=None, string=None, assumeValid=0,
validatedDigests=None):
@@ -129,10 +137,25 @@
if k == 'Descriptor-Version' and v.strip() != '0.2':
raise ConfigError("Unrecognized descriptor version: %s"
% v.strip())
- # FFFF005 Remove sections with unrecognized versions.
-
- return contents
+
+
+ # Remove any sections with unrecognized versions.
+ revisedContents = []
+ for name, ents in contents:
+ v = self.expected_versions.get(name)
+ if not v:
+ revisedContents.append((name, ents))
+ continue
+ versionkey, versionval = v
+ for k,v,_ in ents:
+ if k == versionkey and v.strip() != versionval:
+ LOG.warn("Skipping %s section with unrecognized version %s"
+ , name, v.strip())
+ break
+ else:
+ revisedContents.append((name, ents))
+ return revisedContents
def validate(self, lines, contents):
####
Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.147
retrieving revision 1.148
diff -u -d -r1.147 -r1.148
--- test.py 21 Aug 2003 21:34:03 -0000 1.147
+++ test.py 25 Aug 2003 21:05:34 -0000 1.148
@@ -117,7 +117,7 @@
return abs(f1-f2) < .00001
def fileURL(fname):
- """DOCDOC"""
+ """Return a file url suitable for a file named 'fname'"""
fname = os.path.abspath(fname)
return "file:%s"%fname
@@ -165,29 +165,38 @@
# Add some helpful functions to unittest.TestCase
class TestCase(unittest.TestCase):
- """DOCDOC"""
+ """Extended version of unittest.TestCase with a few extra
+ 'assertFoo' functions."""
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
def assertFloatEq(self, f1, f2):
+ """Fail unless f1 and f2 are very close to one another."""
if not floatEq(f1, f2):
self.fail("%s != %s" % (f1, f2))
def assertLongStringEq(self, s1, s2):
+ """Fail unless the string s1 equals the string s2. If they aren't
+ equal, give a failure message that highlights the first point at
+ which they differ."""
if s1 != s2:
d = findFirstDiff(s1, s2)
self.fail("Strings unequal. First difference at %s: %r vs %r"
% (d, s1[d:+10], s2[d:d+10]))
def assertUnorderedEq(self, l1, l2):
+ """Fail unless the lists l1 and l2 have the same elements. The
+ order of elements in the two lists need not be the same."""
l1 = list(l1)[:]
l2 = list(l2)[:]
l1.sort()
l2.sort()
self.assertEquals(l1, l2)
def assertStartsWith(self, s1, s2):
+ """Fail unless string s1 begins with string s2."""
if not s1.startswith(s2):
if len(s1) > min(40, len(s2)+5):
s1 = s1[:len(s2)+5]+"..."
self.fail("%r does not start with %r"%(s1,s2))
def assertEndsWith(self, s1, s2):
+ """Fail unless string s1 ends with string s2."""
if not s1.endswith(s2):
if len(s1) > min(40, len(s2)+5):
s1 = "..."+s1[-(len(s2)+5):]
@@ -6446,28 +6455,24 @@
self.assertEquals([], pool.listReadyMessages())
pool.unchunkMessages()
- #DOCDOC comment this rats' nets
-
####
# Reconstruct: simple case.
####
- pool.addFragment(pkts1[2])
- self.assertEquals(1, pool.store.count())
- self.assertEquals([], pool.listReadyMessages())
- pool.addFragment(pkts1[0])
- self.assertEquals(2, pool.store.count())
- pool.addFragment(pkts1[1]) # should be 'unnneedeed'
- self.assertEquals(2, pool.store.count())
- self.assertEquals([], pool.listReadyMessages())
- pool.unchunkMessages()
- self.assertEquals([pkts1[0].msgID], pool.listReadyMessages())
- mid = pool.listReadyMessages()[0]
- #print len(M1), len(pool.getReadyMessage(mid))
- self.assertLongStringEq(M1, uncompressData(pool.getReadyMessage(mid)))
+ pool.addFragment(pkts1[2]) # Add a single packet.
+ self.assertEquals(1, pool.store.count()) # Is it there?
+ self.assertEquals([], pool.listReadyMessages()) # Message isn't ready.
+ pool.addFragment(pkts1[0]) # Add another packet.
+ self.assertEquals(2, pool.store.count()) # Two packets waiting.
+ pool.addFragment(pkts1[1]) # duplicate; should be 'unnneeded'
+ self.assertEquals(2, pool.store.count()) # Still two waiting.
+ self.assertEquals([], pool.listReadyMessages()) # Still not ready.
+ pool.unchunkMessages() # Now try to unchunk the message.
+ self.assertEquals([pkts1[0].msgID], pool.listReadyMessages()) # Ready!
+ mid = pool.listReadyMessages()[0] # Get the message; is it right?
self.assertLongStringEq(M1, uncompressData(pool.getReadyMessage(mid)))
- pool.markMessageCompleted(mid)
- self.assertEquals([], pool.listReadyMessages())
- pool.addFragment(pkts1[1])
+ pool.markMessageCompleted(mid) # Mark it as done.
+ self.assertEquals([], pool.listReadyMessages()) # Not there any more.
+ pool.addFragment(pkts1[1]) # Adding more fragments to it doesn't work.
self.assertEquals([], pool.store.getAllMessages())
####