Ticket #393: 393status7.dpatch

File 393status7.dpatch, 367.3 KB (added by kevan, at 2010-06-23T23:57:34Z)
Line 
1Sun May 30 18:43:46 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
2  * Code cleanup
3 
4      - Change 'readv' to 'readvs' in remote_slot_readv in the storage
5        server, to more adaquately convey what the argument is.
6
7Fri Jun  4 12:48:04 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
8  * Add a notion of the mutable file version number to interfaces.py
9
10Fri Jun  4 12:52:17 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
11  * Add a salt hasher for MDMF uploads
12
13Fri Jun  4 12:55:27 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
14  * Add MDMF and SDMF version numbers to interfaces.py
15
16Fri Jun 11 12:17:29 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
17  * Alter the mutable file servermap to read MDMF files
18
19Fri Jun 11 12:21:50 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
20  * Add tests for new MDMF proxies
21
22Mon Jun 14 14:34:59 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
23  * Alter MDMF proxy tests to reflect the new form of caching
24
25Mon Jun 14 14:37:21 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
26  * Add tests and support functions for servermap tests
27
28Tue Jun 22 17:13:32 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
29  * Make a segmented downloader
30 
31  Rework the current mutable file Retrieve class to download segmented
32  files. The rewrite preserves the semantics and basic conceptual state
33  machine of the old Retrieve class, but adapts them to work with
34  files with more than one segment, which involves a fairly substantial
35  rewrite.
36 
37  I've also adapted some existing SDMF tests to work with the new
38  downloader, as necessary.
39 
40  TODO:
41      - Write tests for MDMF functionality.
42      - Finish writing and testing salt functionality
43
44Tue Jun 22 17:17:08 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
45  * Tell NodeMaker and MutableFileNode about the distinction between SDMF and MDMF
46
47Tue Jun 22 17:17:32 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
48  * Assorted servermap fixes
49 
50  - Check for failure when setting the private key
51  - Check for failure when setting other things
52  - Check for doneness in a way that is resilient to hung servers
53  - Remove dead code
54  - Reorganize error and success handling methods, and make sure they get
55    used.
56 
57
58Wed Jun 23 16:32:03 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
59  * Add objects for MDMF shares in support of a new segmented uploader
60 
61  This patch adds the following:
62      - MDMFSlotWriteProxy, which can write MDMF shares to the storage
63        server in the new format.
64      - MDMFSlotReadProxy, which can read both SDMF and MDMF shares from
65        the storage server.
66 
67  This patch also includes tests for these new object.
68
69Wed Jun 23 16:32:48 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
70  * A first stab at a segmented uploader
71 
72  This uploader will upload, segment-by-segment, MDMF files. It will only
73  do this if it thinks that the filenode that it is uploading represents
74  an MDMF file; otherwise, it uploads the file as SDMF.
75 
76  My TODO list so far:
77      - More robust peer selection; we'll want to use something like
78        servers of happiness to figure out reliability and unreliability.
79      - Clean up.
80
81Wed Jun 23 16:35:03 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
82  * Make the mutable downloader batch its reads
83
84New patches:
85
86[Code cleanup
87Kevan Carstensen <kevan@isnotajoke.com>**20100531014346
88 Ignore-this: 697378037e83290267f108a4a88b8776
89 
90     - Change 'readv' to 'readvs' in remote_slot_readv in the storage
91       server, to more adaquately convey what the argument is.
92] {
93hunk ./src/allmydata/storage/server.py 569
94                                          self)
95         return share
96 
97-    def remote_slot_readv(self, storage_index, shares, readv):
98+    def remote_slot_readv(self, storage_index, shares, readvs):
99         start = time.time()
100         self.count("readv")
101         si_s = si_b2a(storage_index)
102hunk ./src/allmydata/storage/server.py 590
103             if sharenum in shares or not shares:
104                 filename = os.path.join(bucketdir, sharenum_s)
105                 msf = MutableShareFile(filename, self)
106-                datavs[sharenum] = msf.readv(readv)
107+                datavs[sharenum] = msf.readv(readvs)
108         log.msg("returning shares %s" % (datavs.keys(),),
109                 facility="tahoe.storage", level=log.NOISY, parent=lp)
110         self.add_latency("readv", time.time() - start)
111}
112[Add a notion of the mutable file version number to interfaces.py
113Kevan Carstensen <kevan@isnotajoke.com>**20100604194804
114 Ignore-this: fd767043437c3cd694807687e6dc677
115] hunk ./src/allmydata/interfaces.py 807
116         writer-visible data using this writekey.
117         """
118 
119+    def set_version(version):
120+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
121+        we upload in SDMF for reasons of compatibility. If you want to
122+        change this, set_version will let you do that.
123+
124+        To say that this file should be uploaded in SDMF, pass in a 0. To
125+        say that the file should be uploaded as MDMF, pass in a 1.
126+        """
127+
128+    def get_version():
129+        """Returns the mutable file protocol version."""
130+
131 class NotEnoughSharesError(Exception):
132     """Download was unable to get enough shares"""
133 
134[Add a salt hasher for MDMF uploads
135Kevan Carstensen <kevan@isnotajoke.com>**20100604195217
136 Ignore-this: 3072f4c4e75efa078f31aac3a56d36b2
137] {
138hunk ./src/allmydata/util/hashutil.py 90
139 MUTABLE_READKEY_TAG = "allmydata_mutable_writekey_to_readkey_v1"
140 MUTABLE_DATAKEY_TAG = "allmydata_mutable_readkey_to_datakey_v1"
141 MUTABLE_STORAGEINDEX_TAG = "allmydata_mutable_readkey_to_storage_index_v1"
142+MUTABLE_SALT_TAG = "allmydata_mutable_segment_salt_v1"
143 
144 # dirnodes
145 DIRNODE_CHILD_WRITECAP_TAG = "allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1"
146hunk ./src/allmydata/util/hashutil.py 134
147 def plaintext_segment_hasher():
148     return tagged_hasher(PLAINTEXT_SEGMENT_TAG)
149 
150+def mutable_salt_hash(data):
151+    return tagged_hash(MUTABLE_SALT_TAG, data)
152+def mutable_salt_hasher():
153+    return tagged_hasher(MUTABLE_SALT_TAG)
154+
155 KEYLEN = 16
156 IVLEN = 16
157 
158}
159[Add MDMF and SDMF version numbers to interfaces.py
160Kevan Carstensen <kevan@isnotajoke.com>**20100604195527
161 Ignore-this: 5736d229076ea432b9cf40fcee9b4749
162] hunk ./src/allmydata/interfaces.py 8
163 
164 HASH_SIZE=32
165 
166+SDMF_VERSION=0
167+MDMF_VERSION=1
168+
169 Hash = StringConstraint(maxLength=HASH_SIZE,
170                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
171 Nodeid = StringConstraint(maxLength=20,
172[Alter the mutable file servermap to read MDMF files
173Kevan Carstensen <kevan@isnotajoke.com>**20100611191729
174 Ignore-this: f05748597749f07b16cdbb711fae92e5
175] {
176hunk ./src/allmydata/mutable/servermap.py 7
177 from itertools import count
178 from twisted.internet import defer
179 from twisted.python import failure
180-from foolscap.api import DeadReferenceError, RemoteException, eventually
181+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
182+                         fireEventually
183 from allmydata.util import base32, hashutil, idlib, log
184 from allmydata.storage.server import si_b2a
185 from allmydata.interfaces import IServermapUpdaterStatus
186hunk ./src/allmydata/mutable/servermap.py 17
187 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
188      DictOfSets, CorruptShareError, NeedMoreDataError
189 from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
190-     SIGNED_PREFIX_LENGTH
191+     SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
192 
193 class UpdateStatus:
194     implements(IServermapUpdaterStatus)
195hunk ./src/allmydata/mutable/servermap.py 254
196         """Return a set of versionids, one for each version that is currently
197         recoverable."""
198         versionmap = self.make_versionmap()
199-
200         recoverable_versions = set()
201         for (verinfo, shares) in versionmap.items():
202             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
203hunk ./src/allmydata/mutable/servermap.py 366
204         self._servers_responded = set()
205 
206         # how much data should we read?
207+        # SDMF:
208         #  * if we only need the checkstring, then [0:75]
209         #  * if we need to validate the checkstring sig, then [543ish:799ish]
210         #  * if we need the verification key, then [107:436ish]
211hunk ./src/allmydata/mutable/servermap.py 374
212         #  * if we need the encrypted private key, we want [-1216ish:]
213         #   * but we can't read from negative offsets
214         #   * the offset table tells us the 'ish', also the positive offset
215-        # A future version of the SMDF slot format should consider using
216-        # fixed-size slots so we can retrieve less data. For now, we'll just
217-        # read 2000 bytes, which also happens to read enough actual data to
218-        # pre-fetch a 9-entry dirnode.
219+        # MDMF:
220+        #  * Checkstring? [0:72]
221+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
222+        #    the offset table will tell us for sure.
223+        #  * If we need the verification key, we have to consult the offset
224+        #    table as well.
225+        # At this point, we don't know which we are. Our filenode can
226+        # tell us, but it might be lying -- in some cases, we're
227+        # responsible for telling it which kind of file it is.
228         self._read_size = 4000
229         if mode == MODE_CHECK:
230             # we use unpack_prefix_and_signature, so we need 1k
231hunk ./src/allmydata/mutable/servermap.py 432
232         self._queries_completed = 0
233 
234         sb = self._storage_broker
235+        # All of the peers, permuted by the storage index, as usual.
236         full_peerlist = sb.get_servers_for_index(self._storage_index)
237         self.full_peerlist = full_peerlist # for use later, immutable
238         self.extra_peers = full_peerlist[:] # peers are removed as we use them
239hunk ./src/allmydata/mutable/servermap.py 439
240         self._good_peers = set() # peers who had some shares
241         self._empty_peers = set() # peers who don't have any shares
242         self._bad_peers = set() # peers to whom our queries failed
243+        self._readers = {} # peerid -> dict(sharewriters), filled in
244+                           # after responses come in.
245 
246         k = self._node.get_required_shares()
247hunk ./src/allmydata/mutable/servermap.py 443
248+        # For what cases can these conditions work?
249         if k is None:
250             # make a guess
251             k = 3
252hunk ./src/allmydata/mutable/servermap.py 456
253         self.num_peers_to_query = k + self.EPSILON
254 
255         if self.mode == MODE_CHECK:
256+            # We want to query all of the peers.
257             initial_peers_to_query = dict(full_peerlist)
258             must_query = set(initial_peers_to_query.keys())
259             self.extra_peers = []
260hunk ./src/allmydata/mutable/servermap.py 464
261             # we're planning to replace all the shares, so we want a good
262             # chance of finding them all. We will keep searching until we've
263             # seen epsilon that don't have a share.
264+            # We don't query all of the peers because that could take a while.
265             self.num_peers_to_query = N + self.EPSILON
266             initial_peers_to_query, must_query = self._build_initial_querylist()
267             self.required_num_empty_peers = self.EPSILON
268hunk ./src/allmydata/mutable/servermap.py 474
269             # might also avoid the round trip required to read the encrypted
270             # private key.
271 
272-        else:
273+        else: # MODE_READ, MODE_ANYTHING
274+            # 2k peers is good enough.
275             initial_peers_to_query, must_query = self._build_initial_querylist()
276 
277         # this is a set of peers that we are required to get responses from:
278hunk ./src/allmydata/mutable/servermap.py 485
279         # set as we get responses.
280         self._must_query = must_query
281 
282+        # This tells the done check whether requests are still being
283+        # processed. We should wait before returning until at least
284+        # updated correctly (and dealing with connection errors.
285+        self._processing = 0
286+
287         # now initial_peers_to_query contains the peers that we should ask,
288         # self.must_query contains the peers that we must have heard from
289         # before we can consider ourselves finished, and self.extra_peers
290hunk ./src/allmydata/mutable/servermap.py 495
291         # contains the overflow (peers that we should tap if we don't get
292         # enough responses)
293+        # I guess that self._must_query is a subset of
294+        # initial_peers_to_query?
295+        assert set(must_query).issubset(set(initial_peers_to_query))
296 
297         self._send_initial_requests(initial_peers_to_query)
298         self._status.timings["initial_queries"] = time.time() - self._started
299hunk ./src/allmydata/mutable/servermap.py 554
300         # errors that aren't handled by _query_failed (and errors caused by
301         # _query_failed) get logged, but we still want to check for doneness.
302         d.addErrback(log.err)
303-        d.addBoth(self._check_for_done)
304         d.addErrback(self._fatal_error)
305         return d
306 
307hunk ./src/allmydata/mutable/servermap.py 584
308         self._servermap.reachable_peers.add(peerid)
309         self._must_query.discard(peerid)
310         self._queries_completed += 1
311+        # self._processing counts the number of queries that have
312+        # completed, but are still processing. We wait until all queries
313+        # are done processing before returning a result to the client.
314+        # TODO: Should we do this? A response to the initial query means
315+        # that we may not have to query the server for anything else,
316+        # but if we're dealing with an MDMF share, we'll probably have
317+        # to ask it for its signature, unless we cache those sometplace,
318+        # and even then.
319+        self._processing += 1
320         if not self._running:
321             self.log("but we're not running, so we'll ignore it", parent=lp,
322                      level=log.NOISY)
323hunk ./src/allmydata/mutable/servermap.py 605
324         else:
325             self._empty_peers.add(peerid)
326 
327-        last_verinfo = None
328-        last_shnum = None
329+        ss, storage_index = stuff
330+        ds = []
331+
332+
333+        def _tattle(ignored, status):
334+            print status
335+            print ignored
336+            return ignored
337+
338+        def _cache(verinfo, shnum, now, data):
339+            self._queries_oustand
340+            self._node._add_to_cache(verinfo, shnum, 0, data, now)
341+            return shnum, verinfo
342+
343+        def _corrupt(e, shnum, data):
344+            # This gets raised when there was something wrong with
345+            # the remote server. Specifically, when there was an
346+            # error unpacking the remote data from the server, or
347+            # when the signature is invalid.
348+            print e
349+            f = failure.Failure()
350+            self.log(format="bad share: %(f_value)s", f_value=str(f.value),
351+                     failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
352+            # Notify the server that its share is corrupt.
353+            self.notify_server_corruption(peerid, shnum, str(e))
354+            # By flagging this as a bad peer, we won't count any of
355+            # the other shares on that peer as valid, though if we
356+            # happen to find a valid version string amongst those
357+            # shares, we'll keep track of it so that we don't need
358+            # to validate the signature on those again.
359+            self._bad_peers.add(peerid)
360+            self._last_failure = f
361+            # 393CHANGE: Use the reader for this.
362+            checkstring = data[:SIGNED_PREFIX_LENGTH]
363+            self._servermap.mark_bad_share(peerid, shnum, checkstring)
364+            self._servermap.problems.append(f)
365+
366         for shnum,datav in datavs.items():
367             data = datav[0]
368hunk ./src/allmydata/mutable/servermap.py 644
369-            try:
370-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
371-                last_verinfo = verinfo
372-                last_shnum = shnum
373-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
374-            except CorruptShareError, e:
375-                # log it and give the other shares a chance to be processed
376-                f = failure.Failure()
377-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
378-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
379-                self.notify_server_corruption(peerid, shnum, str(e))
380-                self._bad_peers.add(peerid)
381-                self._last_failure = f
382-                checkstring = data[:SIGNED_PREFIX_LENGTH]
383-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
384-                self._servermap.problems.append(f)
385-                pass
386-
387-        self._status.timings["cumulative_verify"] += (time.time() - now)
388+            reader = MDMFSlotReadProxy(ss,
389+                                       storage_index,
390+                                       shnum,
391+                                       data)
392+            self._readers.setdefault(peerid, dict())[shnum] = reader
393+            # our goal, with each response, is to validate the version
394+            # information and share data as best we can at this point --
395+            # we do this by validating the signature. To do this, we
396+            # need to do the following:
397+            #   - If we don't already have the public key, fetch the
398+            #     public key. We use this to validate the signature.
399+            friendly_peer = idlib.shortnodeid_b2a(peerid)
400+            if not self._node.get_pubkey():
401+                # fetch and set the public key.
402+                d = reader.get_verification_key()
403+                d.addCallback(self._try_to_set_pubkey)
404+            else:
405+                # we already have the public key.
406+                d = defer.succeed(None)
407+            # Neither of these two branches return anything of
408+            # consequence, so the first entry in our deferredlist will
409+            # be None.
410 
411hunk ./src/allmydata/mutable/servermap.py 667
412-        if self._need_privkey and last_verinfo:
413-            # send them a request for the privkey. We send one request per
414-            # server.
415-            lp2 = self.log("sending privkey request",
416-                           parent=lp, level=log.NOISY)
417-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
418-             offsets_tuple) = last_verinfo
419-            o = dict(offsets_tuple)
420+            # - Next, we need the version information. We almost
421+            #   certainly got this by reading the first thousand or so
422+            #   bytes of the share on the storage server, so we
423+            #   shouldn't need to fetch anything at this step.
424+            d2 = reader.get_verinfo()
425+            # - Next, we need the signature. For an SDMF share, it is
426+            #   likely that we fetched this when doing our initial fetch
427+            #   to get the version information. In MDMF, this lives at
428+            #   the end of the share, so unless the file is quite small,
429+            #   we'll need to do a remote fetch to get it.
430+            d3 = reader.get_signature()
431+            #  Once we have all three of these responses, we can move on
432+            #  to validating the signature
433 
434hunk ./src/allmydata/mutable/servermap.py 681
435-            self._queries_outstanding.add(peerid)
436-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
437-            ss = self._servermap.connections[peerid]
438-            privkey_started = time.time()
439-            d = self._do_read(ss, peerid, self._storage_index,
440-                              [last_shnum], readv)
441-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
442-                          privkey_started, lp2)
443-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
444-            d.addErrback(log.err)
445-            d.addCallback(self._check_for_done)
446-            d.addErrback(self._fatal_error)
447+            # Does the node already have a privkey? If not, we'll try to
448+            # fetch it here.
449+            if not self._node.get_privkey():
450+                d4 = reader.get_encprivkey()
451+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
452+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
453+            else:
454+                d4 = defer.succeed(None)
455 
456hunk ./src/allmydata/mutable/servermap.py 690
457+            dl = defer.DeferredList([d, d2, d3, d4])
458+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
459+                self._got_signature_one_share(results, shnum, peerid, lp))
460+            dl.addErrback(lambda error, shnum=shnum, data=data:
461+               _corrupt(error, shnum, data))
462+            ds.append(dl)
463+        # dl is a deferred list that will fire when all of the shares
464+        # that we found on this peer are done processing. When dl fires,
465+        # we know that processing is done, so we can decrement the
466+        # semaphore-like thing that we incremented earlier.
467+        dl = defer.DeferredList(ds)
468+        def _done_processing(ignored):
469+            self._processing -= 1
470+            return ignored
471+        dl.addCallback(_done_processing)
472+        # Are we done? Done means that there are no more queries to
473+        # send, that there are no outstanding queries, and that we
474+        # haven't received any queries that are still processing. If we
475+        # are done, self._check_for_done will cause the done deferred
476+        # that we returned to our caller to fire, which tells them that
477+        # they have a complete servermap, and that we won't be touching
478+        # the servermap anymore.
479+        dl.addBoth(self._check_for_done)
480+        dl.addErrback(self._fatal_error)
481         # all done!
482hunk ./src/allmydata/mutable/servermap.py 715
483+        return dl
484         self.log("_got_results done", parent=lp, level=log.NOISY)
485 
486hunk ./src/allmydata/mutable/servermap.py 718
487+    def _try_to_set_pubkey(self, pubkey_s):
488+        if self._node.get_pubkey():
489+            return # don't go through this again if we don't have to
490+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
491+        assert len(fingerprint) == 32
492+        if fingerprint != self._node.get_fingerprint():
493+            raise CorruptShareError(peerid, shnum,
494+                                "pubkey doesn't match fingerprint")
495+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
496+        assert self._node.get_pubkey()
497+
498+
499     def notify_server_corruption(self, peerid, shnum, reason):
500         ss = self._servermap.connections[peerid]
501         ss.callRemoteOnly("advise_corrupt_share",
502hunk ./src/allmydata/mutable/servermap.py 735
503                           "mutable", self._storage_index, shnum, reason)
504 
505-    def _got_results_one_share(self, shnum, data, peerid, lp):
506+
507+    def _got_signature_one_share(self, results, shnum, peerid, lp):
508+        # It is our job to give versioninfo to our caller. We need to
509+        # raise CorruptShareError if the share is corrupt for any
510+        # reason, something that our caller will handle.
511         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
512                  shnum=shnum,
513                  peerid=idlib.shortnodeid_b2a(peerid),
514hunk ./src/allmydata/mutable/servermap.py 745
515                  level=log.NOISY,
516                  parent=lp)
517-
518-        # this might raise NeedMoreDataError, if the pubkey and signature
519-        # live at some weird offset. That shouldn't happen, so I'm going to
520-        # treat it as a bad share.
521-        (seqnum, root_hash, IV, k, N, segsize, datalength,
522-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
523-
524-        if not self._node.get_pubkey():
525-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
526-            assert len(fingerprint) == 32
527-            if fingerprint != self._node.get_fingerprint():
528-                raise CorruptShareError(peerid, shnum,
529-                                        "pubkey doesn't match fingerprint")
530-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
531-
532-        if self._need_privkey:
533-            self._try_to_extract_privkey(data, peerid, shnum, lp)
534-
535-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
536-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
537+        _, verinfo, signature, __ = results
538+        (seqnum,
539+         root_hash,
540+         saltish,
541+         segsize,
542+         datalen,
543+         k,
544+         n,
545+         prefix,
546+         offsets) = verinfo[1]
547         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
548 
549hunk ./src/allmydata/mutable/servermap.py 757
550-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
551+        # XXX: This should be done for us in the method, so
552+        # presumably you can go in there and fix it.
553+        verinfo = (seqnum,
554+                   root_hash,
555+                   saltish,
556+                   segsize,
557+                   datalen,
558+                   k,
559+                   n,
560+                   prefix,
561                    offsets_tuple)
562hunk ./src/allmydata/mutable/servermap.py 768
563+        # This tuple uniquely identifies a share on the grid; we use it
564+        # to keep track of the ones that we've already seen.
565 
566         if verinfo not in self._valid_versions:
567hunk ./src/allmydata/mutable/servermap.py 772
568-            # it's a new pair. Verify the signature.
569-            valid = self._node.get_pubkey().verify(prefix, signature)
570+            # This is a new version tuple, and we need to validate it
571+            # against the public key before keeping track of it.
572+            valid = self._node.get_pubkey().verify(prefix, signature[1])
573             if not valid:
574hunk ./src/allmydata/mutable/servermap.py 776
575-                raise CorruptShareError(peerid, shnum, "signature is invalid")
576+                raise CorruptShareError(peerid, shnum,
577+                                        "signature is invalid")
578 
579hunk ./src/allmydata/mutable/servermap.py 779
580-            # ok, it's a valid verinfo. Add it to the list of validated
581-            # versions.
582-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
583-                     % (seqnum, base32.b2a(root_hash)[:4],
584-                        idlib.shortnodeid_b2a(peerid), shnum,
585-                        k, N, segsize, datalength),
586-                     parent=lp)
587-            self._valid_versions.add(verinfo)
588-        # We now know that this is a valid candidate verinfo.
589+        # ok, it's a valid verinfo. Add it to the list of validated
590+        # versions.
591+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
592+                 % (seqnum, base32.b2a(root_hash)[:4],
593+                    idlib.shortnodeid_b2a(peerid), shnum,
594+                    k, n, segsize, datalen),
595+                    parent=lp)
596+        self._valid_versions.add(verinfo)
597+        # We now know that this is a valid candidate verinfo. Whether or
598+        # not this instance of it is valid is a matter for the next
599+        # statement; at this point, we just know that if we see this
600+        # version info again, that its signature checks out and that
601+        # we're okay to skip the signature-checking step.
602 
603hunk ./src/allmydata/mutable/servermap.py 793
604+        # (peerid, shnum) are bound in the method invocation.
605         if (peerid, shnum) in self._servermap.bad_shares:
606             # we've been told that the rest of the data in this share is
607             # unusable, so don't add it to the servermap.
608hunk ./src/allmydata/mutable/servermap.py 808
609         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
610         return verinfo
611 
612+
613     def _deserialize_pubkey(self, pubkey_s):
614         verifier = rsa.create_verifying_key_from_string(pubkey_s)
615         return verifier
616hunk ./src/allmydata/mutable/servermap.py 813
617 
618-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
619-        try:
620-            r = unpack_share(data)
621-        except NeedMoreDataError, e:
622-            # this share won't help us. oh well.
623-            offset = e.encprivkey_offset
624-            length = e.encprivkey_length
625-            self.log("shnum %d on peerid %s: share was too short (%dB) "
626-                     "to get the encprivkey; [%d:%d] ought to hold it" %
627-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
628-                      offset, offset+length),
629-                     parent=lp)
630-            # NOTE: if uncoordinated writes are taking place, someone might
631-            # change the share (and most probably move the encprivkey) before
632-            # we get a chance to do one of these reads and fetch it. This
633-            # will cause us to see a NotEnoughSharesError(unable to fetch
634-            # privkey) instead of an UncoordinatedWriteError . This is a
635-            # nuisance, but it will go away when we move to DSA-based mutable
636-            # files (since the privkey will be small enough to fit in the
637-            # write cap).
638-
639-            return
640-
641-        (seqnum, root_hash, IV, k, N, segsize, datalen,
642-         pubkey, signature, share_hash_chain, block_hash_tree,
643-         share_data, enc_privkey) = r
644-
645-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
646 
647     def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
648hunk ./src/allmydata/mutable/servermap.py 815
649-
650+        """
651+        Given a writekey from a remote server, I validate it against the
652+        writekey stored in my node. If it is valid, then I set the
653+        privkey and encprivkey properties of the node.
654+        """
655         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
656         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
657         if alleged_writekey != self._node.get_writekey():
658hunk ./src/allmydata/mutable/servermap.py 925
659         #  return self._send_more_queries(outstanding) : send some more queries
660         #  return self._done() : all done
661         #  return : keep waiting, no new queries
662-
663         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
664                               "%(outstanding)d queries outstanding, "
665                               "%(extra)d extra peers available, "
666hunk ./src/allmydata/mutable/servermap.py 943
667             self.log("but we're not running", parent=lp, level=log.NOISY)
668             return
669 
670+        if self._processing > 0:
671+            # wait until more results are done before returning.
672+            return
673+
674         if self._must_query:
675             # we are still waiting for responses from peers that used to have
676             # a share, so we must continue to wait. No additional queries are
677hunk ./src/allmydata/mutable/servermap.py 1134
678         self._servermap.last_update_time = self._started
679         # the servermap will not be touched after this
680         self.log("servermap: %s" % self._servermap.summarize_versions())
681+
682         eventually(self._done_deferred.callback, self._servermap)
683 
684     def _fatal_error(self, f):
685}
686[Add tests for new MDMF proxies
687Kevan Carstensen <kevan@isnotajoke.com>**20100611192150
688 Ignore-this: 986d2cb867cbd4477b131cd951cd9eac
689] {
690hunk ./src/allmydata/test/test_storage.py 2
691 
692-import time, os.path, stat, re, simplejson, struct
693+import time, os.path, stat, re, simplejson, struct, shutil
694 
695 from twisted.trial import unittest
696 
697hunk ./src/allmydata/test/test_storage.py 22
698 from allmydata.storage.expirer import LeaseCheckingCrawler
699 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
700      ReadBucketProxy
701-from allmydata.interfaces import BadWriteEnablerError
702-from allmydata.test.common import LoggingServiceParent
703+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
704+                                     LayoutInvalid
705+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
706+                                 SDMF_VERSION
707+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
708 from allmydata.test.common_web import WebRenderingMixin
709 from allmydata.web.storage import StorageStatus, remove_prefix
710 
711hunk ./src/allmydata/test/test_storage.py 1286
712         self.failUnless(os.path.exists(prefixdir), prefixdir)
713         self.failIf(os.path.exists(bucketdir), bucketdir)
714 
715+
716+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
717+    def setUp(self):
718+        self.sparent = LoggingServiceParent()
719+        self._lease_secret = itertools.count()
720+        self.ss = self.create("MDMFProxies storage test server")
721+        self.rref = RemoteBucket()
722+        self.rref.target = self.ss
723+        self.secrets = (self.write_enabler("we_secret"),
724+                        self.renew_secret("renew_secret"),
725+                        self.cancel_secret("cancel_secret"))
726+        self.segment = "aaaaaa"
727+        self.block = "aa"
728+        self.salt = "a" * 16
729+        self.block_hash = "a" * 32
730+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
731+        self.share_hash = self.block_hash
732+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
733+        self.signature = "foobarbaz"
734+        self.verification_key = "vvvvvv"
735+        self.encprivkey = "private"
736+        self.root_hash = self.block_hash
737+        self.salt_hash = self.root_hash
738+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
739+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
740+
741+
742+    def tearDown(self):
743+        self.sparent.stopService()
744+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
745+
746+
747+    def write_enabler(self, we_tag):
748+        return hashutil.tagged_hash("we_blah", we_tag)
749+
750+
751+    def renew_secret(self, tag):
752+        return hashutil.tagged_hash("renew_blah", str(tag))
753+
754+
755+    def cancel_secret(self, tag):
756+        return hashutil.tagged_hash("cancel_blah", str(tag))
757+
758+
759+    def workdir(self, name):
760+        basedir = os.path.join("storage", "MutableServer", name)
761+        return basedir
762+
763+
764+    def create(self, name):
765+        workdir = self.workdir(name)
766+        ss = StorageServer(workdir, "\x00" * 20)
767+        ss.setServiceParent(self.sparent)
768+        return ss
769+
770+
771+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
772+        # Start with the checkstring
773+        data = struct.pack(">BQ32s32s",
774+                           1,
775+                           0,
776+                           self.root_hash,
777+                           self.salt_hash)
778+        self.checkstring = data
779+        # Next, the encoding parameters
780+        if tail_segment:
781+            data += struct.pack(">BBQQ",
782+                                3,
783+                                10,
784+                                6,
785+                                33)
786+        elif empty:
787+            data += struct.pack(">BBQQ",
788+                                3,
789+                                10,
790+                                0,
791+                                0)
792+        else:
793+            data += struct.pack(">BBQQ",
794+                                3,
795+                                10,
796+                                6,
797+                                36)
798+        # Now we'll build the offsets.
799+        # The header -- everything up to the salts -- is 143 bytes long.
800+        # The shares come after the salts.
801+        if empty:
802+            salts = ""
803+        else:
804+            salts = self.salt * 6
805+        share_offset = 143 + len(salts)
806+        if tail_segment:
807+            sharedata = self.block * 6
808+        elif empty:
809+            sharedata = ""
810+        else:
811+            sharedata = self.block * 6 + "a"
812+        # The encrypted private key comes after the shares
813+        encrypted_private_key_offset = share_offset + len(sharedata)
814+        # The blockhashes come after the private key
815+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
816+        # The sharehashes come after the blockhashes
817+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
818+        # The signature comes after the share hash chain
819+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
820+        # The verification key comes after the signature
821+        verification_offset = signature_offset + len(self.signature)
822+        # The EOF comes after the verification key
823+        eof_offset = verification_offset + len(self.verification_key)
824+        data += struct.pack(">LQQQQQQ",
825+                            share_offset,
826+                            encrypted_private_key_offset,
827+                            blockhashes_offset,
828+                            sharehashes_offset,
829+                            signature_offset,
830+                            verification_offset,
831+                            eof_offset)
832+        self.offsets = {}
833+        self.offsets['share_data'] = share_offset
834+        self.offsets['enc_privkey'] = encrypted_private_key_offset
835+        self.offsets['block_hash_tree'] = blockhashes_offset
836+        self.offsets['share_hash_chain'] = sharehashes_offset
837+        self.offsets['signature'] = signature_offset
838+        self.offsets['verification_key'] = verification_offset
839+        self.offsets['EOF'] = eof_offset
840+        # Next, we'll add in the salts,
841+        data += salts
842+        # the share data,
843+        data += sharedata
844+        # the private key,
845+        data += self.encprivkey
846+        # the block hash tree,
847+        data += self.block_hash_tree_s
848+        # the share hash chain,
849+        data += self.share_hash_chain_s
850+        # the signature,
851+        data += self.signature
852+        # and the verification key
853+        data += self.verification_key
854+        return data
855+
856+
857+    def write_test_share_to_server(self,
858+                                   storage_index,
859+                                   tail_segment=False,
860+                                   empty=False):
861+        """
862+        I write some data for the read tests to read to self.ss
863+
864+        If tail_segment=True, then I will write a share that has a
865+        smaller tail segment than other segments.
866+        """
867+        write = self.ss.remote_slot_testv_and_readv_and_writev
868+        data = self.build_test_mdmf_share(tail_segment, empty)
869+        # Finally, we write the whole thing to the storage server in one
870+        # pass.
871+        testvs = [(0, 1, "eq", "")]
872+        tws = {}
873+        tws[0] = (testvs, [(0, data)], None)
874+        readv = [(0, 1)]
875+        results = write(storage_index, self.secrets, tws, readv)
876+        self.failUnless(results[0])
877+
878+
879+    def build_test_sdmf_share(self, empty=False):
880+        if empty:
881+            sharedata = ""
882+        else:
883+            sharedata = self.segment * 6
884+        blocksize = len(sharedata) / 3
885+        block = sharedata[:blocksize]
886+        prefix = struct.pack(">BQ32s16s BBQQ",
887+                             0, # version,
888+                             0,
889+                             self.root_hash,
890+                             self.salt,
891+                             3,
892+                             10,
893+                             len(sharedata),
894+                             len(sharedata),
895+                            )
896+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
897+        signature_offset = post_offset + len(self.verification_key)
898+        sharehashes_offset = signature_offset + len(self.signature)
899+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
900+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
901+        encprivkey_offset = sharedata_offset + len(block)
902+        eof_offset = encprivkey_offset + len(self.encprivkey)
903+        offsets = struct.pack(">LLLLQQ",
904+                              signature_offset,
905+                              sharehashes_offset,
906+                              blockhashes_offset,
907+                              sharedata_offset,
908+                              encprivkey_offset,
909+                              eof_offset)
910+        final_share = "".join([prefix,
911+                           offsets,
912+                           self.verification_key,
913+                           self.signature,
914+                           self.share_hash_chain_s,
915+                           self.block_hash_tree_s,
916+                           block,
917+                           self.encprivkey])
918+        self.offsets = {}
919+        self.offsets['signature'] = signature_offset
920+        self.offsets['share_hash_chain'] = sharehashes_offset
921+        self.offsets['block_hash_tree'] = blockhashes_offset
922+        self.offsets['share_data'] = sharedata_offset
923+        self.offsets['enc_privkey'] = encprivkey_offset
924+        self.offsets['EOF'] = eof_offset
925+        return final_share
926+
927+
928+    def write_sdmf_share_to_server(self,
929+                                   storage_index,
930+                                   empty=False):
931+        # Some tests need SDMF shares to verify that we can still
932+        # read them. This method writes one, which resembles but is not
933+        assert self.rref
934+        write = self.ss.remote_slot_testv_and_readv_and_writev
935+        share = self.build_test_sdmf_share(empty)
936+        testvs = [(0, 1, "eq", "")]
937+        tws = {}
938+        tws[0] = (testvs, [(0, share)], None)
939+        readv = []
940+        results = write(storage_index, self.secrets, tws, readv)
941+        self.failUnless(results[0])
942+
943+
944+    def test_read(self):
945+        self.write_test_share_to_server("si1")
946+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
947+        # Check that every method equals what we expect it to.
948+        d = defer.succeed(None)
949+        def _check_block_and_salt((block, salt)):
950+            self.failUnlessEqual(block, self.block)
951+            self.failUnlessEqual(salt, self.salt)
952+
953+        for i in xrange(6):
954+            d.addCallback(lambda ignored, i=i:
955+                mr.get_block_and_salt(i))
956+            d.addCallback(_check_block_and_salt)
957+
958+        d.addCallback(lambda ignored:
959+            mr.get_encprivkey())
960+        d.addCallback(lambda encprivkey:
961+            self.failUnlessEqual(self.encprivkey, encprivkey))
962+
963+        d.addCallback(lambda ignored:
964+            mr.get_blockhashes())
965+        d.addCallback(lambda blockhashes:
966+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
967+
968+        d.addCallback(lambda ignored:
969+            mr.get_sharehashes())
970+        d.addCallback(lambda sharehashes:
971+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
972+
973+        d.addCallback(lambda ignored:
974+            mr.get_signature())
975+        d.addCallback(lambda signature:
976+            self.failUnlessEqual(signature, self.signature))
977+
978+        d.addCallback(lambda ignored:
979+            mr.get_verification_key())
980+        d.addCallback(lambda verification_key:
981+            self.failUnlessEqual(verification_key, self.verification_key))
982+
983+        d.addCallback(lambda ignored:
984+            mr.get_seqnum())
985+        d.addCallback(lambda seqnum:
986+            self.failUnlessEqual(seqnum, 0))
987+
988+        d.addCallback(lambda ignored:
989+            mr.get_root_hash())
990+        d.addCallback(lambda root_hash:
991+            self.failUnlessEqual(self.root_hash, root_hash))
992+
993+        d.addCallback(lambda ignored:
994+            mr.get_salt_hash())
995+        d.addCallback(lambda salt_hash:
996+            self.failUnlessEqual(self.salt_hash, salt_hash))
997+
998+        d.addCallback(lambda ignored:
999+            mr.get_seqnum())
1000+        d.addCallback(lambda seqnum:
1001+            self.failUnlessEqual(0, seqnum))
1002+
1003+        d.addCallback(lambda ignored:
1004+            mr.get_encoding_parameters())
1005+        def _check_encoding_parameters((k, n, segsize, datalen)):
1006+            self.failUnlessEqual(k, 3)
1007+            self.failUnlessEqual(n, 10)
1008+            self.failUnlessEqual(segsize, 6)
1009+            self.failUnlessEqual(datalen, 36)
1010+        d.addCallback(_check_encoding_parameters)
1011+
1012+        d.addCallback(lambda ignored:
1013+            mr.get_checkstring())
1014+        d.addCallback(lambda checkstring:
1015+            self.failUnlessEqual(checkstring, checkstring))
1016+        return d
1017+
1018+
1019+    def test_read_with_different_tail_segment_size(self):
1020+        self.write_test_share_to_server("si1", tail_segment=True)
1021+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
1022+        d = mr.get_block_and_salt(5)
1023+        def _check_tail_segment(results):
1024+            block, salt = results
1025+            self.failUnlessEqual(len(block), 1)
1026+            self.failUnlessEqual(block, "a")
1027+        d.addCallback(_check_tail_segment)
1028+        return d
1029+
1030+
1031+    def test_get_block_with_invalid_segnum(self):
1032+        self.write_test_share_to_server("si1")
1033+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
1034+        d = defer.succeed(None)
1035+        d.addCallback(lambda ignored:
1036+            self.shouldFail(LayoutInvalid, "test invalid segnum",
1037+                            None,
1038+                            mr.get_block_and_salt, 7))
1039+        return d
1040+
1041+
1042+    def test_get_encoding_parameters_first(self):
1043+        self.write_test_share_to_server("si1")
1044+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
1045+        d = mr.get_encoding_parameters()
1046+        def _check_encoding_parameters((k, n, segment_size, datalen)):
1047+            self.failUnlessEqual(k, 3)
1048+            self.failUnlessEqual(n, 10)
1049+            self.failUnlessEqual(segment_size, 6)
1050+            self.failUnlessEqual(datalen, 36)
1051+        d.addCallback(_check_encoding_parameters)
1052+        return d
1053+
1054+
1055+    def test_get_seqnum_first(self):
1056+        self.write_test_share_to_server("si1")
1057+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
1058+        d = mr.get_seqnum()
1059+        d.addCallback(lambda seqnum:
1060+            self.failUnlessEqual(seqnum, 0))
1061+        return d
1062+
1063+
1064+    def test_get_root_hash_first(self):
1065+        self.write_test_share_to_server("si1")
1066+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
1067+        d = mr.get_root_hash()
1068+        d.addCallback(lambda root_hash:
1069+            self.failUnlessEqual(root_hash, self.root_hash))
1070+        return d
1071+
1072+
1073+    def test_get_salt_hash_first(self):
1074+        self.write_test_share_to_server("si1")
1075+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
1076+        d = mr.get_salt_hash()
1077+        d.addCallback(lambda salt_hash:
1078+            self.failUnlessEqual(salt_hash, self.salt_hash))
1079+        return d
1080+
1081+
1082+    def test_get_checkstring_first(self):
1083+        self.write_test_share_to_server("si1")
1084+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
1085+        d = mr.get_checkstring()
1086+        d.addCallback(lambda checkstring:
1087+            self.failUnlessEqual(checkstring, self.checkstring))
1088+        return d
1089+
1090+
1091+    def test_write_read_vectors(self):
1092+        # When writing for us, the storage server will return to us a
1093+        # read vector, along with its result. If a write fails because
1094+        # the test vectors failed, this read vector can help us to
1095+        # diagnose the problem. This test ensures that the read vector
1096+        # is working appropriately.
1097+        mw = self._make_new_mw("si1", 0)
1098+        d = defer.succeed(None)
1099+
1100+        # Write one share. This should return a checkstring of nothing,
1101+        # since there is no data there.
1102+        d.addCallback(lambda ignored:
1103+            mw.put_block(self.block, 0, self.salt))
1104+        def _check_first_write(results):
1105+            result, readvs = results
1106+            self.failUnless(result)
1107+            self.failIf(readvs)
1108+        d.addCallback(_check_first_write)
1109+        # Now, there should be a different checkstring returned when
1110+        # we write other shares
1111+        d.addCallback(lambda ignored:
1112+            mw.put_block(self.block, 1, self.salt))
1113+        def _check_next_write(results):
1114+            result, readvs = results
1115+            self.failUnless(result)
1116+            self.expected_checkstring = mw.get_checkstring()
1117+            self.failUnlessIn(0, readvs)
1118+            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
1119+        d.addCallback(_check_next_write)
1120+        # Add the other four shares
1121+        for i in xrange(2, 6):
1122+            d.addCallback(lambda ignored, i=i:
1123+                mw.put_block(self.block, i, self.salt))
1124+            d.addCallback(_check_next_write)
1125+        # Add the encrypted private key
1126+        d.addCallback(lambda ignored:
1127+            mw.put_encprivkey(self.encprivkey))
1128+        d.addCallback(_check_next_write)
1129+        # Add the block hash tree and share hash tree
1130+        d.addCallback(lambda ignored:
1131+            mw.put_blockhashes(self.block_hash_tree))
1132+        d.addCallback(_check_next_write)
1133+        d.addCallback(lambda ignored:
1134+            mw.put_sharehashes(self.share_hash_chain))
1135+        d.addCallback(_check_next_write)
1136+        # Add the root hash and the salt hash. This should change the
1137+        # checkstring, but not in a way that we'll be able to see right
1138+        # now, since the read vectors are applied before the write
1139+        # vectors.
1140+        d.addCallback(lambda ignored:
1141+            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
1142+        def _check_old_testv_after_new_one_is_written(results):
1143+            result, readvs = results
1144+            self.failUnless(result)
1145+            self.failUnlessIn(0, readvs)
1146+            self.failUnlessEqual(self.expected_checkstring,
1147+                                 readvs[0][0])
1148+            new_checkstring = mw.get_checkstring()
1149+            self.failIfEqual(new_checkstring,
1150+                             readvs[0][0])
1151+        d.addCallback(_check_old_testv_after_new_one_is_written)
1152+        # Now add the signature. This should succeed, meaning that the
1153+        # data gets written and the read vector matches what the writer
1154+        # thinks should be there.
1155+        d.addCallback(lambda ignored:
1156+            mw.put_signature(self.signature))
1157+        d.addCallback(_check_next_write)
1158+        # The checkstring remains the same for the rest of the process.
1159+        return d
1160+
1161+
1162+    def test_blockhashes_after_share_hash_chain(self):
1163+        mw = self._make_new_mw("si1", 0)
1164+        d = defer.succeed(None)
1165+        # Put everything up to and including the share hash chain
1166+        for i in xrange(6):
1167+            d.addCallback(lambda ignored, i=i:
1168+                mw.put_block(self.block, i, self.salt))
1169+        d.addCallback(lambda ignored:
1170+            mw.put_encprivkey(self.encprivkey))
1171+        d.addCallback(lambda ignored:
1172+            mw.put_blockhashes(self.block_hash_tree))
1173+        d.addCallback(lambda ignored:
1174+            mw.put_sharehashes(self.share_hash_chain))
1175+        # Now try to put a block hash tree after the share hash chain.
1176+        # This won't necessarily overwrite the share hash chain, but it
1177+        # is a bad idea in general -- if we write one that is anything
1178+        # other than the exact size of the initial one, we will either
1179+        # overwrite the share hash chain, or give the reader (who uses
1180+        # the offset of the share hash chain as an end boundary) a
1181+        # shorter tree than they know to read, which will result in them
1182+        # reading junk. There is little reason to support it as a use
1183+        # case, so we should disallow it altogether.
1184+        d.addCallback(lambda ignored:
1185+            self.shouldFail(LayoutInvalid, "test same blockhashes",
1186+                            None,
1187+                            mw.put_blockhashes, self.block_hash_tree))
1188+        return d
1189+
1190+
1191+    def test_encprivkey_after_blockhashes(self):
1192+        mw = self._make_new_mw("si1", 0)
1193+        d = defer.succeed(None)
1194+        # Put everything up to and including the block hash tree
1195+        for i in xrange(6):
1196+            d.addCallback(lambda ignored, i=i:
1197+                mw.put_block(self.block, i, self.salt))
1198+        d.addCallback(lambda ignored:
1199+            mw.put_encprivkey(self.encprivkey))
1200+        d.addCallback(lambda ignored:
1201+            mw.put_blockhashes(self.block_hash_tree))
1202+        d.addCallback(lambda ignored:
1203+            self.shouldFail(LayoutInvalid, "out of order private key",
1204+                            None,
1205+                            mw.put_encprivkey, self.encprivkey))
1206+        return d
1207+
1208+
1209+    def test_share_hash_chain_after_signature(self):
1210+        mw = self._make_new_mw("si1", 0)
1211+        d = defer.succeed(None)
1212+        # Put everything up to and including the signature
1213+        for i in xrange(6):
1214+            d.addCallback(lambda ignored, i=i:
1215+                mw.put_block(self.block, i, self.salt))
1216+        d.addCallback(lambda ignored:
1217+            mw.put_encprivkey(self.encprivkey))
1218+        d.addCallback(lambda ignored:
1219+            mw.put_blockhashes(self.block_hash_tree))
1220+        d.addCallback(lambda ignored:
1221+            mw.put_sharehashes(self.share_hash_chain))
1222+        d.addCallback(lambda ignored:
1223+            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
1224+        d.addCallback(lambda ignored:
1225+            mw.put_signature(self.signature))
1226+        # Now try to put the share hash chain again. This should fail
1227+        d.addCallback(lambda ignored:
1228+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
1229+                            None,
1230+                            mw.put_sharehashes, self.share_hash_chain))
1231+        return d
1232+
1233+
1234+    def test_signature_after_verification_key(self):
1235+        mw = self._make_new_mw("si1", 0)
1236+        d = defer.succeed(None)
1237+        # Put everything up to and including the verification key.
1238+        for i in xrange(6):
1239+            d.addCallback(lambda ignored, i=i:
1240+                mw.put_block(self.block, i, self.salt))
1241+        d.addCallback(lambda ignored:
1242+            mw.put_encprivkey(self.encprivkey))
1243+        d.addCallback(lambda ignored:
1244+            mw.put_blockhashes(self.block_hash_tree))
1245+        d.addCallback(lambda ignored:
1246+            mw.put_sharehashes(self.share_hash_chain))
1247+        d.addCallback(lambda ignored:
1248+            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
1249+        d.addCallback(lambda ignored:
1250+            mw.put_signature(self.signature))
1251+        d.addCallback(lambda ignored:
1252+            mw.put_verification_key(self.verification_key))
1253+        # Now try to put the signature again. This should fail
1254+        d.addCallback(lambda ignored:
1255+            self.shouldFail(LayoutInvalid, "signature after verification",
1256+                            None,
1257+                            mw.put_signature, self.signature))
1258+        return d
1259+
1260+
1261+    def test_uncoordinated_write(self):
1262+        # Make two mutable writers, both pointing to the same storage
1263+        # server, both at the same storage index, and try writing to the
1264+        # same share.
1265+        mw1 = self._make_new_mw("si1", 0)
1266+        mw2 = self._make_new_mw("si1", 0)
1267+        d = defer.succeed(None)
1268+        def _check_success(results):
1269+            result, readvs = results
1270+            self.failUnless(result)
1271+
1272+        def _check_failure(results):
1273+            result, readvs = results
1274+            self.failIf(result)
1275+
1276+        d.addCallback(lambda ignored:
1277+            mw1.put_block(self.block, 0, self.salt))
1278+        d.addCallback(_check_success)
1279+        d.addCallback(lambda ignored:
1280+            mw2.put_block(self.block, 0, self.salt))
1281+        d.addCallback(_check_failure)
1282+        return d
1283+
1284+
1285+    def test_invalid_salt_size(self):
1286+        # Salts need to be 16 bytes in size. Writes that attempt to
1287+        # write more or less than this should be rejected.
1288+        mw = self._make_new_mw("si1", 0)
1289+        invalid_salt = "a" * 17 # 17 bytes
1290+        another_invalid_salt = "b" * 15 # 15 bytes
1291+        d = defer.succeed(None)
1292+        d.addCallback(lambda ignored:
1293+            self.shouldFail(LayoutInvalid, "salt too big",
1294+                            None,
1295+                            mw.put_block, self.block, 0, invalid_salt))
1296+        d.addCallback(lambda ignored:
1297+            self.shouldFail(LayoutInvalid, "salt too small",
1298+                            None,
1299+                            mw.put_block, self.block, 0,
1300+                            another_invalid_salt))
1301+        return d
1302+
1303+
1304+    def test_write_test_vectors(self):
1305+        # If we give the write proxy a bogus test vector at
1306+        # any point during the process, it should fail to write.
1307+        mw = self._make_new_mw("si1", 0)
1308+        mw.set_checkstring("this is a lie")
1309+        # The initial write should be expecting to find the improbable
1310+        # checkstring above in place; finding nothing, it should fail.
1311+        d = defer.succeed(None)
1312+        d.addCallback(lambda ignored:
1313+            mw.put_block(self.block, 0, self.salt))
1314+        def _check_failure(results):
1315+            result, readv = results
1316+            self.failIf(result)
1317+        d.addCallback(_check_failure)
1318+        # Now set the checkstring to the empty string, which
1319+        # indicates that no share is there.
1320+        d.addCallback(lambda ignored:
1321+            mw.set_checkstring(""))
1322+        d.addCallback(lambda ignored:
1323+            mw.put_block(self.block, 0, self.salt))
1324+        def _check_success(results):
1325+            result, readv = results
1326+            self.failUnless(result)
1327+        d.addCallback(_check_success)
1328+        # Now set the checkstring to something wrong
1329+        d.addCallback(lambda ignored:
1330+            mw.set_checkstring("something wrong"))
1331+        # This should fail to do anything
1332+        d.addCallback(lambda ignored:
1333+            mw.put_block(self.block, 1, self.salt))
1334+        d.addCallback(_check_failure)
1335+        # Now set it back to what it should be.
1336+        d.addCallback(lambda ignored:
1337+            mw.set_checkstring(mw.get_checkstring()))
1338+        for i in xrange(1, 6):
1339+            d.addCallback(lambda ignored, i=i:
1340+                mw.put_block(self.block, i, self.salt))
1341+            d.addCallback(_check_success)
1342+        d.addCallback(lambda ignored:
1343+            mw.put_encprivkey(self.encprivkey))
1344+        d.addCallback(_check_success)
1345+        d.addCallback(lambda ignored:
1346+            mw.put_blockhashes(self.block_hash_tree))
1347+        d.addCallback(_check_success)
1348+        d.addCallback(lambda ignored:
1349+            mw.put_sharehashes(self.share_hash_chain))
1350+        d.addCallback(_check_success)
1351+        def _keep_old_checkstring(ignored):
1352+            self.old_checkstring = mw.get_checkstring()
1353+            mw.set_checkstring("foobarbaz")
1354+        d.addCallback(_keep_old_checkstring)
1355+        d.addCallback(lambda ignored:
1356+            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
1357+        d.addCallback(_check_failure)
1358+        d.addCallback(lambda ignored:
1359+            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
1360+        def _restore_old_checkstring(ignored):
1361+            mw.set_checkstring(self.old_checkstring)
1362+        d.addCallback(_restore_old_checkstring)
1363+        d.addCallback(lambda ignored:
1364+            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
1365+        # The checkstring should have been set appropriately for us on
1366+        # the last write; if we try to change it to something else,
1367+        # that change should cause the verification key step to fail.
1368+        d.addCallback(lambda ignored:
1369+            mw.set_checkstring("something else"))
1370+        d.addCallback(lambda ignored:
1371+            mw.put_signature(self.signature))
1372+        d.addCallback(_check_failure)
1373+        d.addCallback(lambda ignored:
1374+            mw.set_checkstring(mw.get_checkstring()))
1375+        d.addCallback(lambda ignored:
1376+            mw.put_signature(self.signature))
1377+        d.addCallback(_check_success)
1378+        d.addCallback(lambda ignored:
1379+            mw.put_verification_key(self.verification_key))
1380+        d.addCallback(_check_success)
1381+        return d
1382+
1383+
1384+    def test_offset_only_set_on_success(self):
1385+        # The write proxy should be smart enough to detect when a write
1386+        # has failed, and to temper its definition of progress based on
1387+        # that.
1388+        mw = self._make_new_mw("si1", 0)
1389+        d = defer.succeed(None)
1390+        for i in xrange(1, 6):
1391+            d.addCallback(lambda ignored, i=i:
1392+                mw.put_block(self.block, i, self.salt))
1393+        def _break_checkstring(ignored):
1394+            self._old_checkstring = mw.get_checkstring()
1395+            mw.set_checkstring("foobarbaz")
1396+
1397+        def _fix_checkstring(ignored):
1398+            mw.set_checkstring(self._old_checkstring)
1399+
1400+        d.addCallback(_break_checkstring)
1401+
1402+        # Setting the encrypted private key shouldn't work now, which is
1403+        # to be expected and is tested elsewhere. We also want to make
1404+        # sure that we can't add the block hash tree after a failed
1405+        # write of this sort.
1406+        d.addCallback(lambda ignored:
1407+            mw.put_encprivkey(self.encprivkey))
1408+        d.addCallback(lambda ignored:
1409+            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
1410+                            None,
1411+                            mw.put_blockhashes, self.block_hash_tree))
1412+        d.addCallback(_fix_checkstring)
1413+        d.addCallback(lambda ignored:
1414+            mw.put_encprivkey(self.encprivkey))
1415+        d.addCallback(_break_checkstring)
1416+        d.addCallback(lambda ignored:
1417+            mw.put_blockhashes(self.block_hash_tree))
1418+        d.addCallback(lambda ignored:
1419+            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
1420+                            None,
1421+                            mw.put_sharehashes, self.share_hash_chain))
1422+        d.addCallback(_fix_checkstring)
1423+        d.addCallback(lambda ignored:
1424+            mw.put_blockhashes(self.block_hash_tree))
1425+        d.addCallback(_break_checkstring)
1426+        d.addCallback(lambda ignored:
1427+            mw.put_sharehashes(self.share_hash_chain))
1428+        d.addCallback(lambda ignored:
1429+            self.shouldFail(LayoutInvalid, "out-of-order root hash",
1430+                            None,
1431+                            mw.put_root_and_salt_hashes,
1432+                            self.root_hash, self.salt_hash))
1433+        d.addCallback(_fix_checkstring)
1434+        d.addCallback(lambda ignored:
1435+            mw.put_sharehashes(self.share_hash_chain))
1436+        d.addCallback(_break_checkstring)
1437+        d.addCallback(lambda ignored:
1438+            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
1439+        d.addCallback(lambda ignored:
1440+            self.shouldFail(LayoutInvalid, "out-of-order signature",
1441+                            None,
1442+                            mw.put_signature, self.signature))
1443+        d.addCallback(_fix_checkstring)
1444+        d.addCallback(lambda ignored:
1445+            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
1446+        d.addCallback(_break_checkstring)
1447+        d.addCallback(lambda ignored:
1448+            mw.put_signature(self.signature))
1449+        d.addCallback(lambda ignored:
1450+            self.shouldFail(LayoutInvalid, "out-of-order verification key",
1451+                            None,
1452+                            mw.put_verification_key,
1453+                            self.verification_key))
1454+        d.addCallback(_fix_checkstring)
1455+        d.addCallback(lambda ignored:
1456+            mw.put_signature(self.signature))
1457+        d.addCallback(_break_checkstring)
1458+        d.addCallback(lambda ignored:
1459+            mw.put_verification_key(self.verification_key))
1460+        d.addCallback(lambda ignored:
1461+            self.shouldFail(LayoutInvalid, "out-of-order finish",
1462+                            None,
1463+                            mw.finish_publishing))
1464+        return d
1465+
1466+
1467+    def serialize_blockhashes(self, blockhashes):
1468+        return "".join(blockhashes)
1469+
1470+
1471+    def serialize_sharehashes(self, sharehashes):
1472+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
1473+                        for i in sorted(sharehashes.keys())])
1474+        return ret
1475+
1476+
1477+    def test_write(self):
1478+        # This translates to a file with 6 6-byte segments, and with 2-byte
1479+        # blocks.
1480+        mw = self._make_new_mw("si1", 0)
1481+        mw2 = self._make_new_mw("si1", 1)
1482+        # Test writing some blocks.
1483+        read = self.ss.remote_slot_readv
1484+        def _check_block_write(i, share):
1485+            self.failUnlessEqual(read("si1", [share], [(239 + (i * 2), 2)]),
1486+                                {share: [self.block]})
1487+            self.failUnlessEqual(read("si1", [share], [(143 + (i * 16), 16)]),
1488+                                 {share: [self.salt]})
1489+        d = defer.succeed(None)
1490+        for i in xrange(6):
1491+            d.addCallback(lambda ignored, i=i:
1492+                mw.put_block(self.block, i, self.salt))
1493+            d.addCallback(lambda ignored, i=i:
1494+                _check_block_write(i, 0))
1495+        # Now try the same thing, but with share 1 instead of share 0.
1496+        for i in xrange(6):
1497+            d.addCallback(lambda ignored, i=i:
1498+                mw2.put_block(self.block, i, self.salt))
1499+            d.addCallback(lambda ignored, i=i:
1500+                _check_block_write(i, 1))
1501+
1502+        def _spy_on_results(results):
1503+            print read("si1", [], [(0, 40000000)])
1504+            return results
1505+
1506+        # Next, we make a fake encrypted private key, and put it onto the
1507+        # storage server.
1508+        d.addCallback(lambda ignored:
1509+            mw.put_encprivkey(self.encprivkey))
1510+        # So far, we have:
1511+        #  header:  143 bytes
1512+        #  salts:   16 * 6 = 96 bytes
1513+        #  blocks:  2 * 6 = 12 bytes
1514+        #   = 251 bytes
1515+        expected_private_key_offset = 251
1516+        self.failUnlessEqual(len(self.encprivkey), 7)
1517+        d.addCallback(lambda ignored:
1518+            self.failUnlessEqual(read("si1", [0], [(251, 7)]),
1519+                                 {0: [self.encprivkey]}))
1520+
1521+        # Next, we put a fake block hash tree.
1522+        d.addCallback(lambda ignored:
1523+            mw.put_blockhashes(self.block_hash_tree))
1524+        # The block hash tree got inserted at:
1525+        #  header + salts + blocks: 251 bytes
1526+        #  encrypted private key:   7 bytes
1527+        #       = 258 bytes
1528+        expected_block_hash_offset = 258
1529+        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
1530+        d.addCallback(lambda ignored:
1531+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
1532+                                 {0: [self.block_hash_tree_s]}))
1533+
1534+        # Next, put a fake share hash chain
1535+        d.addCallback(lambda ignored:
1536+            mw.put_sharehashes(self.share_hash_chain))
1537+        # The share hash chain got inserted at:
1538+        # header + salts + blocks + private key = 258 bytes
1539+        # block hash tree:                        32 * 6 = 192 bytes
1540+        #   = 450 bytes
1541+        expected_share_hash_offset = 450
1542+        d.addCallback(lambda ignored:
1543+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
1544+                                 {0: [self.share_hash_chain_s]}))
1545+
1546+        # Next, we put what is supposed to be the root hash of
1547+        # our share hash tree but isn't, along with the flat hash
1548+        # of all the salts.
1549+        d.addCallback(lambda ignored:
1550+            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
1551+        # The root hash gets inserted at byte 9 (its position is in the header,
1552+        # and is fixed). The salt is right after it.
1553+        def _check(ignored):
1554+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
1555+                                 {0: [self.root_hash]})
1556+            self.failUnlessEqual(read("si1", [0], [(41, 32)]),
1557+                                 {0: [self.salt_hash]})
1558+        d.addCallback(_check)
1559+
1560+        # Next, we put a signature of the header block.
1561+        d.addCallback(lambda ignored:
1562+            mw.put_signature(self.signature))
1563+        # The signature gets written to:
1564+        #   header + salts + blocks + block and share hash tree = 654
1565+        expected_signature_offset = 654
1566+        self.failUnlessEqual(len(self.signature), 9)
1567+        d.addCallback(lambda ignored:
1568+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
1569+                                 {0: [self.signature]}))
1570+
1571+        # Next, we put the verification key
1572+        d.addCallback(lambda ignored:
1573+            mw.put_verification_key(self.verification_key))
1574+        # The verification key gets written to:
1575+        #   654 + 9 = 663 bytes
1576+        expected_verification_key_offset = 663
1577+        self.failUnlessEqual(len(self.verification_key), 6)
1578+        d.addCallback(lambda ignored:
1579+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
1580+                                 {0: [self.verification_key]}))
1581+
1582+        def _check_signable(ignored):
1583+            # Make sure that the signable is what we think it should be.
1584+            signable = mw.get_signable()
1585+            verno, seq, roothash, salthash, k, n, segsize, datalen = \
1586+                                            struct.unpack(">BQ32s32sBBQQ",
1587+                                                          signable)
1588+            self.failUnlessEqual(verno, 1)
1589+            self.failUnlessEqual(seq, 0)
1590+            self.failUnlessEqual(roothash, self.root_hash)
1591+            self.failUnlessEqual(salthash, self.salt_hash)
1592+            self.failUnlessEqual(k, 3)
1593+            self.failUnlessEqual(n, 10)
1594+            self.failUnlessEqual(segsize, 6)
1595+            self.failUnlessEqual(datalen, 36)
1596+        d.addCallback(_check_signable)
1597+        # Next, we cause the offset table to be published.
1598+        d.addCallback(lambda ignored:
1599+            mw.finish_publishing())
1600+        expected_eof_offset = 669
1601+
1602+        # The offset table starts at byte 91. Happily, we have already
1603+        # worked out most of these offsets above, but we want to make
1604+        # sure that the representation on disk agrees what what we've
1605+        # calculated.
1606+        #
1607+        # (we don't have an explicit offset for the AES salts, because
1608+        # we know that they start right after the header)
1609+        def _check_offsets(ignored):
1610+            # Check the version number to make sure that it is correct.
1611+            expected_version_number = struct.pack(">B", 1)
1612+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
1613+                                 {0: [expected_version_number]})
1614+            # Check the sequence number to make sure that it is correct
1615+            expected_sequence_number = struct.pack(">Q", 0)
1616+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
1617+                                 {0: [expected_sequence_number]})
1618+            # Check that the encoding parameters (k, N, segement size, data
1619+            # length) are what they should be. These are  3, 10, 6, 36
1620+            expected_k = struct.pack(">B", 3)
1621+            self.failUnlessEqual(read("si1", [0], [(73, 1)]),
1622+                                 {0: [expected_k]})
1623+            expected_n = struct.pack(">B", 10)
1624+            self.failUnlessEqual(read("si1", [0], [(74, 1)]),
1625+                                 {0: [expected_n]})
1626+            expected_segment_size = struct.pack(">Q", 6)
1627+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
1628+                                 {0: [expected_segment_size]})
1629+            expected_data_length = struct.pack(">Q", 36)
1630+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
1631+                                 {0: [expected_data_length]})
1632+            # 91          4           The offset of the share data
1633+            expected_offset = struct.pack(">L", 239)
1634+            self.failUnlessEqual(read("si1", [0], [(91, 4)]),
1635+                                 {0: [expected_offset]})
1636+            # 95          8           The offset of the encrypted private key
1637+            expected_offset = struct.pack(">Q", expected_private_key_offset)
1638+            self.failUnlessEqual(read("si1", [0], [(95, 8)]),
1639+                                 {0: [expected_offset]})
1640+            # 103         8           The offset of the block hash tree
1641+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
1642+            self.failUnlessEqual(read("si1", [0], [(103, 8)]),
1643+                                 {0: [expected_offset]})
1644+            # 111         8           The offset of the share hash chain
1645+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
1646+            self.failUnlessEqual(read("si1", [0], [(111, 8)]),
1647+                                 {0: [expected_offset]})
1648+            # 119         8           The offset of the signature
1649+            expected_offset = struct.pack(">Q", expected_signature_offset)
1650+            self.failUnlessEqual(read("si1", [0], [(119, 8)]),
1651+                                 {0: [expected_offset]})
1652+            # 127         8           The offset of the verification key
1653+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
1654+            self.failUnlessEqual(read("si1", [0], [(127, 8)]),
1655+                                 {0: [expected_offset]})
1656+            # 135         8           offset of the EOF
1657+            expected_offset = struct.pack(">Q", expected_eof_offset)
1658+            self.failUnlessEqual(read("si1", [0], [(135, 8)]),
1659+                                 {0: [expected_offset]})
1660+            # = 143 bytes in total.
1661+        d.addCallback(_check_offsets)
1662+        return d
1663+
1664+    def _make_new_mw(self, si, share, datalength=36):
1665+        # This is a file of size 36 bytes. Since it has a segment
1666+        # size of 6, we know that it has 6 byte segments, which will
1667+        # be split into blocks of 2 bytes because our FEC k
1668+        # parameter is 3.
1669+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
1670+                                6, datalength)
1671+        return mw
1672+
1673+
1674+    def test_write_rejected_with_too_many_blocks(self):
1675+        mw = self._make_new_mw("si0", 0)
1676+
1677+        # Try writing too many blocks. We should not be able to write
1678+        # more than 6
1679+        # blocks into each share.
1680+        d = defer.succeed(None)
1681+        for i in xrange(6):
1682+            d.addCallback(lambda ignored, i=i:
1683+                mw.put_block(self.block, i, self.salt))
1684+        d.addCallback(lambda ignored:
1685+            self.shouldFail(LayoutInvalid, "too many blocks",
1686+                            None,
1687+                            mw.put_block, self.block, 7, self.salt))
1688+        return d
1689+
1690+
1691+    def test_write_rejected_with_invalid_salt(self):
1692+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
1693+        # less should cause an error.
1694+        mw = self._make_new_mw("si1", 0)
1695+        bad_salt = "a" * 17 # 17 bytes
1696+        d = defer.succeed(None)
1697+        d.addCallback(lambda ignored:
1698+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
1699+                            None, mw.put_block, self.block, 7, bad_salt))
1700+        return d
1701+
1702+
1703+    def test_write_rejected_with_invalid_salt_hash(self):
1704+        # Try writing an invalid salt hash. These should be SHA256d, and
1705+        # 32 bytes long as a result.
1706+        mw = self._make_new_mw("si2", 0)
1707+        invalid_salt_hash = "b" * 31
1708+        d = defer.succeed(None)
1709+        # Before this test can work, we need to put some blocks + salts,
1710+        # a block hash tree, and a share hash tree. Otherwise, we'll see
1711+        # failures that match what we are looking for, but are caused by
1712+        # the constraints imposed on operation ordering.
1713+        for i in xrange(6):
1714+            d.addCallback(lambda ignored, i=i:
1715+                mw.put_block(self.block, i, self.salt))
1716+        d.addCallback(lambda ignored:
1717+            mw.put_encprivkey(self.encprivkey))
1718+        d.addCallback(lambda ignored:
1719+            mw.put_blockhashes(self.block_hash_tree))
1720+        d.addCallback(lambda ignored:
1721+            mw.put_sharehashes(self.share_hash_chain))
1722+        d.addCallback(lambda ignored:
1723+            self.shouldFail(LayoutInvalid, "invalid root hash",
1724+                            None, mw.put_root_and_salt_hashes,
1725+                            self.root_hash, invalid_salt_hash))
1726+        return d
1727+
1728+
1729+    def test_write_rejected_with_invalid_root_hash(self):
1730+        # Try writing an invalid root hash. This should be SHA256d, and
1731+        # 32 bytes long as a result.
1732+        mw = self._make_new_mw("si2", 0)
1733+        # 17 bytes != 32 bytes
1734+        invalid_root_hash = "a" * 17
1735+        d = defer.succeed(None)
1736+        # Before this test can work, we need to put some blocks + salts,
1737+        # a block hash tree, and a share hash tree. Otherwise, we'll see
1738+        # failures that match what we are looking for, but are caused by
1739+        # the constraints imposed on operation ordering.
1740+        for i in xrange(6):
1741+            d.addCallback(lambda ignored, i=i:
1742+                mw.put_block(self.block, i, self.salt))
1743+        d.addCallback(lambda ignored:
1744+            mw.put_encprivkey(self.encprivkey))
1745+        d.addCallback(lambda ignored:
1746+            mw.put_blockhashes(self.block_hash_tree))
1747+        d.addCallback(lambda ignored:
1748+            mw.put_sharehashes(self.share_hash_chain))
1749+        d.addCallback(lambda ignored:
1750+            self.shouldFail(LayoutInvalid, "invalid root hash",
1751+                            None, mw.put_root_and_salt_hashes,
1752+                            invalid_root_hash, self.salt_hash))
1753+        return d
1754+
1755+
1756+    def test_write_rejected_with_invalid_blocksize(self):
1757+        # The blocksize implied by the writer that we get from
1758+        # _make_new_mw is 2bytes -- any more or any less than this
1759+        # should be cause for failure, unless it is the tail segment, in
1760+        # which case it may not be failure.
1761+        invalid_block = "a"
1762+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
1763+                                             # one byte blocks
1764+        # 1 bytes != 2 bytes
1765+        d = defer.succeed(None)
1766+        d.addCallback(lambda ignored, invalid_block=invalid_block:
1767+            self.shouldFail(LayoutInvalid, "test blocksize too small",
1768+                            None, mw.put_block, invalid_block, 0,
1769+                            self.salt))
1770+        invalid_block = invalid_block * 3
1771+        # 3 bytes != 2 bytes
1772+        d.addCallback(lambda ignored:
1773+            self.shouldFail(LayoutInvalid, "test blocksize too large",
1774+                            None,
1775+                            mw.put_block, invalid_block, 0, self.salt))
1776+        for i in xrange(5):
1777+            d.addCallback(lambda ignored, i=i:
1778+                mw.put_block(self.block, i, self.salt))
1779+        # Try to put an invalid tail segment
1780+        d.addCallback(lambda ignored:
1781+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
1782+                            None,
1783+                            mw.put_block, self.block, 5, self.salt))
1784+        valid_block = "a"
1785+        d.addCallback(lambda ignored:
1786+            mw.put_block(valid_block, 5, self.salt))
1787+        return d
1788+
1789+
1790+    def test_write_enforces_order_constraints(self):
1791+        # We require that the MDMFSlotWriteProxy be interacted with in a
1792+        # specific way.
1793+        # That way is:
1794+        # 0: __init__
1795+        # 1: write blocks and salts
1796+        # 2: Write the encrypted private key
1797+        # 3: Write the block hashes
1798+        # 4: Write the share hashes
1799+        # 5: Write the root hash and salt hash
1800+        # 6: Write the signature and verification key
1801+        # 7: Write the file.
1802+        #
1803+        # Some of these can be performed out-of-order, and some can't.
1804+        # The dependencies that I want to test here are:
1805+        #  - Private key before block hashes
1806+        #  - share hashes and block hashes before root hash
1807+        #  - root hash before signature
1808+        #  - signature before verification key
1809+        mw0 = self._make_new_mw("si0", 0)
1810+        # Write some shares
1811+        d = defer.succeed(None)
1812+        for i in xrange(6):
1813+            d.addCallback(lambda ignored, i=i:
1814+                mw0.put_block(self.block, i, self.salt))
1815+        # Try to write the block hashes before writing the encrypted
1816+        # private key
1817+        d.addCallback(lambda ignored:
1818+            self.shouldFail(LayoutInvalid, "block hashes before key",
1819+                            None, mw0.put_blockhashes,
1820+                            self.block_hash_tree))
1821+
1822+        # Write the private key.
1823+        d.addCallback(lambda ignored:
1824+            mw0.put_encprivkey(self.encprivkey))
1825+
1826+
1827+        # Try to write the share hash chain without writing the block
1828+        # hash tree
1829+        d.addCallback(lambda ignored:
1830+            self.shouldFail(LayoutInvalid, "share hash chain before "
1831+                                           "block hash tree",
1832+                            None,
1833+                            mw0.put_sharehashes, self.share_hash_chain))
1834+
1835+        # Try to write the root hash and salt hash without writing either the
1836+        # block hashes or the share hashes
1837+        d.addCallback(lambda ignored:
1838+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
1839+                            None,
1840+                            mw0.put_root_and_salt_hashes,
1841+                            self.root_hash, self.salt_hash))
1842+
1843+        # Now write the block hashes and try again
1844+        d.addCallback(lambda ignored:
1845+            mw0.put_blockhashes(self.block_hash_tree))
1846+        d.addCallback(lambda ignored:
1847+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
1848+                            None, mw0.put_root_and_salt_hashes,
1849+                            self.root_hash, self.salt_hash))
1850+
1851+        # We haven't yet put the root hash on the share, so we shouldn't
1852+        # be able to sign it.
1853+        d.addCallback(lambda ignored:
1854+            self.shouldFail(LayoutInvalid, "signature before root hash",
1855+                            None, mw0.put_signature, self.signature))
1856+
1857+        d.addCallback(lambda ignored:
1858+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
1859+
1860+        # ..and, since that fails, we also shouldn't be able to put the
1861+        # verification key.
1862+        d.addCallback(lambda ignored:
1863+            self.shouldFail(LayoutInvalid, "key before signature",
1864+                            None, mw0.put_verification_key,
1865+                            self.verification_key))
1866+
1867+        # Now write the share hashes and verify that it works.
1868+        d.addCallback(lambda ignored:
1869+            mw0.put_sharehashes(self.share_hash_chain))
1870+
1871+        # We should still be unable to sign the header
1872+        d.addCallback(lambda ignored:
1873+            self.shouldFail(LayoutInvalid, "signature before hashes",
1874+                            None,
1875+                            mw0.put_signature, self.signature))
1876+
1877+        # We should be able to write the root hash now too
1878+        d.addCallback(lambda ignored:
1879+            mw0.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
1880+
1881+        # We should still be unable to put the verification key
1882+        d.addCallback(lambda ignored:
1883+            self.shouldFail(LayoutInvalid, "key before signature",
1884+                            None, mw0.put_verification_key,
1885+                            self.verification_key))
1886+
1887+        d.addCallback(lambda ignored:
1888+            mw0.put_signature(self.signature))
1889+
1890+        # We shouldn't be able to write the offsets to the remote server
1891+        # until the offset table is finished; IOW, until we have written
1892+        # the verification key.
1893+        d.addCallback(lambda ignored:
1894+            self.shouldFail(LayoutInvalid, "offsets before verification key",
1895+                            None,
1896+                            mw0.finish_publishing))
1897+
1898+        d.addCallback(lambda ignored:
1899+            mw0.put_verification_key(self.verification_key))
1900+        return d
1901+
1902+
1903+    def test_end_to_end(self):
1904+        mw = self._make_new_mw("si1", 0)
1905+        # Write a share using the mutable writer, and make sure that the
1906+        # reader knows how to read everything back to us.
1907+        d = defer.succeed(None)
1908+        for i in xrange(6):
1909+            d.addCallback(lambda ignored, i=i:
1910+                mw.put_block(self.block, i, self.salt))
1911+        d.addCallback(lambda ignored:
1912+            mw.put_encprivkey(self.encprivkey))
1913+        d.addCallback(lambda ignored:
1914+            mw.put_blockhashes(self.block_hash_tree))
1915+        d.addCallback(lambda ignored:
1916+            mw.put_sharehashes(self.share_hash_chain))
1917+        d.addCallback(lambda ignored:
1918+            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
1919+        d.addCallback(lambda ignored:
1920+            mw.put_signature(self.signature))
1921+        d.addCallback(lambda ignored:
1922+            mw.put_verification_key(self.verification_key))
1923+        d.addCallback(lambda ignored:
1924+            mw.finish_publishing())
1925+
1926+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
1927+        def _check_block_and_salt((block, salt)):
1928+            self.failUnlessEqual(block, self.block)
1929+            self.failUnlessEqual(salt, self.salt)
1930+
1931+        for i in xrange(6):
1932+            d.addCallback(lambda ignored, i=i:
1933+                mr.get_block_and_salt(i))
1934+            d.addCallback(_check_block_and_salt)
1935+
1936+        d.addCallback(lambda ignored:
1937+            mr.get_encprivkey())
1938+        d.addCallback(lambda encprivkey:
1939+            self.failUnlessEqual(self.encprivkey, encprivkey))
1940+
1941+        d.addCallback(lambda ignored:
1942+            mr.get_blockhashes())
1943+        d.addCallback(lambda blockhashes:
1944+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
1945+
1946+        d.addCallback(lambda ignored:
1947+            mr.get_sharehashes())
1948+        d.addCallback(lambda sharehashes:
1949+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
1950+
1951+        d.addCallback(lambda ignored:
1952+            mr.get_signature())
1953+        d.addCallback(lambda signature:
1954+            self.failUnlessEqual(signature, self.signature))
1955+
1956+        d.addCallback(lambda ignored:
1957+            mr.get_verification_key())
1958+        d.addCallback(lambda verification_key:
1959+            self.failUnlessEqual(verification_key, self.verification_key))
1960+
1961+        d.addCallback(lambda ignored:
1962+            mr.get_seqnum())
1963+        d.addCallback(lambda seqnum:
1964+            self.failUnlessEqual(seqnum, 0))
1965+
1966+        d.addCallback(lambda ignored:
1967+            mr.get_root_hash())
1968+        d.addCallback(lambda root_hash:
1969+            self.failUnlessEqual(self.root_hash, root_hash))
1970+
1971+        d.addCallback(lambda ignored:
1972+            mr.get_salt_hash())
1973+        d.addCallback(lambda salt_hash:
1974+            self.failUnlessEqual(self.salt_hash, salt_hash))
1975+
1976+        d.addCallback(lambda ignored:
1977+            mr.get_encoding_parameters())
1978+        def _check_encoding_parameters((k, n, segsize, datalen)):
1979+            self.failUnlessEqual(k, 3)
1980+            self.failUnlessEqual(n, 10)
1981+            self.failUnlessEqual(segsize, 6)
1982+            self.failUnlessEqual(datalen, 36)
1983+        d.addCallback(_check_encoding_parameters)
1984+
1985+        d.addCallback(lambda ignored:
1986+            mr.get_checkstring())
1987+        d.addCallback(lambda checkstring:
1988+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
1989+        return d
1990+
1991+
1992+    def test_is_sdmf(self):
1993+        # The MDMFSlotReadProxy should also know how to read SDMF files,
1994+        # since it will encounter them on the grid. Callers use the
1995+        # is_sdmf method to test this.
1996+        self.write_sdmf_share_to_server("si1")
1997+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
1998+        d = mr.is_sdmf()
1999+        d.addCallback(lambda issdmf:
2000+            self.failUnless(issdmf))
2001+        return d
2002+
2003+
2004+    def test_reads_sdmf(self):
2005+        # The slot read proxy should, naturally, know how to tell us
2006+        # about data in the SDMF format
2007+        self.write_sdmf_share_to_server("si1")
2008+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
2009+        d = defer.succeed(None)
2010+        d.addCallback(lambda ignored:
2011+            mr.is_sdmf())
2012+        d.addCallback(lambda issdmf:
2013+            self.failUnless(issdmf))
2014+
2015+        # What do we need to read?
2016+        #  - The sharedata
2017+        #  - The salt
2018+        d.addCallback(lambda ignored:
2019+            mr.get_block_and_salt(0))
2020+        def _check_block_and_salt(results):
2021+            block, salt = results
2022+            self.failUnlessEqual(block, self.block * 6)
2023+            self.failUnlessEqual(salt, self.salt)
2024+        d.addCallback(_check_block_and_salt)
2025+
2026+        #  - The blockhashes
2027+        d.addCallback(lambda ignored:
2028+            mr.get_blockhashes())
2029+        d.addCallback(lambda blockhashes:
2030+            self.failUnlessEqual(self.block_hash_tree,
2031+                                 blockhashes,
2032+                                 blockhashes))
2033+        #  - The sharehashes
2034+        d.addCallback(lambda ignored:
2035+            mr.get_sharehashes())
2036+        d.addCallback(lambda sharehashes:
2037+            self.failUnlessEqual(self.share_hash_chain,
2038+                                 sharehashes))
2039+        #  - The keys
2040+        d.addCallback(lambda ignored:
2041+            mr.get_encprivkey())
2042+        d.addCallback(lambda encprivkey:
2043+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
2044+        d.addCallback(lambda ignored:
2045+            mr.get_verification_key())
2046+        d.addCallback(lambda verification_key:
2047+            self.failUnlessEqual(verification_key,
2048+                                 self.verification_key,
2049+                                 verification_key))
2050+        #  - The signature
2051+        d.addCallback(lambda ignored:
2052+            mr.get_signature())
2053+        d.addCallback(lambda signature:
2054+            self.failUnlessEqual(signature, self.signature, signature))
2055+
2056+        #  - The sequence number
2057+        d.addCallback(lambda ignored:
2058+            mr.get_seqnum())
2059+        d.addCallback(lambda seqnum:
2060+            self.failUnlessEqual(seqnum, 0, seqnum))
2061+
2062+        #  - The root hash
2063+        #  - The salt hash (to verify that it is None)
2064+        d.addCallback(lambda ignored:
2065+            mr.get_root_hash())
2066+        d.addCallback(lambda root_hash:
2067+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
2068+        d.addCallback(lambda ignored:
2069+            mr.get_salt_hash())
2070+        d.addCallback(lambda salt_hash:
2071+            self.failIf(salt_hash))
2072+        return d
2073+
2074+
2075+    def test_only_reads_one_segment_sdmf(self):
2076+        # SDMF shares have only one segment, so it doesn't make sense to
2077+        # read more segments than that. The reader should know this and
2078+        # complain if we try to do that.
2079+        self.write_sdmf_share_to_server("si1")
2080+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
2081+        d = defer.succeed(None)
2082+        d.addCallback(lambda ignored:
2083+            mr.is_sdmf())
2084+        d.addCallback(lambda issdmf:
2085+            self.failUnless(issdmf))
2086+        d.addCallback(lambda ignored:
2087+            self.shouldFail(LayoutInvalid, "test bad segment",
2088+                            None,
2089+                            mr.get_block_and_salt, 1))
2090+        return d
2091+
2092+
2093+    def test_read_with_prefetched_mdmf_data(self):
2094+        # The MDMFSlotReadProxy will prefill certain fields if you pass
2095+        # it data that you have already fetched. This is useful for
2096+        # cases like the Servermap, which prefetches ~2kb of data while
2097+        # finding out which shares are on the remote peer so that it
2098+        # doesn't waste round trips.
2099+        mdmf_data = self.build_test_mdmf_share()
2100+        # We're telling it enough to figure out whether it is SDMF or
2101+        # MDMF.
2102+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:1])
2103+        self.failUnlessEqual(mr._version_number, MDMF_VERSION)
2104+
2105+        # Now we're telling it more, but still not enough to flesh out
2106+        # the rest of the encoding parameter, so none of them should be
2107+        # set.
2108+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:10])
2109+        self.failUnlessEqual(mr._version_number, MDMF_VERSION)
2110+        self.failIf(mr._sequence_number)
2111+
2112+        # This should be enough to flesh out the encoding parameters of
2113+        # an MDMF file.
2114+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:91])
2115+        self.failUnlessEqual(mr._version_number, MDMF_VERSION)
2116+        self.failUnlessEqual(mr._root_hash, self.root_hash),
2117+        self.failUnlessEqual(mr._sequence_number, 0)
2118+        self.failUnlessEqual(mr._required_shares, 3)
2119+        self.failUnlessEqual(mr._total_shares, 10)
2120+
2121+        # This should be enough to fill in the encoding parameters and
2122+        # a little more, but not enough to complete the offset table.
2123+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:100])
2124+        self.failUnlessEqual(mr._version_number, MDMF_VERSION)
2125+        self.failUnlessEqual(mr._root_hash, self.root_hash)
2126+        self.failUnlessEqual(mr._sequence_number, 0)
2127+        self.failUnlessEqual(mr._required_shares, 3)
2128+        self.failUnlessEqual(mr._total_shares, 10)
2129+        self.failIf(mr._offsets)
2130+
2131+        # This should be enough to fill in both the encoding parameters
2132+        # and the table of offsets
2133+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:143])
2134+        self.failUnlessEqual(mr._version_number, MDMF_VERSION)
2135+        self.failUnlessEqual(mr._root_hash, self.root_hash)
2136+        self.failUnlessEqual(mr._sequence_number, 0)
2137+        self.failUnlessEqual(mr._required_shares, 3)
2138+        self.failUnlessEqual(mr._total_shares, 10)
2139+        self.failUnless(mr._offsets)
2140+
2141+
2142+    def test_read_with_prefetched_sdmf_data(self):
2143+        sdmf_data = self.build_test_sdmf_share()
2144+        # Feed it just enough data to check the share type
2145+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:1])
2146+        self.failUnlessEqual(mr._version_number, SDMF_VERSION)
2147+        self.failIf(mr._sequence_number)
2148+
2149+        # Now feed it more data, but not enough data to populate the
2150+        # encoding parameters. The results should be exactly the same as
2151+        # before.
2152+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:10])
2153+        self.failUnlessEqual(mr._version_number, SDMF_VERSION)
2154+        self.failIf(mr._sequence_number)
2155+
2156+        # Now feed it enough data to populate the encoding parameters
2157+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:75])
2158+        self.failUnlessEqual(mr._version_number, SDMF_VERSION)
2159+        self.failUnlessEqual(mr._sequence_number, 0)
2160+        self.failUnlessEqual(mr._root_hash, self.root_hash)
2161+        self.failUnlessEqual(mr._required_shares, 3)
2162+        self.failUnlessEqual(mr._total_shares, 10)
2163+
2164+        # Now feed it enough data to populate the encoding parameters
2165+        # and then some, but not enough to fill in the offset table.
2166+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:100])
2167+        self.failUnlessEqual(mr._version_number, SDMF_VERSION)
2168+        self.failUnlessEqual(mr._sequence_number, 0)
2169+        self.failUnlessEqual(mr._root_hash, self.root_hash)
2170+        self.failUnlessEqual(mr._required_shares, 3)
2171+        self.failUnlessEqual(mr._total_shares, 10)
2172+        self.failIf(mr._offsets)
2173+
2174+        # Now fill in the offset table.
2175+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:107])
2176+        self.failUnlessEqual(mr._version_number, SDMF_VERSION)
2177+        self.failUnlessEqual(mr._sequence_number, 0)
2178+        self.failUnlessEqual(mr._root_hash, self.root_hash)
2179+        self.failUnlessEqual(mr._required_shares, 3)
2180+        self.failUnlessEqual(mr._total_shares, 10)
2181+        self.failUnless(mr._offsets)
2182+
2183+
2184+    def test_read_with_prefetched_bogus_data(self):
2185+        bogus_data = "kjkasdlkjsjkdjksajdjsadjsajdskaj"
2186+        # This shouldn't do anything.
2187+        mr = MDMFSlotReadProxy(self.rref, "si1", 0, bogus_data)
2188+        self.failIf(mr._version_number)
2189+
2190+
2191+    def test_read_with_empty_mdmf_file(self):
2192+        # Some tests upload a file with no contents to test things
2193+        # unrelated to the actual handling of the content of the file.
2194+        # The reader should behave intelligently in these cases.
2195+        self.write_test_share_to_server("si1", empty=True)
2196+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
2197+        # We should be able to get the encoding parameters, and they
2198+        # should be correct.
2199+        d = defer.succeed(None)
2200+        d.addCallback(lambda ignored:
2201+            mr.get_encoding_parameters())
2202+        def _check_encoding_parameters(params):
2203+            self.failUnlessEqual(len(params), 4)
2204+            k, n, segsize, datalen = params
2205+            self.failUnlessEqual(k, 3)
2206+            self.failUnlessEqual(n, 10)
2207+            self.failUnlessEqual(segsize, 0)
2208+            self.failUnlessEqual(datalen, 0)
2209+        d.addCallback(_check_encoding_parameters)
2210+
2211+        # We should not be able to fetch a block, since there are no
2212+        # blocks to fetch
2213+        d.addCallback(lambda ignored:
2214+            self.shouldFail(LayoutInvalid, "get block on empty file",
2215+                            None,
2216+                            mr.get_block_and_salt, 0))
2217+        return d
2218+
2219+
2220+    def test_read_with_empty_sdmf_file(self):
2221+        self.write_sdmf_share_to_server("si1", empty=True)
2222+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
2223+        # We should be able to get the encoding parameters, and they
2224+        # should be correct
2225+        d = defer.succeed(None)
2226+        d.addCallback(lambda ignored:
2227+            mr.get_encoding_parameters())
2228+        def _check_encoding_parameters(params):
2229+            self.failUnlessEqual(len(params), 4)
2230+            k, n, segsize, datalen = params
2231+            self.failUnlessEqual(k, 3)
2232+            self.failUnlessEqual(n, 10)
2233+            self.failUnlessEqual(segsize, 0)
2234+            self.failUnlessEqual(datalen, 0)
2235+        d.addCallback(_check_encoding_parameters)
2236+
2237+        # It does not make sense to get a block in this format, so we
2238+        # should not be able to.
2239+        d.addCallback(lambda ignored:
2240+            self.shouldFail(LayoutInvalid, "get block on an empty file",
2241+                            None,
2242+                            mr.get_block_and_salt, 0))
2243+        return d
2244+
2245+
2246+    def test_verinfo_with_sdmf_file(self):
2247+        self.write_sdmf_share_to_server("si1")
2248+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
2249+        # We should be able to get the version information.
2250+        d = defer.succeed(None)
2251+        d.addCallback(lambda ignored:
2252+            mr.get_verinfo())
2253+        def _check_verinfo(verinfo):
2254+            self.failUnless(verinfo)
2255+            self.failUnlessEqual(len(verinfo), 9)
2256+            (seqnum,
2257+             root_hash,
2258+             salt,
2259+             segsize,
2260+             datalen,
2261+             k,
2262+             n,
2263+             prefix,
2264+             offsets) = verinfo
2265+            self.failUnlessEqual(seqnum, 0)
2266+            self.failUnlessEqual(root_hash, self.root_hash)
2267+            self.failUnlessEqual(salt, self.salt)
2268+            self.failUnlessEqual(segsize, 36)
2269+            self.failUnlessEqual(datalen, 36)
2270+            self.failUnlessEqual(k, 3)
2271+            self.failUnlessEqual(n, 10)
2272+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
2273+                                          0,
2274+                                          seqnum,
2275+                                          root_hash,
2276+                                          salt,
2277+                                          k,
2278+                                          n,
2279+                                          segsize,
2280+                                          datalen)
2281+            self.failUnlessEqual(prefix, expected_prefix)
2282+            self.failUnlessEqual(offsets, self.offsets)
2283+        d.addCallback(_check_verinfo)
2284+        return d
2285+
2286+
2287+    def test_verinfo_with_mdmf_file(self):
2288+        self.write_test_share_to_server("si1")
2289+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
2290+        d = defer.succeed(None)
2291+        d.addCallback(lambda ignored:
2292+            mr.get_verinfo())
2293+        def _check_verinfo(verinfo):
2294+            self.failUnless(verinfo)
2295+            self.failUnlessEqual(len(verinfo), 9)
2296+            (seqnum,
2297+             root_hash,
2298+             salt_hash,
2299+             segsize,
2300+             datalen,
2301+             k,
2302+             n,
2303+             prefix,
2304+             offsets) = verinfo
2305+            self.failUnlessEqual(seqnum, 0)
2306+            self.failUnlessEqual(root_hash, self.root_hash)
2307+            self.failUnlessEqual(salt_hash, self.salt_hash)
2308+            self.failUnlessEqual(segsize, 6)
2309+            self.failUnlessEqual(datalen, 36)
2310+            self.failUnlessEqual(k, 3)
2311+            self.failUnlessEqual(n, 10)
2312+            expected_prefix = struct.pack(">BQ32s32s BBQQ",
2313+                                          1,
2314+                                          seqnum,
2315+                                          root_hash,
2316+                                          salt_hash,
2317+                                          k,
2318+                                          n,
2319+                                          segsize,
2320+                                          datalen)
2321+            self.failUnlessEqual(prefix, expected_prefix)
2322+            self.failUnlessEqual(offsets, self.offsets)
2323+        d.addCallback(_check_verinfo)
2324+        return d
2325+
2326+
2327 class Stats(unittest.TestCase):
2328 
2329     def setUp(self):
2330}
2331[Alter MDMF proxy tests to reflect the new form of caching
2332Kevan Carstensen <kevan@isnotajoke.com>**20100614213459
2333 Ignore-this: 3e84dbd1b6ea103be36e0e98babe79d4
2334] {
2335hunk ./src/allmydata/test/test_storage.py 23
2336 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
2337      ReadBucketProxy
2338 from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
2339-                                     LayoutInvalid
2340+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
2341+                                     SIGNED_PREFIX
2342 from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
2343                                  SDMF_VERSION
2344 from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
2345hunk ./src/allmydata/test/test_storage.py 105
2346 
2347 class RemoteBucket:
2348 
2349+    def __init__(self):
2350+        self.read_count = 0
2351+        self.write_count = 0
2352+
2353     def callRemote(self, methname, *args, **kwargs):
2354         def _call():
2355             meth = getattr(self.target, "remote_" + methname)
2356hunk ./src/allmydata/test/test_storage.py 113
2357             return meth(*args, **kwargs)
2358+
2359+        if methname == "slot_readv":
2360+            self.read_count += 1
2361+        if methname == "slot_writev":
2362+            self.write_count += 1
2363+
2364         return defer.maybeDeferred(_call)
2365 
2366hunk ./src/allmydata/test/test_storage.py 121
2367+
2368 class BucketProxy(unittest.TestCase):
2369     def make_bucket(self, name, size):
2370         basedir = os.path.join("storage", "BucketProxy", name)
2371hunk ./src/allmydata/test/test_storage.py 2605
2372             mr.get_block_and_salt(0))
2373         def _check_block_and_salt(results):
2374             block, salt = results
2375+            # Our original file is 36 bytes long. Then each share is 12
2376+            # bytes in size. The share is composed entirely of the
2377+            # letter a. self.block contains 2 as, so 6 * self.block is
2378+            # what we are looking for.
2379             self.failUnlessEqual(block, self.block * 6)
2380             self.failUnlessEqual(salt, self.salt)
2381         d.addCallback(_check_block_and_salt)
2382hunk ./src/allmydata/test/test_storage.py 2687
2383         # finding out which shares are on the remote peer so that it
2384         # doesn't waste round trips.
2385         mdmf_data = self.build_test_mdmf_share()
2386-        # We're telling it enough to figure out whether it is SDMF or
2387-        # MDMF.
2388-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:1])
2389-        self.failUnlessEqual(mr._version_number, MDMF_VERSION)
2390-
2391-        # Now we're telling it more, but still not enough to flesh out
2392-        # the rest of the encoding parameter, so none of them should be
2393-        # set.
2394-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:10])
2395-        self.failUnlessEqual(mr._version_number, MDMF_VERSION)
2396-        self.failIf(mr._sequence_number)
2397-
2398-        # This should be enough to flesh out the encoding parameters of
2399-        # an MDMF file.
2400-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:91])
2401-        self.failUnlessEqual(mr._version_number, MDMF_VERSION)
2402-        self.failUnlessEqual(mr._root_hash, self.root_hash),
2403-        self.failUnlessEqual(mr._sequence_number, 0)
2404-        self.failUnlessEqual(mr._required_shares, 3)
2405-        self.failUnlessEqual(mr._total_shares, 10)
2406-
2407-        # This should be enough to fill in the encoding parameters and
2408-        # a little more, but not enough to complete the offset table.
2409-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:100])
2410-        self.failUnlessEqual(mr._version_number, MDMF_VERSION)
2411-        self.failUnlessEqual(mr._root_hash, self.root_hash)
2412-        self.failUnlessEqual(mr._sequence_number, 0)
2413-        self.failUnlessEqual(mr._required_shares, 3)
2414-        self.failUnlessEqual(mr._total_shares, 10)
2415-        self.failIf(mr._offsets)
2416+        self.write_test_share_to_server("si1")
2417+        def _make_mr(ignored, length):
2418+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
2419+            return mr
2420 
2421hunk ./src/allmydata/test/test_storage.py 2692
2422+        d = defer.succeed(None)
2423         # This should be enough to fill in both the encoding parameters
2424hunk ./src/allmydata/test/test_storage.py 2694
2425-        # and the table of offsets
2426-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:143])
2427-        self.failUnlessEqual(mr._version_number, MDMF_VERSION)
2428-        self.failUnlessEqual(mr._root_hash, self.root_hash)
2429-        self.failUnlessEqual(mr._sequence_number, 0)
2430-        self.failUnlessEqual(mr._required_shares, 3)
2431-        self.failUnlessEqual(mr._total_shares, 10)
2432-        self.failUnless(mr._offsets)
2433+        # and the table of offsets, which will complete the version
2434+        # information tuple.
2435+        d.addCallback(_make_mr, 143)
2436+        d.addCallback(lambda mr:
2437+            mr.get_verinfo())
2438+        def _check_verinfo(verinfo):
2439+            self.failUnless(verinfo)
2440+            self.failUnlessEqual(len(verinfo), 9)
2441+            (seqnum,
2442+             root_hash,
2443+             salt_hash,
2444+             segsize,
2445+             datalen,
2446+             k,
2447+             n,
2448+             prefix,
2449+             offsets) = verinfo
2450+            self.failUnlessEqual(seqnum, 0)
2451+            self.failUnlessEqual(root_hash, self.root_hash)
2452+            self.failUnlessEqual(salt_hash, self.salt_hash)
2453+            self.failUnlessEqual(segsize, 6)
2454+            self.failUnlessEqual(datalen, 36)
2455+            self.failUnlessEqual(k, 3)
2456+            self.failUnlessEqual(n, 10)
2457+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
2458+                                          1,
2459+                                          seqnum,
2460+                                          root_hash,
2461+                                          salt_hash,
2462+                                          k,
2463+                                          n,
2464+                                          segsize,
2465+                                          datalen)
2466+            self.failUnlessEqual(expected_prefix, prefix)
2467+            self.failUnlessEqual(self.rref.read_count, 0)
2468+        d.addCallback(_check_verinfo)
2469+        # This is not enough data to read a block and a share, so the
2470+        # wrapper should attempt to read this from the remote server.
2471+        d.addCallback(_make_mr, 143)
2472+        d.addCallback(lambda mr:
2473+            mr.get_block_and_salt(0))
2474+        def _check_block_and_salt((block, salt)):
2475+            self.failUnlessEqual(block, self.block)
2476+            self.failUnlessEqual(salt, self.salt)
2477+            self.failUnlessEqual(self.rref.read_count, 1)
2478+        # The file that we're playing with has 6 segments. Then there
2479+        # are 6 * 16 = 96 bytes of salts before we can write shares.
2480+        # Each block has two bytes, so 143 + 96 + 2 = 241 bytes should
2481+        # be enough to read one block.
2482+        d.addCallback(_make_mr, 241)
2483+        d.addCallback(lambda mr:
2484+            mr.get_block_and_salt(0))
2485+        d.addCallback(_check_block_and_salt)
2486+        return d
2487 
2488 
2489     def test_read_with_prefetched_sdmf_data(self):
2490hunk ./src/allmydata/test/test_storage.py 2752
2491         sdmf_data = self.build_test_sdmf_share()
2492-        # Feed it just enough data to check the share type
2493-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:1])
2494-        self.failUnlessEqual(mr._version_number, SDMF_VERSION)
2495-        self.failIf(mr._sequence_number)
2496-
2497-        # Now feed it more data, but not enough data to populate the
2498-        # encoding parameters. The results should be exactly the same as
2499-        # before.
2500-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:10])
2501-        self.failUnlessEqual(mr._version_number, SDMF_VERSION)
2502-        self.failIf(mr._sequence_number)
2503-
2504-        # Now feed it enough data to populate the encoding parameters
2505-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:75])
2506-        self.failUnlessEqual(mr._version_number, SDMF_VERSION)
2507-        self.failUnlessEqual(mr._sequence_number, 0)
2508-        self.failUnlessEqual(mr._root_hash, self.root_hash)
2509-        self.failUnlessEqual(mr._required_shares, 3)
2510-        self.failUnlessEqual(mr._total_shares, 10)
2511-
2512-        # Now feed it enough data to populate the encoding parameters
2513-        # and then some, but not enough to fill in the offset table.
2514-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:100])
2515-        self.failUnlessEqual(mr._version_number, SDMF_VERSION)
2516-        self.failUnlessEqual(mr._sequence_number, 0)
2517-        self.failUnlessEqual(mr._root_hash, self.root_hash)
2518-        self.failUnlessEqual(mr._required_shares, 3)
2519-        self.failUnlessEqual(mr._total_shares, 10)
2520-        self.failIf(mr._offsets)
2521-
2522-        # Now fill in the offset table.
2523-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:107])
2524-        self.failUnlessEqual(mr._version_number, SDMF_VERSION)
2525-        self.failUnlessEqual(mr._sequence_number, 0)
2526-        self.failUnlessEqual(mr._root_hash, self.root_hash)
2527-        self.failUnlessEqual(mr._required_shares, 3)
2528-        self.failUnlessEqual(mr._total_shares, 10)
2529-        self.failUnless(mr._offsets)
2530+        self.write_sdmf_share_to_server("si1")
2531+        def _make_mr(ignored, length):
2532+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
2533+            return mr
2534 
2535hunk ./src/allmydata/test/test_storage.py 2757
2536+        d = defer.succeed(None)
2537+        # This should be enough to get us the encoding parameters,
2538+        # offset table, and everything else we need to build a verinfo
2539+        # string.
2540+        d.addCallback(_make_mr, 107)
2541+        d.addCallback(lambda mr:
2542+            mr.get_verinfo())
2543+        def _check_verinfo(verinfo):
2544+            self.failUnless(verinfo)
2545+            self.failUnlessEqual(len(verinfo), 9)
2546+            (seqnum,
2547+             root_hash,
2548+             salt,
2549+             segsize,
2550+             datalen,
2551+             k,
2552+             n,
2553+             prefix,
2554+             offsets) = verinfo
2555+            self.failUnlessEqual(seqnum, 0)
2556+            self.failUnlessEqual(root_hash, self.root_hash)
2557+            self.failUnlessEqual(salt, self.salt)
2558+            self.failUnlessEqual(segsize, 36)
2559+            self.failUnlessEqual(datalen, 36)
2560+            self.failUnlessEqual(k, 3)
2561+            self.failUnlessEqual(n, 10)
2562+            expected_prefix = struct.pack(SIGNED_PREFIX,
2563+                                          0,
2564+                                          seqnum,
2565+                                          root_hash,
2566+                                          salt,
2567+                                          k,
2568+                                          n,
2569+                                          segsize,
2570+                                          datalen)
2571+            self.failUnlessEqual(expected_prefix, prefix)
2572+            self.failUnlessEqual(self.rref.read_count, 0)
2573+        d.addCallback(_check_verinfo)
2574+        # This shouldn't be enough to read any share data.
2575+        d.addCallback(_make_mr, 107)
2576+        d.addCallback(lambda mr:
2577+            mr.get_block_and_salt(0))
2578+        def _check_block_and_salt((block, salt)):
2579+            self.failUnlessEqual(block, self.block * 6)
2580+            self.failUnlessEqual(salt, self.salt)
2581+            # TODO: Fix the read routine so that it reads only the data
2582+            #       that it has cached if it can't read all of it.
2583+            self.failUnlessEqual(self.rref.read_count, 2)
2584 
2585hunk ./src/allmydata/test/test_storage.py 2806
2586-    def test_read_with_prefetched_bogus_data(self):
2587-        bogus_data = "kjkasdlkjsjkdjksajdjsadjsajdskaj"
2588-        # This shouldn't do anything.
2589-        mr = MDMFSlotReadProxy(self.rref, "si1", 0, bogus_data)
2590-        self.failIf(mr._version_number)
2591+        # This should be enough to read share data.
2592+        d.addCallback(_make_mr, self.offsets['share_data'])
2593+        d.addCallback(lambda mr:
2594+            mr.get_block_and_salt(0))
2595+        d.addCallback(_check_block_and_salt)
2596+        return d
2597 
2598 
2599     def test_read_with_empty_mdmf_file(self):
2600}
2601[Add tests and support functions for servermap tests
2602Kevan Carstensen <kevan@isnotajoke.com>**20100614213721
2603 Ignore-this: 583734d2f728fc80637b5c0c0f4c0fc
2604] {
2605hunk ./src/allmydata/test/test_mutable.py 103
2606         d = fireEventually()
2607         d.addCallback(lambda res: _call())
2608         return d
2609+
2610     def callRemoteOnly(self, methname, *args, **kwargs):
2611         d = self.callRemote(methname, *args, **kwargs)
2612         d.addBoth(lambda ignore: None)
2613hunk ./src/allmydata/test/test_mutable.py 152
2614             chr(ord(original[byte_offset]) ^ 0x01) +
2615             original[byte_offset+1:])
2616 
2617+def add_two(original, byte_offset):
2618+    # It isn't enough to simply flip the bit for the version number,
2619+    # because 1 is a valid version number. So we add two instead.
2620+    return (original[:byte_offset] +
2621+            chr(ord(original[byte_offset]) ^ 0x02) +
2622+            original[byte_offset+1:])
2623+
2624 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
2625     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
2626     # list of shnums to corrupt.
2627hunk ./src/allmydata/test/test_mutable.py 188
2628                 real_offset = offset1
2629             real_offset = int(real_offset) + offset2 + offset_offset
2630             assert isinstance(real_offset, int), offset
2631-            shares[shnum] = flip_bit(data, real_offset)
2632+            if offset1 == 0: # verbyte
2633+                f = add_two
2634+            else:
2635+                f = flip_bit
2636+            shares[shnum] = f(data, real_offset)
2637     return res
2638 
2639 def make_storagebroker(s=None, num_peers=10):
2640hunk ./src/allmydata/test/test_mutable.py 625
2641         d.addCallback(_created)
2642         return d
2643 
2644-    def publish_multiple(self):
2645+    def publish_mdmf(self):
2646+        # like publish_one, except that the result is guaranteed to be
2647+        # an MDMF file.
2648+        # self.CONTENTS should have more than one segment.
2649+        self.CONTENTS = "This is an MDMF file" * 100000
2650+        self._storage = FakeStorage()
2651+        self._nodemaker = make_nodemaker(self._storage)
2652+        self._storage_broker = self._nodemaker.storage_broker
2653+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
2654+        def _created(node):
2655+            self._fn = node
2656+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
2657+        d.addCallback(_created)
2658+        return d
2659+
2660+
2661+    def publish_sdmf(self):
2662+        # like publish_one, except that the result is guaranteed to be
2663+        # an SDMF file
2664+        self.CONTENTS = "This is an SDMF file" * 1000
2665+        self._storage = FakeStorage()
2666+        self._nodemaker = make_nodemaker(self._storage)
2667+        self._storage_broker = self._nodemaker.storage_broker
2668+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
2669+        def _created(node):
2670+            self._fn = node
2671+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
2672+        d.addCallback(_created)
2673+        return d
2674+
2675+
2676+    def publish_multiple(self, version=0):
2677         self.CONTENTS = ["Contents 0",
2678                          "Contents 1",
2679                          "Contents 2",
2680hunk ./src/allmydata/test/test_mutable.py 665
2681         self._copied_shares = {}
2682         self._storage = FakeStorage()
2683         self._nodemaker = make_nodemaker(self._storage)
2684-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
2685+        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
2686         def _created(node):
2687             self._fn = node
2688             # now create multiple versions of the same file, and accumulate
2689hunk ./src/allmydata/test/test_mutable.py 689
2690         d.addCallback(_created)
2691         return d
2692 
2693+
2694     def _copy_shares(self, ignored, index):
2695         shares = self._storage._peers
2696         # we need a deep copy
2697hunk ./src/allmydata/test/test_mutable.py 842
2698         self._storage._peers = {} # delete all shares
2699         ms = self.make_servermap
2700         d = defer.succeed(None)
2701-
2702+#
2703         d.addCallback(lambda res: ms(mode=MODE_CHECK))
2704         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
2705 
2706hunk ./src/allmydata/test/test_mutable.py 894
2707         return d
2708 
2709 
2710+    def test_servermapupdater_finds_mdmf_files(self):
2711+        # setUp already published an MDMF file for us. We just need to
2712+        # make sure that when we run the ServermapUpdater, the file is
2713+        # reported to have one recoverable version.
2714+        d = defer.succeed(None)
2715+        d.addCallback(lambda ignored:
2716+            self.publish_mdmf())
2717+        d.addCallback(lambda ignored:
2718+            self.make_servermap(mode=MODE_CHECK))
2719+        # Calling make_servermap also updates the servermap in the mode
2720+        # that we specify, so we just need to see what it says.
2721+        def _check_servermap(sm):
2722+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
2723+        d.addCallback(_check_servermap)
2724+        # Now, we upload more versions
2725+        d.addCallback(lambda ignored:
2726+            self.publish_multiple(version=1))
2727+        d.addCallback(lambda ignored:
2728+            self.make_servermap(mode=MODE_CHECK))
2729+        def _check_servermap_multiple(sm):
2730+            v = sm.recoverable_versions()
2731+            i = sm.unrecoverable_versions()
2732+        d.addCallback(_check_servermap_multiple)
2733+        return d
2734+    test_servermapupdater_finds_mdmf_files.todo = ("I don't know how to "
2735+                                                   "write this yet")
2736+
2737+
2738+    def test_servermapupdater_finds_sdmf_files(self):
2739+        d = defer.succeed(None)
2740+        d.addCallback(lambda ignored:
2741+            self.publish_sdmf())
2742+        d.addCallback(lambda ignored:
2743+            self.make_servermap(mode=MODE_CHECK))
2744+        d.addCallback(lambda servermap:
2745+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
2746+        return d
2747+
2748 
2749 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
2750     def setUp(self):
2751hunk ./src/allmydata/test/test_mutable.py 1084
2752         return d
2753 
2754     def test_corrupt_all_verbyte(self):
2755-        # when the version byte is not 0, we hit an UnknownVersionError error
2756-        # in unpack_share().
2757+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
2758+        # error in unpack_share().
2759         d = self._test_corrupt_all(0, "UnknownVersionError")
2760         def _check_servermap(servermap):
2761             # and the dump should mention the problems
2762hunk ./src/allmydata/test/test_mutable.py 1091
2763             s = StringIO()
2764             dump = servermap.dump(s).getvalue()
2765-            self.failUnless("10 PROBLEMS" in dump, dump)
2766+            self.failUnless("30 PROBLEMS" in dump, dump)
2767         d.addCallback(_check_servermap)
2768         return d
2769 
2770hunk ./src/allmydata/test/test_mutable.py 2153
2771         self.basedir = "mutable/Problems/test_privkey_query_missing"
2772         self.set_up_grid(num_servers=20)
2773         nm = self.g.clients[0].nodemaker
2774-        LARGE = "These are Larger contents" * 2000 # about 50KB
2775+        LARGE = "These are Larger contents" * 2000 # about 50KiB
2776         nm._node_cache = DevNullDictionary() # disable the nodecache
2777 
2778         d = nm.create_mutable_file(LARGE)
2779}
2780[Make a segmented downloader
2781Kevan Carstensen <kevan@isnotajoke.com>**20100623001332
2782 Ignore-this: f3543532a5d573cc884c17a4ebbf451e
2783 
2784 Rework the current mutable file Retrieve class to download segmented
2785 files. The rewrite preserves the semantics and basic conceptual state
2786 machine of the old Retrieve class, but adapts them to work with
2787 files with more than one segment, which involves a fairly substantial
2788 rewrite.
2789 
2790 I've also adapted some existing SDMF tests to work with the new
2791 downloader, as necessary.
2792 
2793 TODO:
2794     - Write tests for MDMF functionality.
2795     - Finish writing and testing salt functionality
2796] {
2797hunk ./src/allmydata/mutable/retrieve.py 9
2798 from twisted.python import failure
2799 from foolscap.api import DeadReferenceError, eventually, fireEventually
2800 from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
2801-from allmydata.util import hashutil, idlib, log
2802+from allmydata.util import hashutil, idlib, log, mathutil
2803 from allmydata import hashtree, codec
2804 from allmydata.storage.server import si_b2a
2805 from pycryptopp.cipher.aes import AES
2806hunk ./src/allmydata/mutable/retrieve.py 16
2807 from pycryptopp.publickey import rsa
2808 
2809 from allmydata.mutable.common import DictOfSets, CorruptShareError, UncoordinatedWriteError
2810-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
2811+from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data, \
2812+                                     MDMFSlotReadProxy
2813 
2814 class RetrieveStatus:
2815     implements(IRetrieveStatus)
2816hunk ./src/allmydata/mutable/retrieve.py 103
2817         self.verinfo = verinfo
2818         # during repair, we may be called upon to grab the private key, since
2819         # it wasn't picked up during a verify=False checker run, and we'll
2820-        # need it for repair to generate the a new version.
2821+        # need it for repair to generate a new version.
2822         self._need_privkey = fetch_privkey
2823         if self._node.get_privkey():
2824             self._need_privkey = False
2825hunk ./src/allmydata/mutable/retrieve.py 108
2826 
2827+        if self._need_privkey:
2828+            # TODO: Evaluate the need for this. We'll use it if we want
2829+            # to limit how many queries are on the wire for the privkey
2830+            # at once.
2831+            self._privkey_query_markers = [] # one Marker for each time we've
2832+                                             # tried to get the privkey.
2833+
2834         self._status = RetrieveStatus()
2835         self._status.set_storage_index(self._storage_index)
2836         self._status.set_helper(False)
2837hunk ./src/allmydata/mutable/retrieve.py 124
2838          offsets_tuple) = self.verinfo
2839         self._status.set_size(datalength)
2840         self._status.set_encoding(k, N)
2841+        self.readers = {}
2842 
2843     def get_status(self):
2844         return self._status
2845hunk ./src/allmydata/mutable/retrieve.py 148
2846         self.remaining_sharemap = DictOfSets()
2847         for (shnum, peerid, timestamp) in shares:
2848             self.remaining_sharemap.add(shnum, peerid)
2849+            # If the servermap update fetched anything, it fetched at least 1
2850+            # KiB, so we ask for that much.
2851+            # TODO: Change the cache methods to allow us to fetch all of the
2852+            # data that they have, then change this method to do that.
2853+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
2854+                                                               shnum,
2855+                                                               0,
2856+                                                               1000)
2857+            ss = self.servermap.connections[peerid]
2858+            reader = MDMFSlotReadProxy(ss,
2859+                                       self._storage_index,
2860+                                       shnum,
2861+                                       any_cache)
2862+            reader.peerid = peerid
2863+            self.readers[shnum] = reader
2864+
2865 
2866         self.shares = {} # maps shnum to validated blocks
2867hunk ./src/allmydata/mutable/retrieve.py 166
2868+        self._active_readers = [] # list of active readers for this dl.
2869+        self._validated_readers = set() # set of readers that we have
2870+                                        # validated the prefix of
2871+        self._block_hash_trees = {} # shnum => hashtree
2872+        # TODO: Make this into a file-backed consumer or something to
2873+        # conserve memory.
2874+        self._plaintext = ""
2875 
2876         # how many shares do we need?
2877hunk ./src/allmydata/mutable/retrieve.py 175
2878-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2879+        (seqnum,
2880+         root_hash,
2881+         IV,
2882+         segsize,
2883+         datalength,
2884+         k,
2885+         N,
2886+         prefix,
2887          offsets_tuple) = self.verinfo
2888hunk ./src/allmydata/mutable/retrieve.py 184
2889-        assert len(self.remaining_sharemap) >= k
2890-        # we start with the lowest shnums we have available, since FEC is
2891-        # faster if we're using "primary shares"
2892-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
2893-        for shnum in self.active_shnums:
2894-            # we use an arbitrary peer who has the share. If shares are
2895-            # doubled up (more than one share per peer), we could make this
2896-            # run faster by spreading the load among multiple peers. But the
2897-            # algorithm to do that is more complicated than I want to write
2898-            # right now, and a well-provisioned grid shouldn't have multiple
2899-            # shares per peer.
2900-            peerid = list(self.remaining_sharemap[shnum])[0]
2901-            self.get_data(shnum, peerid)
2902 
2903hunk ./src/allmydata/mutable/retrieve.py 185
2904-        # control flow beyond this point: state machine. Receiving responses
2905-        # from queries is the input. We might send out more queries, or we
2906-        # might produce a result.
2907 
2908hunk ./src/allmydata/mutable/retrieve.py 186
2909+        # We need one share hash tree for the entire file; its leaves
2910+        # are the roots of the block hash trees for the shares that
2911+        # comprise it, and its root is in the verinfo.
2912+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
2913+        self.share_hash_tree.set_hashes({0: root_hash})
2914+
2915+        # This will set up both the segment decoder and the tail segment
2916+        # decoder, as well as a variety of other instance variables that
2917+        # the download process will use.
2918+        self._setup_encoding_parameters()
2919+        assert len(self.remaining_sharemap) >= k
2920+
2921+        self.log("starting download")
2922+        self._add_active_peers()
2923+        # The download process beyond this is a state machine.
2924+        # _add_active_peers will select the peers that we want to use
2925+        # for the download, and then attempt to start downloading. After
2926+        # each segment, it will check for doneness, reacting to broken
2927+        # peers and corrupt shares as necessary. If it runs out of good
2928+        # peers before downloading all of the segments, _done_deferred
2929+        # will errback.  Otherwise, it will eventually callback with the
2930+        # contents of the mutable file.
2931         return self._done_deferred
2932 
2933hunk ./src/allmydata/mutable/retrieve.py 210
2934-    def get_data(self, shnum, peerid):
2935-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
2936-                 shnum=shnum,
2937-                 peerid=idlib.shortnodeid_b2a(peerid),
2938-                 level=log.NOISY)
2939-        ss = self.servermap.connections[peerid]
2940-        started = time.time()
2941-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2942+
2943+    def _setup_encoding_parameters(self):
2944+        """
2945+        I set up the encoding parameters, including k, n, the number
2946+        of segments associated with this file, and the segment decoder.
2947+        I do not set the tail segment decoder, which is set in the
2948+        method that decodes the tail segment, as it is single-use.
2949+        """
2950+        # XXX: Or is it? What if servers fail in the last step?
2951+        (seqnum,
2952+         root_hash,
2953+         IV,
2954+         segsize,
2955+         datalength,
2956+         k,
2957+         n,
2958+         known_prefix,
2959          offsets_tuple) = self.verinfo
2960hunk ./src/allmydata/mutable/retrieve.py 228
2961-        offsets = dict(offsets_tuple)
2962+        self._required_shares = k
2963+        self._total_shares = n
2964+        self._segment_size = segsize
2965+        self._data_length = datalength
2966+        if datalength and segsize:
2967+            self._num_segments = mathutil.div_ceil(datalength, segsize)
2968+            self._tail_data_size = datalength % segsize
2969+        else:
2970+            self._num_segments = 0
2971+            self._tail_data_size = 0
2972+
2973+        self._segment_decoder = codec.CRSDecoder()
2974+        self._segment_decoder.set_params(segsize, k, n)
2975+        self._current_segment = 0
2976+
2977+        if  not self._tail_data_size:
2978+            self._tail_data_size = segsize
2979 
2980hunk ./src/allmydata/mutable/retrieve.py 246
2981-        # we read the checkstring, to make sure that the data we grab is from
2982-        # the right version.
2983-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
2984+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
2985+                                                         self._required_shares)
2986+        if self._tail_segment_size == self._segment_size:
2987+            self._tail_decoder = self._segment_decoder
2988+        else:
2989+            self._tail_decoder = codec.CRSDecoder()
2990+            self._tail_decoder.set_params(self._tail_segment_size,
2991+                                          self._required_shares,
2992+                                          self._total_shares)
2993+
2994+        self.log("got encoding parameters: "
2995+                 "k: %d "
2996+                 "n: %d "
2997+                 "%d segments of %d bytes each (%d byte tail segment)" % \
2998+                 (k, n, self._num_segments, self._segment_size,
2999+                  self._tail_segment_size))
3000+
3001+        for i in xrange(self._total_shares):
3002+            # So we don't have to do this later.
3003+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
3004 
3005hunk ./src/allmydata/mutable/retrieve.py 267
3006-        # We also read the data, and the hashes necessary to validate them
3007-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
3008-        # signature or the pubkey, since that was handled during the
3009-        # servermap phase, and we'll be comparing the share hash chain
3010-        # against the roothash that was validated back then.
3011+        # If we have more than one segment, we are an SDMF file, which
3012+        # means that we need to validate the salts as we receive them.
3013+        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
3014+        self._salt_hash_tree[0] = IV # from the prefix.
3015+
3016 
3017hunk ./src/allmydata/mutable/retrieve.py 273
3018-        readv.append( (offsets['share_hash_chain'],
3019-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
3020+    def _add_active_peers(self):
3021+        """
3022+        I populate self._active_readers with enough active readers to
3023+        retrieve the contents of this mutable file. I am called before
3024+        downloading starts, and (eventually) after each validation
3025+        error, connection error, or other problem in the download.
3026+        """
3027+        # TODO: It would be cool to investigate other heuristics for
3028+        # reader selection. For instance, the cost (in time the user
3029+        # spends waiting for their file) of selecting a really slow peer
3030+        # that happens to have a primary share is probably more than
3031+        # selecting a really fast peer that doesn't have a primary
3032+        # share. Maybe the servermap could be extended to provide this
3033+        # information; it could keep track of latency information while
3034+        # it gathers more important data, and then this routine could
3035+        # use that to select active readers.
3036+        #
3037+        # (these and other questions would be easier to answer with a
3038+        #  robust, configurable tahoe-lafs simulator, which modeled node
3039+        #  failures, differences in node speed, and other characteristics
3040+        #  that we expect storage servers to have.  You could have
3041+        #  presets for really stable grids (like allmydata.com),
3042+        #  friendnets, make it easy to configure your own settings, and
3043+        #  then simulate the effect of big changes on these use cases
3044+        #  instead of just reasoning about what the effect might be. Out
3045+        #  of scope for MDMF, though.)
3046 
3047hunk ./src/allmydata/mutable/retrieve.py 300
3048-        # if we need the private key (for repair), we also fetch that
3049-        if self._need_privkey:
3050-            readv.append( (offsets['enc_privkey'],
3051-                           offsets['EOF'] - offsets['enc_privkey']) )
3052+        # We need at least self._required_shares readers to download a
3053+        # segment.
3054+        needed = self._required_shares - len(self._active_readers)
3055+        # XXX: Why don't format= log messages work here?
3056+        self.log("adding %d peers to the active peers list" % needed)
3057 
3058hunk ./src/allmydata/mutable/retrieve.py 306
3059-        m = Marker()
3060-        self._outstanding_queries[m] = (peerid, shnum, started)
3061+        # We favor lower numbered shares, since FEC is faster with
3062+        # primary shares than with other shares, and lower-numbered
3063+        # shares are more likely to be primary than higher numbered
3064+        # shares.
3065+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
3066+        active_shnums = list(active_shnums)[:needed]
3067+        if len(active_shnums) < needed:
3068+            # We don't have enough readers to retrieve the file; fail.
3069+            return self._failed()
3070 
3071hunk ./src/allmydata/mutable/retrieve.py 316
3072-        # ask the cache first
3073-        got_from_cache = False
3074-        datavs = []
3075-        for (offset, length) in readv:
3076-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
3077-                                                            offset, length)
3078-            if data is not None:
3079-                datavs.append(data)
3080-        if len(datavs) == len(readv):
3081-            self.log("got data from cache")
3082-            got_from_cache = True
3083-            d = fireEventually({shnum: datavs})
3084-            # datavs is a dict mapping shnum to a pair of strings
3085-        else:
3086-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
3087-        self.remaining_sharemap.discard(shnum, peerid)
3088+        for shnum in active_shnums:
3089+            self._active_readers.append(self.readers[shnum])
3090+            self.log("added reader for share %d" % shnum)
3091+        assert len(self._active_readers) == self._required_shares
3092+        # Conceptually, this is part of the _add_active_peers step. It
3093+        # validates the prefixes of newly added readers to make sure
3094+        # that they match what we are expecting for self.verinfo. If
3095+        # validation is successful, _validate_active_prefixes will call
3096+        # _download_current_segment for us. If validation is
3097+        # unsuccessful, then _validate_prefixes will remove the peer and
3098+        # call _add_active_peers again, where we will attempt to rectify
3099+        # the problem by choosing another peer.
3100+        return self._validate_active_prefixes()
3101 
3102hunk ./src/allmydata/mutable/retrieve.py 330
3103-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
3104-        d.addErrback(self._query_failed, m, peerid)
3105-        # errors that aren't handled by _query_failed (and errors caused by
3106-        # _query_failed) get logged, but we still want to check for doneness.
3107-        def _oops(f):
3108-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
3109-                     shnum=shnum,
3110-                     peerid=idlib.shortnodeid_b2a(peerid),
3111-                     failure=f,
3112-                     level=log.WEIRD, umid="W0xnQA")
3113-        d.addErrback(_oops)
3114-        d.addBoth(self._check_for_done)
3115-        # any error during _check_for_done means the download fails. If the
3116-        # download is successful, _check_for_done will fire _done by itself.
3117-        d.addErrback(self._done)
3118-        d.addErrback(log.err)
3119-        return d # purely for testing convenience
3120 
3121hunk ./src/allmydata/mutable/retrieve.py 331
3122-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
3123-        # isolate the callRemote to a separate method, so tests can subclass
3124-        # Publish and override it
3125-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
3126-        return d
3127+    def _validate_active_prefixes(self):
3128+        """
3129+        I check to make sure that the prefixes on the peers that I am
3130+        currently reading from match the prefix that we want to see, as
3131+        said in self.verinfo.
3132+
3133+        If I find that all of the active peers have acceptable prefixes,
3134+        I pass control to _download_current_segment, which will use
3135+        those peers to do cool things. If I find that some of the active
3136+        peers have unacceptable prefixes, I will remove them from active
3137+        peers (and from further consideration) and call
3138+        _add_active_peers to attempt to rectify the situation. I keep
3139+        track of which peers I have already validated so that I don't
3140+        need to do so again.
3141+        """
3142+        assert self._active_readers, "No more active readers"
3143 
3144hunk ./src/allmydata/mutable/retrieve.py 348
3145-    def remove_peer(self, peerid):
3146+        ds = []
3147+        new_readers = set(self._active_readers) - self._validated_readers
3148+        self.log('validating %d newly-added active readers' % len(new_readers))
3149+
3150+        for reader in new_readers:
3151+            # We force a remote read here -- otherwise, we are relying
3152+            # on cached data that we already verified as valid, and we
3153+            # won't detect an uncoordinated write that has occurred
3154+            # since the last servermap update.
3155+            d = reader.get_prefix(force_remote=True)
3156+            d.addCallback(self._try_to_validate_prefix, reader)
3157+            ds.append(d)
3158+        dl = defer.DeferredList(ds, consumeErrors=True)
3159+        def _check_results(results):
3160+            # Each result in results will be of the form (success, msg).
3161+            # We don't care about msg, but success will tell us whether
3162+            # or not the checkstring validated. If it didn't, we need to
3163+            # remove the offending (peer,share) from our active readers,
3164+            # and ensure that active readers is again populated.
3165+            bad_readers = []
3166+            for i, result in enumerate(results):
3167+                if not result[0]:
3168+                    reader = self._active_readers[i]
3169+                    f = result[1]
3170+                    assert isinstance(f, failure.Failure)
3171+
3172+                    self.log("The reader %s failed to "
3173+                             "properly validate: %s" % \
3174+                             (reader, str(f.value)))
3175+                    bad_readers.append((reader, f))
3176+                else:
3177+                    reader = self._active_readers[i]
3178+                    self.log("the reader %s checks out, so we'll use it" % \
3179+                             reader)
3180+                    self._validated_readers.add(reader)
3181+                    # Each time we validate a reader, we check to see if
3182+                    # we need the private key. If we do, we politely ask
3183+                    # for it and then continue computing. If we find
3184+                    # that we haven't gotten it at the end of
3185+                    # segment decoding, then we'll take more drastic
3186+                    # measures.
3187+                    if self._need_privkey:
3188+                        d = reader.get_encprivkey()
3189+                        d.addCallback(self._try_to_validate_privkey, reader)
3190+            if bad_readers:
3191+                # We do them all at once, or else we screw up list indexing.
3192+                for (reader, f) in bad_readers:
3193+                    self._mark_bad_share(reader, f)
3194+                return self._add_active_peers()
3195+            else:
3196+                return self._download_current_segment()
3197+            # The next step will assert that it has enough active
3198+            # readers to fetch shares; we just need to remove it.
3199+        dl.addCallback(_check_results)
3200+        return dl
3201+
3202+
3203+    def _try_to_validate_prefix(self, prefix, reader):
3204+        """
3205+        I check that the prefix returned by a candidate server for
3206+        retrieval matches the prefix that the servermap knows about
3207+        (and, hence, the prefix that was validated earlier). If it does,
3208+        I return True, which means that I approve of the use of the
3209+        candidate server for segment retrieval. If it doesn't, I return
3210+        False, which means that another server must be chosen.
3211+        """
3212+        (seqnum,
3213+         root_hash,
3214+         IV,
3215+         segsize,
3216+         datalength,
3217+         k,
3218+         N,
3219+         known_prefix,
3220+         offsets_tuple) = self.verinfo
3221+        if known_prefix != prefix:
3222+            self.log("prefix from share %d doesn't match" % reader.shnum)
3223+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
3224+                                          "indicate an uncoordinated write")
3225+        # Otherwise, we're okay -- no issues.
3226+
3227+
3228+    def _remove_reader(self, reader):
3229+        """
3230+        At various points, we will wish to remove a peer from
3231+        consideration and/or use. These include, but are not necessarily
3232+        limited to:
3233+
3234+            - A connection error.
3235+            - A mismatched prefix (that is, a prefix that does not match
3236+              our conception of the version information string).
3237+            - A failing block hash, salt hash, or share hash, which can
3238+              indicate disk failure/bit flips, or network trouble.
3239+
3240+        This method will do that. I will make sure that the
3241+        (shnum,reader) combination represented by my reader argument is
3242+        not used for anything else during this download. I will not
3243+        advise the reader of any corruption, something that my callers
3244+        may wish to do on their own.
3245+        """
3246+        # TODO: When you're done writing this, see if this is ever
3247+        # actually used for something that _mark_bad_share isn't. I have
3248+        # a feeling that they will be used for very similar things, and
3249+        # that having them both here is just going to be an epic amount
3250+        # of code duplication.
3251+        #
3252+        # (well, okay, not epic, but meaningful)
3253+        self.log("removing reader %s" % reader)
3254+        # Remove the reader from _active_readers
3255+        self._active_readers.remove(reader)
3256+        # TODO: self.readers.remove(reader)?
3257         for shnum in list(self.remaining_sharemap.keys()):
3258hunk ./src/allmydata/mutable/retrieve.py 460
3259-            self.remaining_sharemap.discard(shnum, peerid)
3260+            # TODO: Make sure that we set reader.peerid somewhere.
3261+            self.remaining_sharemap.discard(shnum, reader.peerid)
3262 
3263hunk ./src/allmydata/mutable/retrieve.py 463
3264-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
3265-        now = time.time()
3266-        elapsed = now - started
3267-        if not got_from_cache:
3268-            self._status.add_fetch_timing(peerid, elapsed)
3269-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
3270-                 shares=len(datavs),
3271-                 peerid=idlib.shortnodeid_b2a(peerid),
3272-                 level=log.NOISY)
3273-        self._outstanding_queries.pop(marker, None)
3274-        if not self._running:
3275-            return
3276 
3277hunk ./src/allmydata/mutable/retrieve.py 464
3278-        # note that we only ask for a single share per query, so we only
3279-        # expect a single share back. On the other hand, we use the extra
3280-        # shares if we get them.. seems better than an assert().
3281+    def _mark_bad_share(self, reader, f):
3282+        """
3283+        I mark the (peerid, shnum) encapsulated by my reader argument as
3284+        a bad share, which means that it will not be used anywhere else.
3285+
3286+        There are several reasons to want to mark something as a bad
3287+        share. These include:
3288+
3289+            - A connection error to the peer.
3290+            - A mismatched prefix (that is, a prefix that does not match
3291+              our local conception of the version information string).
3292+            - A failing block hash, salt hash, share hash, or other
3293+              integrity check.
3294 
3295hunk ./src/allmydata/mutable/retrieve.py 478
3296-        for shnum,datav in datavs.items():
3297-            (prefix, hash_and_data) = datav[:2]
3298+        This method will ensure that readers that we wish to mark bad
3299+        (for these reasons or other reasons) are not used for the rest
3300+        of the download. Additionally, it will attempt to tell the
3301+        remote peer (with no guarantee of success) that its share is
3302+        corrupt.
3303+        """
3304+        self.log("marking share %d on server %s as bad" % \
3305+                 (reader.shnum, reader))
3306+        self._remove_reader(reader)
3307+        self._bad_shares.add((reader.peerid, reader.shnum))
3308+        self._status.problems[reader.peerid] = f
3309+        self._last_failure = f
3310+        self.notify_server_corruption(reader.peerid, reader.shnum, f.value)
3311+
3312+
3313+    def _download_current_segment(self):
3314+        """
3315+        I download, validate, decode, decrypt, and assemble the segment
3316+        that this Retrieve is currently responsible for downloading.
3317+        """
3318+        assert len(self._active_readers) >= self._required_shares
3319+        if self._current_segment < self._num_segments:
3320+            d = self._process_segment(self._current_segment)
3321+        else:
3322+            d = defer.succeed(None)
3323+        d.addCallback(self._check_for_done)
3324+        return d
3325+
3326+
3327+    def _process_segment(self, segnum):
3328+        """
3329+        I download, validate, decode, and decrypt one segment of the
3330+        file that this Retrieve is retrieving. This means coordinating
3331+        the process of getting k blocks of that file, validating them,
3332+        assembling them into one segment with the decoder, and then
3333+        decrypting them.
3334+        """
3335+        self.log("processing segment %d" % segnum)
3336+
3337+        # TODO: The old code uses a marker. Should this code do that
3338+        # too? What did the Marker do?
3339+        assert len(self._active_readers) >= self._required_shares
3340+
3341+        # We need to ask each of our active readers for its block and
3342+        # salt. We will then validate those. If validation is
3343+        # successful, we will assemble the results into plaintext.
3344+        ds = []
3345+        for reader in self._active_readers:
3346+            d = reader.get_block_and_salt(segnum)
3347+            d.addCallback(self._validate_block, segnum, reader)
3348+            d.addErrback(self._validation_failed, reader)
3349+            ds.append(d)
3350+        dl = defer.DeferredList(ds)
3351+        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3352+        return dl
3353+
3354+
3355+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
3356+        """
3357+        I take the results of fetching and validating the blocks from a
3358+        callback chain in another method. If the results are such that
3359+        they tell me that validation and fetching succeeded without
3360+        incident, I will proceed with decoding and decryption.
3361+        Otherwise, I will do nothing.
3362+        """
3363+        self.log("trying to decode and decrypt segment %d" % segnum)
3364+        failures = False
3365+        for block_and_salt in blocks_and_salts:
3366+            if not block_and_salt[0] or block_and_salt[1] == None:
3367+                self.log("some validation operations failed; not proceeding")
3368+                failures = True
3369+                break
3370+        if not failures:
3371+            self.log("everything looks ok, building segment %d" % segnum)
3372+            d = self._decode_blocks(blocks_and_salts, segnum)
3373+            d.addCallback(self._decrypt_segment)
3374+            d.addErrback(self._decoding_or_decrypting_failed)
3375+            d.addCallback(self._set_segment)
3376+            return d
3377+        else:
3378+            return defer.succeed(None)
3379+
3380+
3381+    def _set_segment(self, segment):
3382+        """
3383+        Given a plaintext segment, I register that segment with the
3384+        target that is handling the file download.
3385+        """
3386+        self.log("got plaintext for segment %d" % self._current_segment)
3387+        self._plaintext += segment
3388+        self._current_segment += 1
3389+
3390+
3391+    def _validation_failed(self, f, reader):
3392+        """
3393+        I am called when a block or a salt fails to correctly validate.
3394+        I react to this failure by notifying the remote server of
3395+        corruption, and then removing the remote peer from further
3396+        activity.
3397+        """
3398+        self.log("validation failed on share %d, peer %s, segment %d: %s" % \
3399+                 (reader.shnum, reader, self._current_segment, str(f)))
3400+        self._mark_bad_share(reader, f)
3401+        return
3402+
3403+
3404+    def _decoding_or_decrypting_failed(self, f):
3405+        """
3406+        I am called when a list of blocks fails to decode into a segment
3407+        of crypttext, or fails to decrypt (for whatever reason) into a
3408+        segment of plaintext. I exist to make a log message about this
3409+        failure: my other job is to mark a share as corrupt, which is
3410+        not hard.
3411+        """
3412+        # XXX: Is this correct? When we're dealing with validation
3413+        # failures, it's easy to say that one share or one server was
3414+        # responsible for the failure. Is it so easy when decoding or
3415+        # decrypting fails? Maybe we should just log here, and try
3416+        # again? Of course, that could lead to infinite loops if
3417+        # something *is* wrong, because the state machine will just keep
3418+        # trying to download the broken segment over and over and
3419+        # over...
3420+        self.log("decoding or decrypting failed on segment %d: %s" % \
3421+                 (self._current_segment, str(f.value)))
3422+        for reader in self._active_readers:
3423+            self._mark_bad_share(reader, f)
3424+
3425+        assert len(self._active_readers) == 0
3426+        return
3427+
3428+
3429+    def _validate_block(self, (block, salt), segnum, reader):
3430+        """
3431+        I validate a block from one share on a remote server.
3432+        """
3433+        # Grab the part of the block hash tree that is necessary to
3434+        # validate this block, then generate the block hash root.
3435+        d = self._get_needed_hashes(reader, segnum)
3436+        def _handle_validation(block_and_sharehashes):
3437+            self.log("validating share %d for segment %d" % (reader.shnum,
3438+                                                             segnum))
3439+            blockhashes, sharehashes = block_and_sharehashes
3440+            blockhashes = dict(enumerate(blockhashes[1]))
3441+            bht = self._block_hash_trees[reader.shnum]
3442+            # If we needed sharehashes in the last step, we'll want to
3443+            # get those dealt with before we start processing the
3444+            # blockhashes.
3445+            if self.share_hash_tree.needed_hashes(reader.shnum):
3446+                try:
3447+                    self.share_hash_tree.set_hashes(hashes=sharehashes[1])
3448+                except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
3449+                        IndexError), e:
3450+                    # XXX: This is a stupid message -- make it more
3451+                    # informative.
3452+                    raise CorruptShareError(reader.peerid,
3453+                                            reader.shnum,
3454+                                            "corrupt hashes: %s" % e)
3455+
3456+            if not bht[0]:
3457+                share_hash = self.share_hash_tree.get_leaf(reader.shnum)
3458+                if not share_hash:
3459+                    raise CorruptShareError(reader.peerid,
3460+                                            reader.shnum,
3461+                                            "missing the root hash")
3462+                bht.set_hashes({0: share_hash})
3463+
3464+            if bht.needed_hashes(segnum, include_leaf=True):
3465+                try:
3466+                    bht.set_hashes(blockhashes)
3467+                except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
3468+                        IndexError), e:
3469+                    raise CorruptShareError(reader.peerid,
3470+                                            reader.shnum,
3471+                                            "block hash tree failure: %s" % e)
3472+
3473+            blockhash = hashutil.block_hash(block)
3474+            self.log("got blockhash %s" % [blockhash])
3475+            self.log("comparing to tree %s" % bht)
3476+            # If this works without an error, then validation is
3477+            # successful.
3478             try:
3479hunk ./src/allmydata/mutable/retrieve.py 659
3480-                self._got_results_one_share(shnum, peerid,
3481-                                            prefix, hash_and_data)
3482-            except CorruptShareError, e:
3483-                # log it and give the other shares a chance to be processed
3484-                f = failure.Failure()
3485-                self.log(format="bad share: %(f_value)s",
3486-                         f_value=str(f.value), failure=f,
3487-                         level=log.WEIRD, umid="7fzWZw")
3488-                self.notify_server_corruption(peerid, shnum, str(e))
3489-                self.remove_peer(peerid)
3490-                self.servermap.mark_bad_share(peerid, shnum, prefix)
3491-                self._bad_shares.add( (peerid, shnum) )
3492-                self._status.problems[peerid] = f
3493-                self._last_failure = f
3494-                pass
3495-            if self._need_privkey and len(datav) > 2:
3496-                lp = None
3497-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
3498-        # all done!
3499+                bht.set_hashes(leaves={segnum: blockhash})
3500+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
3501+                    IndexError), e:
3502+                raise CorruptShareError(reader.peerid,
3503+                                        reader.shnum,
3504+                                        "block hash tree failure: %s" % e)
3505+
3506+            # TODO: Validate the salt, too.
3507+            self.log('share %d is valid for segment %d' % (reader.shnum,
3508+                                                           segnum))
3509+            return {reader.shnum: (block, salt)}
3510+        d.addCallback(_handle_validation)
3511+        return d
3512+
3513+
3514+    def _get_needed_hashes(self, reader, segnum):
3515+        """
3516+        I get the hashes needed to validate segnum from the reader, then return
3517+        to my caller when this is done.
3518+        """
3519+        bht = self._block_hash_trees[reader.shnum]
3520+        needed = bht.needed_hashes(segnum, include_leaf=True)
3521+        # The root of the block hash tree is also a leaf in the share
3522+        # hash tree. So we don't need to fetch it from the remote
3523+        # server. In the case of files with one segment, this means that
3524+        # we won't fetch any block hash tree from the remote server,
3525+        # since the hash of each share of the file is the entire block
3526+        # hash tree, and is a leaf in the share hash tree. This is fine,
3527+        # since any share corruption will be detected in the share hash
3528+        # tree.
3529+        needed.discard(0)
3530+        # XXX: not now, causes test failures.
3531+        self.log("getting blockhashes for segment %d, share %d: %s" % \
3532+                 (segnum, reader.shnum, str(needed)))
3533+        d1 = reader.get_blockhashes(needed)
3534+        if self.share_hash_tree.needed_hashes(reader.shnum):
3535+            need = self.share_hash_tree.needed_hashes(reader.shnum)
3536+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
3537+                                                                 str(need)))
3538+            d2 = reader.get_sharehashes(need)
3539+        else:
3540+            d2 = defer.succeed(None)
3541+        dl = defer.DeferredList([d1, d2])
3542+        return dl
3543+
3544+
3545+    def _decode_blocks(self, blocks_and_salts, segnum):
3546+        """
3547+        I take a list of k blocks and salts, and decode that into a
3548+        single encrypted segment.
3549+        """
3550+        d = {}
3551+        # We want to merge our dictionaries to the form
3552+        # {shnum: blocks_and_salts}
3553+        #
3554+        # The dictionaries come from validate block that way, so we just
3555+        # need to merge them.
3556+        for block_and_salt in blocks_and_salts:
3557+            d.update(block_and_salt[1])
3558+
3559+        # All of these blocks should have the same salt; in SDMF, it is
3560+        # the file-wide IV, while in MDMF it is the per-segment salt. In
3561+        # either case, we just need to get one of them and use it.
3562+        #
3563+        # d.items()[0] is like (shnum, (block, salt))
3564+        # d.items()[0][1] is like (block, salt)
3565+        # d.items()[0][1][1] is the salt.
3566+        salt = d.items()[0][1][1]
3567+        # Next, extract just the blocks from the dict. We'll use the
3568+        # salt in the next step.
3569+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
3570+        d2 = dict(share_and_shareids)
3571+        shareids = []
3572+        shares = []
3573+        for shareid, share in d2.items():
3574+            shareids.append(shareid)
3575+            shares.append(share)
3576+
3577+        assert len(shareids) >= self._required_shares, len(shareids)
3578+        # zfec really doesn't want extra shares
3579+        shareids = shareids[:self._required_shares]
3580+        shares = shares[:self._required_shares]
3581+        self.log("decoding segment %d" % segnum)
3582+        if segnum == self._num_segments - 1:
3583+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
3584+        else:
3585+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
3586+        def _process(buffers):
3587+            segment = "".join(buffers)
3588+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
3589+                     segnum=segnum,
3590+                     numsegs=self._num_segments,
3591+                     level=log.NOISY)
3592+            self.log(" joined length %d, datalength %d" %
3593+                     (len(segment), self._data_length))
3594+            if segnum == self._num_segments - 1:
3595+                size_to_use = self._tail_data_size
3596+            else:
3597+                size_to_use = self._segment_size
3598+            segment = segment[:size_to_use]
3599+            self.log(" segment len=%d" % len(segment))
3600+            return segment, salt
3601+        d.addCallback(_process)
3602+        return d
3603+
3604+
3605+    def _decrypt_segment(self, segment_and_salt):
3606+        """
3607+        I take a single segment and its salt, and decrypt it. I return
3608+        the plaintext of the segment that is in my argument.
3609+        """
3610+        segment, salt = segment_and_salt
3611+        self._status.set_status("decrypting")
3612+        self.log("decrypting segment %d" % self._current_segment)
3613+        started = time.time()
3614+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
3615+        decryptor = AES(key)
3616+        plaintext = decryptor.process(segment)
3617+        self._status.timings["decrypt"] = time.time() - started
3618+        return plaintext
3619+
3620 
3621     def notify_server_corruption(self, peerid, shnum, reason):
3622         ss = self.servermap.connections[peerid]
3623hunk ./src/allmydata/mutable/retrieve.py 786
3624         ss.callRemoteOnly("advise_corrupt_share",
3625                           "mutable", self._storage_index, shnum, reason)
3626 
3627-    def _got_results_one_share(self, shnum, peerid,
3628-                               got_prefix, got_hash_and_data):
3629-        self.log("_got_results: got shnum #%d from peerid %s"
3630-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
3631-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3632-         offsets_tuple) = self.verinfo
3633-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
3634-        if got_prefix != prefix:
3635-            msg = "someone wrote to the data since we read the servermap: prefix changed"
3636-            raise UncoordinatedWriteError(msg)
3637-        (share_hash_chain, block_hash_tree,
3638-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
3639-
3640-        assert isinstance(share_data, str)
3641-        # build the block hash tree. SDMF has only one leaf.
3642-        leaves = [hashutil.block_hash(share_data)]
3643-        t = hashtree.HashTree(leaves)
3644-        if list(t) != block_hash_tree:
3645-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
3646-        share_hash_leaf = t[0]
3647-        t2 = hashtree.IncompleteHashTree(N)
3648-        # root_hash was checked by the signature
3649-        t2.set_hashes({0: root_hash})
3650-        try:
3651-            t2.set_hashes(hashes=share_hash_chain,
3652-                          leaves={shnum: share_hash_leaf})
3653-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
3654-                IndexError), e:
3655-            msg = "corrupt hashes: %s" % (e,)
3656-            raise CorruptShareError(peerid, shnum, msg)
3657-        self.log(" data valid! len=%d" % len(share_data))
3658-        # each query comes down to this: placing validated share data into
3659-        # self.shares
3660-        self.shares[shnum] = share_data
3661 
3662hunk ./src/allmydata/mutable/retrieve.py 787
3663-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
3664+    def _try_to_validate_privkey(self, enc_privkey, reader):
3665 
3666         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3667         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3668hunk ./src/allmydata/mutable/retrieve.py 793
3669         if alleged_writekey != self._node.get_writekey():
3670             self.log("invalid privkey from %s shnum %d" %
3671-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
3672-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
3673+                     (reader, reader.shnum),
3674+                     level=log.WEIRD, umid="YIw4tA")
3675             return
3676 
3677         # it's good
3678hunk ./src/allmydata/mutable/retrieve.py 798
3679-        self.log("got valid privkey from shnum %d on peerid %s" %
3680-                 (shnum, idlib.shortnodeid_b2a(peerid)),
3681-                 parent=lp)
3682+        self.log("got valid privkey from shnum %d on reader %s" %
3683+                 (reader.shnum, reader))
3684         privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
3685         self._node._populate_encprivkey(enc_privkey)
3686         self._node._populate_privkey(privkey)
3687hunk ./src/allmydata/mutable/retrieve.py 805
3688         self._need_privkey = False
3689 
3690+
3691     def _query_failed(self, f, marker, peerid):
3692         self.log(format="query to [%(peerid)s] failed",
3693                  peerid=idlib.shortnodeid_b2a(peerid),
3694hunk ./src/allmydata/mutable/retrieve.py 822
3695         self.log(format="error during query: %(f_value)s",
3696                  f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
3697 
3698-    def _check_for_done(self, res):
3699-        # exit paths:
3700-        #  return : keep waiting, no new queries
3701-        #  return self._send_more_queries(outstanding) : send some more queries
3702-        #  fire self._done(plaintext) : download successful
3703-        #  raise exception : download fails
3704-
3705-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
3706-                 running=self._running, decoding=self._decoding,
3707-                 level=log.NOISY)
3708-        if not self._running:
3709-            return
3710-        if self._decoding:
3711-            return
3712-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3713-         offsets_tuple) = self.verinfo
3714-
3715-        if len(self.shares) < k:
3716-            # we don't have enough shares yet
3717-            return self._maybe_send_more_queries(k)
3718-        if self._need_privkey:
3719-            # we got k shares, but none of them had a valid privkey. TODO:
3720-            # look further. Adding code to do this is a bit complicated, and
3721-            # I want to avoid that complication, and this should be pretty
3722-            # rare (k shares with bitflips in the enc_privkey but not in the
3723-            # data blocks). If we actually do get here, the subsequent repair
3724-            # will fail for lack of a privkey.
3725-            self.log("got k shares but still need_privkey, bummer",
3726-                     level=log.WEIRD, umid="MdRHPA")
3727-
3728-        # we have enough to finish. All the shares have had their hashes
3729-        # checked, so if something fails at this point, we don't know how
3730-        # to fix it, so the download will fail.
3731 
3732hunk ./src/allmydata/mutable/retrieve.py 823
3733-        self._decoding = True # avoid reentrancy
3734-        self._status.set_status("decoding")
3735-        now = time.time()
3736-        elapsed = now - self._started
3737-        self._status.timings["fetch"] = elapsed
3738-
3739-        d = defer.maybeDeferred(self._decode)
3740-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
3741-        d.addBoth(self._done)
3742-        return d # purely for test convenience
3743-
3744-    def _maybe_send_more_queries(self, k):
3745-        # we don't have enough shares yet. Should we send out more queries?
3746-        # There are some number of queries outstanding, each for a single
3747-        # share. If we can generate 'needed_shares' additional queries, we do
3748-        # so. If we can't, then we know this file is a goner, and we raise
3749-        # NotEnoughSharesError.
3750-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
3751-                         "outstanding=%(outstanding)d"),
3752-                 have=len(self.shares), k=k,
3753-                 outstanding=len(self._outstanding_queries),
3754-                 level=log.NOISY)
3755-
3756-        remaining_shares = k - len(self.shares)
3757-        needed = remaining_shares - len(self._outstanding_queries)
3758-        if not needed:
3759-            # we have enough queries in flight already
3760-
3761-            # TODO: but if they've been in flight for a long time, and we
3762-            # have reason to believe that new queries might respond faster
3763-            # (i.e. we've seen other queries come back faster, then consider
3764-            # sending out new queries. This could help with peers which have
3765-            # silently gone away since the servermap was updated, for which
3766-            # we're still waiting for the 15-minute TCP disconnect to happen.
3767-            self.log("enough queries are in flight, no more are needed",
3768-                     level=log.NOISY)
3769-            return
3770-
3771-        outstanding_shnums = set([shnum
3772-                                  for (peerid, shnum, started)
3773-                                  in self._outstanding_queries.values()])
3774-        # prefer low-numbered shares, they are more likely to be primary
3775-        available_shnums = sorted(self.remaining_sharemap.keys())
3776-        for shnum in available_shnums:
3777-            if shnum in outstanding_shnums:
3778-                # skip ones that are already in transit
3779-                continue
3780-            if shnum not in self.remaining_sharemap:
3781-                # no servers for that shnum. note that DictOfSets removes
3782-                # empty sets from the dict for us.
3783-                continue
3784-            peerid = list(self.remaining_sharemap[shnum])[0]
3785-            # get_data will remove that peerid from the sharemap, and add the
3786-            # query to self._outstanding_queries
3787-            self._status.set_status("Retrieving More Shares")
3788-            self.get_data(shnum, peerid)
3789-            needed -= 1
3790-            if not needed:
3791-                break
3792-
3793-        # at this point, we have as many outstanding queries as we can. If
3794-        # needed!=0 then we might not have enough to recover the file.
3795-        if needed:
3796-            format = ("ran out of peers: "
3797-                      "have %(have)d shares (k=%(k)d), "
3798-                      "%(outstanding)d queries in flight, "
3799-                      "need %(need)d more, "
3800-                      "found %(bad)d bad shares")
3801-            args = {"have": len(self.shares),
3802-                    "k": k,
3803-                    "outstanding": len(self._outstanding_queries),
3804-                    "need": needed,
3805-                    "bad": len(self._bad_shares),
3806-                    }
3807-            self.log(format=format,
3808-                     level=log.WEIRD, umid="ezTfjw", **args)
3809-            err = NotEnoughSharesError("%s, last failure: %s" %
3810-                                      (format % args, self._last_failure))
3811-            if self._bad_shares:
3812-                self.log("We found some bad shares this pass. You should "
3813-                         "update the servermap and try again to check "
3814-                         "more peers",
3815-                         level=log.WEIRD, umid="EFkOlA")
3816-                err.servermap = self.servermap
3817-            raise err
3818-
3819-        return
3820-
3821-    def _decode(self):
3822-        started = time.time()
3823-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3824-         offsets_tuple) = self.verinfo
3825+    def _check_for_done(self, res):
3826+        """
3827+        I check to see if this Retrieve object has successfully finished
3828+        its work.
3829 
3830hunk ./src/allmydata/mutable/retrieve.py 828
3831-        # shares_dict is a dict mapping shnum to share data, but the codec
3832-        # wants two lists.
3833-        shareids = []; shares = []
3834-        for shareid, share in self.shares.items():
3835-            shareids.append(shareid)
3836-            shares.append(share)
3837+        I can exit in the following ways:
3838+            - If there are no more segments to download, then I exit by
3839+              causing self._done_deferred to fire with the plaintext
3840+              content requested by the caller.
3841+            - If there are still segments to be downloaded, and there
3842+              are enough active readers (readers which have not broken
3843+              and have not given us corrupt data) to continue
3844+              downloading, I send control back to
3845+              _download_current_segment.
3846+            - If there are still segments to be downloaded but there are
3847+              not enough active peers to download them, I ask
3848+              _add_active_peers to add more peers. If it is successful,
3849+              it will call _download_current_segment. If there are not
3850+              enough peers to retrieve the file, then that will cause
3851+              _done_deferred to errback.
3852+        """
3853+        self.log("checking for doneness")
3854+        if self._current_segment == self._num_segments:
3855+            # No more segments to download, we're done.
3856+            self.log("got plaintext, done")
3857+            return self._done()
3858 
3859hunk ./src/allmydata/mutable/retrieve.py 850
3860-        assert len(shareids) >= k, len(shareids)
3861-        # zfec really doesn't want extra shares
3862-        shareids = shareids[:k]
3863-        shares = shares[:k]
3864+        if len(self._active_readers) >= self._required_shares:
3865+            # More segments to download, but we have enough good peers
3866+            # in self._active_readers that we can do that without issue,
3867+            # so go nab the next segment.
3868+            self.log("not done yet: on segment %d of %d" % \
3869+                     (self._current_segment + 1, self._num_segments))
3870+            return self._download_current_segment()
3871 
3872hunk ./src/allmydata/mutable/retrieve.py 858
3873-        fec = codec.CRSDecoder()
3874-        fec.set_params(segsize, k, N)
3875+        self.log("not done yet: on segment %d of %d, need to add peers" % \
3876+                 (self._current_segment + 1, self._num_segments))
3877+        return self._add_active_peers()
3878 
3879hunk ./src/allmydata/mutable/retrieve.py 862
3880-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
3881-        self.log("about to decode, shareids=%s" % (shareids,))
3882-        d = defer.maybeDeferred(fec.decode, shares, shareids)
3883-        def _done(buffers):
3884-            self._status.timings["decode"] = time.time() - started
3885-            self.log(" decode done, %d buffers" % len(buffers))
3886-            segment = "".join(buffers)
3887-            self.log(" joined length %d, datalength %d" %
3888-                     (len(segment), datalength))
3889-            segment = segment[:datalength]
3890-            self.log(" segment len=%d" % len(segment))
3891-            return segment
3892-        def _err(f):
3893-            self.log(" decode failed: %s" % f)
3894-            return f
3895-        d.addCallback(_done)
3896-        d.addErrback(_err)
3897-        return d
3898 
3899hunk ./src/allmydata/mutable/retrieve.py 863
3900-    def _decrypt(self, crypttext, IV, readkey):
3901-        self._status.set_status("decrypting")
3902-        started = time.time()
3903-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
3904-        decryptor = AES(key)
3905-        plaintext = decryptor.process(crypttext)
3906-        self._status.timings["decrypt"] = time.time() - started
3907-        return plaintext
3908+    def _done(self):
3909+        """
3910+        I am called by _check_for_done when the download process has
3911+        finished successfully. After making some useful logging
3912+        statements, I return the decrypted contents to the owner of this
3913+        Retrieve object through self._done_deferred.
3914+        """
3915+        eventually(self._done_deferred.callback, self._plaintext)
3916 
3917hunk ./src/allmydata/mutable/retrieve.py 872
3918-    def _done(self, res):
3919-        if not self._running:
3920-            return
3921-        self._running = False
3922-        self._status.set_active(False)
3923-        self._status.timings["total"] = time.time() - self._started
3924-        # res is either the new contents, or a Failure
3925-        if isinstance(res, failure.Failure):
3926-            self.log("Retrieve done, with failure", failure=res,
3927-                     level=log.UNUSUAL)
3928-            self._status.set_status("Failed")
3929-        else:
3930-            self.log("Retrieve done, success!")
3931-            self._status.set_status("Finished")
3932-            self._status.set_progress(1.0)
3933-            # remember the encoding parameters, use them again next time
3934-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3935-             offsets_tuple) = self.verinfo
3936-            self._node._populate_required_shares(k)
3937-            self._node._populate_total_shares(N)
3938-        eventually(self._done_deferred.callback, res)
3939 
3940hunk ./src/allmydata/mutable/retrieve.py 873
3941+    def _failed(self):
3942+        """
3943+        I am called by _add_active_peers when there are not enough
3944+        active peers left to complete the download. After making some
3945+        useful logging statements, I return an exception to that effect
3946+        to the caller of this Retrieve object through
3947+        self._done_deferred.
3948+        """
3949+        format = ("ran out of peers: "
3950+                  "have %(have)d of %(total)d segments "
3951+                  "found %(bad)d bad shares "
3952+                  "encoding %(k)d-of-%(n)d")
3953+        args = {"have": self._current_segment,
3954+                "total": self._num_segments,
3955+                "k": self._required_shares,
3956+                "n": self._total_shares,
3957+                "bad": len(self._bad_shares)}
3958+        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
3959+                                                        str(self._last_failure)))
3960+        f = failure.Failure(e)
3961+        eventually(self._done_deferred.callback, f)
3962hunk ./src/allmydata/test/test_mutable.py 309
3963         d.addCallback(_created)
3964         return d
3965 
3966+
3967     def test_create_with_initial_contents_function(self):
3968         data = "initial contents"
3969         def _make_contents(n):
3970hunk ./src/allmydata/test/test_mutable.py 594
3971                 self.failUnless(p._pubkey.verify(sig_material, signature))
3972                 #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
3973                 self.failUnless(isinstance(share_hash_chain, dict))
3974-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3975+                # TODO: Revisit this to make sure that the additional
3976+                # share hashes are really necessary.
3977+                #
3978+                # (just because they magically make the tests pass does
3979+                # not mean that they are necessary)
3980+                # ln2(10)++ + 1 for leaves.
3981+                self.failUnlessEqual(len(share_hash_chain), 5)
3982                 for shnum,share_hash in share_hash_chain.items():
3983                     self.failUnless(isinstance(shnum, int))
3984                     self.failUnless(isinstance(share_hash, str))
3985hunk ./src/allmydata/test/test_mutable.py 915
3986         def _check_servermap(sm):
3987             self.failUnlessEqual(len(sm.recoverable_versions()), 1)
3988         d.addCallback(_check_servermap)
3989-        # Now, we upload more versions
3990-        d.addCallback(lambda ignored:
3991-            self.publish_multiple(version=1))
3992-        d.addCallback(lambda ignored:
3993-            self.make_servermap(mode=MODE_CHECK))
3994-        def _check_servermap_multiple(sm):
3995-            v = sm.recoverable_versions()
3996-            i = sm.unrecoverable_versions()
3997-        d.addCallback(_check_servermap_multiple)
3998         return d
3999hunk ./src/allmydata/test/test_mutable.py 916
4000-    test_servermapupdater_finds_mdmf_files.todo = ("I don't know how to "
4001-                                                   "write this yet")
4002 
4003 
4004     def test_servermapupdater_finds_sdmf_files(self):
4005hunk ./src/allmydata/test/test_mutable.py 1163
4006         def _check(res):
4007             f = res[0]
4008             self.failUnless(f.check(NotEnoughSharesError))
4009-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
4010+            self.failUnless("uncoordinated write" in str(f))
4011         return self._test_corrupt_all(1, "ran out of peers",
4012                                       corrupt_early=False,
4013                                       failure_checker=_check)
4014hunk ./src/allmydata/test/test_mutable.py 1937
4015             d.addCallback(lambda res:
4016                           self.shouldFail(NotEnoughSharesError,
4017                                           "test_retrieve_surprise",
4018-                                          "ran out of peers: have 0 shares (k=3)",
4019+                                          "ran out of peers: have 0 of 1",
4020                                           n.download_version,
4021                                           self.old_map,
4022                                           self.old_map.best_recoverable_version(),
4023hunk ./src/allmydata/test/test_mutable.py 1946
4024         d.addCallback(_created)
4025         return d
4026 
4027+
4028     def test_unexpected_shares(self):
4029         # upload the file, take a servermap, shut down one of the servers,
4030         # upload it again (causing shares to appear on a new server), then
4031}
4032[Tell NodeMaker and MutableFileNode about the distinction between SDMF and MDMF
4033Kevan Carstensen <kevan@isnotajoke.com>**20100623001708
4034 Ignore-this: 92c723fd536264be2eef9e2a919d334f
4035] {
4036hunk ./src/allmydata/mutable/filenode.py 8
4037 from twisted.internet import defer, reactor
4038 from foolscap.api import eventually
4039 from allmydata.interfaces import IMutableFileNode, \
4040-     ICheckable, ICheckResults, NotEnoughSharesError
4041+     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
4042 from allmydata.util import hashutil, log
4043 from allmydata.util.assertutil import precondition
4044 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
4045hunk ./src/allmydata/mutable/filenode.py 67
4046         self._sharemap = {} # known shares, shnum-to-[nodeids]
4047         self._cache = ResponseCache()
4048         self._most_recent_size = None
4049+        # filled in after __init__ if we're being created for the first time;
4050+        # filled in by the servermap updater before publishing, otherwise.
4051+        # set to this default value in case neither of those things happen,
4052+        # or in case the servermap can't find any shares to tell us what
4053+        # to publish as.
4054+        # TODO: Set this back to None, and find out why the tests fail
4055+        #       with it set to None.
4056+        self._protocol_version = SDMF_VERSION
4057 
4058         # all users of this MutableFileNode go through the serializer. This
4059         # takes advantage of the fact that Deferreds discard the callbacks
4060hunk ./src/allmydata/mutable/filenode.py 472
4061     def _did_upload(self, res, size):
4062         self._most_recent_size = size
4063         return res
4064+
4065+
4066+    def set_version(self, version):
4067+        # I can be set in two ways:
4068+        #  1. When the node is created.
4069+        #  2. (for an existing share) when the Servermap is updated
4070+        #     before I am read.
4071+        assert version in (MDMF_VERSION, SDMF_VERSION)
4072+        self._protocol_version = version
4073+
4074+
4075+    def get_version(self):
4076+        return self._protocol_version
4077hunk ./src/allmydata/nodemaker.py 3
4078 import weakref
4079 from zope.interface import implements
4080-from allmydata.interfaces import INodeMaker
4081+from allmydata.util.assertutil import precondition
4082+from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError, \
4083+                                 SDMF_VERSION, MDMF_VERSION
4084 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
4085 from allmydata.immutable.upload import Data
4086 from allmydata.mutable.filenode import MutableFileNode
4087hunk ./src/allmydata/nodemaker.py 92
4088             return self._create_dirnode(filenode)
4089         return None
4090 
4091-    def create_mutable_file(self, contents=None, keysize=None):
4092+    def create_mutable_file(self, contents=None, keysize=None,
4093+                            version=SDMF_VERSION):
4094         n = MutableFileNode(self.storage_broker, self.secret_holder,
4095                             self.default_encoding_parameters, self.history)
4096hunk ./src/allmydata/nodemaker.py 96
4097+        n.set_version(version)
4098         d = self.key_generator.generate(keysize)
4099         d.addCallback(n.create_with_keys, contents)
4100         d.addCallback(lambda res: n)
4101hunk ./src/allmydata/nodemaker.py 102
4102         return d
4103 
4104-    def create_new_mutable_directory(self, initial_children={}):
4105+    def create_new_mutable_directory(self, initial_children={},
4106+                                     version=SDMF_VERSION):
4107+        # initial_children must have metadata (i.e. {} instead of None)
4108+        for (name, (node, metadata)) in initial_children.iteritems():
4109+            precondition(isinstance(metadata, dict),
4110+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
4111+            node.raise_error()
4112         d = self.create_mutable_file(lambda n:
4113hunk ./src/allmydata/nodemaker.py 110
4114-                                     pack_children(n, initial_children))
4115+                                     pack_children(n, initial_children),
4116+                                     version)
4117         d.addCallback(self._create_dirnode)
4118         return d
4119 
4120}
4121[Assorted servermap fixes
4122Kevan Carstensen <kevan@isnotajoke.com>**20100623001732
4123 Ignore-this: d54c4b5de327960ea4ffe096664b5a65
4124 
4125 - Check for failure when setting the private key
4126 - Check for failure when setting other things
4127 - Check for doneness in a way that is resilient to hung servers
4128 - Remove dead code
4129 - Reorganize error and success handling methods, and make sure they get
4130   used.
4131 
4132] {
4133hunk ./src/allmydata/mutable/servermap.py 485
4134         # set as we get responses.
4135         self._must_query = must_query
4136 
4137-        # This tells the done check whether requests are still being
4138-        # processed. We should wait before returning until at least
4139-        # updated correctly (and dealing with connection errors.
4140-        self._processing = 0
4141-
4142         # now initial_peers_to_query contains the peers that we should ask,
4143         # self.must_query contains the peers that we must have heard from
4144         # before we can consider ourselves finished, and self.extra_peers
4145hunk ./src/allmydata/mutable/servermap.py 550
4146         # _query_failed) get logged, but we still want to check for doneness.
4147         d.addErrback(log.err)
4148         d.addErrback(self._fatal_error)
4149+        d.addCallback(self._check_for_done)
4150         return d
4151 
4152     def _do_read(self, ss, peerid, storage_index, shnums, readv):
4153hunk ./src/allmydata/mutable/servermap.py 569
4154         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
4155         return d
4156 
4157+
4158+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
4159+        """
4160+        I am called when a remote server returns a corrupt share in
4161+        response to one of our queries. By corrupt, I mean a share
4162+        without a valid signature. I then record the failure, notify the
4163+        server of the corruption, and record the share as bad.
4164+        """
4165+        f = failure.Failure(e)
4166+        self.log(format="bad share: %(f_value)s", f_value=str(f.value),
4167+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
4168+        # Notify the server that its share is corrupt.
4169+        self.notify_server_corruption(peerid, shnum, str(e))
4170+        # By flagging this as a bad peer, we won't count any of
4171+        # the other shares on that peer as valid, though if we
4172+        # happen to find a valid version string amongst those
4173+        # shares, we'll keep track of it so that we don't need
4174+        # to validate the signature on those again.
4175+        self._bad_peers.add(peerid)
4176+        self._last_failure = f
4177+        # XXX: Use the reader for this?
4178+        checkstring = data[:SIGNED_PREFIX_LENGTH]
4179+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
4180+        self._servermap.problems.append(f)
4181+
4182+
4183+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
4184+        """
4185+        If one of my queries returns successfully (which means that we
4186+        were able to and successfully did validate the signature), I
4187+        cache the data that we initially fetched from the storage
4188+        server. This will help reduce the number of roundtrips that need
4189+        to occur when the file is downloaded, or when the file is
4190+        updated.
4191+        """
4192+        self._node._add_to_cache(verinfo, shnum, 0, data, now)
4193+
4194+
4195     def _got_results(self, datavs, peerid, readsize, stuff, started):
4196         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
4197                       peerid=idlib.shortnodeid_b2a(peerid),
4198hunk ./src/allmydata/mutable/servermap.py 618
4199         self._servermap.reachable_peers.add(peerid)
4200         self._must_query.discard(peerid)
4201         self._queries_completed += 1
4202-        # self._processing counts the number of queries that have
4203-        # completed, but are still processing. We wait until all queries
4204-        # are done processing before returning a result to the client.
4205-        # TODO: Should we do this? A response to the initial query means
4206-        # that we may not have to query the server for anything else,
4207-        # but if we're dealing with an MDMF share, we'll probably have
4208-        # to ask it for its signature, unless we cache those sometplace,
4209-        # and even then.
4210-        self._processing += 1
4211         if not self._running:
4212             self.log("but we're not running, so we'll ignore it", parent=lp,
4213                      level=log.NOISY)
4214hunk ./src/allmydata/mutable/servermap.py 633
4215         ss, storage_index = stuff
4216         ds = []
4217 
4218-
4219-        def _tattle(ignored, status):
4220-            print status
4221-            print ignored
4222-            return ignored
4223-
4224-        def _cache(verinfo, shnum, now, data):
4225-            self._queries_oustand
4226-            self._node._add_to_cache(verinfo, shnum, 0, data, now)
4227-            return shnum, verinfo
4228-
4229-        def _corrupt(e, shnum, data):
4230-            # This gets raised when there was something wrong with
4231-            # the remote server. Specifically, when there was an
4232-            # error unpacking the remote data from the server, or
4233-            # when the signature is invalid.
4234-            print e
4235-            f = failure.Failure()
4236-            self.log(format="bad share: %(f_value)s", f_value=str(f.value),
4237-                     failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
4238-            # Notify the server that its share is corrupt.
4239-            self.notify_server_corruption(peerid, shnum, str(e))
4240-            # By flagging this as a bad peer, we won't count any of
4241-            # the other shares on that peer as valid, though if we
4242-            # happen to find a valid version string amongst those
4243-            # shares, we'll keep track of it so that we don't need
4244-            # to validate the signature on those again.
4245-            self._bad_peers.add(peerid)
4246-            self._last_failure = f
4247-            # 393CHANGE: Use the reader for this.
4248-            checkstring = data[:SIGNED_PREFIX_LENGTH]
4249-            self._servermap.mark_bad_share(peerid, shnum, checkstring)
4250-            self._servermap.problems.append(f)
4251-
4252         for shnum,datav in datavs.items():
4253             data = datav[0]
4254             reader = MDMFSlotReadProxy(ss,
4255hunk ./src/allmydata/mutable/servermap.py 646
4256             # need to do the following:
4257             #   - If we don't already have the public key, fetch the
4258             #     public key. We use this to validate the signature.
4259-            friendly_peer = idlib.shortnodeid_b2a(peerid)
4260             if not self._node.get_pubkey():
4261                 # fetch and set the public key.
4262                 d = reader.get_verification_key()
4263hunk ./src/allmydata/mutable/servermap.py 649
4264-                d.addCallback(self._try_to_set_pubkey)
4265+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
4266+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
4267+                # XXX: Make self._pubkey_query_failed?
4268+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
4269+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
4270             else:
4271                 # we already have the public key.
4272                 d = defer.succeed(None)
4273hunk ./src/allmydata/mutable/servermap.py 666
4274             #   bytes of the share on the storage server, so we
4275             #   shouldn't need to fetch anything at this step.
4276             d2 = reader.get_verinfo()
4277+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
4278+                self._got_corrupt_share(error, shnum, peerid, data, lp))
4279             # - Next, we need the signature. For an SDMF share, it is
4280             #   likely that we fetched this when doing our initial fetch
4281             #   to get the version information. In MDMF, this lives at
4282hunk ./src/allmydata/mutable/servermap.py 674
4283             #   the end of the share, so unless the file is quite small,
4284             #   we'll need to do a remote fetch to get it.
4285             d3 = reader.get_signature()
4286+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
4287+                self._got_corrupt_share(error, shnum, peerid, data, lp))
4288             #  Once we have all three of these responses, we can move on
4289             #  to validating the signature
4290 
4291hunk ./src/allmydata/mutable/servermap.py 681
4292             # Does the node already have a privkey? If not, we'll try to
4293             # fetch it here.
4294-            if not self._node.get_privkey():
4295+            if self._need_privkey:
4296                 d4 = reader.get_encprivkey()
4297                 d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
4298                     self._try_to_validate_privkey(results, peerid, shnum, lp))
4299hunk ./src/allmydata/mutable/servermap.py 685
4300+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
4301+                    self._privkey_query_failed(error, shnum, data, lp))
4302             else:
4303                 d4 = defer.succeed(None)
4304 
4305hunk ./src/allmydata/mutable/servermap.py 694
4306             dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
4307                 self._got_signature_one_share(results, shnum, peerid, lp))
4308             dl.addErrback(lambda error, shnum=shnum, data=data:
4309-               _corrupt(error, shnum, data))
4310+               self._got_corrupt_share(error, shnum, peerid, data, lp))
4311+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
4312+                self._cache_good_sharedata(verinfo, shnum, now, data))
4313             ds.append(dl)
4314         # dl is a deferred list that will fire when all of the shares
4315         # that we found on this peer are done processing. When dl fires,
4316hunk ./src/allmydata/mutable/servermap.py 702
4317         # we know that processing is done, so we can decrement the
4318         # semaphore-like thing that we incremented earlier.
4319-        dl = defer.DeferredList(ds)
4320-        def _done_processing(ignored):
4321-            self._processing -= 1
4322-            return ignored
4323-        dl.addCallback(_done_processing)
4324+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
4325         # Are we done? Done means that there are no more queries to
4326         # send, that there are no outstanding queries, and that we
4327         # haven't received any queries that are still processing. If we
4328hunk ./src/allmydata/mutable/servermap.py 710
4329         # that we returned to our caller to fire, which tells them that
4330         # they have a complete servermap, and that we won't be touching
4331         # the servermap anymore.
4332-        dl.addBoth(self._check_for_done)
4333+        dl.addCallback(self._check_for_done)
4334         dl.addErrback(self._fatal_error)
4335         # all done!
4336hunk ./src/allmydata/mutable/servermap.py 713
4337-        return dl
4338         self.log("_got_results done", parent=lp, level=log.NOISY)
4339hunk ./src/allmydata/mutable/servermap.py 714
4340+        return dl
4341+
4342 
4343hunk ./src/allmydata/mutable/servermap.py 717
4344-    def _try_to_set_pubkey(self, pubkey_s):
4345+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
4346         if self._node.get_pubkey():
4347             return # don't go through this again if we don't have to
4348         fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
4349hunk ./src/allmydata/mutable/servermap.py 773
4350         if verinfo not in self._valid_versions:
4351             # This is a new version tuple, and we need to validate it
4352             # against the public key before keeping track of it.
4353+            assert self._node.get_pubkey()
4354             valid = self._node.get_pubkey().verify(prefix, signature[1])
4355             if not valid:
4356                 raise CorruptShareError(peerid, shnum,
4357hunk ./src/allmydata/mutable/servermap.py 892
4358         self._queries_completed += 1
4359         self._last_failure = f
4360 
4361-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
4362-        now = time.time()
4363-        elapsed = now - started
4364-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
4365-        self._queries_outstanding.discard(peerid)
4366-        if not self._need_privkey:
4367-            return
4368-        if shnum not in datavs:
4369-            self.log("privkey wasn't there when we asked it",
4370-                     level=log.WEIRD, umid="VA9uDQ")
4371-            return
4372-        datav = datavs[shnum]
4373-        enc_privkey = datav[0]
4374-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
4375 
4376     def _privkey_query_failed(self, f, peerid, shnum, lp):
4377         self._queries_outstanding.discard(peerid)
4378hunk ./src/allmydata/mutable/servermap.py 906
4379         self._servermap.problems.append(f)
4380         self._last_failure = f
4381 
4382+
4383     def _check_for_done(self, res):
4384         # exit paths:
4385         #  return self._send_more_queries(outstanding) : send some more queries
4386hunk ./src/allmydata/mutable/servermap.py 930
4387             self.log("but we're not running", parent=lp, level=log.NOISY)
4388             return
4389 
4390-        if self._processing > 0:
4391-            # wait until more results are done before returning.
4392-            return
4393-
4394         if self._must_query:
4395             # we are still waiting for responses from peers that used to have
4396             # a share, so we must continue to wait. No additional queries are
4397}
4398[Add objects for MDMF shares in support of a new segmented uploader
4399Kevan Carstensen <kevan@isnotajoke.com>**20100623233203
4400 Ignore-this: 9fa8319bc5e9142da7e70e1c91da2300
4401 
4402 This patch adds the following:
4403     - MDMFSlotWriteProxy, which can write MDMF shares to the storage
4404       server in the new format.
4405     - MDMFSlotReadProxy, which can read both SDMF and MDMF shares from
4406       the storage server.
4407 
4408 This patch also includes tests for these new object.
4409] {
4410hunk ./src/allmydata/interfaces.py 7
4411      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
4412 
4413 HASH_SIZE=32
4414+SALT_SIZE=16
4415 
4416 SDMF_VERSION=0
4417 MDMF_VERSION=1
4418hunk ./src/allmydata/mutable/layout.py 4
4419 
4420 import struct
4421 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
4422+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
4423+                                 MDMF_VERSION
4424+from allmydata.util import mathutil, observer
4425+from twisted.python import failure
4426+from twisted.internet import defer
4427+
4428+
4429+# These strings describe the format of the packed structs they help process
4430+# Here's what they mean:
4431+#
4432+#  PREFIX:
4433+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4434+#    B: The version information; an 8 bit version identifier. Stored as
4435+#       an unsigned char. This is currently 00 00 00 00; our modifications
4436+#       will turn it into 00 00 00 01.
4437+#    Q: The sequence number; this is sort of like a revision history for
4438+#       mutable files; they start at 1 and increase as they are changed after
4439+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4440+#       length.
4441+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4442+#       characters = 32 bytes to store the value.
4443+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4444+#       16 characters.
4445+#
4446+#  SIGNED_PREFIX additions, things that are covered by the signature:
4447+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4448+#       which is convenient because our erasure coding scheme cannot
4449+#       encode if you ask for more than 255 pieces.
4450+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4451+#       same reasons as above.
4452+#    Q: The segment size of the uploaded file. This will essentially be the
4453+#       length of the file in SDMF. An unsigned long long, so we can store
4454+#       files of quite large size.
4455+#    Q: The data length of the uploaded file. Modulo padding, this will be
4456+#       the same of the data length field. Like the data length field, it is
4457+#       an unsigned long long and can be quite large.
4458+#
4459+#   HEADER additions:
4460+#     L: The offset of the signature of this. An unsigned long.
4461+#     L: The offset of the share hash chain. An unsigned long.
4462+#     L: The offset of the block hash tree. An unsigned long.
4463+#     L: The offset of the share data. An unsigned long.
4464+#     Q: The offset of the encrypted private key. An unsigned long long, to
4465+#        account for the possibility of a lot of share data.
4466+#     Q: The offset of the EOF. An unsigned long long, to account for the
4467+#        possibility of a lot of share data.
4468+#
4469+#  After all of these, we have the following:
4470+#    - The verification key: Occupies the space between the end of the header
4471+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4472+#    - The signature, which goes from the signature offset to the share hash
4473+#      chain offset.
4474+#    - The share hash chain, which goes from the share hash chain offset to
4475+#      the block hash tree offset.
4476+#    - The share data, which goes from the share data offset to the encrypted
4477+#      private key offset.
4478+#    - The encrypted private key offset, which goes until the end of the file.
4479+#
4480+#  The block hash tree in this encoding has only one share, so the offset of
4481+#  the share data will be 32 bits more than the offset of the block hash tree.
4482+#  Given this, we may need to check to see how many bytes a reasonably sized
4483+#  block hash tree will take up.
4484 
4485 PREFIX = ">BQ32s16s" # each version has a different prefix
4486 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4487hunk ./src/allmydata/mutable/layout.py 191
4488     return (share_hash_chain, block_hash_tree, share_data)
4489 
4490 
4491-def pack_checkstring(seqnum, root_hash, IV):
4492+def pack_checkstring(seqnum, root_hash, IV, version=0):
4493     return struct.pack(PREFIX,
4494hunk ./src/allmydata/mutable/layout.py 193
4495-                       0, # version,
4496+                       version,
4497                        seqnum,
4498                        root_hash,
4499                        IV)
4500hunk ./src/allmydata/mutable/layout.py 266
4501                            encprivkey])
4502     return final_share
4503 
4504+def pack_prefix(seqnum, root_hash, IV,
4505+                required_shares, total_shares,
4506+                segment_size, data_length):
4507+    prefix = struct.pack(SIGNED_PREFIX,
4508+                         0, # version,
4509+                         seqnum,
4510+                         root_hash,
4511+                         IV,
4512+                         required_shares,
4513+                         total_shares,
4514+                         segment_size,
4515+                         data_length,
4516+                         )
4517+    return prefix
4518+
4519+
4520+MDMFHEADER = ">BQ32s32sBBQQ LQQQQQQQ"
4521+MDMFHEADERWITHOUTOFFSETS = ">BQ32s32sBBQQ"
4522+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4523+MDMFCHECKSTRING = ">BQ32s32s"
4524+MDMFSIGNABLEHEADER = ">BQ32s32sBBQQ"
4525+MDMFOFFSETS = ">LQQQQQQQ"
4526+
4527+class MDMFSlotWriteProxy:
4528+    #implements(IMutableSlotWriter) TODO
4529+
4530+    """
4531+    I represent a remote write slot for an MDMF mutable file.
4532+
4533+    I abstract away from my caller the details of block and salt
4534+    management, and the implementation of the on-disk format for MDMF
4535+    shares.
4536+    """
4537+
4538+    # Expected layout, MDMF:
4539+    # offset:     size:       name:
4540+    #-- signed part --
4541+    # 0           1           version number (01)
4542+    # 1           8           sequence number
4543+    # 9           32          share tree root hash
4544+    # 41          32          salt tree root hash
4545+    # 73          1           The "k" encoding parameter
4546+    # 74          1           The "N" encoding parameter
4547+    # 75          8           The segment size of the uploaded file
4548+    # 83          8           The data length of the uploaded file
4549+    #-- end signed part --
4550+    # 91          4           The offset of the share data
4551+    # 95          8           The offset of the encrypted private key
4552+    # 103         8           The offset of the block hash tree
4553+    # 111         8           The offset of the salt hash tree
4554+    # 119         8           The offset of the signature hash chain
4555+    # 127         8           The offset of the signature
4556+    # 135         8           The offset of the verification key
4557+    # 143         8           offset of the EOF
4558+    #
4559+    # followed by salts, share data, the encrypted private key, the
4560+    # block hash tree, the salt hash tree, the share hash chain, a
4561+    # signature over the first eight fields, and a verification key.
4562+    #
4563+    # The checkstring is the first four fields -- the version number,
4564+    # sequence number, root hash and root salt hash. This is consistent
4565+    # in meaning to what we have with SDMF files, except now instead of
4566+    # using the literal salt, we use a value derived from all of the
4567+    # salts.
4568+    #
4569+    # The ordering of the offsets is different to reflect the dependencies
4570+    # that we'll run into with an MDMF file. The expected write flow is
4571+    # something like this:
4572+    #
4573+    #   0: Initialize with the sequence number, encoding
4574+    #      parameters and data length. From this, we can deduce the
4575+    #      number of segments, and from that we can deduce the size of
4576+    #      the AES salt field, telling us where to write AES salts, and
4577+    #      where to write share data. We can also figure out where the
4578+    #      encrypted private key should go, because we can figure out
4579+    #      how big the share data will be.
4580+    #
4581+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4582+    #      like
4583+    #
4584+    #       put_block(data, segnum, salt)
4585+    #
4586+    #      to write a block and a salt to the disk. We can do both of
4587+    #      these operations now because we have enough of the offsets to
4588+    #      know where to put them.
4589+    #
4590+    #   2: Put the encrypted private key. Use:
4591+    #
4592+    #        put_encprivkey(encprivkey)
4593+    #
4594+    #      Now that we know the length of the private key, we can fill
4595+    #      in the offset for the block hash tree.
4596+    #
4597+    #   3: We're now in a position to upload the block hash tree for
4598+    #      a share. Put that using something like:
4599+    #       
4600+    #        put_blockhashes(block_hash_tree)
4601+    #
4602+    #      Note that block_hash_tree is a list of hashes -- we'll take
4603+    #      care of the details of serializing that appropriately. When
4604+    #      we get the block hash tree, we are also in a position to
4605+    #      calculate the offset for the share hash chain, and fill that
4606+    #      into the offsets table.
4607+    #
4608+    #   4: At the same time, we're in a position to upload the salt hash
4609+    #      tree. This is a Merkle tree over all of the salts. We use a
4610+    #      Merkle tree so that we can validate each block,salt pair as
4611+    #      we download them later. We do this using
4612+    #
4613+    #        put_salthashes(salt_hash_tree)
4614+    #
4615+    #      When you do this, I automatically put the root of the tree
4616+    #      (the hash at index 0 of the list) in its appropriate slot in
4617+    #      the signed prefix of the share.
4618+    #
4619+    #   5: We're now in a position to upload the share hash chain for
4620+    #      a share. Do that with something like:
4621+    #     
4622+    #        put_sharehashes(share_hash_chain)
4623+    #
4624+    #      share_hash_chain should be a dictionary mapping shnums to
4625+    #      32-byte hashes -- the wrapper handles serialization.
4626+    #      We'll know where to put the signature at this point, also.
4627+    #      The root of this tree will be put explicitly in the next
4628+    #      step.
4629+    #
4630+    #      TODO: Why? Why not just include it in the tree here?
4631+    #
4632+    #   6: Before putting the signature, we must first put the
4633+    #      root_hash. Do this with:
4634+    #
4635+    #        put_root_hash(root_hash).
4636+    #     
4637+    #      In terms of knowing where to put this value, it was always
4638+    #      possible to place it, but it makes sense semantically to
4639+    #      place it after the share hash tree, so that's why you do it
4640+    #      in this order.
4641+    #
4642+    #   6: With the root hash put, we can now sign the header. Use:
4643+    #
4644+    #        get_signable()
4645+    #
4646+    #      to get the part of the header that you want to sign, and use:
4647+    #       
4648+    #        put_signature(signature)
4649+    #
4650+    #      to write your signature to the remote server.
4651+    #
4652+    #   6: Add the verification key, and finish. Do:
4653+    #
4654+    #        put_verification_key(key)
4655+    #
4656+    #      and
4657+    #
4658+    #        finish_publish()
4659+    #
4660+    # Checkstring management:
4661+    #
4662+    # To write to a mutable slot, we have to provide test vectors to ensure
4663+    # that we are writing to the same data that we think we are. These
4664+    # vectors allow us to detect uncoordinated writes; that is, writes
4665+    # where both we and some other shareholder are writing to the
4666+    # mutable slot, and to report those back to the parts of the program
4667+    # doing the writing.
4668+    #
4669+    # With SDMF, this was easy -- all of the share data was written in
4670+    # one go, so it was easy to detect uncoordinated writes, and we only
4671+    # had to do it once. With MDMF, not all of the file is written at
4672+    # once.
4673+    #
4674+    # If a share is new, we write out as much of the header as we can
4675+    # before writing out anything else. This gives other writers a
4676+    # canary that they can use to detect uncoordinated writes, and, if
4677+    # they do the same thing, gives us the same canary. We them update
4678+    # the share. We won't be able to write out two fields of the header
4679+    # -- the share tree hash and the salt hash -- until we finish
4680+    # writing out the share. We only require the writer to provide the
4681+    # initial checkstring, and keep track of what it should be after
4682+    # updates ourselves.
4683+    #
4684+    # If we haven't written anything yet, then on the first write (which
4685+    # will probably be a block + salt of a share), we'll also write out
4686+    # the header. On subsequent passes, we'll expect to see the header.
4687+    # This changes in two places:
4688+    #
4689+    #   - When we write out the salt hash
4690+    #   - When we write out the root of the share hash tree
4691+    #
4692+    # since these values will change the header. It is possible that we
4693+    # can just make those be written in one operation to minimize
4694+    # disruption.
4695+    def __init__(self,
4696+                 shnum,
4697+                 rref, # a remote reference to a storage server
4698+                 storage_index,
4699+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4700+                 seqnum, # the sequence number of the mutable file
4701+                 required_shares,
4702+                 total_shares,
4703+                 segment_size,
4704+                 data_length): # the length of the original file
4705+        self._shnum = shnum
4706+        self._rref = rref
4707+        self._storage_index = storage_index
4708+        self._seqnum = seqnum
4709+        self._required_shares = required_shares
4710+        assert self._shnum >= 0 and self._shnum < total_shares
4711+        self._total_shares = total_shares
4712+        # We build up the offset table as we write things. It is the
4713+        # last thing we write to the remote server.
4714+        self._offsets = {}
4715+        self._testvs = []
4716+        self._secrets = secrets
4717+        # The segment size needs to be a multiple of the k parameter --
4718+        # any padding should have been carried out by the publisher
4719+        # already.
4720+        assert segment_size % required_shares == 0
4721+        self._segment_size = segment_size
4722+        self._data_length = data_length
4723+
4724+        # These are set later -- we define them here so that we can
4725+        # check for their existence easily
4726+
4727+        # This is the root of the share hash tree -- the Merkle tree
4728+        # over the roots of the block hash trees computed for shares in
4729+        # this upload.
4730+        self._root_hash = None
4731+        # This is the root of the salt hash tree -- the Merkle tree over
4732+        # the hashes of the salts used for each segment of the file.
4733+        self._salt_hash = None
4734+
4735+        # We haven't yet written anything to the remote bucket. By
4736+        # setting this, we tell the _write method as much. The write
4737+        # method will then know that it also needs to add a write vector
4738+        # for the checkstring (or what we have of it) to the first write
4739+        # request. We'll then record that value for future use.  If
4740+        # we're expecting something to be there already, we need to call
4741+        # set_checkstring before we write anything to tell the first
4742+        # write about that.
4743+        self._written = False
4744+
4745+        # When writing data to the storage servers, we get a read vector
4746+        # for free. We'll read the checkstring, which will help us
4747+        # figure out what's gone wrong if a write fails.
4748+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
4749+
4750+        # We calculate the number of segments because it tells us
4751+        # where the salt part of the file ends/share segment begins,
4752+        # and also because it provides a useful amount of bounds checking.
4753+        self._num_segments = mathutil.div_ceil(self._data_length,
4754+                                               self._segment_size)
4755+        self._block_size = self._segment_size / self._required_shares
4756+        # We also calculate the share size, to help us with block
4757+        # constraints later.
4758+        tail_size = self._data_length % self._segment_size
4759+        if not tail_size:
4760+            self._tail_block_size = self._block_size
4761+        else:
4762+            self._tail_block_size = mathutil.next_multiple(tail_size,
4763+                                                           self._required_shares)
4764+            self._tail_block_size /= self._required_shares
4765+
4766+        # We already know where the AES salts start; right after the end
4767+        # of the header (which is defined as the signable part + the offsets)
4768+        # We need to calculate where the share data starts, since we're
4769+        # responsible (after this method) for being able to write it.
4770+        self._offsets['share-data'] = MDMFHEADERSIZE
4771+        self._offsets['share-data'] += self._num_segments * SALT_SIZE
4772+        # We can also calculate where the encrypted private key begins
4773+        # from what we know know.
4774+        self._offsets['enc_privkey'] = self._offsets['share-data']
4775+        self._offsets['enc_privkey'] += self._block_size * (self._num_segments - 1)
4776+        self._offsets['enc_privkey'] += self._tail_block_size
4777+        # We'll wait for the rest. Callers can now call my "put_block" and
4778+        # "set_checkstring" methods.
4779+
4780+
4781+    def set_checkstring(self, checkstring):
4782+        """
4783+        Set checkstring checkstring for the given shnum.
4784+
4785+        By default, I assume that I am writing new shares to the grid.
4786+        If you don't explcitly set your own checkstring, I will use
4787+        one that requires that the remote share not exist. You will want
4788+        to use this method if you are updating a share in-place;
4789+        otherwise, writes will fail.
4790+        """
4791+        # You're allowed to overwrite checkstrings with this method;
4792+        # I assume that users know what they are doing when they call
4793+        # it.
4794+        if checkstring == "":
4795+            # We special-case this, since len("") = 0, but we need
4796+            # length of 1 for the case of an empty share to work on the
4797+            # storage server, which is what a checkstring that is the
4798+            # empty string means.
4799+            self._testvs = []
4800+        else:
4801+            self._testvs = []
4802+            self._testvs.append((0, len(checkstring), "eq", checkstring))
4803+
4804+
4805+    def __repr__(self):
4806+        return "MDMFSlotWriteProxy for share %d" % self._shnum
4807+
4808+
4809+    def get_checkstring(self):
4810+        """
4811+        Given a share number, I return a representation of what the
4812+        checkstring for that share on the server will look like.
4813+        """
4814+        if self._root_hash:
4815+            roothash = self._root_hash
4816+        else:
4817+            roothash = "\x00" * 32
4818+        # self._salt_hash and self._root_hash means that we've written
4819+        # both of these things to the server. self._salt_hash will be
4820+        # set first, though, and if self._root_hash isn't also set then
4821+        # neither of them are written to the server, so we need to leave
4822+        # them alone.
4823+        if self._salt_hash and self._root_hash:
4824+            salthash = self._salt_hash
4825+        else:
4826+            salthash = "\x00" * 32
4827+        checkstring = struct.pack(MDMFCHECKSTRING,
4828+                                  1,
4829+                                  self._seqnum,
4830+                                  roothash,
4831+                                  salthash)
4832+        return checkstring
4833+
4834+
4835+    def put_block(self, data, segnum, salt):
4836+        """
4837+        Put the encrypted-and-encoded data segment in the slot, along
4838+        with the salt.
4839+        """
4840+        if segnum >= self._num_segments:
4841+            raise LayoutInvalid("I won't overwrite the private key")
4842+        if len(salt) != SALT_SIZE:
4843+            raise LayoutInvalid("I was given a salt of size %d, but "
4844+                                "I wanted a salt of size %d")
4845+        if segnum + 1 == self._num_segments:
4846+            if len(data) != self._tail_block_size:
4847+                raise LayoutInvalid("I was given the wrong size block to write")
4848+        elif len(data) != self._block_size:
4849+            raise LayoutInvalid("I was given the wrong size block to write")
4850+
4851+        # We want to write at offsets['share-data'] + segnum * block_size.
4852+        assert self._offsets
4853+        assert self._offsets['share-data']
4854+
4855+        offset = self._offsets['share-data'] + segnum * self._block_size
4856+        datavs = [tuple([offset, data])]
4857+        # We also have to write the salt. This is at:
4858+        salt_offset = MDMFHEADERSIZE + SALT_SIZE * segnum
4859+        datavs.append(tuple([salt_offset, salt]))
4860+        return self._write(datavs)
4861+
4862+
4863+    def put_encprivkey(self, encprivkey):
4864+        """
4865+        Put the encrypted private key in the remote slot.
4866+        """
4867+        assert self._offsets
4868+        assert self._offsets['enc_privkey']
4869+        # You shouldn't re-write the encprivkey after the block hash
4870+        # tree is written, since that could cause the private key to run
4871+        # into the block hash tree. Before it writes the block hash
4872+        # tree, the block hash tree writing method writes the offset of
4873+        # the salt hash tree. So that's a good indicator of whether or
4874+        # not the block hash tree has been written.
4875+        if "salt_hash_tree" in self._offsets:
4876+            raise LayoutInvalid("You must write this before the block hash tree")
4877+
4878+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
4879+        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
4880+        def _on_failure():
4881+            del(self._offsets['block_hash_tree'])
4882+        return self._write(datavs, on_failure=_on_failure)
4883+
4884+
4885+    def put_blockhashes(self, blockhashes):
4886+        """
4887+        Put the block hash tree in the remote slot.
4888+
4889+        The encrypted private key must be put before the block hash
4890+        tree, since we need to know how large it is to know where the
4891+        block hash tree should go. The block hash tree must be put
4892+        before the salt hash tree, since its size determines the
4893+        offset of the share hash chain.
4894+        """
4895+        assert self._offsets
4896+        assert isinstance(blockhashes, list)
4897+        if "block_hash_tree" not in self._offsets:
4898+            raise LayoutInvalid("You must put the encrypted private key "
4899+                                "before you put the block hash tree")
4900+        # If written, the share hash chain causes the signature offset
4901+        # to be defined.
4902+        if "share_hash_chain" in self._offsets:
4903+            raise LayoutInvalid("You must put the block hash tree before "
4904+                                "you put the salt hash tree")
4905+        blockhashes_s = "".join(blockhashes)
4906+        self._offsets['salt_hash_tree'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
4907+        datavs = []
4908+        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
4909+        def _on_failure():
4910+            del(self._offsets['salt_hash_tree'])
4911+        return self._write(datavs, on_failure=_on_failure)
4912+
4913+
4914+    def put_salthashes(self, salthashes):
4915+        """
4916+        Put the salt hash tree in the remote slot.
4917+
4918+        The block hash tree must be put before the salt hash tree, since
4919+        its size tells us where we need to put the salt hash tree. This
4920+        method must be called before the share hash chain can be
4921+        uploaded, since the size of the salt hash tree tells us where
4922+        the share hash chain can go
4923+        """
4924+        assert self._offsets
4925+        assert isinstance(salthashes, list)
4926+        if "salt_hash_tree" not in self._offsets:
4927+            raise LayoutInvalid("You must put the block hash tree "
4928+                                "before putting the salt hash tree")
4929+        if "signature" in self._offsets:
4930+            raise LayoutInvalid("You must put the salt hash tree "
4931+                                "before you put the share hash chain")
4932+        # The root of the salt hash tree is at index 0. We'll write this when
4933+        # we put the root hash later; we just keep track of it for now.
4934+        self._salt_hash = salthashes[0]
4935+        salthashes_s = "".join(salthashes[1:])
4936+        self._offsets['share_hash_chain'] = self._offsets['salt_hash_tree'] + len(salthashes_s)
4937+        datavs = []
4938+        datavs.append(tuple([self._offsets['salt_hash_tree'], salthashes_s]))
4939+        def _on_failure():
4940+            del(self._offsets['share_hash_chain'])
4941+        return self._write(datavs, on_failure=_on_failure)
4942+
4943+
4944+    def put_sharehashes(self, sharehashes):
4945+        """
4946+        Put the share hash chain in the remote slot.
4947+
4948+        The salt hash tree must be put before the share hash chain,
4949+        since we need to know where the salt hash tree ends before we
4950+        can know where the share hash chain starts. The share hash chain
4951+        must be put before the signature, since the length of the packed
4952+        share hash chain determines the offset of the signature. Also,
4953+        semantically, you must know what the root of the salt hash tree
4954+        is before you can generate a valid signature.
4955+        """
4956+        assert isinstance(sharehashes, dict)
4957+        if "share_hash_chain" not in self._offsets:
4958+            raise LayoutInvalid("You need to put the salt hash tree before "
4959+                                "you can put the share hash chain")
4960+        # The signature comes after the share hash chain. If the
4961+        # signature has already been written, we must not write another
4962+        # share hash chain. The signature writes the verification key
4963+        # offset when it gets sent to the remote server, so we look for
4964+        # that.
4965+        if "verification_key" in self._offsets:
4966+            raise LayoutInvalid("You must write the share hash chain "
4967+                                "before you write the signature")
4968+        datavs = []
4969+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4970+                                  for i in sorted(sharehashes.keys())])
4971+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
4972+        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
4973+        def _on_failure():
4974+            del(self._offsets['signature'])
4975+        return self._write(datavs, on_failure=_on_failure)
4976+
4977+
4978+    def put_root_hash(self, roothash):
4979+        """
4980+        Put the root hash (the root of the share hash tree) in the
4981+        remote slot.
4982+        """
4983+        # It does not make sense to be able to put the root
4984+        # hash without first putting the share hashes, since you need
4985+        # the share hashes to generate the root hash.
4986+        #
4987+        # Signature is defined by the routine that places the share hash
4988+        # chain, so it's a good thing to look for in finding out whether
4989+        # or not the share hash chain exists on the remote server.
4990+        if "signature" not in self._offsets:
4991+            raise LayoutInvalid("You need to put the share hash chain "
4992+                                "before you can put the root share hash")
4993+        if len(roothash) != HASH_SIZE:
4994+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
4995+                                 % HASH_SIZE)
4996+        datavs = []
4997+        self._root_hash = roothash
4998+        # To write both of these values, we update the checkstring on
4999+        # the remote server, which includes them
5000+        checkstring = self.get_checkstring()
5001+        datavs.append(tuple([0, checkstring]))
5002+        # This write, if successful, changes the checkstring, so we need
5003+        # to update our internal checkstring to be consistent with the
5004+        # one on the server.
5005+        def _on_success():
5006+            self._testvs = [(0, len(checkstring), "eq", checkstring)]
5007+        def _on_failure():
5008+            self._root_hash = None
5009+            self._salt_hash = None
5010+        return self._write(datavs,
5011+                           on_success=_on_success,
5012+                           on_failure=_on_failure)
5013+
5014+
5015+    def get_signable(self):
5016+        """
5017+        Get the first eight fields of the mutable file; the parts that
5018+        are signed.
5019+        """
5020+        if not self._root_hash or not self._salt_hash:
5021+            raise LayoutInvalid("You need to set the root hash and the "
5022+                                "salt hash before getting something to "
5023+                                "sign")
5024+        return struct.pack(MDMFSIGNABLEHEADER,
5025+                           1,
5026+                           self._seqnum,
5027+                           self._root_hash,
5028+                           self._salt_hash,
5029+                           self._required_shares,
5030+                           self._total_shares,
5031+                           self._segment_size,
5032+                           self._data_length)
5033+
5034+
5035+    def put_signature(self, signature):
5036+        """
5037+        Put the signature field to the remote slot.
5038+
5039+        I require that the root hash and share hash chain have been put
5040+        to the grid before I will write the signature to the grid.
5041+        """
5042+        if "signature" not in self._offsets:
5043+            raise LayoutInvalid("You must put the share hash chain "
5044+        # It does not make sense to put a signature without first
5045+        # putting the root hash and the salt hash (since otherwise
5046+        # the signature would be incomplete), so we don't allow that.
5047+                       "before putting the signature")
5048+        if not self._root_hash:
5049+            raise LayoutInvalid("You must complete the signed prefix "
5050+                                "before computing a signature")
5051+        # If we put the signature after we put the verification key, we
5052+        # could end up running into the verification key, and will
5053+        # probably screw up the offsets as well. So we don't allow that.
5054+        # The method that writes the verification key defines the EOF
5055+        # offset before writing the verification key, so look for that.
5056+        if "EOF" in self._offsets:
5057+            raise LayoutInvalid("You must write the signature before the verification key")
5058+
5059+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
5060+        datavs = []
5061+        datavs.append(tuple([self._offsets['signature'], signature]))
5062+        def _on_failure():
5063+            del(self._offsets['verification_key'])
5064+        return self._write(datavs, on_failure=_on_failure)
5065+
5066+
5067+    def put_verification_key(self, verification_key):
5068+        """
5069+        Put the verification key into the remote slot.
5070+
5071+        I require that the signature have been written to the storage
5072+        server before I allow the verification key to be written to the
5073+        remote server.
5074+        """
5075+        if "verification_key" not in self._offsets:
5076+            raise LayoutInvalid("You must put the signature before you "
5077+                                "can put the verification key")
5078+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
5079+        datavs = []
5080+        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
5081+        def _on_failure():
5082+            del(self._offsets['EOF'])
5083+        return self._write(datavs, on_failure=_on_failure)
5084+
5085+
5086+    def finish_publishing(self):
5087+        """
5088+        Write the offset table and encoding parameters to the remote
5089+        slot, since that's the only thing we have yet to publish at this
5090+        point.
5091+        """
5092+        if "EOF" not in self._offsets:
5093+            raise LayoutInvalid("You must put the verification key before "
5094+                                "you can publish the offsets")
5095+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
5096+        offsets = struct.pack(MDMFOFFSETS,
5097+                              self._offsets['share-data'],
5098+                              self._offsets['enc_privkey'],
5099+                              self._offsets['block_hash_tree'],
5100+                              self._offsets['salt_hash_tree'],
5101+                              self._offsets['share_hash_chain'],
5102+                              self._offsets['signature'],
5103+                              self._offsets['verification_key'],
5104+                              self._offsets['EOF'])
5105+        datavs = []
5106+        datavs.append(tuple([offsets_offset, offsets]))
5107+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5108+        params = struct.pack(">BBQQ",
5109+                             self._required_shares,
5110+                             self._total_shares,
5111+                             self._segment_size,
5112+                             self._data_length)
5113+        datavs.append(tuple([encoding_parameters_offset, params]))
5114+        return self._write(datavs)
5115+
5116+
5117+    def _write(self, datavs, on_failure=None, on_success=None):
5118+        """I write the data vectors in datavs to the remote slot."""
5119+        tw_vectors = {}
5120+        new_share = False
5121+        if not self._testvs:
5122+            self._testvs = []
5123+            self._testvs.append(tuple([0, 1, "eq", ""]))
5124+            new_share = True
5125+        if not self._written:
5126+            # Write a new checkstring to the share when we write it, so
5127+            # that we have something to check later.
5128+            new_checkstring = self.get_checkstring()
5129+            datavs.append((0, new_checkstring))
5130+            def _first_write():
5131+                self._written = True
5132+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5133+            on_success = _first_write
5134+        tw_vectors[self._shnum] = (self._testvs, datavs, None)
5135+        datalength = sum([len(x[1]) for x in datavs])
5136+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5137+                                  self._storage_index,
5138+                                  self._secrets,
5139+                                  tw_vectors,
5140+                                  self._readv)
5141+        def _result(results):
5142+            if isinstance(results, failure.Failure) or not results[0]:
5143+                # Do nothing; the write was unsuccessful.
5144+                if on_failure: on_failure()
5145+            else:
5146+                if on_success: on_success()
5147+            return results
5148+        d.addCallback(_result)
5149+        return d
5150+
5151+
5152+class MDMFSlotReadProxy:
5153+    """
5154+    I read from a mutable slot filled with data written in the MDMF data
5155+    format (which is described above).
5156+
5157+    I can be initialized with some amount of data, which I will use (if
5158+    it is valid) to eliminate some of the need to fetch it from servers.
5159+    """
5160+    def __init__(self,
5161+                 rref,
5162+                 storage_index,
5163+                 shnum,
5164+                 data=""):
5165+        # Start the initialization process.
5166+        self._rref = rref
5167+        self._storage_index = storage_index
5168+        self.shnum = shnum
5169+
5170+        # Before doing anything, the reader is probably going to want to
5171+        # verify that the signature is correct. To do that, they'll need
5172+        # the verification key, and the signature. To get those, we'll
5173+        # need the offset table. So fetch the offset table on the
5174+        # assumption that that will be the first thing that a reader is
5175+        # going to do.
5176+
5177+        # The fact that these encoding parameters are None tells us
5178+        # that we haven't yet fetched them from the remote share, so we
5179+        # should. We could just not set them, but the checks will be
5180+        # easier to read if we don't have to use hasattr.
5181+        self._version_number = None
5182+        self._sequence_number = None
5183+        self._root_hash = None
5184+        self._salt_hash = None
5185+        self._salt = None
5186+        self._required_shares = None
5187+        self._total_shares = None
5188+        self._segment_size = None
5189+        self._data_length = None
5190+        self._offsets = None
5191+
5192+        # If the user has chosen to initialize us with some data, we'll
5193+        # try to satisfy subsequent data requests with that data before
5194+        # asking the storage server for it. If
5195+        self._data = data
5196+        # The way callers interact with cache in the filenode returns
5197+        # None if there isn't any cached data, but the way we index the
5198+        # cached data requires a string, so convert None to "".
5199+        if self._data == None:
5200+            self._data = ""
5201+
5202+        self._queue_observers = observer.ObserverList()
5203+        self._readvs = []
5204+
5205+
5206+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5207+        """
5208+        I fetch the offset table and the header from the remote slot if
5209+        I don't already have them. If I do have them, I do nothing and
5210+        return an empty Deferred.
5211+        """
5212+        if self._offsets:
5213+            return defer.succeed(None)
5214+        # At this point, we may be either SDMF or MDMF. Fetching 91
5215+        # bytes will be enough to get information for both SDMF and
5216+        # MDMF, though we'll be left with about 20 more bytes than we
5217+        # need if this ends up being SDMF. We could just fetch the first
5218+        # byte, which would save the extra bytes at the cost of an
5219+        # additional roundtrip after we parse the result.
5220+        readvs = [(0, 91)]
5221+        d = self._read(readvs, force_remote)
5222+        d.addCallback(self._process_encoding_parameters)
5223+
5224+        # Now, we have the encoding parameters, which will tell us
5225+        # where we need to look for the offset table.
5226+        def _fetch_offsets(ignored):
5227+            if self._version_number == 0:
5228+                # In SDMF, the offset table starts at byte 75, and
5229+                # extends for 32 bytes
5230+                readv = [(75, 32)] # struct.calcsize(">LLLLQQ") == 32
5231+
5232+            elif self._version_number == 1:
5233+                # In MDMF, the offset table starts at byte 91 and
5234+                # extends for 60 bytes
5235+                readv = [(91, 60)] # struct.calcsize(">LQQQQQQQ") == 60
5236+            else:
5237+                raise LayoutInvalid("I only understand SDMF and MDMF")
5238+            return readv
5239+
5240+        d.addCallback(_fetch_offsets)
5241+        d.addCallback(lambda readv:
5242+            self._read(readv, force_remote))
5243+        d.addCallback(self._process_offsets)
5244+        return d
5245+
5246+
5247+    def _process_encoding_parameters(self, encoding_parameters):
5248+        assert self.shnum in encoding_parameters
5249+        encoding_parameters = encoding_parameters[self.shnum][0]
5250+        # The first byte is the version number. It will tell us what
5251+        # to do next.
5252+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5253+        if verno == MDMF_VERSION:
5254+            (verno,
5255+             seqnum,
5256+             root_hash,
5257+             salt_hash,
5258+             k,
5259+             n,
5260+             segsize,
5261+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5262+                                      encoding_parameters)
5263+            self._salt_hash = salt_hash
5264+            if segsize == 0 and datalen == 0:
5265+                # Empty file, no segments.
5266+                self._num_segments = 0
5267+            else:
5268+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5269+
5270+        elif verno == SDMF_VERSION:
5271+            (verno,
5272+             seqnum,
5273+             root_hash,
5274+             salt,
5275+             k,
5276+             n,
5277+             segsize,
5278+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5279+                                      encoding_parameters[:75])
5280+            self._salt = salt
5281+            if segsize == 0 and datalen == 0:
5282+                # empty file
5283+                self._num_segments = 0
5284+            else:
5285+                # non-empty SDMF files have one segment.
5286+                self._num_segments = 1
5287+        else:
5288+            raise UnknownVersionError("You asked me to read mutable file "
5289+                                      "version %d, but I only understand "
5290+                                      "%d and %d" % (verno, SDMF_VERSION,
5291+                                                     MDMF_VERSION))
5292+
5293+        self._version_number = verno
5294+        self._sequence_number = seqnum
5295+        self._root_hash = root_hash
5296+        self._required_shares = k
5297+        self._total_shares = n
5298+        self._segment_size = segsize
5299+        self._data_length = datalen
5300+
5301+        self._block_size = self._segment_size / self._required_shares
5302+        # We can upload empty files, and need to account for this fact
5303+        # so as to avoid zero-division and zero-modulo errors.
5304+        if datalen > 0:
5305+            tail_size = self._data_length % self._segment_size
5306+        else:
5307+            tail_size = 0
5308+        if not tail_size:
5309+            self._tail_block_size = self._block_size
5310+        else:
5311+            self._tail_block_size = mathutil.next_multiple(tail_size,
5312+                                                    self._required_shares)
5313+            self._tail_block_size /= self._required_shares
5314+
5315+
5316+    def _process_offsets(self, offsets):
5317+        assert self.shnum in offsets
5318+        offsets = offsets[self.shnum][0]
5319+        if self._version_number == 0:
5320+            (signature,
5321+             share_hash_chain,
5322+             block_hash_tree,
5323+             share_data,
5324+             enc_privkey,
5325+             EOF) = struct.unpack(">LLLLQQ", offsets)
5326+            self._offsets = {}
5327+            self._offsets['signature'] = signature
5328+            self._offsets['share_data'] = share_data
5329+            self._offsets['block_hash_tree'] = block_hash_tree
5330+            self._offsets['share_hash_chain'] = share_hash_chain
5331+            self._offsets['enc_privkey'] = enc_privkey
5332+            self._offsets['EOF'] = EOF
5333+        elif self._version_number == 1:
5334+            (share_data,
5335+             encprivkey,
5336+             blockhashes,
5337+             salthashes,
5338+             sharehashes,
5339+             signature,
5340+             verification_key,
5341+             eof) = struct.unpack(MDMFOFFSETS, offsets)
5342+            self._offsets = {}
5343+            self._offsets['share_data'] = share_data
5344+            self._offsets['enc_privkey'] = encprivkey
5345+            self._offsets['block_hash_tree'] = blockhashes
5346+            self._offsets['salt_hash_tree']= salthashes
5347+            self._offsets['share_hash_chain'] = sharehashes
5348+            self._offsets['signature'] = signature
5349+            self._offsets['verification_key'] = verification_key
5350+            self._offsets['EOF'] = eof
5351+
5352+
5353+    def get_block_and_salt(self, segnum, queue=False):
5354+        """
5355+        I return (block, salt), where block is the block data and
5356+        salt is the salt used to encrypt that segment.
5357+        """
5358+        d = self._maybe_fetch_offsets_and_header()
5359+        def _then(ignored):
5360+            base_share_offset = self._offsets['share_data']
5361+            if self._version_number == 1:
5362+                base_salt_offset = struct.calcsize(MDMFHEADER)
5363+                salt_offset = base_salt_offset + SALT_SIZE * segnum
5364+            else:
5365+                salt_offset = None # no per-segment salts in SDMF
5366+            return base_share_offset, salt_offset
5367+
5368+        d.addCallback(_then)
5369+
5370+        def _calculate_share_offset(share_and_salt_offset):
5371+            base_share_offset, salt_offset = share_and_salt_offset
5372+            if segnum + 1 > self._num_segments:
5373+                raise LayoutInvalid("Not a valid segment number")
5374+
5375+            share_offset = base_share_offset + self._block_size * segnum
5376+            if segnum + 1 == self._num_segments:
5377+                data = self._tail_block_size
5378+            else:
5379+                data = self._block_size
5380+            readvs = [(share_offset, data)]
5381+            if salt_offset:
5382+                readvs.insert(0,(salt_offset, SALT_SIZE))
5383+            return readvs
5384+
5385+        d.addCallback(_calculate_share_offset)
5386+        d.addCallback(lambda readvs:
5387+            self._read(readvs, queue=queue))
5388+        def _process_results(results):
5389+            assert self.shnum in results
5390+            if self._version_number == 0:
5391+                # We only read the share data, but we know the salt from
5392+                # when we fetched the header
5393+                data = results[self.shnum]
5394+                if not data:
5395+                    data = ""
5396+                else:
5397+                    assert len(data) == 1
5398+                    data = data[0]
5399+                salt = self._salt
5400+            else:
5401+                data = results[self.shnum]
5402+                if not data:
5403+                    salt = data = ""
5404+                else:
5405+                    assert len(data) == 2
5406+                    salt, data = results[self.shnum]
5407+            return data, salt
5408+        d.addCallback(_process_results)
5409+        return d
5410+
5411+
5412+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5413+        """
5414+        I return the block hash tree
5415+
5416+        I take an optional argument, needed, which is a set of indices
5417+        correspond to hashes that I should fetch. If this argument is
5418+        missing, I will fetch the entire block hash tree; otherwise, I
5419+        may attempt to fetch fewer hashes, based on what needed says
5420+        that I should do. Note that I may fetch as many hashes as I
5421+        want, so long as the set of hashes that I do fetch is a superset
5422+        of the ones that I am asked for, so callers should be prepared
5423+        to tolerate additional hashes.
5424+        """
5425+        # TODO: Return only the parts of the block hash tree necessary
5426+        # to validate the blocknum provided?
5427+        # This is a good idea, but it is hard to implement correctly. It
5428+        # is bad to fetch any one block hash more than once, so we
5429+        # probably just want to fetch the whole thing at once and then
5430+        # serve it.
5431+        if needed == set([]):
5432+            return defer.succeed([])
5433+        d = self._maybe_fetch_offsets_and_header()
5434+        def _then(ignored):
5435+            blockhashes_offset = self._offsets['block_hash_tree']
5436+            if self._version_number == 1:
5437+                blockhashes_length = self._offsets['salt_hash_tree'] - blockhashes_offset
5438+            else:
5439+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5440+            readvs = [(blockhashes_offset, blockhashes_length)]
5441+            return readvs
5442+        d.addCallback(_then)
5443+        d.addCallback(lambda readvs:
5444+            self._read(readvs, queue=queue, force_remote=force_remote))
5445+        def _build_block_hash_tree(results):
5446+            assert self.shnum in results
5447+
5448+            rawhashes = results[self.shnum][0]
5449+            results = [rawhashes[i:i+HASH_SIZE]
5450+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5451+            return results
5452+        d.addCallback(_build_block_hash_tree)
5453+        return d
5454+
5455+
5456+    def get_salthashes(self, needed=None, queue=False):
5457+        """
5458+        I return the salt hash tree.
5459+
5460+        I accept an optional argument, needed, which is a set of indices
5461+        corresponding to hashes that I should fetch. If this argument is
5462+        missing, I will fetch and return the entire salt hash tree.
5463+        Otherwise, I may fetch any part of the salt hash tree, so long
5464+        as the part that I fetch and return is a superset of the part
5465+        that my caller has asked for. Callers should be prepared to
5466+        tolerate this behavior.
5467+
5468+        This method is only meaningful for MDMF files, as only MDMF
5469+        files have a salt hash tree. If the remote file is an SDMF file,
5470+        this method will return False.
5471+        """
5472+        # TODO: Only get the leaves nodes implied by salthashes
5473+        if needed == set([]):
5474+            return defer.succeed([])
5475+        d = self._maybe_fetch_offsets_and_header()
5476+        def _then(ignored):
5477+            if self._version_number == 0:
5478+                return []
5479+            else:
5480+                salthashes_offset = self._offsets['salt_hash_tree']
5481+                salthashes_length = self._offsets['share_hash_chain'] - salthashes_offset
5482+                return [(salthashes_offset, salthashes_length)]
5483+        d.addCallback(_then)
5484+        def _maybe_read(readvs):
5485+            if readvs:
5486+                return self._read(readvs, queue=queue)
5487+            else:
5488+                return False
5489+        d.addCallback(_maybe_read)
5490+        def _process_results(results):
5491+            if not results:
5492+                return False
5493+            assert self.shnum in results
5494+
5495+            rawhashes = results[self.shnum][0]
5496+            results = [rawhashes[i:i+HASH_SIZE]
5497+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5498+            return results
5499+        d.addCallback(_process_results)
5500+        return d
5501+
5502+
5503+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5504+        """
5505+        I return the part of the share hash chain placed to validate
5506+        this share.
5507+
5508+        I take an optional argument, needed. Needed is a set of indices
5509+        that correspond to the hashes that I should fetch. If needed is
5510+        not present, I will fetch and return the entire share hash
5511+        chain. Otherwise, I may fetch and return any part of the share
5512+        hash chain that is a superset of the part that I am asked to
5513+        fetch. Callers should be prepared to deal with more hashes than
5514+        they've asked for.
5515+        """
5516+        if needed == set([]):
5517+            return defer.succeed([])
5518+        d = self._maybe_fetch_offsets_and_header()
5519+
5520+        def _make_readvs(ignored):
5521+            sharehashes_offset = self._offsets['share_hash_chain']
5522+            if self._version_number == 0:
5523+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5524+            else:
5525+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5526+            readvs = [(sharehashes_offset, sharehashes_length)]
5527+            return readvs
5528+        d.addCallback(_make_readvs)
5529+        d.addCallback(lambda readvs:
5530+            self._read(readvs, queue=queue, force_remote=force_remote))
5531+        def _build_share_hash_chain(results):
5532+            assert self.shnum in results
5533+
5534+            sharehashes = results[self.shnum][0]
5535+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5536+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5537+            results = dict([struct.unpack(">H32s", data)
5538+                            for data in results])
5539+            return results
5540+        d.addCallback(_build_share_hash_chain)
5541+        return d
5542+
5543+
5544+    def get_encprivkey(self, queue=False):
5545+        """
5546+        I return the encrypted private key.
5547+        """
5548+        d = self._maybe_fetch_offsets_and_header()
5549+
5550+        def _make_readvs(ignored):
5551+            privkey_offset = self._offsets['enc_privkey']
5552+            if self._version_number == 0:
5553+                privkey_length = self._offsets['EOF'] - privkey_offset
5554+            else:
5555+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5556+            readvs = [(privkey_offset, privkey_length)]
5557+            return readvs
5558+        d.addCallback(_make_readvs)
5559+        d.addCallback(lambda readvs:
5560+            self._read(readvs, queue=queue))
5561+        def _process_results(results):
5562+            assert self.shnum in results
5563+            privkey = results[self.shnum][0]
5564+            return privkey
5565+        d.addCallback(_process_results)
5566+        return d
5567+
5568+
5569+    def get_signature(self, queue=False):
5570+        """
5571+        I return the signature of my share.
5572+        """
5573+        d = self._maybe_fetch_offsets_and_header()
5574+
5575+        def _make_readvs(ignored):
5576+            signature_offset = self._offsets['signature']
5577+            if self._version_number == 1:
5578+                signature_length = self._offsets['verification_key'] - signature_offset
5579+            else:
5580+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5581+            readvs = [(signature_offset, signature_length)]
5582+            return readvs
5583+        d.addCallback(_make_readvs)
5584+        d.addCallback(lambda readvs:
5585+            self._read(readvs, queue=queue))
5586+        def _process_results(results):
5587+            assert self.shnum in results
5588+            signature = results[self.shnum][0]
5589+            return signature
5590+        d.addCallback(_process_results)
5591+        return d
5592+
5593+
5594+    def get_verification_key(self, queue=False):
5595+        """
5596+        I return the verification key.
5597+        """
5598+        d = self._maybe_fetch_offsets_and_header()
5599+
5600+        def _make_readvs(ignored):
5601+            if self._version_number == 1:
5602+                vk_offset = self._offsets['verification_key']
5603+                vk_length = self._offsets['EOF'] - vk_offset
5604+            else:
5605+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5606+                vk_length = self._offsets['signature'] - vk_offset
5607+            readvs = [(vk_offset, vk_length)]
5608+            return readvs
5609+        d.addCallback(_make_readvs)
5610+        d.addCallback(lambda readvs:
5611+            self._read(readvs, queue=queue))
5612+        def _process_results(results):
5613+            assert self.shnum in results
5614+            verification_key = results[self.shnum][0]
5615+            return verification_key
5616+        d.addCallback(_process_results)
5617+        return d
5618+
5619+
5620+    def get_encoding_parameters(self):
5621+        """
5622+        I return (k, n, segsize, datalen)
5623+        """
5624+        d = self._maybe_fetch_offsets_and_header()
5625+        d.addCallback(lambda ignored:
5626+            (self._required_shares,
5627+             self._total_shares,
5628+             self._segment_size,
5629+             self._data_length))
5630+        return d
5631+
5632+
5633+    def get_seqnum(self):
5634+        """
5635+        I return the sequence number for this share.
5636+        """
5637+        d = self._maybe_fetch_offsets_and_header()
5638+        d.addCallback(lambda ignored:
5639+            self._sequence_number)
5640+        return d
5641+
5642+
5643+    def get_root_hash(self):
5644+        """
5645+        I return the root of the block hash tree
5646+        """
5647+        d = self._maybe_fetch_offsets_and_header()
5648+        d.addCallback(lambda ignored: self._root_hash)
5649+        return d
5650+
5651+
5652+    def get_salt_hash(self):
5653+        """
5654+        I return the flat salt hash
5655+        """
5656+        d = self._maybe_fetch_offsets_and_header()
5657+        d.addCallback(lambda ignored: self._salt_hash)
5658+        return d
5659+
5660+
5661+    def get_checkstring(self):
5662+        """
5663+        I return the packed representation of the following:
5664+
5665+            - version number
5666+            - sequence number
5667+            - root hash
5668+            - salt hash
5669+
5670+        which my users use as a checkstring to detect other writers.
5671+        """
5672+        d = self._maybe_fetch_offsets_and_header()
5673+        def _build_checkstring(ignored):
5674+            if self._salt_hash:
5675+                checkstring = struct.pack(MDMFCHECKSTRING,
5676+                                          self._version_number,
5677+                                          self._sequence_number,
5678+                                          self._root_hash,
5679+                                          self._salt_hash)
5680+            else:
5681+                checkstring = strut.pack(PREFIX,
5682+                                         self._version_number,
5683+                                         self._sequence_number,
5684+                                         self._root_hash,
5685+                                         self._salt)
5686+            return checkstring
5687+        d.addCallback(_build_checkstring)
5688+        return d
5689+
5690+
5691+    def get_prefix(self, force_remote):
5692+        d = self._maybe_fetch_offsets_and_header(force_remote)
5693+        d.addCallback(lambda ignored:
5694+            self._build_prefix())
5695+        return d
5696+
5697+
5698+    def _build_prefix(self):
5699+        # The prefix is another name for the part of the remote share
5700+        # that gets signed. It consists of everything up to and
5701+        # including the datalength, packed by struct.
5702+        if self._version_number == SDMF_VERSION:
5703+            format_string = SIGNED_PREFIX
5704+            salt_to_use = self._salt
5705+        else:
5706+            format_string = MDMFSIGNABLEHEADER
5707+            salt_to_use = self._salt_hash
5708+        return struct.pack(format_string,
5709+                           self._version_number,
5710+                           self._sequence_number,
5711+                           self._root_hash,
5712+                           salt_to_use,
5713+                           self._required_shares,
5714+                           self._total_shares,
5715+                           self._segment_size,
5716+                           self._data_length)
5717+
5718+
5719+    def _get_offsets_tuple(self):
5720+        # The offsets tuple is another component of the version
5721+        # information tuple. It is basically our offsets dictionary,
5722+        # itemized and in a tuple.
5723+        return self._offsets.copy()
5724+
5725+
5726+    def get_verinfo(self):
5727+        """
5728+        I return my verinfo tuple. This is used by the ServermapUpdater
5729+        to keep track of versions of mutable files.
5730+
5731+        The verinfo tuple for MDMF files contains:
5732+            - seqnum
5733+            - root hash
5734+            - salt hash
5735+            - segsize
5736+            - datalen
5737+            - k
5738+            - n
5739+            - prefix (the thing that you sign)
5740+            - a tuple of offsets
5741+
5742+        The verinfo tuple for SDMF files is the same, but contains a
5743+        16-byte IV instead of a hash of salts.
5744+        """
5745+        d = self._maybe_fetch_offsets_and_header()
5746+        def _build_verinfo(ignored):
5747+            if self._version_number == SDMF_VERSION:
5748+                salt_to_use = self._salt
5749+            else:
5750+                salt_to_use = self._salt_hash
5751+            return (self._sequence_number,
5752+                    self._root_hash,
5753+                    salt_to_use,
5754+                    self._segment_size,
5755+                    self._data_length,
5756+                    self._required_shares,
5757+                    self._total_shares,
5758+                    self._build_prefix(),
5759+                    self._get_offsets_tuple())
5760+        d.addCallback(_build_verinfo)
5761+        return d
5762+
5763+
5764+    def flush(self):
5765+        """
5766+        I flush my queue of read vectors.
5767+        """
5768+        d = self._read(self._readvs)
5769+        def _then(results):
5770+            self_readv = []
5771+            self._queue_observers.notify(results)
5772+            self._queue_observers = observer.ObserverList()
5773+        d.addCallback(_then)
5774+
5775+
5776+    def _read(self, readvs, force_remote=False, queue=False):
5777+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
5778+        # TODO: It's entirely possible to tweak this so that it just
5779+        # fulfills the requests that it can, and not demand that all
5780+        # requests are satisfiable before running it.
5781+        if not unsatisfiable and not force_remote:
5782+            results = [self._data[offset:offset+length]
5783+                       for (offset, length) in readvs]
5784+            results = {self.shnum: results}
5785+            return defer.succeed(results)
5786+        else:
5787+            if queue:
5788+                start = len(self._readvs)
5789+                self._readvs += readvs
5790+                end = len(self._readvs)
5791+                def _get_results(results, start, end):
5792+                    if not self.shnum in results:
5793+                        return {self._shnum: [""]}
5794+                    return {self.shnum: results[self.shnum][start:end]}
5795+                d = defer.Deferred()
5796+                d.addCallback(_get_results, start, end)
5797+                self._queue_observers.subscribe(d.callback)
5798+                return d
5799+            return self._rref.callRemote("slot_readv",
5800+                                         self._storage_index,
5801+                                         [self.shnum],
5802+                                         readvs)
5803+
5804+
5805+    def is_sdmf(self):
5806+        """I tell my caller whether or not my remote file is SDMF or MDMF
5807+        """
5808+        d = self._maybe_fetch_offsets_and_header()
5809+        d.addCallback(lambda ignored:
5810+            self._version_number == 0)
5811+        return d
5812+
5813+
5814+class LayoutInvalid(Exception):
5815+    """
5816+    This isn't a valid MDMF mutable file
5817+    """
5818hunk ./src/allmydata/test/test_storage.py 24
5819      ReadBucketProxy
5820 from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
5821                                      LayoutInvalid, MDMFSIGNABLEHEADER, \
5822-                                     SIGNED_PREFIX
5823+                                     SIGNED_PREFIX, MDMFHEADER
5824 from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
5825                                  SDMF_VERSION
5826 from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
5827hunk ./src/allmydata/test/test_storage.py 1321
5828         self.encprivkey = "private"
5829         self.root_hash = self.block_hash
5830         self.salt_hash = self.root_hash
5831+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
5832         self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
5833         self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
5834hunk ./src/allmydata/test/test_storage.py 1324
5835+        # blockhashes and salt hashes are serialized in the same way,
5836+        # only we lop off the first element and store that in the
5837+        # header.
5838+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
5839 
5840 
5841     def tearDown(self):
5842hunk ./src/allmydata/test/test_storage.py 1393
5843             salts = ""
5844         else:
5845             salts = self.salt * 6
5846-        share_offset = 143 + len(salts)
5847+        share_offset = 151 + len(salts)
5848         if tail_segment:
5849             sharedata = self.block * 6
5850         elif empty:
5851hunk ./src/allmydata/test/test_storage.py 1404
5852         encrypted_private_key_offset = share_offset + len(sharedata)
5853         # The blockhashes come after the private key
5854         blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
5855-        # The sharehashes come after the blockhashes
5856-        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
5857+        # The salthashes come after the blockhashes
5858+        salthashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
5859+        # The sharehashes come after the salt hashes
5860+        sharehashes_offset = salthashes_offset + len(self.salt_hash_tree_s)
5861         # The signature comes after the share hash chain
5862         signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
5863         # The verification key comes after the signature
5864hunk ./src/allmydata/test/test_storage.py 1414
5865         verification_offset = signature_offset + len(self.signature)
5866         # The EOF comes after the verification key
5867         eof_offset = verification_offset + len(self.verification_key)
5868-        data += struct.pack(">LQQQQQQ",
5869+        data += struct.pack(">LQQQQQQQ",
5870                             share_offset,
5871                             encrypted_private_key_offset,
5872                             blockhashes_offset,
5873hunk ./src/allmydata/test/test_storage.py 1418
5874+                            salthashes_offset,
5875                             sharehashes_offset,
5876                             signature_offset,
5877                             verification_offset,
5878hunk ./src/allmydata/test/test_storage.py 1427
5879         self.offsets['share_data'] = share_offset
5880         self.offsets['enc_privkey'] = encrypted_private_key_offset
5881         self.offsets['block_hash_tree'] = blockhashes_offset
5882+        self.offsets['salt_hash_tree'] = salthashes_offset
5883         self.offsets['share_hash_chain'] = sharehashes_offset
5884         self.offsets['signature'] = signature_offset
5885         self.offsets['verification_key'] = verification_offset
5886hunk ./src/allmydata/test/test_storage.py 1440
5887         data += self.encprivkey
5888         # the block hash tree,
5889         data += self.block_hash_tree_s
5890+        # the salt hash tree
5891+        data += self.salt_hash_tree_s
5892         # the share hash chain,
5893         data += self.share_hash_chain_s
5894         # the signature,
5895hunk ./src/allmydata/test/test_storage.py 1562
5896         d.addCallback(lambda blockhashes:
5897             self.failUnlessEqual(self.block_hash_tree, blockhashes))
5898 
5899+        d.addCallback(lambda ignored:
5900+            mr.get_salthashes())
5901+        d.addCallback(lambda salthashes:
5902+            self.failUnlessEqual(self.salt_hash_tree[1:], salthashes))
5903+
5904         d.addCallback(lambda ignored:
5905             mr.get_sharehashes())
5906         d.addCallback(lambda sharehashes:
5907hunk ./src/allmydata/test/test_storage.py 1618
5908         return d
5909 
5910 
5911+    def test_read_salthashes_on_sdmf_file(self):
5912+        self.write_sdmf_share_to_server("si1")
5913+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
5914+        d = defer.succeed(None)
5915+        d.addCallback(lambda ignored:
5916+            mr.get_salthashes())
5917+        d.addCallback(lambda results:
5918+            self.failIf(results))
5919+        return d
5920+
5921+
5922     def test_read_with_different_tail_segment_size(self):
5923         self.write_test_share_to_server("si1", tail_segment=True)
5924         mr = MDMFSlotReadProxy(self.rref, "si1", 0)
5925hunk ./src/allmydata/test/test_storage.py 1744
5926             mw.put_blockhashes(self.block_hash_tree))
5927         d.addCallback(_check_next_write)
5928         d.addCallback(lambda ignored:
5929+            mw.put_salthashes(self.salt_hash_tree))
5930+        d.addCallback(_check_next_write)
5931+        d.addCallback(lambda ignored:
5932             mw.put_sharehashes(self.share_hash_chain))
5933         d.addCallback(_check_next_write)
5934         # Add the root hash and the salt hash. This should change the
5935hunk ./src/allmydata/test/test_storage.py 1754
5936         # now, since the read vectors are applied before the write
5937         # vectors.
5938         d.addCallback(lambda ignored:
5939-            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
5940+            mw.put_root_hash(self.root_hash))
5941         def _check_old_testv_after_new_one_is_written(results):
5942             result, readvs = results
5943             self.failUnless(result)
5944hunk ./src/allmydata/test/test_storage.py 1775
5945         return d
5946 
5947 
5948-    def test_blockhashes_after_share_hash_chain(self):
5949+    def test_blockhashes_after_salt_hash_tree(self):
5950         mw = self._make_new_mw("si1", 0)
5951         d = defer.succeed(None)
5952hunk ./src/allmydata/test/test_storage.py 1778
5953-        # Put everything up to and including the share hash chain
5954+        # Put everything up to and including the salt hash tree
5955         for i in xrange(6):
5956             d.addCallback(lambda ignored, i=i:
5957                 mw.put_block(self.block, i, self.salt))
5958hunk ./src/allmydata/test/test_storage.py 1787
5959         d.addCallback(lambda ignored:
5960             mw.put_blockhashes(self.block_hash_tree))
5961         d.addCallback(lambda ignored:
5962-            mw.put_sharehashes(self.share_hash_chain))
5963-        # Now try to put a block hash tree after the share hash chain.
5964+            mw.put_salthashes(self.salt_hash_tree))
5965+        # Now try to put a block hash tree after the salt hash tree
5966         # This won't necessarily overwrite the share hash chain, but it
5967         # is a bad idea in general -- if we write one that is anything
5968         # other than the exact size of the initial one, we will either
5969hunk ./src/allmydata/test/test_storage.py 1804
5970         return d
5971 
5972 
5973+    def test_salt_hash_tree_after_share_hash_chain(self):
5974+        mw = self._make_new_mw("si1", 0)
5975+        d = defer.succeed(None)
5976+        # Put everything up to and including the share hash chain
5977+        for i in xrange(6):
5978+            d.addCallback(lambda ignored, i=i:
5979+                mw.put_block(self.block, i, self.salt))
5980+        d.addCallback(lambda ignored:
5981+            mw.put_encprivkey(self.encprivkey))
5982+        d.addCallback(lambda ignored:
5983+            mw.put_blockhashes(self.block_hash_tree))
5984+        d.addCallback(lambda ignored:
5985+            mw.put_salthashes(self.salt_hash_tree))
5986+        d.addCallback(lambda ignored:
5987+            mw.put_sharehashes(self.share_hash_chain))
5988+
5989+        # Now try to put the salt hash tree again. This should fail for
5990+        # the same reason that it fails in the previous test.
5991+        d.addCallback(lambda ignored:
5992+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
5993+                            None,
5994+                            mw.put_salthashes, self.salt_hash_tree))
5995+        return d
5996+
5997+
5998     def test_encprivkey_after_blockhashes(self):
5999         mw = self._make_new_mw("si1", 0)
6000         d = defer.succeed(None)
6001hunk ./src/allmydata/test/test_storage.py 1859
6002         d.addCallback(lambda ignored:
6003             mw.put_blockhashes(self.block_hash_tree))
6004         d.addCallback(lambda ignored:
6005+            mw.put_salthashes(self.salt_hash_tree))
6006+        d.addCallback(lambda ignored:
6007             mw.put_sharehashes(self.share_hash_chain))
6008         d.addCallback(lambda ignored:
6009hunk ./src/allmydata/test/test_storage.py 1863
6010-            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
6011+            mw.put_root_hash(self.root_hash))
6012         d.addCallback(lambda ignored:
6013             mw.put_signature(self.signature))
6014         # Now try to put the share hash chain again. This should fail
6015hunk ./src/allmydata/test/test_storage.py 1886
6016         d.addCallback(lambda ignored:
6017             mw.put_blockhashes(self.block_hash_tree))
6018         d.addCallback(lambda ignored:
6019+            mw.put_salthashes(self.salt_hash_tree))
6020+        d.addCallback(lambda ignored:
6021             mw.put_sharehashes(self.share_hash_chain))
6022         d.addCallback(lambda ignored:
6023hunk ./src/allmydata/test/test_storage.py 1890
6024-            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
6025+            mw.put_root_hash(self.root_hash))
6026         d.addCallback(lambda ignored:
6027             mw.put_signature(self.signature))
6028         d.addCallback(lambda ignored:
6029hunk ./src/allmydata/test/test_storage.py 1991
6030             mw.put_blockhashes(self.block_hash_tree))
6031         d.addCallback(_check_success)
6032         d.addCallback(lambda ignored:
6033+            mw.put_salthashes(self.salt_hash_tree))
6034+        d.addCallback(_check_success)
6035+        d.addCallback(lambda ignored:
6036             mw.put_sharehashes(self.share_hash_chain))
6037         d.addCallback(_check_success)
6038         def _keep_old_checkstring(ignored):
6039hunk ./src/allmydata/test/test_storage.py 2001
6040             mw.set_checkstring("foobarbaz")
6041         d.addCallback(_keep_old_checkstring)
6042         d.addCallback(lambda ignored:
6043-            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
6044+            mw.put_root_hash(self.root_hash))
6045         d.addCallback(_check_failure)
6046         d.addCallback(lambda ignored:
6047             self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
6048hunk ./src/allmydata/test/test_storage.py 2009
6049             mw.set_checkstring(self.old_checkstring)
6050         d.addCallback(_restore_old_checkstring)
6051         d.addCallback(lambda ignored:
6052-            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
6053+            mw.put_root_hash(self.root_hash))
6054+        d.addCallback(_check_success)
6055         # The checkstring should have been set appropriately for us on
6056         # the last write; if we try to change it to something else,
6057         # that change should cause the verification key step to fail.
6058hunk ./src/allmydata/test/test_storage.py 2071
6059         d.addCallback(_fix_checkstring)
6060         d.addCallback(lambda ignored:
6061             mw.put_blockhashes(self.block_hash_tree))
6062+        d.addCallback(lambda ignored:
6063+            mw.put_salthashes(self.salt_hash_tree))
6064         d.addCallback(_break_checkstring)
6065         d.addCallback(lambda ignored:
6066             mw.put_sharehashes(self.share_hash_chain))
6067hunk ./src/allmydata/test/test_storage.py 2079
6068         d.addCallback(lambda ignored:
6069             self.shouldFail(LayoutInvalid, "out-of-order root hash",
6070                             None,
6071-                            mw.put_root_and_salt_hashes,
6072-                            self.root_hash, self.salt_hash))
6073+                            mw.put_root_hash, self.root_hash))
6074         d.addCallback(_fix_checkstring)
6075         d.addCallback(lambda ignored:
6076             mw.put_sharehashes(self.share_hash_chain))
6077hunk ./src/allmydata/test/test_storage.py 2085
6078         d.addCallback(_break_checkstring)
6079         d.addCallback(lambda ignored:
6080-            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
6081+            mw.put_root_hash(self.root_hash))
6082         d.addCallback(lambda ignored:
6083             self.shouldFail(LayoutInvalid, "out-of-order signature",
6084                             None,
6085hunk ./src/allmydata/test/test_storage.py 2092
6086                             mw.put_signature, self.signature))
6087         d.addCallback(_fix_checkstring)
6088         d.addCallback(lambda ignored:
6089-            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
6090+            mw.put_root_hash(self.root_hash))
6091         d.addCallback(_break_checkstring)
6092         d.addCallback(lambda ignored:
6093             mw.put_signature(self.signature))
6094hunk ./src/allmydata/test/test_storage.py 2131
6095         mw2 = self._make_new_mw("si1", 1)
6096         # Test writing some blocks.
6097         read = self.ss.remote_slot_readv
6098+        expected_salt_offset = struct.calcsize(MDMFHEADER)
6099+        expected_share_offset = expected_salt_offset + (16 * 6)
6100         def _check_block_write(i, share):
6101hunk ./src/allmydata/test/test_storage.py 2134
6102-            self.failUnlessEqual(read("si1", [share], [(239 + (i * 2), 2)]),
6103+            self.failUnlessEqual(read("si1", [share], [(expected_share_offset + (i * 2), 2)]),
6104                                 {share: [self.block]})
6105hunk ./src/allmydata/test/test_storage.py 2136
6106-            self.failUnlessEqual(read("si1", [share], [(143 + (i * 16), 16)]),
6107+            self.failUnlessEqual(read("si1", [share], [(expected_salt_offset + (i * 16), 16)]),
6108                                  {share: [self.salt]})
6109         d = defer.succeed(None)
6110         for i in xrange(6):
6111hunk ./src/allmydata/test/test_storage.py 2151
6112             d.addCallback(lambda ignored, i=i:
6113                 _check_block_write(i, 1))
6114 
6115-        def _spy_on_results(results):
6116-            print read("si1", [], [(0, 40000000)])
6117-            return results
6118-
6119         # Next, we make a fake encrypted private key, and put it onto the
6120         # storage server.
6121         d.addCallback(lambda ignored:
6122hunk ./src/allmydata/test/test_storage.py 2160
6123         #  salts:   16 * 6 = 96 bytes
6124         #  blocks:  2 * 6 = 12 bytes
6125         #   = 251 bytes
6126-        expected_private_key_offset = 251
6127+        expected_private_key_offset = expected_share_offset + len(self.block) * 6
6128         self.failUnlessEqual(len(self.encprivkey), 7)
6129         d.addCallback(lambda ignored:
6130hunk ./src/allmydata/test/test_storage.py 2163
6131-            self.failUnlessEqual(read("si1", [0], [(251, 7)]),
6132+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6133                                  {0: [self.encprivkey]}))
6134 
6135         # Next, we put a fake block hash tree.
6136hunk ./src/allmydata/test/test_storage.py 2173
6137         #  header + salts + blocks: 251 bytes
6138         #  encrypted private key:   7 bytes
6139         #       = 258 bytes
6140-        expected_block_hash_offset = 258
6141+        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6142         self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6143         d.addCallback(lambda ignored:
6144             self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6145hunk ./src/allmydata/test/test_storage.py 2179
6146                                  {0: [self.block_hash_tree_s]}))
6147 
6148+        # Next, we put a fake salt hash tree.
6149+        d.addCallback(lambda ignored:
6150+            mw.put_salthashes(self.salt_hash_tree))
6151+        # The salt hash tree got inserted at
6152+        # header + salts + blocks + private key = 258 bytes
6153+        # block hash tree:          32 * 6 = 192 bytes
6154+        #   = 450 bytes
6155+        expected_salt_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6156+        d.addCallback(lambda ignored:
6157+            self.failUnlessEqual(read("si1", [0], [(expected_salt_hash_offset, 32 * 5)]), {0: [self.salt_hash_tree_s]}))
6158+
6159         # Next, put a fake share hash chain
6160         d.addCallback(lambda ignored:
6161             mw.put_sharehashes(self.share_hash_chain))
6162hunk ./src/allmydata/test/test_storage.py 2196
6163         # The share hash chain got inserted at:
6164         # header + salts + blocks + private key = 258 bytes
6165         # block hash tree:                        32 * 6 = 192 bytes
6166-        #   = 450 bytes
6167-        expected_share_hash_offset = 450
6168+        # salt hash tree:                         32 * 5 = 160 bytes
6169+        #   = 610
6170+        expected_share_hash_offset = expected_salt_hash_offset + len(self.salt_hash_tree_s)
6171         d.addCallback(lambda ignored:
6172             self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6173                                  {0: [self.share_hash_chain_s]}))
6174hunk ./src/allmydata/test/test_storage.py 2204
6175 
6176         # Next, we put what is supposed to be the root hash of
6177-        # our share hash tree but isn't, along with the flat hash
6178-        # of all the salts.
6179+        # our share hash tree but isn't       
6180         d.addCallback(lambda ignored:
6181hunk ./src/allmydata/test/test_storage.py 2206
6182-            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
6183+            mw.put_root_hash(self.root_hash))
6184         # The root hash gets inserted at byte 9 (its position is in the header,
6185         # and is fixed). The salt is right after it.
6186         def _check(ignored):
6187hunk ./src/allmydata/test/test_storage.py 2220
6188         d.addCallback(lambda ignored:
6189             mw.put_signature(self.signature))
6190         # The signature gets written to:
6191-        #   header + salts + blocks + block and share hash tree = 654
6192-        expected_signature_offset = 654
6193+        #   header + salts + blocks + block and salt and share hash tree = 814
6194+        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6195         self.failUnlessEqual(len(self.signature), 9)
6196         d.addCallback(lambda ignored:
6197             self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6198hunk ./src/allmydata/test/test_storage.py 2231
6199         d.addCallback(lambda ignored:
6200             mw.put_verification_key(self.verification_key))
6201         # The verification key gets written to:
6202-        #   654 + 9 = 663 bytes
6203-        expected_verification_key_offset = 663
6204+        #   804 + 9 = 815 bytes
6205+        expected_verification_key_offset = expected_signature_offset + len(self.signature)
6206         self.failUnlessEqual(len(self.verification_key), 6)
6207         d.addCallback(lambda ignored:
6208             self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6209hunk ./src/allmydata/test/test_storage.py 2256
6210         # Next, we cause the offset table to be published.
6211         d.addCallback(lambda ignored:
6212             mw.finish_publishing())
6213-        expected_eof_offset = 669
6214+        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6215 
6216         # The offset table starts at byte 91. Happily, we have already
6217         # worked out most of these offsets above, but we want to make
6218hunk ./src/allmydata/test/test_storage.py 2289
6219             self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6220                                  {0: [expected_data_length]})
6221             # 91          4           The offset of the share data
6222-            expected_offset = struct.pack(">L", 239)
6223+            expected_offset = struct.pack(">L", expected_share_offset)
6224             self.failUnlessEqual(read("si1", [0], [(91, 4)]),
6225                                  {0: [expected_offset]})
6226             # 95          8           The offset of the encrypted private key
6227hunk ./src/allmydata/test/test_storage.py 2300
6228             expected_offset = struct.pack(">Q", expected_block_hash_offset)
6229             self.failUnlessEqual(read("si1", [0], [(103, 8)]),
6230                                  {0: [expected_offset]})
6231-            # 111         8           The offset of the share hash chain
6232-            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6233+            # 111         8           The offset of the salt hash tree
6234+            expected_offset = struct.pack(">Q", expected_salt_hash_offset)
6235             self.failUnlessEqual(read("si1", [0], [(111, 8)]),
6236                                  {0: [expected_offset]})
6237hunk ./src/allmydata/test/test_storage.py 2304
6238-            # 119         8           The offset of the signature
6239-            expected_offset = struct.pack(">Q", expected_signature_offset)
6240+            # 119         8           The offset of the share hash chain
6241+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6242             self.failUnlessEqual(read("si1", [0], [(119, 8)]),
6243                                  {0: [expected_offset]})
6244hunk ./src/allmydata/test/test_storage.py 2308
6245-            # 127         8           The offset of the verification key
6246-            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6247+            # 127         8           The offset of the signature
6248+            expected_offset = struct.pack(">Q", expected_signature_offset)
6249             self.failUnlessEqual(read("si1", [0], [(127, 8)]),
6250                                  {0: [expected_offset]})
6251hunk ./src/allmydata/test/test_storage.py 2312
6252-            # 135         8           offset of the EOF
6253-            expected_offset = struct.pack(">Q", expected_eof_offset)
6254+            # 135         8           offset of the verification_key
6255+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6256             self.failUnlessEqual(read("si1", [0], [(135, 8)]),
6257                                  {0: [expected_offset]})
6258hunk ./src/allmydata/test/test_storage.py 2316
6259-            # = 143 bytes in total.
6260+            # 143         8           offset of the EOF
6261+            expected_offset = struct.pack(">Q", expected_eof_offset)
6262+            self.failUnlessEqual(read("si1", [0], [(143, 8)]),
6263+                                 {0: [expected_offset]})
6264         d.addCallback(_check_offsets)
6265         return d
6266 
6267hunk ./src/allmydata/test/test_storage.py 2362
6268         return d
6269 
6270 
6271-    def test_write_rejected_with_invalid_salt_hash(self):
6272-        # Try writing an invalid salt hash. These should be SHA256d, and
6273-        # 32 bytes long as a result.
6274-        mw = self._make_new_mw("si2", 0)
6275-        invalid_salt_hash = "b" * 31
6276-        d = defer.succeed(None)
6277-        # Before this test can work, we need to put some blocks + salts,
6278-        # a block hash tree, and a share hash tree. Otherwise, we'll see
6279-        # failures that match what we are looking for, but are caused by
6280-        # the constraints imposed on operation ordering.
6281-        for i in xrange(6):
6282-            d.addCallback(lambda ignored, i=i:
6283-                mw.put_block(self.block, i, self.salt))
6284-        d.addCallback(lambda ignored:
6285-            mw.put_encprivkey(self.encprivkey))
6286-        d.addCallback(lambda ignored:
6287-            mw.put_blockhashes(self.block_hash_tree))
6288-        d.addCallback(lambda ignored:
6289-            mw.put_sharehashes(self.share_hash_chain))
6290-        d.addCallback(lambda ignored:
6291-            self.shouldFail(LayoutInvalid, "invalid root hash",
6292-                            None, mw.put_root_and_salt_hashes,
6293-                            self.root_hash, invalid_salt_hash))
6294-        return d
6295-
6296-
6297     def test_write_rejected_with_invalid_root_hash(self):
6298         # Try writing an invalid root hash. This should be SHA256d, and
6299         # 32 bytes long as a result.
6300hunk ./src/allmydata/test/test_storage.py 2381
6301         d.addCallback(lambda ignored:
6302             mw.put_blockhashes(self.block_hash_tree))
6303         d.addCallback(lambda ignored:
6304+            mw.put_salthashes(self.salt_hash_tree))
6305+        d.addCallback(lambda ignored:
6306             mw.put_sharehashes(self.share_hash_chain))
6307         d.addCallback(lambda ignored:
6308             self.shouldFail(LayoutInvalid, "invalid root hash",
6309hunk ./src/allmydata/test/test_storage.py 2386
6310-                            None, mw.put_root_and_salt_hashes,
6311-                            invalid_root_hash, self.salt_hash))
6312+                            None, mw.put_root_hash, invalid_root_hash))
6313         return d
6314 
6315 
6316hunk ./src/allmydata/test/test_storage.py 2461
6317             mw0.put_encprivkey(self.encprivkey))
6318 
6319 
6320+        # Try to write the salt hash tree without writing the block hash
6321+        # tree.
6322+        d.addCallback(lambda ignored:
6323+            self.shouldFail(LayoutInvalid, "salt hash tree before bht",
6324+                            None,
6325+                            mw0.put_salthashes, self.salt_hash_tree))
6326+
6327+
6328         # Try to write the share hash chain without writing the block
6329         # hash tree
6330         d.addCallback(lambda ignored:
6331hunk ./src/allmydata/test/test_storage.py 2473
6332             self.shouldFail(LayoutInvalid, "share hash chain before "
6333-                                           "block hash tree",
6334+                                           "salt hash tree",
6335                             None,
6336                             mw0.put_sharehashes, self.share_hash_chain))
6337 
6338hunk ./src/allmydata/test/test_storage.py 2478
6339         # Try to write the root hash and salt hash without writing either the
6340-        # block hashes or the share hashes
6341+        # block hashes or the salt hashes or the share hashes
6342         d.addCallback(lambda ignored:
6343             self.shouldFail(LayoutInvalid, "root hash before share hashes",
6344                             None,
6345hunk ./src/allmydata/test/test_storage.py 2482
6346-                            mw0.put_root_and_salt_hashes,
6347-                            self.root_hash, self.salt_hash))
6348+                            mw0.put_root_hash, self.root_hash))
6349 
6350         # Now write the block hashes and try again
6351         d.addCallback(lambda ignored:
6352hunk ./src/allmydata/test/test_storage.py 2487
6353             mw0.put_blockhashes(self.block_hash_tree))
6354+
6355+        d.addCallback(lambda ignored:
6356+            self.shouldFail(LayoutInvalid, "share hash before salt hashes",
6357+                            None,
6358+                            mw0.put_sharehashes, self.share_hash_chain))
6359         d.addCallback(lambda ignored:
6360             self.shouldFail(LayoutInvalid, "root hash before share hashes",
6361hunk ./src/allmydata/test/test_storage.py 2494
6362-                            None, mw0.put_root_and_salt_hashes,
6363-                            self.root_hash, self.salt_hash))
6364+                            None, mw0.put_root_hash, self.root_hash))
6365 
6366         # We haven't yet put the root hash on the share, so we shouldn't
6367         # be able to sign it.
6368hunk ./src/allmydata/test/test_storage.py 2512
6369                             None, mw0.put_verification_key,
6370                             self.verification_key))
6371 
6372-        # Now write the share hashes and verify that it works.
6373+        # Now write the salt hashes, and try again.
6374         d.addCallback(lambda ignored:
6375hunk ./src/allmydata/test/test_storage.py 2514
6376-            mw0.put_sharehashes(self.share_hash_chain))
6377+            mw0.put_salthashes(self.salt_hash_tree))
6378+
6379+        d.addCallback(lambda ignored:
6380+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6381+                            None,
6382+                            mw0.put_root_hash, self.root_hash))
6383 
6384         # We should still be unable to sign the header
6385         d.addCallback(lambda ignored:
6386hunk ./src/allmydata/test/test_storage.py 2527
6387                             None,
6388                             mw0.put_signature, self.signature))
6389 
6390+        # Now write the share hashes.
6391+        d.addCallback(lambda ignored:
6392+            mw0.put_sharehashes(self.share_hash_chain))
6393         # We should be able to write the root hash now too
6394         d.addCallback(lambda ignored:
6395hunk ./src/allmydata/test/test_storage.py 2532
6396-            mw0.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
6397+            mw0.put_root_hash(self.root_hash))
6398 
6399         # We should still be unable to put the verification key
6400         d.addCallback(lambda ignored:
6401hunk ./src/allmydata/test/test_storage.py 2569
6402         d.addCallback(lambda ignored:
6403             mw.put_blockhashes(self.block_hash_tree))
6404         d.addCallback(lambda ignored:
6405+            mw.put_salthashes(self.salt_hash_tree))
6406+        d.addCallback(lambda ignored:
6407             mw.put_sharehashes(self.share_hash_chain))
6408         d.addCallback(lambda ignored:
6409hunk ./src/allmydata/test/test_storage.py 2573
6410-            mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash))
6411+            mw.put_root_hash(self.root_hash))
6412         d.addCallback(lambda ignored:
6413             mw.put_signature(self.signature))
6414         d.addCallback(lambda ignored:
6415hunk ./src/allmydata/test/test_storage.py 2768
6416         # This should be enough to fill in both the encoding parameters
6417         # and the table of offsets, which will complete the version
6418         # information tuple.
6419-        d.addCallback(_make_mr, 143)
6420+        d.addCallback(_make_mr, 151)
6421         d.addCallback(lambda mr:
6422             mr.get_verinfo())
6423         def _check_verinfo(verinfo):
6424hunk ./src/allmydata/test/test_storage.py 2804
6425         d.addCallback(_check_verinfo)
6426         # This is not enough data to read a block and a share, so the
6427         # wrapper should attempt to read this from the remote server.
6428-        d.addCallback(_make_mr, 143)
6429+        d.addCallback(_make_mr, 151)
6430         d.addCallback(lambda mr:
6431             mr.get_block_and_salt(0))
6432         def _check_block_and_salt((block, salt)):
6433hunk ./src/allmydata/test/test_storage.py 2815
6434         # are 6 * 16 = 96 bytes of salts before we can write shares.
6435         # Each block has two bytes, so 143 + 96 + 2 = 241 bytes should
6436         # be enough to read one block.
6437-        d.addCallback(_make_mr, 241)
6438+        d.addCallback(_make_mr, 249)
6439         d.addCallback(lambda mr:
6440             mr.get_block_and_salt(0))
6441         d.addCallback(_check_block_and_salt)
6442hunk ./src/allmydata/test/test_storage.py 3022
6443         return d
6444 
6445 
6446+    def test_reader_queue(self):
6447+        self.write_test_share_to_server('si1')
6448+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6449+        d1 = mr.get_block_and_salt(0, queue=True)
6450+        d2 = mr.get_blockhashes(queue=True)
6451+        d3 = mr.get_salthashes(queue=True)
6452+        d4 = mr.get_sharehashes(queue=True)
6453+        d5 = mr.get_signature(queue=True)
6454+        d6 = mr.get_verification_key(queue=True)
6455+        dl = defer.DeferredList([d1, d2, d3, d4, d5, d6])
6456+        mr.flush()
6457+        def _print(results):
6458+            self.failUnlessEqual(len(results), 6)
6459+            # We have one read for version information, one for offsets, and
6460+            # one for everything else.
6461+            self.failUnlessEqual(self.rref.read_count, 3)
6462+            block, salt = results[0][1] # results[0] is a boolean that says
6463+                                           # whether or not the operation
6464+                                           # worked.
6465+            self.failUnlessEqual(self.block, block)
6466+            self.failUnlessEqual(self.salt, salt)
6467+
6468+            blockhashes = results[1][1]
6469+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
6470+
6471+            salthashes = results[2][1]
6472+            self.failUnlessEqual(self.salt_hash_tree[1:], salthashes)
6473+
6474+            sharehashes = results[3][1]
6475+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
6476+
6477+            signature = results[4][1]
6478+            self.failUnlessEqual(self.signature, signature)
6479+
6480+            verification_key = results[5][1]
6481+            self.failUnlessEqual(self.verification_key, verification_key)
6482+        dl.addCallback(_print)
6483+        return dl
6484+
6485+
6486 class Stats(unittest.TestCase):
6487 
6488     def setUp(self):
6489}
6490[A first stab at a segmented uploader
6491Kevan Carstensen <kevan@isnotajoke.com>**20100623233248
6492 Ignore-this: 8df33da0795f4ff5948d4878933d03a2
6493 
6494 This uploader will upload, segment-by-segment, MDMF files. It will only
6495 do this if it thinks that the filenode that it is uploading represents
6496 an MDMF file; otherwise, it uploads the file as SDMF.
6497 
6498 My TODO list so far:
6499     - More robust peer selection; we'll want to use something like
6500       servers of happiness to figure out reliability and unreliability.
6501     - Clean up.
6502] {
6503hunk ./src/allmydata/mutable/publish.py 8
6504 from zope.interface import implements
6505 from twisted.internet import defer
6506 from twisted.python import failure
6507-from allmydata.interfaces import IPublishStatus
6508+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
6509 from allmydata.util import base32, hashutil, mathutil, idlib, log
6510 from allmydata import hashtree, codec
6511 from allmydata.storage.server import si_b2a
6512hunk ./src/allmydata/mutable/publish.py 19
6513      UncoordinatedWriteError, NotEnoughServersError
6514 from allmydata.mutable.servermap import ServerMap
6515 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
6516-     unpack_checkstring, SIGNED_PREFIX
6517+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
6518+
6519+KiB = 1024
6520+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
6521 
6522 class PublishStatus:
6523     implements(IPublishStatus)
6524hunk ./src/allmydata/mutable/publish.py 112
6525         self._status.set_helper(False)
6526         self._status.set_progress(0.0)
6527         self._status.set_active(True)
6528+        # We use this to control how the file is written.
6529+        version = self._node.get_version()
6530+        assert version in (SDMF_VERSION, MDMF_VERSION)
6531+        self._version = version
6532 
6533     def get_status(self):
6534         return self._status
6535hunk ./src/allmydata/mutable/publish.py 134
6536         simultaneous write.
6537         """
6538 
6539-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
6540-        # 2: perform peer selection, get candidate servers
6541-        #  2a: send queries to n+epsilon servers, to determine current shares
6542-        #  2b: based upon responses, create target map
6543-        # 3: send slot_testv_and_readv_and_writev messages
6544-        # 4: as responses return, update share-dispatch table
6545-        # 4a: may need to run recovery algorithm
6546-        # 5: when enough responses are back, we're done
6547+        # 0. Setup encoding parameters, encoder, and other such things.
6548+        # 1. Encrypt, encode, and publish segments.
6549 
6550         self.log("starting publish, datalen is %s" % len(newdata))
6551         self._status.set_size(len(newdata))
6552hunk ./src/allmydata/mutable/publish.py 187
6553         self.bad_peers = set() # peerids who have errbacked/refused requests
6554 
6555         self.newdata = newdata
6556-        self.salt = os.urandom(16)
6557 
6558hunk ./src/allmydata/mutable/publish.py 188
6559+        # This will set self.segment_size, self.num_segments, and
6560+        # self.fec.
6561         self.setup_encoding_parameters()
6562 
6563         # if we experience any surprises (writes which were rejected because
6564hunk ./src/allmydata/mutable/publish.py 238
6565             self.bad_share_checkstrings[key] = old_checkstring
6566             self.connections[peerid] = self._servermap.connections[peerid]
6567 
6568-        # create the shares. We'll discard these as they are delivered. SDMF:
6569-        # we're allowed to hold everything in memory.
6570+        # Now, the process dovetails -- if this is an SDMF file, we need
6571+        # to write an SDMF file. Otherwise, we need to write an MDMF
6572+        # file.
6573+        if self._version == MDMF_VERSION:
6574+            return self._publish_mdmf()
6575+        else:
6576+            return self._publish_sdmf()
6577+        #return self.done_deferred
6578+
6579+    def _publish_mdmf(self):
6580+        # Next, we find homes for all of the shares that we don't have
6581+        # homes for yet.
6582+        # TODO: Make this part do peer selection.
6583+        self.update_goal()
6584+        self.writers = {}
6585+        # For each (peerid, shnum) in self.goal, we make an
6586+        # MDMFSlotWriteProxy for that peer. We'll use this to write
6587+        # shares to the peer.
6588+        for key in self.goal:
6589+            peerid, shnum = key
6590+            write_enabler = self._node.get_write_enabler(peerid)
6591+            renew_secret = self._node.get_renewal_secret(peerid)
6592+            cancel_secret = self._node.get_cancel_secret(peerid)
6593+            secrets = (write_enabler, renew_secret, cancel_secret)
6594+
6595+            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
6596+                                                      self.connections[peerid],
6597+                                                      self._storage_index,
6598+                                                      secrets,
6599+                                                      self._new_seqnum,
6600+                                                      self.required_shares,
6601+                                                      self.total_shares,
6602+                                                      self.segment_size,
6603+                                                      len(self.newdata))
6604+            if (peerid, shnum) in self._servermap.servermap:
6605+                old_versionid, old_timestamp = self._servermap.servermap[key]
6606+                (old_seqnum, old_root_hash, old_salt, old_segsize,
6607+                 old_datalength, old_k, old_N, old_prefix,
6608+                 old_offsets_tuple) = old_versionid
6609+                old_checkstring = pack_checkstring(old_seqnum,
6610+                                                   old_root_hash,
6611+                                                   old_salt, 1)
6612+                self.writers[shnum].set_checkstring(old_checkstring)
6613+
6614+        # Now, we start pushing shares.
6615+        self._status.timings["setup"] = time.time() - self._started
6616+        def _start_pushing(res):
6617+            self._started_pushing = time.time()
6618+            return res
6619+
6620+        # First, we encrypt, encode, and publish the shares that we need
6621+        # to encrypt, encode, and publish.
6622+
6623+        # This will eventually hold the block hash chain for each share
6624+        # that we publish. We define it this way so that empty publishes
6625+        # will still have something to write to the remote slot.
6626+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
6627+        self.sharehash_leaves = None # eventually [sharehashes]
6628+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
6629+                              # validate the share]
6630 
6631hunk ./src/allmydata/mutable/publish.py 299
6632+        d = defer.succeed(None)
6633+        self.log("Starting push")
6634+        for i in xrange(self.num_segments - 1):
6635+            d.addCallback(lambda ignored, i=i:
6636+                self.push_segment(i))
6637+            d.addCallback(self._turn_barrier)
6638+        # We have at least one segment, so we will have a tail segment
6639+        if self.num_segments > 0:
6640+            d.addCallback(lambda ignored:
6641+                self.push_tail_segment())
6642+
6643+        d.addCallback(lambda ignored:
6644+            self.push_encprivkey())
6645+        d.addCallback(lambda ignored:
6646+            self.push_blockhashes())
6647+        d.addCallback(lambda ignored:
6648+            self.push_salthashes())
6649+        d.addCallback(lambda ignored:
6650+            self.push_sharehashes())
6651+        d.addCallback(lambda ignored:
6652+            self.push_toplevel_hashes_and_signature())
6653+        d.addCallback(lambda ignored:
6654+            self.finish_publishing())
6655+        return d
6656+
6657+
6658+    def _publish_sdmf(self):
6659         self._status.timings["setup"] = time.time() - self._started
6660hunk ./src/allmydata/mutable/publish.py 327
6661+        self.salt = os.urandom(16)
6662+
6663         d = self._encrypt_and_encode()
6664         d.addCallback(self._generate_shares)
6665         def _start_pushing(res):
6666hunk ./src/allmydata/mutable/publish.py 340
6667 
6668         return self.done_deferred
6669 
6670+
6671     def setup_encoding_parameters(self):
6672hunk ./src/allmydata/mutable/publish.py 342
6673-        segment_size = len(self.newdata)
6674+        if self._version == MDMF_VERSION:
6675+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
6676+        else:
6677+            segment_size = len(self.newdata) # SDMF is only one segment
6678         # this must be a multiple of self.required_shares
6679         segment_size = mathutil.next_multiple(segment_size,
6680                                               self.required_shares)
6681hunk ./src/allmydata/mutable/publish.py 355
6682                                                   segment_size)
6683         else:
6684             self.num_segments = 0
6685-        assert self.num_segments in [0, 1,] # SDMF restrictions
6686+        if self._version == SDMF_VERSION:
6687+            assert self.num_segments in (0, 1) # SDMF
6688+            return
6689+        # calculate the tail segment size.
6690+        self.tail_segment_size = len(self.newdata) % segment_size
6691+
6692+        if self.tail_segment_size == 0:
6693+            # The tail segment is the same size as the other segments.
6694+            self.tail_segment_size = segment_size
6695+
6696+        # We'll make an encoder ahead-of-time for the normal-sized
6697+        # segments (defined as any segment of segment_size size.
6698+        # (the part of the code that puts the tail segment will make its
6699+        #  own encoder for that part)
6700+        fec = codec.CRSEncoder()
6701+        fec.set_params(self.segment_size,
6702+                       self.required_shares, self.total_shares)
6703+        self.piece_size = fec.get_block_size()
6704+        self.fec = fec
6705+        # This is not technically part of the encoding parameters, but
6706+        # that we are setting up the encoder and encoding parameters is
6707+        # a good indicator that we will soon need it.
6708+        self.salt_hashes = []
6709+
6710+
6711+    def push_segment(self, segnum):
6712+        started = time.time()
6713+        segsize = self.segment_size
6714+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
6715+        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
6716+        assert len(data) == segsize
6717+
6718+        salt = os.urandom(16)
6719+        self.salt_hashes.append(hashutil.mutable_salt_hash(salt))
6720+
6721+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
6722+        enc = AES(key)
6723+        crypttext = enc.process(data)
6724+        assert len(crypttext) == len(data)
6725+
6726+        now = time.time()
6727+        self._status.timings["encrypt"] = now - started
6728+        started = now
6729+
6730+        # now apply FEC
6731+
6732+        self._status.set_status("Encoding")
6733+        crypttext_pieces = [None] * self.required_shares
6734+        piece_size = self.piece_size
6735+        for i in range(len(crypttext_pieces)):
6736+            offset = i * piece_size
6737+            piece = crypttext[offset:offset+piece_size]
6738+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
6739+            crypttext_pieces[i] = piece
6740+            assert len(piece) == piece_size
6741+        d = self.fec.encode(crypttext_pieces)
6742+        def _done_encoding(res):
6743+            elapsed = time.time() - started
6744+            self._status.timings["encode"] = elapsed
6745+            return res
6746+        d.addCallback(_done_encoding)
6747+
6748+        def _push_shares_and_salt(results):
6749+            shares, shareids = results
6750+            dl = []
6751+            for i in xrange(len(shares)):
6752+                sharedata = shares[i]
6753+                shareid = shareids[i]
6754+                block_hash = hashutil.block_hash(sharedata)
6755+                self.blockhashes[shareid].append(block_hash)
6756+
6757+                # find the writer for this share
6758+                d = self.writers[shareid].put_block(sharedata, segnum, salt)
6759+                dl.append(d)
6760+            # TODO: Naturally, we need to check on the results of these.
6761+            return defer.DeferredList(dl)
6762+        d.addCallback(_push_shares_and_salt)
6763+        return d
6764+
6765+
6766+    def push_tail_segment(self):
6767+        # This is essentially the same as push_segment, except that we
6768+        # don't use the cached encoder that we use elsewhere.
6769+        self.log("Pushing tail segment")
6770+        started = time.time()
6771+        segsize = self.segment_size
6772+        data = self.newdata[segsize * (self.num_segments-1):]
6773+        assert len(data) == self.tail_segment_size
6774+        salt = os.urandom(16)
6775+        self.salt_hashes.append(hashutil.mutable_salt_hash(salt))
6776+
6777+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
6778+        enc = AES(key)
6779+        crypttext = enc.process(data)
6780+        assert len(crypttext) == len(data)
6781+
6782+        now = time.time()
6783+        self._status.timings['encrypt'] = now - started
6784+        started = now
6785+
6786+        self._status.set_status("Encoding")
6787+        tail_fec = codec.CRSEncoder()
6788+        tail_fec.set_params(self.tail_segment_size,
6789+                            self.required_shares,
6790+                            self.total_shares)
6791+
6792+        crypttext_pieces = [None] * self.required_shares
6793+        piece_size = tail_fec.get_block_size()
6794+        for i in range(len(crypttext_pieces)):
6795+            offset = i * piece_size
6796+            piece = crypttext[offset:offset+piece_size]
6797+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
6798+            crypttext_pieces[i] = piece
6799+            assert len(piece) == piece_size
6800+        d = tail_fec.encode(crypttext_pieces)
6801+        def _push_shares_and_salt(results):
6802+            shares, shareids = results
6803+            dl = []
6804+            for i in xrange(len(shares)):
6805+                sharedata = shares[i]
6806+                shareid = shareids[i]
6807+                block_hash = hashutil.block_hash(sharedata)
6808+                self.blockhashes[shareid].append(block_hash)
6809+                # find the writer for this share
6810+                d = self.writers[shareid].put_block(sharedata,
6811+                                                    self.num_segments - 1,
6812+                                                    salt)
6813+                dl.append(d)
6814+            # TODO: Naturally, we need to check on the results of these.
6815+            return defer.DeferredList(dl)
6816+        d.addCallback(_push_shares_and_salt)
6817+        return d
6818+
6819+
6820+    def push_encprivkey(self):
6821+        started = time.time()
6822+        encprivkey = self._encprivkey
6823+        dl = []
6824+        def _spy_on_writer(results):
6825+            print results
6826+            return results
6827+        for shnum, writer in self.writers.iteritems():
6828+            d = writer.put_encprivkey(encprivkey)
6829+            dl.append(d)
6830+        d = defer.DeferredList(dl)
6831+        return d
6832+
6833+
6834+    def push_blockhashes(self):
6835+        started = time.time()
6836+        dl = []
6837+        def _spy_on_results(results):
6838+            print results
6839+            return results
6840+        self.sharehash_leaves = [None] * len(self.blockhashes)
6841+        for shnum, blockhashes in self.blockhashes.iteritems():
6842+            t = hashtree.HashTree(blockhashes)
6843+            self.blockhashes[shnum] = list(t)
6844+            # set the leaf for future use.
6845+            self.sharehash_leaves[shnum] = t[0]
6846+            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
6847+            dl.append(d)
6848+        d = defer.DeferredList(dl)
6849+        return d
6850+
6851+
6852+    def push_salthashes(self):
6853+        started = time.time()
6854+        dl = []
6855+        t = hashtree.HashTree(self.salt_hashes)
6856+        pushing = list(t)
6857+        for shnum in self.writers.iterkeys():
6858+            d = self.writers[shnum].put_salthashes(t)
6859+            dl.append(d)
6860+        dl = defer.DeferredList(dl)
6861+        return dl
6862+
6863+
6864+    def push_sharehashes(self):
6865+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
6866+        share_hash_chain = {}
6867+        ds = []
6868+        def _spy_on_results(results):
6869+            print results
6870+            return results
6871+        for shnum in xrange(len(self.sharehash_leaves)):
6872+            needed_indices = share_hash_tree.needed_hashes(shnum)
6873+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
6874+                                             for i in needed_indices] )
6875+            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
6876+            ds.append(d)
6877+        self.root_hash = share_hash_tree[0]
6878+        d = defer.DeferredList(ds)
6879+        return d
6880+
6881+
6882+    def push_toplevel_hashes_and_signature(self):
6883+        # We need to to three things here:
6884+        #   - Push the root hash and salt hash
6885+        #   - Get the checkstring of the resulting layout; sign that.
6886+        #   - Push the signature
6887+        ds = []
6888+        def _spy_on_results(results):
6889+            print results
6890+            return results
6891+        for shnum in xrange(self.total_shares):
6892+            d = self.writers[shnum].put_root_hash(self.root_hash)
6893+            ds.append(d)
6894+        d = defer.DeferredList(ds)
6895+        def _make_and_place_signature(ignored):
6896+            signable = self.writers[0].get_signable()
6897+            self.signature = self._privkey.sign(signable)
6898+
6899+            ds = []
6900+            for (shnum, writer) in self.writers.iteritems():
6901+                d = writer.put_signature(self.signature)
6902+                ds.append(d)
6903+            return defer.DeferredList(ds)
6904+        d.addCallback(_make_and_place_signature)
6905+        return d
6906+
6907+
6908+    def finish_publishing(self):
6909+        # We're almost done -- we just need to put the verification key
6910+        # and the offsets
6911+        ds = []
6912+        verification_key = self._pubkey.serialize()
6913+
6914+        def _spy_on_results(results):
6915+            print results
6916+            return results
6917+        for (shnum, writer) in self.writers.iteritems():
6918+            d = writer.put_verification_key(verification_key)
6919+            d.addCallback(lambda ignored, writer=writer:
6920+                writer.finish_publishing())
6921+            ds.append(d)
6922+        return defer.DeferredList(ds)
6923+
6924+
6925+    def _turn_barrier(self, res):
6926+        # putting this method in a Deferred chain imposes a guaranteed
6927+        # reactor turn between the pre- and post- portions of that chain.
6928+        # This can be useful to limit memory consumption: since Deferreds do
6929+        # not do tail recursion, code which uses defer.succeed(result) for
6930+        # consistency will cause objects to live for longer than you might
6931+        # normally expect.
6932+        return fireEventually(res)
6933+
6934 
6935     def _fatal_error(self, f):
6936         self.log("error during loop", failure=f, level=log.UNUSUAL)
6937hunk ./src/allmydata/mutable/publish.py 739
6938             self.log_goal(self.goal, "after update: ")
6939 
6940 
6941-
6942     def _encrypt_and_encode(self):
6943         # this returns a Deferred that fires with a list of (sharedata,
6944         # sharenum) tuples. TODO: cache the ciphertext, only produce the
6945hunk ./src/allmydata/mutable/publish.py 780
6946         d.addCallback(_done_encoding)
6947         return d
6948 
6949+
6950     def _generate_shares(self, shares_and_shareids):
6951         # this sets self.shares and self.root_hash
6952         self.log("_generate_shares")
6953hunk ./src/allmydata/mutable/publish.py 1168
6954             self._status.set_progress(1.0)
6955         eventually(self.done_deferred.callback, res)
6956 
6957-
6958hunk ./src/allmydata/test/test_mutable.py 593
6959                                            k, N, segsize, datalen)
6960                 self.failUnless(p._pubkey.verify(sig_material, signature))
6961                 #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
6962-                self.failUnless(isinstance(share_hash_chain, dict))
6963-                # TODO: Revisit this to make sure that the additional
6964-                # share hashes are really necessary.
6965-                #
6966-                # (just because they magically make the tests pass does
6967-                # not mean that they are necessary)
6968-                # ln2(10)++ + 1 for leaves.
6969-                self.failUnlessEqual(len(share_hash_chain), 5)
6970+                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
6971                 for shnum,share_hash in share_hash_chain.items():
6972                     self.failUnless(isinstance(shnum, int))
6973                     self.failUnless(isinstance(share_hash, str))
6974}
6975[Make the mutable downloader batch its reads
6976Kevan Carstensen <kevan@isnotajoke.com>**20100623233503
6977 Ignore-this: a948f48080d11f5d0c2c67be9105452b
6978] {
6979hunk ./src/allmydata/mutable/retrieve.py 215
6980         """
6981         I set up the encoding parameters, including k, n, the number
6982         of segments associated with this file, and the segment decoder.
6983-        I do not set the tail segment decoder, which is set in the
6984-        method that decodes the tail segment, as it is single-use.
6985         """
6986hunk ./src/allmydata/mutable/retrieve.py 216
6987-        # XXX: Or is it? What if servers fail in the last step?
6988         (seqnum,
6989          root_hash,
6990          IV,
6991hunk ./src/allmydata/mutable/retrieve.py 457
6992         self._active_readers.remove(reader)
6993         # TODO: self.readers.remove(reader)?
6994         for shnum in list(self.remaining_sharemap.keys()):
6995-            # TODO: Make sure that we set reader.peerid somewhere.
6996             self.remaining_sharemap.discard(shnum, reader.peerid)
6997 
6998 
6999hunk ./src/allmydata/mutable/retrieve.py 486
7000         self._bad_shares.add((reader.peerid, reader.shnum))
7001         self._status.problems[reader.peerid] = f
7002         self._last_failure = f
7003-        self.notify_server_corruption(reader.peerid, reader.shnum, f.value)
7004+        self.notify_server_corruption(reader.peerid, reader.shnum,
7005+                                      str(f.value))
7006 
7007 
7008     def _download_current_segment(self):
7009hunk ./src/allmydata/mutable/retrieve.py 523
7010         # successful, we will assemble the results into plaintext.
7011         ds = []
7012         for reader in self._active_readers:
7013-            d = reader.get_block_and_salt(segnum)
7014-            d.addCallback(self._validate_block, segnum, reader)
7015-            d.addErrback(self._validation_failed, reader)
7016-            ds.append(d)
7017+            d = reader.get_block_and_salt(segnum, queue=True)
7018+            d2 = self._get_needed_hashes(reader, segnum)
7019+            dl = defer.DeferredList([d, d2])
7020+            dl.addCallback(self._validate_block, segnum, reader)
7021+            dl.addErrback(self._validation_failed, reader)
7022+            ds.append(dl)
7023+            reader.flush()
7024         dl = defer.DeferredList(ds)
7025         dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
7026         return dl
7027hunk ./src/allmydata/mutable/retrieve.py 609
7028         return
7029 
7030 
7031-    def _validate_block(self, (block, salt), segnum, reader):
7032+    def _validate_block(self, results, segnum, reader):
7033         """
7034         I validate a block from one share on a remote server.
7035         """
7036hunk ./src/allmydata/mutable/retrieve.py 615
7037         # Grab the part of the block hash tree that is necessary to
7038         # validate this block, then generate the block hash root.
7039-        d = self._get_needed_hashes(reader, segnum)
7040-        def _handle_validation(block_and_sharehashes):
7041-            self.log("validating share %d for segment %d" % (reader.shnum,
7042+        self.log("validating share %d for segment %d" % (reader.shnum,
7043                                                              segnum))
7044hunk ./src/allmydata/mutable/retrieve.py 617
7045-            blockhashes, sharehashes = block_and_sharehashes
7046-            blockhashes = dict(enumerate(blockhashes[1]))
7047-            bht = self._block_hash_trees[reader.shnum]
7048-            # If we needed sharehashes in the last step, we'll want to
7049-            # get those dealt with before we start processing the
7050-            # blockhashes.
7051-            if self.share_hash_tree.needed_hashes(reader.shnum):
7052-                try:
7053-                    self.share_hash_tree.set_hashes(hashes=sharehashes[1])
7054-                except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
7055-                        IndexError), e:
7056-                    # XXX: This is a stupid message -- make it more
7057-                    # informative.
7058-                    raise CorruptShareError(reader.peerid,
7059-                                            reader.shnum,
7060-                                            "corrupt hashes: %s" % e)
7061+        # Did we fail to fetch either of the things that we were
7062+        # supposed to? Fail if so.
7063+        if not results[0][0] and results[1][0]:
7064+            # handled by the errback handler.
7065+            raise CorruptShareError("Connection error")
7066 
7067hunk ./src/allmydata/mutable/retrieve.py 623
7068-            if not bht[0]:
7069-                share_hash = self.share_hash_tree.get_leaf(reader.shnum)
7070-                if not share_hash:
7071-                    raise CorruptShareError(reader.peerid,
7072-                                            reader.shnum,
7073-                                            "missing the root hash")
7074-                bht.set_hashes({0: share_hash})
7075+        block_and_salt, block_and_sharehashes = results
7076+        block, salt = block_and_salt[1]
7077+        blockhashes, sharehashes = block_and_sharehashes[1]
7078 
7079hunk ./src/allmydata/mutable/retrieve.py 627
7080-            if bht.needed_hashes(segnum, include_leaf=True):
7081-                try:
7082-                    bht.set_hashes(blockhashes)
7083-                except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
7084-                        IndexError), e:
7085-                    raise CorruptShareError(reader.peerid,
7086-                                            reader.shnum,
7087-                                            "block hash tree failure: %s" % e)
7088+        blockhashes = dict(enumerate(blockhashes[1]))
7089+        self.log("the reader gave me the following blockhashes: %s" % \
7090+                 blockhashes.keys())
7091+        self.log("the reader gave me the following sharehashes: %s" % \
7092+                 sharehashes[1].keys())
7093+        bht = self._block_hash_trees[reader.shnum]
7094 
7095hunk ./src/allmydata/mutable/retrieve.py 634
7096-            blockhash = hashutil.block_hash(block)
7097-            self.log("got blockhash %s" % [blockhash])
7098-            self.log("comparing to tree %s" % bht)
7099-            # If this works without an error, then validation is
7100-            # successful.
7101+        if bht.needed_hashes(segnum, include_leaf=True):
7102             try:
7103hunk ./src/allmydata/mutable/retrieve.py 636
7104-                bht.set_hashes(leaves={segnum: blockhash})
7105+                bht.set_hashes(blockhashes)
7106             except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
7107                     IndexError), e:
7108                 raise CorruptShareError(reader.peerid,
7109hunk ./src/allmydata/mutable/retrieve.py 643
7110                                         reader.shnum,
7111                                         "block hash tree failure: %s" % e)
7112 
7113-            # TODO: Validate the salt, too.
7114-            self.log('share %d is valid for segment %d' % (reader.shnum,
7115-                                                           segnum))
7116-            return {reader.shnum: (block, salt)}
7117-        d.addCallback(_handle_validation)
7118-        return d
7119+        blockhash = hashutil.block_hash(block)
7120+        # If this works without an error, then validation is
7121+        # successful.
7122+        try:
7123+           bht.set_hashes(leaves={segnum: blockhash})
7124+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
7125+                IndexError), e:
7126+            raise CorruptShareError(reader.peerid,
7127+                                    reader.shnum,
7128+                                    "block hash tree failure: %s" % e)
7129+
7130+        # Reaching this point means that we know that this segment
7131+        # is correct. Now we need to check to see whether the share
7132+        # hash chain is also correct.
7133+        # SDMF wrote share hash chains that didn't contain the
7134+        # leaves, which would be produced from the block hash tree.
7135+        # So we need to validate the block hash tree first. If
7136+        # successful, then bht[0] will contain the root for the
7137+        # shnum, which will be a leaf in the share hash tree, which
7138+        # will allow us to validate the rest of the tree.
7139+        if self.share_hash_tree.needed_hashes(reader.shnum,
7140+                                               include_leaf=True):
7141+            try:
7142+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
7143+                                            leaves={reader.shnum: bht[0]})
7144+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
7145+                    IndexError), e:
7146+                raise CorruptShareError(reader.peerid,
7147+                                        reader.shnum,
7148+                                        "corrupt hashes: %s" % e)
7149+
7150+        # TODO: Validate the salt, too.
7151+        self.log('share %d is valid for segment %d' % (reader.shnum,
7152+                                                       segnum))
7153+        return {reader.shnum: (block, salt)}
7154 
7155 
7156     def _get_needed_hashes(self, reader, segnum):
7157hunk ./src/allmydata/mutable/retrieve.py 695
7158         # hash tree, and is a leaf in the share hash tree. This is fine,
7159         # since any share corruption will be detected in the share hash
7160         # tree.
7161-        needed.discard(0)
7162-        # XXX: not now, causes test failures.
7163+        #needed.discard(0)
7164         self.log("getting blockhashes for segment %d, share %d: %s" % \
7165                  (segnum, reader.shnum, str(needed)))
7166hunk ./src/allmydata/mutable/retrieve.py 698
7167-        d1 = reader.get_blockhashes(needed)
7168+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
7169         if self.share_hash_tree.needed_hashes(reader.shnum):
7170             need = self.share_hash_tree.needed_hashes(reader.shnum)
7171             self.log("also need sharehashes for share %d: %s" % (reader.shnum,
7172hunk ./src/allmydata/mutable/retrieve.py 703
7173                                                                  str(need)))
7174-            d2 = reader.get_sharehashes(need)
7175+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
7176         else:
7177hunk ./src/allmydata/mutable/retrieve.py 705
7178-            d2 = defer.succeed(None)
7179+            d2 = defer.succeed({}) # the logic in the next method
7180+                                   # expects a dict
7181         dl = defer.DeferredList([d1, d2])
7182         return dl
7183 
7184}
7185
7186Context:
7187
7188[docs: about.html link to home page early on, and be decentralized storage instead of cloud storage this time around
7189zooko@zooko.com**20100619065318
7190 Ignore-this: dc6db03f696e5b6d2848699e754d8053
7191] 
7192[docs: update about.html, especially to have a non-broken link to quickstart.html, and also to comment out the broken links to "for Paranoids" and "for Corporates"
7193zooko@zooko.com**20100619065124
7194 Ignore-this: e292c7f51c337a84ebfeb366fbd24d6c
7195] 
7196[TAG allmydata-tahoe-1.7.0
7197zooko@zooko.com**20100619052631
7198 Ignore-this: d21e27afe6d85e2e3ba6a3292ba2be1
7199] 
7200[docs: update relnotes.txt for Tahoe-LAFS v1.7.0!
7201zooko@zooko.com**20100619052048
7202 Ignore-this: 1dd2c851f02adf3ab5a33040051fe05a
7203 ... and remove relnotes-short.txt (just use the first section of relnotes.txt for that purpose)
7204] 
7205[docs: update known_issues.txt with more detail about web browser "safe-browsing" features and slightly tweaked formatting
7206zooko@zooko.com**20100619051734
7207 Ignore-this: afc10be0da2517ddd0b58e42ef9aa46d
7208] 
7209[docs: quickstart.html: link to 1.7.0 zip file and add UTF-8 BOM
7210zooko@zooko.com**20100619050124
7211 Ignore-this: 5104fc90af542b97662b4016da975f34
7212] 
7213[docs: more CREDITS for Kevan, plus utf-8 BOM
7214zooko@zooko.com**20100619045809
7215 Ignore-this: ee9c3b7cf7e385c8ca396091cebc9ca6
7216] 
7217[docs: update NEWS for release 1.7.0
7218zooko@zooko.com**20100619045750
7219 Ignore-this: 112c352fd52297ebff8138896fc6353d
7220] 
7221[docs: apply patch from duck for #937 about "tahoe run" not working on introducers
7222zooko@zooko.com**20100619040754
7223 Ignore-this: d7213313f16e524996e91058e287a954
7224] 
7225[webapi.txt: fix statement about leap seconds.
7226david-sarah@jacaranda.org**20100619035603
7227 Ignore-this: 80b685446e915877a421cf3e31cedf30
7228] 
7229[running.html: Tahoe->Tahoe-LAFS in what used to be using.html, and #tahoe->#tahoe-lafs (IRC channel).
7230david-sarah@jacaranda.org**20100619033152
7231 Ignore-this: a0dfdfb46eab639aaa064981fb933c5c
7232] 
7233[test_backupdb.py: skip test_unicode if we can't represent the test filenames.
7234david-sarah@jacaranda.org**20100619022620
7235 Ignore-this: 6ee564b6c07f9bb0e89a25dc5b37194f
7236] 
7237[test_web.py: correct a test that was missed in the change to not write ctime/mtime.
7238david-sarah@jacaranda.org**20100619021718
7239 Ignore-this: 92edc2e1fd43b3e86e6b49bc43bae122
7240] 
7241[dirnode.py: stop writing 'ctime' and 'mtime' fields. Includes documentation and test changes.
7242david-sarah@jacaranda.org**20100618230119
7243 Ignore-this: 709119898499769dd64c7977db7c84a6
7244] 
7245[test_storage.py: print more information on test failures.
7246david-sarah@jacaranda.org**20100617034623
7247 Ignore-this: cc9a8656802a718ca4f2a6a530d35977
7248] 
7249[running.html: describe where 'bin/tahoe' is only once.
7250david-sarah@jacaranda.org**20100617033603
7251 Ignore-this: 6d92d9d8c77f3dfddfa7d061cbf2a791
7252] 
7253[Merge using.html into running.html.
7254david-sarah@jacaranda.org**20100617012857
7255 Ignore-this: a0fa8b56621fdb976bef4e5f4f6c824a
7256] 
7257[Remove firewall section from running.html and say to read configuration.txt instead.
7258david-sarah@jacaranda.org**20100617004513
7259 Ignore-this: d2e46fffa4855b01093e8240b5fd1eff
7260] 
7261[FTP-and-SFTP.txt: add Known Issues section.
7262david-sarah@jacaranda.org**20100619004311
7263 Ignore-this: 8d9b1da941cbc24657bb6ec268f984dd
7264] 
7265[FTP-and-SFTP.txt: remove description of public key format that is not actually implemented. Document that SFTP does not support server private keys with passphrases, and that FTP cannot list directories containing mutable files.
7266david-sarah@jacaranda.org**20100619001738
7267 Ignore-this: bf9ef53b85b934822ec76060e1fcb3cb
7268] 
7269[configuration.txt and servers-of-happiness.txt: 1 <= happy <= N, not k <= happy <= N. Also minor wording changes.
7270david-sarah@jacaranda.org**20100618050710
7271 Ignore-this: edac0716e753e1f1c4c755c85bec9a19
7272] 
7273[test_cli.py: fix test failure in CLI.test_listdir_unicode_good due to filenames returned from listdir_unicode no longer being normalized.
7274david-sarah@jacaranda.org**20100618045110
7275 Ignore-this: 598ffaef02d71e075f7e08fac44f48ff
7276] 
7277[tahoe backup: unicode tests.
7278david-sarah@jacaranda.org**20100618035211
7279 Ignore-this: 88ebab9f3218f083fdc635bff6599b60
7280] 
7281[CLI: allow Unicode patterns in exclude option to 'tahoe backup'.
7282david-sarah@jacaranda.org**20100617033901
7283 Ignore-this: 9d971129e1c8bae3c1cc3220993d592e
7284] 
7285[dirnodes: fix normalization hole where childnames in directories created by nodemaker.create_mutable/immutable_directory would not be normalized. Add a test that we normalize names coming out of a directory.
7286david-sarah@jacaranda.org**20100618000249
7287 Ignore-this: 46a9226eff1003013b067edbdbd4c25b
7288] 
7289[dirnode.py: comments about normalization changes.
7290david-sarah@jacaranda.org**20100617041411
7291 Ignore-this: 9040c4854e73a71dbbb55b50ea3b41b2
7292] 
7293[stringutils.py: remove unused import.
7294david-sarah@jacaranda.org**20100617034440
7295 Ignore-this: 16ec7d737c34665156c2ac486acd545a
7296] 
7297[test_stringutils.py: take account of the output of listdir_unicode no longer being normalized. Also use Unicode escapes, not UTF-8.
7298david-sarah@jacaranda.org**20100617034409
7299 Ignore-this: 47f3f072f0e2efea0abeac130f84c56f
7300] 
7301[test_dirnode.py: partial tests for normalization changes.
7302david-sarah@jacaranda.org**20100617034025
7303 Ignore-this: 2e3169dd8b120d42dff35bd267dcb417
7304] 
7305[SFTP: get 'ctime' attribute from 'tahoe:linkmotime'.
7306david-sarah@jacaranda.org**20100617033744
7307 Ignore-this: b2fabe12235f2e2a487c0b56c39953e7
7308] 
7309[stringutils.py: don't NFC-normalize the output of listdir_unicode.
7310david-sarah@jacaranda.org**20100617015537
7311 Ignore-this: 93c9b6f3d7c6812a0afa8d9e1b0b4faa
7312] 
7313[stringutils.py: Add encoding argument to quote_output. Also work around a bug in locale.getpreferredencoding on older Pythons.
7314david-sarah@jacaranda.org**20100616042012
7315 Ignore-this: 48174c37ad95205997e4d3cdd81f1e28
7316] 
7317[Provisional patch to NFC-normalize filenames going in and out of Tahoe directories.
7318david-sarah@jacaranda.org**20100616031450
7319 Ignore-this: ed08c9d8df37ef0b7cca42bb562c996b
7320] 
7321[how_to_make_a_tahoe-lafs_release.txt: reordering, add fuse-sshfs@lists.sourceforge.list as place to send relnotes.
7322david-sarah@jacaranda.org**20100618041854
7323 Ignore-this: 2e380a6e72917d3a20a65ceccd9a4df
7324] 
7325[running.html: fix overeager replacement of 'tahoe' with 'Tahoe-LAFS', and some simplifications.
7326david-sarah@jacaranda.org**20100617000952
7327 Ignore-this: 472b4b531c866574ed79f076b58495b5
7328] 
7329[Add a specification for servers of happiness.
7330Kevan Carstensen <kevan@isnotajoke.com>**20100524003508
7331 Ignore-this: 982e2be8a411be5beaf3582bdfde6151
7332] 
7333[Note that servers of happiness only applies to immutable files for the moment
7334Kevan Carstensen <kevan@isnotajoke.com>**20100524042836
7335 Ignore-this: cf83cac7a2b3ed347ae278c1a7d9a176
7336] 
7337[Add a note about running Tahoe-LAFS on a small grid to running.html
7338zooko@zooko.com**20100616140227
7339 Ignore-this: 14dfbff0d47144f7c2375108c6055dc2
7340 also Change "tahoe" and "Tahoe" to "Tahoe-LAFS" in running.html
7341 author: Kevan Carstensen
7342] 
7343[test_system.py: investigate failure in allmydata.test.test_system.SystemTest.test_upload_and_download_random_key due to bytes_sent not being an int
7344david-sarah@jacaranda.org**20100616001648
7345 Ignore-this: 9c78092ab7bfdc909acae3a144ddd1f8
7346] 
7347[SFTP: remove a dubious use of 'pragma: no cover'.
7348david-sarah@jacaranda.org**20100613164356
7349 Ignore-this: 8f96a81b1196017ed6cfa1d914e56fa5
7350] 
7351[SFTP: test that renaming onto a just-opened file fails.
7352david-sarah@jacaranda.org**20100612033709
7353 Ignore-this: 9b14147ad78b16a5ab0e0e4813491414
7354] 
7355[SFTP: further small improvements to test coverage. Also ensure that after a test failure, later tests don't fail spuriously due to the checks for heisenfile leaks.
7356david-sarah@jacaranda.org**20100612030737
7357 Ignore-this: 4ec1dd3d7542be42007987a2f51508e7
7358] 
7359[SFTP: further improve test coverage (paths containing '.', bad data for posix-rename extension, and error in test of openShell).
7360david-sarah@jacaranda.org**20100611213142
7361 Ignore-this: 956f9df7f9e8a66b506ca58dd9a5dbe7
7362] 
7363[SFTP: improve test coverage for no-write on mutable files, and check for heisenfile table leaks in all relevant tests. Delete test_memory_leak since it is now redundant.
7364david-sarah@jacaranda.org**20100611205752
7365 Ignore-this: 88be1cf323c10dd534a4b8fdac121e31
7366] 
7367[CLI.txt: introduce 'create-alias' before 'add-alias', document Unicode argument support, and other minor updates.
7368david-sarah@jacaranda.org**20100610225547
7369 Ignore-this: de7326e98d79291cdc15aed86ae61fe8
7370] 
7371[SFTP: add test for extension of file opened with FXF_APPEND.
7372david-sarah@jacaranda.org**20100610182647
7373 Ignore-this: c0216d26453ce3cb4b92eef37d218fb4
7374] 
7375[NEWS: add UTF-8 coding declaration.
7376david-sarah@jacaranda.org**20100609234851
7377 Ignore-this: 3e6ef125b278e0a982c88d23180a78ae
7378] 
7379[tests: bump up the timeout on this iputil test from 2s to 4s
7380zooko@zooko.com**20100609143017
7381 Ignore-this: 786b7f7bbc85d45cdf727a6293750798
7382] 
7383[docs: a few tweaks to NEWS and CREDITS and make quickstart.html point to 1.7.0β!
7384zooko@zooko.com**20100609142927
7385 Ignore-this: f8097d3062f41f06c4420a7c84a56481
7386] 
7387[docs: Update NEWS file with new features and bugfixes in 1.7.0
7388francois@ctrlaltdel.ch**20100609091120
7389 Ignore-this: 8c1014e4469ef530e5ff48d7d6ae71c5
7390] 
7391[docs: wording fix, thanks to Jeremy Visser, fix #987
7392francois@ctrlaltdel.ch**20100609081103
7393 Ignore-this: 6d2e627e0f1cd58c0e1394e193287a4b
7394] 
7395[SFTP: fix most significant memory leak described in #1045 (due to a file being added to all_heisenfiles under more than one direntry when renamed).
7396david-sarah@jacaranda.org**20100609080003
7397 Ignore-this: 490b4c14207f6725d0dd32c395fbcefa
7398] 
7399[test_stringutils.py: Fix test failure on CentOS builder, possibly Python 2.4.3-related.
7400david-sarah@jacaranda.org**20100609065056
7401 Ignore-this: 503b561b213baf1b92ae641f2fdf080a
7402] 
7403[Fix for Unicode-related test failures on Zooko's OS X 10.6 machine.
7404david-sarah@jacaranda.org**20100609055448
7405 Ignore-this: 395ad16429e56623edfa74457a121190
7406] 
7407[docs: update relnote.txt for Tahoe-LAFS v1.7.0β
7408zooko@zooko.com**20100609054602
7409 Ignore-this: 52e1bf86a91d45315960fb8806b7a479
7410] 
7411[stringutils.py, sftpd.py: Portability fixes for Python <= 2.5.
7412david-sarah@jacaranda.org**20100609013302
7413 Ignore-this: 9d9ce476ee1b96796e0f48cc5338f852
7414] 
7415[setup: move the mock library from install_requires to tests_require (re: #1016)
7416zooko@zooko.com**20100609050542
7417 Ignore-this: c51a4ff3e19ed630755be752d2233db4
7418] 
7419[Back out Windows-specific Unicode argument support for v1.7.
7420david-sarah@jacaranda.org**20100609000803
7421 Ignore-this: b230ffe6fdaf9a0d85dfe745b37b42fb
7422] 
7423[_auto_deps.py: allow Python 2.4.3 on Redhat-based distributions.
7424david-sarah@jacaranda.org**20100609003646
7425 Ignore-this: ad3cafdff200caf963024873d0ebff3c
7426] 
7427[setup: show-tool-versions.py: print out the output from the unix command "locale" and re-arrange encoding data a little bit
7428zooko@zooko.com**20100609040714
7429 Ignore-this: 69382719b462d13ff940fcd980776004
7430] 
7431[setup: add zope.interface to the packages described by show-tool-versions.py
7432zooko@zooko.com**20100609034915
7433 Ignore-this: b5262b2af5c953a5f68a60bd48dcaa75
7434] 
7435[CREDITS: update François's Description
7436zooko@zooko.com**20100608155513
7437 Ignore-this: a266b438d25ca2cb28eafff75aa4b2a
7438] 
7439[CREDITS: jsgf
7440zooko@zooko.com**20100608143052
7441 Ignore-this: 10abe06d40b88e22a9107d30f1b84810
7442] 
7443[setup: rename the setuptools_trial .egg that comes bundled in the base dir to not have "-py2.6" in its name, since it works with other versions of python as well
7444zooko@zooko.com**20100608041607
7445 Ignore-this: 64fe386d2e5fba0ab441116e74dad5a3
7446] 
7447[setup: rename the darcsver .egg that comes bundled in the base dir to not have "-py2.6" in its name, since it works with other versions of python as well
7448zooko@zooko.com**20100608041534
7449 Ignore-this: 53f925f160256409cf01b76d2583f83f
7450] 
7451[SFTP: suppress NoSuchChildError if heisenfile attributes have been updated in setAttrs, in the case where the parent is available.
7452david-sarah@jacaranda.org**20100608063753
7453 Ignore-this: 8c72a5a9c15934f8fe4594ba3ee50ddd
7454] 
7455[SFTP: ignore permissions when opening a file (needed for sshfs interoperability).
7456david-sarah@jacaranda.org**20100608055700
7457 Ignore-this: f87f6a430f629326a324ddd94426c797
7458] 
7459[test_web.py: fix pyflakes warnings introduced by byterange patch.
7460david-sarah@jacaranda.org**20100608042012
7461 Ignore-this: a7612724893b51d1154dec4372e0508
7462] 
7463[Improve HTTP/1.1 byterange handling
7464Jeremy Fitzhardinge <jeremy@goop.org>**20100310025913
7465 Ignore-this: 6d69e694973d618f0dc65983735cd9be
7466 
7467 Fix parsing of a Range: header to support:
7468  - multiple ranges (parsed, but not returned)
7469  - suffix byte ranges ("-2139")
7470  - correct handling of incorrectly formatted range headers
7471    (correct behaviour is to ignore the header and return the full
7472     file)
7473  - return appropriate error for ranges outside the file
7474 
7475 Multiple ranges are parsed, but only the first range is returned.
7476 Returning multiple ranges requires using the multipart/byterange
7477 content type.
7478 
7479] 
7480[tests: bump up the timeout on these tests; MM's buildslave is sometimes extremely slow on tests, but it will complete them if given enough time. MM is working on making that buildslave more predictable in how long it takes to run tests.
7481zooko@zooko.com**20100608033754
7482 Ignore-this: 98dc27692c5ace1e4b0650b6680629d7
7483] 
7484[test_cli.py: remove invalid 'test_listdir_unicode_bad' test.
7485david-sarah@jacaranda.org**20100607183730
7486 Ignore-this: fadfe87980dc1862f349bfcc21b2145f
7487] 
7488[check_memory.py: adapt to servers-of-happiness changes.
7489david-sarah@jacaranda.org**20100608013528
7490 Ignore-this: c6b28411c543d1aea2f148a955f7998
7491] 
7492[show-tool-versions.py: platform.linux_distribution() is not always available
7493david-sarah@jacaranda.org**20100608004523
7494 Ignore-this: 793fb4050086723af05d06bed8b1b92a
7495] 
7496[show-tool-versions.py: show platform.linux_distribution()
7497david-sarah@jacaranda.org**20100608003829
7498 Ignore-this: 81cb5e5fc6324044f0fc6d82903c8223
7499] 
7500[Remove the 'tahoe debug consolidate' subcommand.
7501david-sarah@jacaranda.org**20100607183757
7502 Ignore-this: 4b14daa3ae557cea07d6e119d25dafe9
7503] 
7504[common_http.py, tahoe_cp.py: Fix an error in calling the superclass constructor in HTTPError and MissingSourceError (introduced by the Unicode fixes).
7505david-sarah@jacaranda.org**20100607174714
7506 Ignore-this: 1a118d593d81c918a4717c887f033aec
7507] 
7508[tests: drastically increase timeout of this very time-consuming test in honor of François's ARM box
7509zooko@zooko.com**20100607115929
7510 Ignore-this: bf1bb52ffb6b5ccae71d4dde14621bc8
7511] 
7512[setup: update authorship, datestamp, licensing, and add special exceptions to allow combination with Eclipse- and QPL- licensed code
7513zooko@zooko.com**20100607062329
7514 Ignore-this: 5a1d7b12dfafd61283ea65a245416381
7515] 
7516[FTP-and-SFTP.txt: minor technical correction to doc for 'no-write' flag.
7517david-sarah@jacaranda.org**20100607061600
7518 Ignore-this: 66aee0c1b6c00538602d08631225e114
7519] 
7520[test_stringutils.py: trivial error in exception message for skipped test.
7521david-sarah@jacaranda.org**20100607061455
7522 Ignore-this: f261a5d4e2b8fe3bcc37e02539ba1ae2
7523] 
7524[More Unicode test fixes.
7525david-sarah@jacaranda.org**20100607053358
7526 Ignore-this: 6a271fb77c31f28cb7bdba63b26a2dd2
7527] 
7528[Unicode fixes for platforms with non-native-Unicode filesystems.
7529david-sarah@jacaranda.org**20100607043238
7530 Ignore-this: 2134dc1793c4f8e50350bd749c4c98c2
7531] 
7532[Unicode fixes.
7533david-sarah@jacaranda.org**20100607010215
7534 Ignore-this: d58727b5cd2ce00e6b6dae3166030138
7535] 
7536[setup: organize misc/ scripts and tools and remove obsolete ones
7537zooko@zooko.com**20100607051618
7538 Ignore-this: 161db1158c6b7be8365b0b3dee2e0b28
7539 This is for ticket #1068.
7540] 
7541[quickstart.html: link to snapshots page, sorted with most recent first.
7542david-sarah@jacaranda.org**20100606221127
7543 Ignore-this: 93ea7e6ee47acc66f6daac9cabffed2d
7544] 
7545[quickstart.html: We haven't released 1.7beta yet.
7546david-sarah@jacaranda.org**20100606220301
7547 Ignore-this: 4e18898cfdb08cc3ddd1ff94d43fdda7
7548] 
7549[setup: loosen the Desert Island test to allow it to check the network for new packages as long as it doesn't actually download any
7550zooko@zooko.com**20100606175717
7551 Ignore-this: e438a8eb3c1b0e68080711ec6ff93ffa
7552 (You can look but don't touch.)
7553] 
7554[Raise Python version requirement to 2.4.4 for non-UCS-2 builds, to avoid a critical Python security bug.
7555david-sarah@jacaranda.org**20100605031713
7556 Ignore-this: 2df2b6d620c5d8191c79eefe655059e2
7557] 
7558[setup: have the buildbots print out locale.getpreferredencoding(), locale.getdefaultlocale(), locale.getlocale(), and os.path.supports_unicode_filenames
7559zooko@zooko.com**20100605162932
7560 Ignore-this: 85e31e0e0e1364e9215420e272d58116
7561 Even though that latter one is completely useless, I'm curious.
7562] 
7563[unicode tests: fix missing import
7564zooko@zooko.com**20100604142630
7565 Ignore-this: db437fe8009971882aaea9de05e2bc3
7566] 
7567[unicode: make test_cli test a non-ascii argument, and make the fallback term encoding be locale.getpreferredencoding()
7568zooko@zooko.com**20100604141251
7569 Ignore-this: b2bfc07942f69141811e59891842bd8c
7570] 
7571[unicode: always decode json manifest as utf-8 then encode for stdout
7572zooko@zooko.com**20100604084840
7573 Ignore-this: ac481692315fae870a0f3562bd7db48e
7574 pyflakes pointed out that the exception handler fallback called an un-imported function, showing that the fallback wasn't being exercised.
7575 I'm not 100% sure that this patch is right and would appreciate François or someone reviewing it.
7576] 
7577[fix flakes
7578zooko@zooko.com**20100604075845
7579 Ignore-this: 3e6a84b78771b0ad519e771a13605f0
7580] 
7581[fix syntax of assertion handling that isn't portable to older versions of Python
7582zooko@zooko.com**20100604075805
7583 Ignore-this: 3a12b293aad25883fb17230266eb04ec
7584] 
7585[test_stringutils.py: Skip test test_listdir_unicode_good if filesystem supports only ASCII filenames
7586Francois Deppierraz <francois@ctrlaltdel.ch>**20100521160839
7587 Ignore-this: f2ccdbd04c8d9f42f1efb0eb80018257
7588] 
7589[test_stringutils.py: Skip test_listdir_unicode on mocked platform which cannot store non-ASCII filenames
7590Francois Deppierraz <francois@ctrlaltdel.ch>**20100521160559
7591 Ignore-this: b93fde736a8904712b506e799250a600
7592] 
7593[test_stringutils.py: Add a test class for OpenBSD 4.1 with LANG=C
7594Francois Deppierraz <francois@ctrlaltdel.ch>**20100521140053
7595 Ignore-this: 63f568aec259cef0e807752fc8150b73
7596] 
7597[test_stringutils.py: Mock the open() call in test_open_unicode
7598Francois Deppierraz <francois@ctrlaltdel.ch>**20100521135817
7599 Ignore-this: d8be4e56a6eefe7d60f97f01ea20ac67
7600 
7601 This test ensure that open(a_unicode_string) is used on Unicode platforms
7602 (Windows or MacOS X) and that open(a_correctly_encoded_bytestring) on other
7603 platforms such as Unix.
7604 
7605] 
7606[test_stringutils.py: Fix a trivial Python 2.4 syntax incompatibility
7607Francois Deppierraz <francois@ctrlaltdel.ch>**20100521093345
7608 Ignore-this: 9297e3d14a0dd37d0c1a4c6954fd59d3
7609] 
7610[test_cli.py: Fix tests when sys.stdout.encoding=None and refactor this code into functions
7611Francois Deppierraz <francois@ctrlaltdel.ch>**20100520084447
7612 Ignore-this: cf2286e225aaa4d7b1927c78c901477f
7613] 
7614[Fix handling of correctly encoded unicode filenames (#534)
7615Francois Deppierraz <francois@ctrlaltdel.ch>**20100520004356
7616 Ignore-this: 8a3a7df214a855f5a12dc0eeab6f2e39
7617 
7618 Tahoe CLI commands working on local files, for instance 'tahoe cp' or 'tahoe
7619 backup', have been improved to correctly handle filenames containing non-ASCII
7620 characters.
7621   
7622 In the case where Tahoe encounters a filename which cannot be decoded using the
7623 system encoding, an error will be returned and the operation will fail.  Under
7624 Linux, this typically happens when the filesystem contains filenames encoded
7625 with another encoding, for instance latin1, than the system locale, for
7626 instance UTF-8.  In such case, you'll need to fix your system with tools such
7627 as 'convmv' before using Tahoe CLI.
7628   
7629 All CLI commands have been improved to support non-ASCII parameters such as
7630 filenames and aliases on all supported Operating Systems except Windows as of
7631 now.
7632] 
7633[stringutils.py: Unicode helper functions + associated tests
7634Francois Deppierraz <francois@ctrlaltdel.ch>**20100520004105
7635 Ignore-this: 7a73fc31de2fd39d437d6abd278bfa9a
7636 
7637 This file contains a bunch of helper functions which converts
7638 unicode string from and to argv, filenames and stdout.
7639] 
7640[Add dependency on Michael Foord's mock library
7641Francois Deppierraz <francois@ctrlaltdel.ch>**20100519233325
7642 Ignore-this: 9bb01bf1e4780f6b98ed394c3b772a80
7643] 
7644[Resolve merge conflict for sftpd.py
7645david-sarah@jacaranda.org**20100603182537
7646 Ignore-this: ba8b543e51312ac949798eb8f5bd9d9c
7647] 
7648[SFTP: possible fix for metadata times being shown as the epoch.
7649david-sarah@jacaranda.org**20100602234514
7650 Ignore-this: bdd7dfccf34eff818ff88aa4f3d28790
7651] 
7652[SFTP: further improvements to test coverage.
7653david-sarah@jacaranda.org**20100602234422
7654 Ignore-this: 87eeee567e8d7562659442ea491e187c
7655] 
7656[SFTP: improve test coverage. Also make creating a directory fail when permissions are read-only (rather than ignoring the permissions).
7657david-sarah@jacaranda.org**20100602041934
7658 Ignore-this: a5e9d9081677bc7f3ddb18ca7a1f531f
7659] 
7660[dirnode.py: fix a bug in the no-write change for Adder, and improve test coverage. Add a 'metadata' argument to create_subdirectory, with documentation. Also update some comments in test_dirnode.py made stale by the ctime/mtime change.
7661david-sarah@jacaranda.org**20100602032641
7662 Ignore-this: 48817b54cd63f5422cb88214c053b03b
7663] 
7664[SFTP: fix a bug that caused the temporary files underlying EncryptedTemporaryFiles not to be closed.
7665david-sarah@jacaranda.org**20100601055310
7666 Ignore-this: 44fee4cfe222b2b1690f4c5e75083a52
7667] 
7668[SFTP: changes for #1063 ('no-write' field) including comment:1 (clearing owner write permission diminishes to a read cap). Includes documentation changes, but not tests for the new behaviour.
7669david-sarah@jacaranda.org**20100601051139
7670 Ignore-this: eff7c08bd47fd52bfe2b844dabf02558
7671] 
7672[SFTP: the same bug as in _sync_heisenfiles also occurred in two other places.
7673david-sarah@jacaranda.org**20100530060127
7674 Ignore-this: 8d137658fc6e4596fa42697476c39aa3
7675] 
7676[SFTP: another try at fixing the _sync_heisenfiles bug.
7677david-sarah@jacaranda.org**20100530055254
7678 Ignore-this: c15f76f32a60083a6b7de6ca0e917934
7679] 
7680[SFTP: fix silly bug in _sync_heisenfiles ('f is not ignore' vs 'not (f is ignore)').
7681david-sarah@jacaranda.org**20100530053807
7682 Ignore-this: 71c4bc62613bf8fef835886d8eb61c27
7683] 
7684[SFTP: log when a sync completes.
7685david-sarah@jacaranda.org**20100530051840
7686 Ignore-this: d99765663ceb673c8a693dfcf88c25ea
7687] 
7688[SFTP: fix bug in previous logging patch.
7689david-sarah@jacaranda.org**20100530050000
7690 Ignore-this: 613e4c115f03fe2d04c621b510340817
7691] 
7692[SFTP: more logging to track down OpenOffice hang.
7693david-sarah@jacaranda.org**20100530040809
7694 Ignore-this: 6c11f2d1eac9f62e2d0f04f006476a03
7695] 
7696[SFTP: avoid blocking close on a heisenfile that has been abandoned or never changed. Also, improve the logging to help track down a case where OpenOffice hangs on opening a file with FXF_READ|FXF_WRITE.
7697david-sarah@jacaranda.org**20100530025544
7698 Ignore-this: 9919dddd446fff64de4031ad51490d1c
7699] 
7700[Move suppression of DeprecationWarning about BaseException.message from sftpd.py to main __init__.py. Also, remove the global suppression of the 'integer argument expected, got float' warning, which turned out to be a bug.
7701david-sarah@jacaranda.org**20100529050537
7702 Ignore-this: 87648afa0dec0d2e73614007de102a16
7703] 
7704[SFTP: cater to clients that assume a file is created as soon as they have made an open request; also, fix some race conditions associated with closing a file at about the same time as renaming or removing it.
7705david-sarah@jacaranda.org**20100529045253
7706 Ignore-this: 2404076b2154ff2659e2b10e0b9e813c
7707] 
7708[SFTP: 'sync' any open files at a direntry before opening any new file at that direntry. This works around the sshfs misbehaviour of returning success to clients immediately on close.
7709david-sarah@jacaranda.org**20100525230257
7710 Ignore-this: 63245d6d864f8f591c86170864d7c57f
7711] 
7712[SFTP: handle removing a file while it is open. Also some simplifications of the logout handling.
7713david-sarah@jacaranda.org**20100525184210
7714 Ignore-this: 660ee80be6ecab783c60452a9da896de
7715] 
7716[SFTP: a posix-rename response should actually return an FXP_STATUS reply, not an FXP_EXTENDED_REPLY as Twisted Conch assumes. Work around this by raising an SFTPError with code FX_OK.
7717david-sarah@jacaranda.org**20100525033323
7718 Ignore-this: fe2914d3ef7f5194bbeaf3f2dda2ad7d
7719] 
7720[SFTP: fix problem with posix-rename code returning a Deferred for the renamed filenode, not for the result of the request (an empty string).
7721david-sarah@jacaranda.org**20100525020209
7722 Ignore-this: 69f7491df2a8f7ea92d999a6d9f0581d
7723] 
7724[SFTP: fix time handling to make sure floats are not passed into twisted.conch, and to print times in the future less ambiguously in directory listings.
7725david-sarah@jacaranda.org**20100524230412
7726 Ignore-this: eb1a3fb72492fa2fb19667b6e4300440
7727] 
7728[SFTP: name of the POSIX rename extension should be 'posix-rename@openssh.com', not 'extposix-rename@openssh.com'.
7729david-sarah@jacaranda.org**20100524021156
7730 Ignore-this: f90eb1ff9560176635386ee797a3fdc7
7731] 
7732[SFTP: avoid race condition where .write could be called on an OverwriteableFileConsumer after it had been closed.
7733david-sarah@jacaranda.org**20100523233830
7734 Ignore-this: 55d381064a15bd64381163341df4d09f
7735] 
7736[SFTP: log tracebacks for RAISEd exceptions.
7737david-sarah@jacaranda.org**20100523221535
7738 Ignore-this: c76a7852df099b358642f0631237cc89
7739] 
7740[SFTP: more logging to investigate behaviour of getAttrs(path).
7741david-sarah@jacaranda.org**20100523204236
7742 Ignore-this: e58fd35dc9015316e16a9f49f19bb469
7743] 
7744[SFTP: fix pyflakes warnings; drop 'noisy' versions of eventually_callback and eventually_errback; robustify conversion of exception messages to UTF-8.
7745david-sarah@jacaranda.org**20100523140905
7746 Ignore-this: 420196fc58646b05bbc9c3732b6eb314
7747] 
7748[SFTP: fixes and test cases for renaming of open files.
7749david-sarah@jacaranda.org**20100523032549
7750 Ignore-this: 32e0726be0fc89335f3035157e202c68
7751] 
7752[SFTP: Increase test_sftp timeout to cater for francois' ARM buildslave.
7753david-sarah@jacaranda.org**20100522191639
7754 Ignore-this: a5acf9660d304677048ab4dd72908ad8
7755] 
7756[SFTP: Fix error in support for getAttrs on an open file, to index open files by directory entry rather than path. Extend that support to renaming open files. Also, implement the extposix-rename@openssh.org extension, and some other minor refactoring.
7757david-sarah@jacaranda.org**20100522035836
7758 Ignore-this: 8ef93a828e927cce2c23b805250b81a4
7759] 
7760[SFTP tests: fix test_openDirectory_and_attrs that was failing in timezones west of UTC.
7761david-sarah@jacaranda.org**20100520181027
7762 Ignore-this: 9beaf602beef437c11c7e97f54ce2599
7763] 
7764[SFTP: allow getAttrs to succeed on a file that has been opened for creation but not yet uploaded or linked (part of #1050).
7765david-sarah@jacaranda.org**20100520035613
7766 Ignore-this: 2f59107d60d5476edac19361ccf6cf94
7767] 
7768[SFTP: improve logging so that results of requests are (usually) logged.
7769david-sarah@jacaranda.org**20100520003652
7770 Ignore-this: 3f59eeee374a3eba71db9be31d5a95
7771] 
7772[SFTP: add tests for more combinations of open flags.
7773david-sarah@jacaranda.org**20100519053933
7774 Ignore-this: b97ee351b1e8ecfecabac70698060665
7775] 
7776[SFTP: allow FXF_WRITE | FXF_TRUNC (#1050).
7777david-sarah@jacaranda.org**20100519043240
7778 Ignore-this: bd70009f11d07ac6e9fd0d1e3fa87a9b
7779] 
7780[SFTP: remove another case where we were logging data.
7781david-sarah@jacaranda.org**20100519012713
7782 Ignore-this: 83115daf3a90278fed0e3fc267607584
7783] 
7784[SFTP: avoid logging all data passed to callbacks.
7785david-sarah@jacaranda.org**20100519000651
7786 Ignore-this: ade6d69a473ada50acef6389fc7fdf69
7787] 
7788[SFTP: fixes related to reporting of permissions (needed for sshfs).
7789david-sarah@jacaranda.org**20100518054521
7790 Ignore-this: c51f8a5d0dc76b80d33ffef9b0541325
7791] 
7792[SFTP: change error code returned for ExistingChildError to FX_FAILURE (fixes gvfs with some picky programs such as gedit).
7793david-sarah@jacaranda.org**20100518004205
7794 Ignore-this: c194c2c9aaf3edba7af84b7413cec375
7795] 
7796[SFTP: fixed bugs that caused hangs during write (#1037).
7797david-sarah@jacaranda.org**20100517044228
7798 Ignore-this: b8b95e82c4057367388a1e6baada993b
7799] 
7800[SFTP: work around a probable bug in twisted.conch.ssh.session:loseConnection(). Also some minor error handling cleanups.
7801david-sarah@jacaranda.org**20100517012606
7802 Ignore-this: 5d3da7c4219cb0c14547e7fd70c74204
7803] 
7804[SFTP: Support statvfs extensions, avoid logging actual data, and decline shell sessions politely.
7805david-sarah@jacaranda.org**20100516154347
7806 Ignore-this: 9d05d23ba77693c03a61accd348ccbe5
7807] 
7808[SFTP: fix error in SFTPUserHandler arguments introduced by execCommand patch.
7809david-sarah@jacaranda.org**20100516014045
7810 Ignore-this: f5ee494dc6ad6aa536cc8144bd2e3d19
7811] 
7812[SFTP: implement execCommand to interoperate with clients that issue a 'df -P -k /' command. Also eliminate use of Zope adaptation.
7813david-sarah@jacaranda.org**20100516012754
7814 Ignore-this: 2d0ed28b759f67f83875b1eaf5778992
7815] 
7816[sftpd.py: 'log.OPERATIONAL' should be just 'OPERATIONAL'.
7817david-sarah@jacaranda.org**20100515155533
7818 Ignore-this: f2347cb3301bbccc086356f6edc685
7819] 
7820[Attempt to fix #1040 by making SFTPUser implement ISession.
7821david-sarah@jacaranda.org**20100515005719
7822 Ignore-this: b3baaf088ba567e861e61e347195dfc4
7823] 
7824[Eliminate Windows newlines from sftpd.py.
7825david-sarah@jacaranda.org**20100515005656
7826 Ignore-this: cd54fd25beb957887514ae76e08c277
7827] 
7828[Update SFTP implementation and tests: fix #1038 and switch to foolscap logging; also some code reorganization.
7829david-sarah@jacaranda.org**20100514043113
7830 Ignore-this: 262f76d953dcd4317210789f2b2bf5da
7831] 
7832[Tests for new SFTP implementation
7833david-sarah@jacaranda.org**20100512060552
7834 Ignore-this: 20308d4a59b3ebc868aad55ae0a7a981
7835] 
7836[New SFTP implementation: mutable files, read/write support, streaming download, Unicode filenames, and more
7837david-sarah@jacaranda.org**20100512055407
7838 Ignore-this: 906f51c48d974ba9cf360c27845c55eb
7839] 
7840[setup: adjust make clean target to ignore our bundled build tools
7841zooko@zooko.com**20100604051250
7842 Ignore-this: d24d2a3b849000790cfbfab69237454e
7843] 
7844[setup: bundle a copy of setuptools_trial as an unzipped egg in the base dir of the Tahoe-LAFS source tree
7845zooko@zooko.com**20100604044648
7846 Ignore-this: a4736e9812b4dab2d5a2bc4bfc5c3b28
7847 This is to work-around this Distribute issue:
7848 http://bitbucket.org/tarek/distribute/issue/55/revision-control-plugin-automatically-installed-as-a-build-dependency-is-not-present-when-another-build-dependency-is-being
7849] 
7850[setup: bundle a copy of darcsver in unzipped egg form in the root of the Tahoe-LAFS source tree
7851zooko@zooko.com**20100604044146
7852 Ignore-this: a51a52e82dd3a39225657ffa27decae2
7853 This is to work-around this Distribute issue:
7854 http://bitbucket.org/tarek/distribute/issue/55/revision-control-plugin-automatically-installed-as-a-build-dependency-is-not-present-when-another-build-dependency-is-being
7855] 
7856[quickstart.html: warn against installing Python at a path containing spaces.
7857david-sarah@jacaranda.org**20100604032413
7858 Ignore-this: c7118332573abd7762d9a897e650bc6a
7859] 
7860[setup: undo the previous patch to quote the executable in scripts
7861zooko@zooko.com**20100604025204
7862 Ignore-this: beda3b951c49d1111478618b8cabe005
7863 The problem isn't in the script, it is in the cli.exe script that is built by setuptools. This might be related to
7864 http://bugs.python.org/issue6792
7865 and
7866 http://bugs.python.org/setuptools/issue2
7867 Or it might be a separate issue involving the launcher.c code e.g. http://tahoe-lafs.org/trac/zetuptoolz/browser/launcher.c?rev=576#L210 and its handling of the interpreter name.
7868] 
7869[setup: put quotes around the path to executable in case it has spaces in it, when building a tahoe.exe for win32
7870zooko@zooko.com**20100604020836
7871 Ignore-this: 478684843169c94a9c14726fedeeed7d
7872] 
7873[Add must_exist, must_be_directory, and must_be_file arguments to DirectoryNode.delete. This will be used to fixes a minor condition in the SFTP frontend.
7874david-sarah@jacaranda.org**20100527194529
7875 Ignore-this: 6d8114cef4450c52c57639f82852716f
7876] 
7877[Fix test failures in test_web caused by changes to web page titles in #1062. Also, change a 'target' field to '_blank' instead of 'blank' in welcome.xhtml.
7878david-sarah@jacaranda.org**20100603232105
7879 Ignore-this: 6e2cc63f42b07e2a3b2d1a857abc50a6
7880] 
7881[misc/show-tool-versions.py: Display additional Python interpreter encoding informations (stdout, stdin and filesystem)
7882Francois Deppierraz <francois@ctrlaltdel.ch>**20100521094313
7883 Ignore-this: 3ae9b0b07fd1d53fb632ef169f7c5d26
7884] 
7885[dirnode.py: Fix bug that caused 'tahoe' fields, 'ctime' and 'mtime' not to be updated when new metadata is present.
7886david-sarah@jacaranda.org**20100602014644
7887 Ignore-this: 5bac95aa897b68f2785d481e49b6a66
7888] 
7889[dirnode.py: Fix #1034 (MetadataSetter does not enforce restriction on setting 'tahoe' subkeys), and expose the metadata updater for use by SFTP. Also, support diminishing a child cap to read-only if 'no-write' is set in the metadata.
7890david-sarah@jacaranda.org**20100601045428
7891 Ignore-this: 14f26e17e58db97fad0dcfd350b38e95
7892] 
7893[Change doc comments in interfaces.py to take into account unknown nodes.
7894david-sarah@jacaranda.org**20100528171922
7895 Ignore-this: d2fde6890b3bca9c7275775f64fbff56
7896] 
7897[Trivial whitespace changes.
7898david-sarah@jacaranda.org**20100527194114
7899 Ignore-this: 98d611bc54ee20b01a5f6b334ff61b2d
7900] 
7901[Suppress 'integer argument expected, got float' DeprecationWarning everywhere
7902david-sarah@jacaranda.org**20100523221157
7903 Ignore-this: 80efd7e27798f5d2ad66c7a53e7048e5
7904] 
7905[Change shouldFail to avoid Unicode errors when converting Failure to str
7906david-sarah@jacaranda.org**20100512060754
7907 Ignore-this: 86ed419d332d9c33090aae2cde1dc5df
7908] 
7909[SFTP: relax pyasn1 version dependency to >= 0.0.8a.
7910david-sarah@jacaranda.org**20100520181437
7911 Ignore-this: 2c7b3dee7b7e14ba121d3118193a386a
7912] 
7913[SFTP: add pyasn1 as dependency, needed if we are using Twisted >= 9.0.0.
7914david-sarah@jacaranda.org**20100516193710
7915 Ignore-this: 76fd92e8a950bb1983a90a09e89c54d3
7916] 
7917[allmydata.org -> tahoe-lafs.org in __init__.py
7918david-sarah@jacaranda.org**20100603063530
7919 Ignore-this: f7d82331d5b4a3c4c0938023409335af
7920] 
7921[small change to CREDITS
7922david-sarah@jacaranda.org**20100603062421
7923 Ignore-this: 2909cdbedc19da5573dec810fc23243
7924] 
7925[Resolve conflict in patch to change imports to absolute.
7926david-sarah@jacaranda.org**20100603054608
7927 Ignore-this: 15aa1caa88e688ffa6dc53bed7dcca7d
7928] 
7929[Minor documentation tweaks.
7930david-sarah@jacaranda.org**20100603054458
7931 Ignore-this: e30ae407b0039dfa5b341d8f88e7f959
7932] 
7933[title_rename_xhtml.dpatch.txt
7934freestorm77@gmail.com**20100529172542
7935 Ignore-this: d2846afcc9ea72ac443a62ecc23d121b
7936 
7937 - Renamed xhtml Title from "Allmydata - Tahoe" to "Tahoe-LAFS"
7938 - Renamed Tahoe to Tahoe-LAFS in page content
7939 - Changed Tahoe-LAFS home page link to http://tahoe-lafs.org (added target="blank")
7940 - Deleted commented css script in info.xhtml
7941 
7942 
7943] 
7944[tests: refactor test_web.py to have less duplication of literal caps-from-the-future
7945zooko@zooko.com**20100519055146
7946 Ignore-this: 49e5412e6cc4566ca67f069ffd850af6
7947 This is a prelude to a patch which will add tests of caps from the future which have non-ascii chars in them.
7948] 
7949[doc_reformat_stats.txt
7950freestorm77@gmail.com**20100424114615
7951 Ignore-this: af315db5f7e3a17219ff8fb39bcfcd60
7952 
7953 
7954    - Added heading format begining and ending by "=="
7955    - Added Index
7956    - Added Title
7957           
7958    Note: No change are made in paragraphs content
7959 
7960 
7961 **END OF DESCRIPTION***
7962 
7963 Place the long patch description above the ***END OF DESCRIPTION*** marker.
7964 The first line of this file will be the patch name.
7965 
7966 
7967 This patch contains the following changes:
7968 
7969 M ./docs/stats.txt -2 +2
7970] 
7971[doc_reformat_performance.txt
7972freestorm77@gmail.com**20100424114444
7973 Ignore-this: 55295ff5cd8a5b67034eb661a5b0699d
7974 
7975    - Added heading format begining and ending by "=="
7976    - Added Index
7977    - Added Title
7978         
7979    Note: No change are made in paragraphs content
7980 
7981 
7982] 
7983[doc_refomat_logging.txt
7984freestorm77@gmail.com**20100424114316
7985 Ignore-this: 593f0f9914516bf1924dfa6eee74e35f
7986 
7987    - Added heading format begining and ending by "=="
7988    - Added Index
7989    - Added Title
7990         
7991    Note: No change are made in paragraphs content
7992 
7993] 
7994[doc_reformat_known_issues.txt
7995freestorm77@gmail.com**20100424114118
7996 Ignore-this: 9577c3965d77b7ac18698988cfa06049
7997 
7998     - Added heading format begining and ending by "=="
7999     - Added Index
8000     - Added Title
8001           
8002     Note: No change are made in paragraphs content
8003   
8004 
8005] 
8006[doc_reformat_helper.txt
8007freestorm77@gmail.com**20100424120649
8008 Ignore-this: de2080d6152ae813b20514b9908e37fb
8009 
8010 
8011    - Added heading format begining and ending by "=="
8012    - Added Index
8013    - Added Title
8014             
8015    Note: No change are made in paragraphs content
8016 
8017] 
8018[doc_reformat_garbage-collection.txt
8019freestorm77@gmail.com**20100424120830
8020 Ignore-this: aad3e4c99670871b66467062483c977d
8021 
8022 
8023    - Added heading format begining and ending by "=="
8024    - Added Index
8025    - Added Title
8026             
8027    Note: No change are made in paragraphs content
8028 
8029] 
8030[doc_reformat_FTP-and-SFTP.txt
8031freestorm77@gmail.com**20100424121334
8032 Ignore-this: 3736b3d8f9a542a3521fbb566d44c7cf
8033 
8034 
8035    - Added heading format begining and ending by "=="
8036    - Added Index
8037    - Added Title
8038           
8039    Note: No change are made in paragraphs content
8040 
8041] 
8042[doc_reformat_debian.txt
8043freestorm77@gmail.com**20100424120537
8044 Ignore-this: 45fe4355bb869e55e683405070f47eff
8045 
8046 
8047    - Added heading format begining and ending by "=="
8048    - Added Index
8049    - Added Title
8050             
8051    Note: No change are made in paragraphs content
8052 
8053] 
8054[doc_reformat_configuration.txt
8055freestorm77@gmail.com**20100424104903
8056 Ignore-this: 4fbabc51b8122fec69ce5ad1672e79f2
8057 
8058 
8059 - Added heading format begining and ending by "=="
8060 - Added Index
8061 - Added Title
8062 
8063 Note: No change are made in paragraphs content
8064 
8065] 
8066[doc_reformat_CLI.txt
8067freestorm77@gmail.com**20100424121512
8068 Ignore-this: 2d3a59326810adcb20ea232cea405645
8069 
8070      - Added heading format begining and ending by "=="
8071      - Added Index
8072      - Added Title
8073           
8074      Note: No change are made in paragraphs content
8075 
8076] 
8077[doc_reformat_backupdb.txt
8078freestorm77@gmail.com**20100424120416
8079 Ignore-this: fed696530e9d2215b6f5058acbedc3ab
8080 
8081 
8082    - Added heading format begining and ending by "=="
8083    - Added Index
8084    - Added Title
8085             
8086    Note: No change are made in paragraphs content
8087 
8088] 
8089[doc_reformat_architecture.txt
8090freestorm77@gmail.com**20100424120133
8091 Ignore-this: 6e2cab4635080369f2b8cadf7b2f58e
8092 
8093 
8094     - Added heading format begining and ending by "=="
8095     - Added Index
8096     - Added Title
8097             
8098     Note: No change are made in paragraphs content
8099 
8100 
8101] 
8102[Correct harmless indentation errors found by pylint
8103david-sarah@jacaranda.org**20100226052151
8104 Ignore-this: 41335bce830700b18b80b6e00b45aef5
8105] 
8106[Change relative imports to absolute
8107david-sarah@jacaranda.org**20100226071433
8108 Ignore-this: 32e6ce1a86e2ffaaba1a37d9a1a5de0e
8109] 
8110[Document reason for the trialcoverage version requirement being 0.3.3.
8111david-sarah@jacaranda.org**20100525004444
8112 Ignore-this: 2f9f1df6882838b000c063068f258aec
8113] 
8114[Downgrade version requirement for trialcoverage to 0.3.3 (from 0.3.10), to avoid needing to compile coveragepy on Windows.
8115david-sarah@jacaranda.org**20100524233707
8116 Ignore-this: 9c397a374c8b8017e2244b8a686432a8
8117] 
8118[Suppress deprecation warning for twisted.web.error.NoResource when using Twisted >= 9.0.0.
8119david-sarah@jacaranda.org**20100516205625
8120 Ignore-this: 2361a3023cd3db86bde5e1af759ed01
8121] 
8122[docs: CREDITS for Jeremy Visser
8123zooko@zooko.com**20100524081829
8124 Ignore-this: d7c1465fd8d4e25b8d46d38a1793465b
8125] 
8126[test: show stdout and stderr in case of non-zero exit code from "tahoe" command
8127zooko@zooko.com**20100524073348
8128 Ignore-this: 695e81cd6683f4520229d108846cd551
8129] 
8130[setup: upgrade bundled zetuptoolz to zetuptoolz-0.6c15dev and make it unpacked and directly loaded by setup.py
8131zooko@zooko.com**20100523205228
8132 Ignore-this: 24fb32aaee3904115a93d1762f132c7
8133 Also fix the relevant "make clean" target behavior.
8134] 
8135[setup: remove bundled zipfile egg of setuptools
8136zooko@zooko.com**20100523205120
8137 Ignore-this: c68b5f2635bb93d1c1fa7b613a026f9e
8138 We're about to replace it with bundled unpacked source code of setuptools, which is much nicer for debugging and evolving under revision control.
8139] 
8140[setup: remove bundled copy of setuptools_trial-0.5.2.tar
8141zooko@zooko.com**20100522221539
8142 Ignore-this: 140f90eb8fb751a509029c4b24afe647
8143 Hopefully it will get installed automatically as needed and we won't bundle it anymore.
8144] 
8145[setup: remove bundled setuptools_darcs-1.2.8.tar
8146zooko@zooko.com**20100522015333
8147 Ignore-this: 378b1964b513ae7fe22bae2d3478285d
8148 This version of setuptools_darcs had a bug when used on Windows which has been fixed in setuptools_darcs-1.2.9. Hopefully we will not need to bundle a copy of setuptools_darcs-1.2.9 in with Tahoe-LAFS and can instead rely on it to be downloaded from PyPI or bundled in the "tahoe deps" separate tarball.
8149] 
8150[tests: fix pyflakes warnings in bench_dirnode.py
8151zooko@zooko.com**20100521202511
8152 Ignore-this: f23d55b4ed05e52865032c65a15753c4
8153] 
8154[setup: if the string '--reporter=bwverbose-coverage' appears on sys.argv then you need trialcoverage
8155zooko@zooko.com**20100521122226
8156 Ignore-this: e760c45dcfb5a43c1dc1e8a27346bdc2
8157] 
8158[tests: don't let bench_dirnode.py do stuff and have side-effects at import time (unless __name__ == '__main__')
8159zooko@zooko.com**20100521122052
8160 Ignore-this: 96144a412250d9bbb5fccbf83b8753b8
8161] 
8162[tests: increase timeout to give François's ARM buildslave a chance to complete the tests
8163zooko@zooko.com**20100520134526
8164 Ignore-this: 3dd399fdc8b91149c82b52f955b50833
8165] 
8166[run_trial.darcspath
8167freestorm77@gmail.com**20100510232829
8168 Ignore-this: 5ebb4df74e9ea8a4bdb22b65373d1ff2
8169] 
8170[docs: line-wrap README.txt
8171zooko@zooko.com**20100518174240
8172 Ignore-this: 670a02d360df7de51ebdcf4fae752577
8173] 
8174[Hush pyflakes warnings
8175Kevan Carstensen <kevan@isnotajoke.com>**20100515184344
8176 Ignore-this: fd602c3bba115057770715c36a87b400
8177] 
8178[setup: new improved misc/show-tool-versions.py
8179zooko@zooko.com**20100516050122
8180 Ignore-this: ce9b1de1b35b07d733e6cf823b66335a
8181] 
8182[Improve code coverage of the Tahoe2PeerSelector tests.
8183Kevan Carstensen <kevan@isnotajoke.com>**20100515032913
8184 Ignore-this: 793151b63ffa65fdae6915db22d9924a
8185] 
8186[Remove a comment that no longer makes sense.
8187Kevan Carstensen <kevan@isnotajoke.com>**20100514203516
8188 Ignore-this: 956983c7e7c7e4477215494dfce8f058
8189] 
8190[docs: update docs/architecture.txt to more fully and correctly explain the upload procedure
8191zooko@zooko.com**20100514043458
8192 Ignore-this: 538b6ea256a49fed837500342092efa3
8193] 
8194[Fix up the behavior of #778, per reviewers' comments
8195Kevan Carstensen <kevan@isnotajoke.com>**20100514004917
8196 Ignore-this: 9c20b60716125278b5456e8feb396bff
8197 
8198   - Make some important utility functions clearer and more thoroughly
8199     documented.
8200   - Assert in upload.servers_of_happiness that the buckets attributes
8201     of PeerTrackers passed to it are mutually disjoint.
8202   - Get rid of some silly non-Pythonisms that I didn't see when I first
8203     wrote these patches.
8204   - Make sure that should_add_server returns true when queried about a
8205     shnum that it doesn't know about yet.
8206   - Change Tahoe2PeerSelector.preexisting_shares to map a shareid to a set
8207     of peerids, alter dependencies to deal with that.
8208   - Remove upload.should_add_servers, because it is no longer necessary
8209   - Move upload.shares_of_happiness and upload.shares_by_server to a utility
8210     file.
8211   - Change some points in Tahoe2PeerSelector.
8212   - Compute servers_of_happiness using a bipartite matching algorithm that
8213     we know is optimal instead of an ad-hoc greedy algorithm that isn't.
8214   - Change servers_of_happiness to just take a sharemap as an argument,
8215     change its callers to merge existing_shares and used_peers before
8216     calling it.
8217   - Change an error message in the encoder to be more appropriate for
8218     servers of happiness.
8219   - Clarify the wording of an error message in immutable/upload.py
8220   - Refactor a happiness failure message to happinessutil.py, and make
8221     immutable/upload.py and immutable/encode.py use it.
8222   - Move the word "only" as far to the right as possible in failure
8223     messages.
8224   - Use a better definition of progress during peer selection.
8225   - Do read-only peer share detection queries in parallel, not sequentially.
8226   - Clean up logging semantics; print the query statistics whenever an
8227     upload is unsuccessful, not just in one case.
8228 
8229] 
8230[Alter the error message when an upload fails, per some comments in #778.
8231Kevan Carstensen <kevan@isnotajoke.com>**20091230210344
8232 Ignore-this: ba97422b2f9737c46abeb828727beb1
8233 
8234 When I first implemented #778, I just altered the error messages to refer to
8235 servers where they referred to shares. The resulting error messages weren't
8236 very good. These are a bit better.
8237] 
8238[Change "UploadHappinessError" to "UploadUnhappinessError"
8239Kevan Carstensen <kevan@isnotajoke.com>**20091205043037
8240 Ignore-this: 236b64ab19836854af4993bb5c1b221a
8241] 
8242[Alter the error message returned when peer selection fails
8243Kevan Carstensen <kevan@isnotajoke.com>**20091123002405
8244 Ignore-this: b2a7dc163edcab8d9613bfd6907e5166
8245 
8246 The Tahoe2PeerSelector returned either NoSharesError or NotEnoughSharesError
8247 for a variety of error conditions that weren't informatively described by them.
8248 This patch creates a new error, UploadHappinessError, replaces uses of
8249 NoSharesError and NotEnoughSharesError with it, and alters the error message
8250 raised with the errors to be more in line with the new servers_of_happiness
8251 behavior. See ticket #834 for more information.
8252] 
8253[Eliminate overcounting iof servers_of_happiness in Tahoe2PeerSelector; also reorganize some things.
8254Kevan Carstensen <kevan@isnotajoke.com>**20091118014542
8255 Ignore-this: a6cb032cbff74f4f9d4238faebd99868
8256] 
8257[Change stray "shares_of_happiness" to "servers_of_happiness"
8258Kevan Carstensen <kevan@isnotajoke.com>**20091116212459
8259 Ignore-this: 1c971ba8c3c4d2e7ba9f020577b28b73
8260] 
8261[Alter Tahoe2PeerSelector to make sure that it recognizes existing shares on readonly servers, fixing an issue in #778
8262Kevan Carstensen <kevan@isnotajoke.com>**20091116192805
8263 Ignore-this: 15289f4d709e03851ed0587b286fd955
8264] 
8265[Alter 'immutable/encode.py' and 'immutable/upload.py' to use servers_of_happiness instead of shares_of_happiness.
8266Kevan Carstensen <kevan@isnotajoke.com>**20091104111222
8267 Ignore-this: abb3283314820a8bbf9b5d0cbfbb57c8
8268] 
8269[Alter the signature of set_shareholders in IEncoder to add a 'servermap' parameter, which gives IEncoders enough information to perform a sane check for servers_of_happiness.
8270Kevan Carstensen <kevan@isnotajoke.com>**20091104033241
8271 Ignore-this: b3a6649a8ac66431beca1026a31fed94
8272] 
8273[Alter CiphertextDownloader to work with servers_of_happiness
8274Kevan Carstensen <kevan@isnotajoke.com>**20090924041932
8275 Ignore-this: e81edccf0308c2d3bedbc4cf217da197
8276] 
8277[Revisions of the #778 tests, per reviewers' comments
8278Kevan Carstensen <kevan@isnotajoke.com>**20100514012542
8279 Ignore-this: 735bbc7f663dce633caeb3b66a53cf6e
8280 
8281 - Fix comments and confusing naming.
8282 - Add tests for the new error messages suggested by David-Sarah
8283   and Zooko.
8284 - Alter existing tests for new error messages.
8285 - Make sure that the tests continue to work with the trunk.
8286 - Add a test for a mutual disjointedness assertion that I added to
8287   upload.servers_of_happiness.
8288 - Fix the comments to correctly reflect read-onlyness
8289 - Add a test for an edge case in should_add_server
8290 - Add an assertion to make sure that share redistribution works as it
8291   should
8292 - Alter tests to work with revised servers_of_happiness semantics
8293 - Remove tests for should_add_server, since that function no longer exists.
8294 - Alter tests to know about merge_peers, and to use it before calling
8295   servers_of_happiness.
8296 - Add tests for merge_peers.
8297 - Add Zooko's puzzles to the tests.
8298 - Edit encoding tests to expect the new kind of failure message.
8299 - Edit tests to expect error messages with the word "only" moved as far
8300   to the right as possible.
8301 - Extended and cleaned up some helper functions.
8302 - Changed some tests to call more appropriate helper functions.
8303 - Added a test for the failing redistribution algorithm
8304 - Added a test for the progress message
8305 - Added a test for the upper bound on readonly peer share discovery.
8306 
8307] 
8308[Alter various unit tests to work with the new happy behavior
8309Kevan Carstensen <kevan@isnotajoke.com>**20100107181325
8310 Ignore-this: 132032bbf865e63a079f869b663be34a
8311] 
8312[Replace "UploadHappinessError" with "UploadUnhappinessError" in tests.
8313Kevan Carstensen <kevan@isnotajoke.com>**20091205043453
8314 Ignore-this: 83f4bc50c697d21b5f4e2a4cd91862ca
8315] 
8316[Add tests for the behavior described in #834.
8317Kevan Carstensen <kevan@isnotajoke.com>**20091123012008
8318 Ignore-this: d8e0aa0f3f7965ce9b5cea843c6d6f9f
8319] 
8320[Re-work 'test_upload.py' to be more readable; add more tests for #778
8321Kevan Carstensen <kevan@isnotajoke.com>**20091116192334
8322 Ignore-this: 7e8565f92fe51dece5ae28daf442d659
8323] 
8324[Test Tahoe2PeerSelector to make sure that it recognizeses existing shares on readonly servers
8325Kevan Carstensen <kevan@isnotajoke.com>**20091109003735
8326 Ignore-this: 12f9b4cff5752fca7ed32a6ebcff6446
8327] 
8328[Add more tests for comment:53 in ticket #778
8329Kevan Carstensen <kevan@isnotajoke.com>**20091104112849
8330 Ignore-this: 3bb2edd299a944cc9586e14d5d83ec8c
8331] 
8332[Add a test for upload.shares_by_server
8333Kevan Carstensen <kevan@isnotajoke.com>**20091104111324
8334 Ignore-this: f9802e82d6982a93e00f92e0b276f018
8335] 
8336[Minor tweak to an existing test -- make the first server read-write, instead of read-only
8337Kevan Carstensen <kevan@isnotajoke.com>**20091104034232
8338 Ignore-this: a951a46c93f7f58dd44d93d8623b2aee
8339] 
8340[Alter tests to use the new form of set_shareholders
8341Kevan Carstensen <kevan@isnotajoke.com>**20091104033602
8342 Ignore-this: 3deac11fc831618d11441317463ef830
8343] 
8344[Refactor some behavior into a mixin, and add tests for the behavior described in #778
8345"Kevan Carstensen" <kevan@isnotajoke.com>**20091030091908
8346 Ignore-this: a6f9797057ca135579b249af3b2b66ac
8347] 
8348[Alter NoNetworkGrid to allow the creation of readonly servers for testing purposes.
8349Kevan Carstensen <kevan@isnotajoke.com>**20091018013013
8350 Ignore-this: e12cd7c4ddeb65305c5a7e08df57c754
8351] 
8352[Update 'docs/architecture.txt' to reflect readonly share discovery
8353kevan@isnotajoke.com**20100514003852
8354 Ignore-this: 7ead71b34df3b1ecfdcfd3cb2882e4f9
8355] 
8356[Alter the wording in docs/architecture.txt to more accurately describe the servers_of_happiness behavior.
8357Kevan Carstensen <kevan@isnotajoke.com>**20100428002455
8358 Ignore-this: 6eff7fa756858a1c6f73728d989544cc
8359] 
8360[Alter wording in 'interfaces.py' to be correct wrt #778
8361"Kevan Carstensen" <kevan@isnotajoke.com>**20091205034005
8362 Ignore-this: c9913c700ac14e7a63569458b06980e0
8363] 
8364[Update 'docs/configuration.txt' to reflect the servers_of_happiness behavior.
8365Kevan Carstensen <kevan@isnotajoke.com>**20091205033813
8366 Ignore-this: 5e1cb171f8239bfb5b565d73c75ac2b8
8367] 
8368[Clarify quickstart instructions for installing pywin32
8369david-sarah@jacaranda.org**20100511180300
8370 Ignore-this: d4668359673600d2acbc7cd8dd44b93c
8371] 
8372[web: add a simple test that you can load directory.xhtml
8373zooko@zooko.com**20100510063729
8374 Ignore-this: e49b25fa3c67b3c7a56c8b1ae01bb463
8375] 
8376[setup: fix typos in misc/show-tool-versions.py
8377zooko@zooko.com**20100510063615
8378 Ignore-this: 2181b1303a0e288e7a9ebd4c4855628
8379] 
8380[setup: show code-coverage tool versions in show-tools-versions.py
8381zooko@zooko.com**20100510062955
8382 Ignore-this: 4b4c68eb3780b762c8dbbd22b39df7cf
8383] 
8384[docs: update README, mv it to README.txt, update setup.py
8385zooko@zooko.com**20100504094340
8386 Ignore-this: 40e28ca36c299ea1fd12d3b91e5b421c
8387] 
8388[Dependency on Windmill test framework is not needed yet.
8389david-sarah@jacaranda.org**20100504161043
8390 Ignore-this: be088712bec650d4ef24766c0026ebc8
8391] 
8392[tests: pass z to tar so that BSD tar will know to ungzip
8393zooko@zooko.com**20100504090628
8394 Ignore-this: 1339e493f255e8fc0b01b70478f23a09
8395] 
8396[setup: update comments and URLs in setup.cfg
8397zooko@zooko.com**20100504061653
8398 Ignore-this: f97692807c74bcab56d33100c899f829
8399] 
8400[setup: reorder and extend the show-tool-versions script, the better to glean information about our new buildslaves
8401zooko@zooko.com**20100504045643
8402 Ignore-this: 836084b56b8d4ee8f1de1f4efb706d36
8403] 
8404[CLI: Support for https url in option --node-url
8405Francois Deppierraz <francois@ctrlaltdel.ch>**20100430185609
8406 Ignore-this: 1717176b4d27c877e6bc67a944d9bf34
8407 
8408 This patch modifies the regular expression used for verifying of '--node-url'
8409 parameter.  Support for accessing a Tahoe gateway over HTTPS was already
8410 present, thanks to Python's urllib.
8411 
8412] 
8413[backupdb.did_create_directory: use REPLACE INTO, not INSERT INTO + ignore error
8414Brian Warner <warner@lothar.com>**20100428050803
8415 Ignore-this: 1fca7b8f364a21ae413be8767161e32f
8416 
8417 This handles the case where we upload a new tahoe directory for a
8418 previously-processed local directory, possibly creating a new dircap (if the
8419 metadata had changed). Now we replace the old dirhash->dircap record. The
8420 previous behavior left the old record in place (with the old dircap and
8421 timestamps), so we'd never stop creating new directories and never converge
8422 on a null backup.
8423] 
8424["tahoe webopen": add --info flag, to get ?t=info
8425Brian Warner <warner@lothar.com>**20100424233003
8426 Ignore-this: 126b0bb6db340fabacb623d295eb45fa
8427 
8428 Also fix some trailing whitespace.
8429] 
8430[docs: install.html http-equiv refresh to quickstart.html
8431zooko@zooko.com**20100421165708
8432 Ignore-this: 52b4b619f9dde5886ae2cd7f1f3b734b
8433] 
8434[docs: install.html -> quickstart.html
8435zooko@zooko.com**20100421155757
8436 Ignore-this: 6084e203909306bed93efb09d0e6181d
8437 It is not called "installing" because that implies that it is going to change the configuration of your operating system. It is not called "building" because that implies that you need developer tools like a compiler. Also I added a stern warning against looking at the "InstallDetails" wiki page, which I have renamed to "AdvancedInstall".
8438] 
8439[Fix another typo in tahoe_storagespace munin plugin
8440david-sarah@jacaranda.org**20100416220935
8441 Ignore-this: ad1f7aa66b554174f91dfb2b7a3ea5f3
8442] 
8443[Add dependency on windmill >= 1.3
8444david-sarah@jacaranda.org**20100416190404
8445 Ignore-this: 4437a7a464e92d6c9012926b18676211
8446] 
8447[licensing: phrase the OpenSSL-exemption in the vocabulary of copyright instead of computer technology, and replicate the exemption from the GPL to the TGPPL
8448zooko@zooko.com**20100414232521
8449 Ignore-this: a5494b2f582a295544c6cad3f245e91
8450] 
8451[munin-tahoe_storagespace
8452freestorm77@gmail.com**20100221203626
8453 Ignore-this: 14d6d6a587afe1f8883152bf2e46b4aa
8454 
8455 Plugin configuration rename
8456 
8457] 
8458[setup: add licensing declaration for setuptools (noticed by the FSF compliance folks)
8459zooko@zooko.com**20100309184415
8460 Ignore-this: 2dfa7d812d65fec7c72ddbf0de609ccb
8461] 
8462[setup: fix error in licensing declaration from Shawn Willden, as noted by the FSF compliance division
8463zooko@zooko.com**20100309163736
8464 Ignore-this: c0623d27e469799d86cabf67921a13f8
8465] 
8466[CREDITS to Jacob Appelbaum
8467zooko@zooko.com**20100304015616
8468 Ignore-this: 70db493abbc23968fcc8db93f386ea54
8469] 
8470[desert-island-build-with-proper-versions
8471jacob@appelbaum.net**20100304013858] 
8472[docs: a few small edits to try to guide newcomers through the docs
8473zooko@zooko.com**20100303231902
8474 Ignore-this: a6aab44f5bf5ad97ea73e6976bc4042d
8475 These edits were suggested by my watching over Jake Appelbaum's shoulder as he completely ignored/skipped/missed install.html and also as he decided that debian.txt wouldn't help him with basic installation. Then I threw in a few docs edits that have been sitting around in my sandbox asking to be committed for months.
8476] 
8477[TAG allmydata-tahoe-1.6.1
8478david-sarah@jacaranda.org**20100228062314
8479 Ignore-this: eb5f03ada8ea953ee7780e7fe068539
8480] 
8481Patch bundle hash:
8482d56838e7d35a3bd2740093ea894b5cf64e5169ba