Ticket #393: 393status19.dpatch

File 393status19.dpatch, 482.8 KB (added by kevan, at 2010-07-14T00:11:30Z)
Line 
1Thu Jun 24 16:46:37 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
2  * Misc. changes to support the work I'm doing
3 
4      - Add a notion of file version number to interfaces.py
5      - Alter mutable file node interfaces to have a notion of version,
6        though this may be changed later.
7      - Alter mutable/filenode.py to conform to these changes.
8      - Add a salt hasher to util/hashutil.py
9
10Thu Jun 24 16:48:33 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
11  * nodemaker.py: create MDMF files when asked to
12
13Thu Jun 24 16:49:05 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
14  * storage/server.py: minor code cleanup
15
16Thu Jun 24 16:49:24 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
17  * test/test_mutable.py: alter some tests that were failing due to MDMF; minor code cleanup.
18
19Fri Jun 25 17:35:20 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
20  * test/test_mutable.py: change the definition of corrupt() to work with MDMF as well as SDMF files, change users of corrupt to use the new definition
21
22Sat Jun 26 16:41:18 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
23  * Alter the ServermapUpdater to find MDMF files
24 
25  The servermapupdater should find MDMF files on a grid in the same way
26  that it finds SDMF files. This patch makes it do that.
27
28Sat Jun 26 16:42:04 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
29  * Make a segmented mutable uploader
30 
31  The mutable file uploader should be able to publish files with one
32  segment and files with multiple segments. This patch makes it do that.
33  This is still incomplete, and rather ugly -- I need to flesh out error
34  handling, I need to write tests, and I need to remove some of the uglier
35  kludges in the process before I can call this done.
36
37Sat Jun 26 16:43:14 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
38  * Write a segmented mutable downloader
39 
40  The segmented mutable downloader can deal with MDMF files (files with
41  one or more segments in MDMF format) and SDMF files (files with one
42  segment in SDMF format). It is backwards compatible with the old
43  file format.
44 
45  This patch also contains tests for the segmented mutable downloader.
46
47Mon Jun 28 15:50:48 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
48  * mutable/checker.py: check MDMF files
49 
50  This patch adapts the mutable file checker and verifier to check and
51  verify MDMF files. It does this by using the new segmented downloader,
52  which is trained to perform verification operations on request. This
53  removes some code duplication.
54
55Mon Jun 28 15:52:01 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
56  * mutable/retrieve.py: learn how to verify mutable files
57
58Wed Jun 30 11:33:05 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
59  * interfaces.py: add IMutableSlotWriter
60
61Thu Jul  1 16:28:06 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
62  * test/test_mutable.py: temporarily disable two tests that are now irrelevant
63
64Fri Jul  2 15:55:31 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
65  * Add MDMF reader and writer, and SDMF writer
66 
67  The MDMF/SDMF reader MDMF writer, and SDMF writer are similar to the
68  object proxies that exist for immutable files. They abstract away
69  details of connection, state, and caching from their callers (in this
70  case, the download, servermap updater, and uploader), and expose methods
71  to get and set information on the remote server.
72 
73  MDMFSlotReadProxy reads a mutable file from the server, doing the right
74  thing (in most cases) regardless of whether the file is MDMF or SDMF. It
75  allows callers to tell it how to batch and flush reads.
76 
77  MDMFSlotWriteProxy writes an MDMF mutable file to a server.
78 
79  SDMFSlotWriteProxy writes an SDMF mutable file to a server.
80 
81  This patch also includes tests for MDMFSlotReadProxy,
82  SDMFSlotWriteProxy, and MDMFSlotWriteProxy.
83
84Fri Jul  2 15:55:54 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
85  * mutable/publish.py: cleanup + simplification
86
87Fri Jul  2 15:57:10 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
88  * test/test_mutable.py: remove tests that are no longer relevant
89
90Tue Jul  6 14:52:17 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
91  * interfaces.py: create IMutableUploadable
92
93Tue Jul  6 14:52:57 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
94  * mutable/publish.py: add MutableDataHandle and MutableFileHandle
95
96Tue Jul  6 14:55:41 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
97  * mutable/publish.py: reorganize in preparation of file-like uploadables
98
99Tue Jul  6 14:56:49 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
100  * test/test_mutable.py: write tests for MutableFileHandle and MutableDataHandle
101
102Wed Jul  7 17:00:31 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
103  * Alter tests to work with the new APIs
104
105Wed Jul  7 17:07:32 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
106  * Alter mutable files to use file-like objects for publishing instead of strings.
107
108Thu Jul  8 12:35:22 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
109  * test/test_sftp.py: alter a setup routine to work with new mutable file APIs.
110
111Thu Jul  8 12:36:00 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
112  * mutable/publish.py: make MutableFileHandle seek to the beginning of its file handle before reading.
113
114Fri Jul  9 16:29:12 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
115  * Refactor download interfaces to be more uniform, per #993
116
117Fri Jul  9 16:29:51 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
118  * frontends/sftpd.py: alter a mutable file overwrite to work with the new API
119
120Tue Jul 13 16:17:58 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
121  * mutable/filenode.py: implement most of IVersion, per #993
122
123New patches:
124
125[Misc. changes to support the work I'm doing
126Kevan Carstensen <kevan@isnotajoke.com>**20100624234637
127 Ignore-this: fdd18fa8cc05f4b4b15ff53ee24a1819
128 
129     - Add a notion of file version number to interfaces.py
130     - Alter mutable file node interfaces to have a notion of version,
131       though this may be changed later.
132     - Alter mutable/filenode.py to conform to these changes.
133     - Add a salt hasher to util/hashutil.py
134] {
135hunk ./src/allmydata/interfaces.py 7
136      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
137 
138 HASH_SIZE=32
139+SALT_SIZE=16
140+
141+SDMF_VERSION=0
142+MDMF_VERSION=1
143 
144 Hash = StringConstraint(maxLength=HASH_SIZE,
145                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
146hunk ./src/allmydata/interfaces.py 811
147         writer-visible data using this writekey.
148         """
149 
150+    def set_version(version):
151+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
152+        we upload in SDMF for reasons of compatibility. If you want to
153+        change this, set_version will let you do that.
154+
155+        To say that this file should be uploaded in SDMF, pass in a 0. To
156+        say that the file should be uploaded as MDMF, pass in a 1.
157+        """
158+
159+    def get_version():
160+        """Returns the mutable file protocol version."""
161+
162 class NotEnoughSharesError(Exception):
163     """Download was unable to get enough shares"""
164 
165hunk ./src/allmydata/mutable/filenode.py 8
166 from twisted.internet import defer, reactor
167 from foolscap.api import eventually
168 from allmydata.interfaces import IMutableFileNode, \
169-     ICheckable, ICheckResults, NotEnoughSharesError
170+     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
171 from allmydata.util import hashutil, log
172 from allmydata.util.assertutil import precondition
173 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
174hunk ./src/allmydata/mutable/filenode.py 67
175         self._sharemap = {} # known shares, shnum-to-[nodeids]
176         self._cache = ResponseCache()
177         self._most_recent_size = None
178+        # filled in after __init__ if we're being created for the first time;
179+        # filled in by the servermap updater before publishing, otherwise.
180+        # set to this default value in case neither of those things happen,
181+        # or in case the servermap can't find any shares to tell us what
182+        # to publish as.
183+        # TODO: Set this back to None, and find out why the tests fail
184+        #       with it set to None.
185+        self._protocol_version = SDMF_VERSION
186 
187         # all users of this MutableFileNode go through the serializer. This
188         # takes advantage of the fact that Deferreds discard the callbacks
189hunk ./src/allmydata/mutable/filenode.py 472
190     def _did_upload(self, res, size):
191         self._most_recent_size = size
192         return res
193+
194+
195+    def set_version(self, version):
196+        # I can be set in two ways:
197+        #  1. When the node is created.
198+        #  2. (for an existing share) when the Servermap is updated
199+        #     before I am read.
200+        assert version in (MDMF_VERSION, SDMF_VERSION)
201+        self._protocol_version = version
202+
203+
204+    def get_version(self):
205+        return self._protocol_version
206hunk ./src/allmydata/util/hashutil.py 90
207 MUTABLE_READKEY_TAG = "allmydata_mutable_writekey_to_readkey_v1"
208 MUTABLE_DATAKEY_TAG = "allmydata_mutable_readkey_to_datakey_v1"
209 MUTABLE_STORAGEINDEX_TAG = "allmydata_mutable_readkey_to_storage_index_v1"
210+MUTABLE_SALT_TAG = "allmydata_mutable_segment_salt_v1"
211 
212 # dirnodes
213 DIRNODE_CHILD_WRITECAP_TAG = "allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1"
214hunk ./src/allmydata/util/hashutil.py 134
215 def plaintext_segment_hasher():
216     return tagged_hasher(PLAINTEXT_SEGMENT_TAG)
217 
218+def mutable_salt_hash(data):
219+    return tagged_hash(MUTABLE_SALT_TAG, data)
220+def mutable_salt_hasher():
221+    return tagged_hasher(MUTABLE_SALT_TAG)
222+
223 KEYLEN = 16
224 IVLEN = 16
225 
226}
227[nodemaker.py: create MDMF files when asked to
228Kevan Carstensen <kevan@isnotajoke.com>**20100624234833
229 Ignore-this: 26c16aaca9ddab7a7ce37a4530bc970
230] {
231hunk ./src/allmydata/nodemaker.py 3
232 import weakref
233 from zope.interface import implements
234-from allmydata.interfaces import INodeMaker
235+from allmydata.util.assertutil import precondition
236+from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError, \
237+                                 SDMF_VERSION, MDMF_VERSION
238 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
239 from allmydata.immutable.upload import Data
240 from allmydata.mutable.filenode import MutableFileNode
241hunk ./src/allmydata/nodemaker.py 92
242             return self._create_dirnode(filenode)
243         return None
244 
245-    def create_mutable_file(self, contents=None, keysize=None):
246+    def create_mutable_file(self, contents=None, keysize=None,
247+                            version=SDMF_VERSION):
248         n = MutableFileNode(self.storage_broker, self.secret_holder,
249                             self.default_encoding_parameters, self.history)
250hunk ./src/allmydata/nodemaker.py 96
251+        n.set_version(version)
252         d = self.key_generator.generate(keysize)
253         d.addCallback(n.create_with_keys, contents)
254         d.addCallback(lambda res: n)
255hunk ./src/allmydata/nodemaker.py 102
256         return d
257 
258-    def create_new_mutable_directory(self, initial_children={}):
259+    def create_new_mutable_directory(self, initial_children={},
260+                                     version=SDMF_VERSION):
261+        # initial_children must have metadata (i.e. {} instead of None)
262+        for (name, (node, metadata)) in initial_children.iteritems():
263+            precondition(isinstance(metadata, dict),
264+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
265+            node.raise_error()
266         d = self.create_mutable_file(lambda n:
267hunk ./src/allmydata/nodemaker.py 110
268-                                     pack_children(n, initial_children))
269+                                     pack_children(n, initial_children),
270+                                     version)
271         d.addCallback(self._create_dirnode)
272         return d
273 
274}
275[storage/server.py: minor code cleanup
276Kevan Carstensen <kevan@isnotajoke.com>**20100624234905
277 Ignore-this: 2358c531c39e48d3c8e56b62b5768228
278] {
279hunk ./src/allmydata/storage/server.py 569
280                                          self)
281         return share
282 
283-    def remote_slot_readv(self, storage_index, shares, readv):
284+    def remote_slot_readv(self, storage_index, shares, readvs):
285         start = time.time()
286         self.count("readv")
287         si_s = si_b2a(storage_index)
288hunk ./src/allmydata/storage/server.py 590
289             if sharenum in shares or not shares:
290                 filename = os.path.join(bucketdir, sharenum_s)
291                 msf = MutableShareFile(filename, self)
292-                datavs[sharenum] = msf.readv(readv)
293+                datavs[sharenum] = msf.readv(readvs)
294         log.msg("returning shares %s" % (datavs.keys(),),
295                 facility="tahoe.storage", level=log.NOISY, parent=lp)
296         self.add_latency("readv", time.time() - start)
297}
298[test/test_mutable.py: alter some tests that were failing due to MDMF; minor code cleanup.
299Kevan Carstensen <kevan@isnotajoke.com>**20100624234924
300 Ignore-this: afb86ec1fbdbfe1a5ef6f46f350273c0
301] {
302hunk ./src/allmydata/test/test_mutable.py 151
303             chr(ord(original[byte_offset]) ^ 0x01) +
304             original[byte_offset+1:])
305 
306+def add_two(original, byte_offset):
307+    # It isn't enough to simply flip the bit for the version number,
308+    # because 1 is a valid version number. So we add two instead.
309+    return (original[:byte_offset] +
310+            chr(ord(original[byte_offset]) ^ 0x02) +
311+            original[byte_offset+1:])
312+
313 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
314     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
315     # list of shnums to corrupt.
316hunk ./src/allmydata/test/test_mutable.py 187
317                 real_offset = offset1
318             real_offset = int(real_offset) + offset2 + offset_offset
319             assert isinstance(real_offset, int), offset
320-            shares[shnum] = flip_bit(data, real_offset)
321+            if offset1 == 0: # verbyte
322+                f = add_two
323+            else:
324+                f = flip_bit
325+            shares[shnum] = f(data, real_offset)
326     return res
327 
328 def make_storagebroker(s=None, num_peers=10):
329hunk ./src/allmydata/test/test_mutable.py 423
330         d.addCallback(_created)
331         return d
332 
333+
334     def test_modify_backoffer(self):
335         def _modifier(old_contents, servermap, first_time):
336             return old_contents + "line2"
337hunk ./src/allmydata/test/test_mutable.py 658
338         d.addCallback(_created)
339         return d
340 
341+
342     def _copy_shares(self, ignored, index):
343         shares = self._storage._peers
344         # we need a deep copy
345}
346[test/test_mutable.py: change the definition of corrupt() to work with MDMF as well as SDMF files, change users of corrupt to use the new definition
347Kevan Carstensen <kevan@isnotajoke.com>**20100626003520
348 Ignore-this: 836e59e2fde0535f6b4bea3468dc8244
349] {
350hunk ./src/allmydata/test/test_mutable.py 168
351                 and shnum not in shnums_to_corrupt):
352                 continue
353             data = shares[shnum]
354-            (version,
355-             seqnum,
356-             root_hash,
357-             IV,
358-             k, N, segsize, datalen,
359-             o) = unpack_header(data)
360-            if isinstance(offset, tuple):
361-                offset1, offset2 = offset
362-            else:
363-                offset1 = offset
364-                offset2 = 0
365-            if offset1 == "pubkey":
366-                real_offset = 107
367-            elif offset1 in o:
368-                real_offset = o[offset1]
369-            else:
370-                real_offset = offset1
371-            real_offset = int(real_offset) + offset2 + offset_offset
372-            assert isinstance(real_offset, int), offset
373-            if offset1 == 0: # verbyte
374-                f = add_two
375-            else:
376-                f = flip_bit
377-            shares[shnum] = f(data, real_offset)
378-    return res
379+            # We're feeding the reader all of the share data, so it
380+            # won't need to use the rref that we didn't provide, nor the
381+            # storage index that we didn't provide. We do this because
382+            # the reader will work for both MDMF and SDMF.
383+            reader = MDMFSlotReadProxy(None, None, shnum, data)
384+            # We need to get the offsets for the next part.
385+            d = reader.get_verinfo()
386+            def _do_corruption(verinfo, data, shnum):
387+                (seqnum,
388+                 root_hash,
389+                 IV,
390+                 segsize,
391+                 datalen,
392+                 k, n, prefix, o) = verinfo
393+                if isinstance(offset, tuple):
394+                    offset1, offset2 = offset
395+                else:
396+                    offset1 = offset
397+                    offset2 = 0
398+                if offset1 == "pubkey":
399+                    real_offset = 107
400+                elif offset1 in o:
401+                    real_offset = o[offset1]
402+                else:
403+                    real_offset = offset1
404+                real_offset = int(real_offset) + offset2 + offset_offset
405+                assert isinstance(real_offset, int), offset
406+                if offset1 == 0: # verbyte
407+                    f = add_two
408+                else:
409+                    f = flip_bit
410+                shares[shnum] = f(data, real_offset)
411+            d.addCallback(_do_corruption, data, shnum)
412+            ds.append(d)
413+    dl = defer.DeferredList(ds)
414+    dl.addCallback(lambda ignored: res)
415+    return dl
416 
417 def make_storagebroker(s=None, num_peers=10):
418     if not s:
419hunk ./src/allmydata/test/test_mutable.py 1177
420         return d
421 
422     def test_download_fails(self):
423-        corrupt(None, self._storage, "signature")
424-        d = self.shouldFail(UnrecoverableFileError, "test_download_anyway",
425+        d = corrupt(None, self._storage, "signature")
426+        d.addCallback(lambda ignored:
427+            self.shouldFail(UnrecoverableFileError, "test_download_anyway",
428                             "no recoverable versions",
429                             self._fn.download_best_version)
430         return d
431hunk ./src/allmydata/test/test_mutable.py 1232
432         return d
433 
434     def test_check_all_bad_sig(self):
435-        corrupt(None, self._storage, 1) # bad sig
436-        d = self._fn.check(Monitor())
437+        d = corrupt(None, self._storage, 1) # bad sig
438+        d.addCallback(lambda ignored:
439+            self._fn.check(Monitor()))
440         d.addCallback(self.check_bad, "test_check_all_bad_sig")
441         return d
442 
443hunk ./src/allmydata/test/test_mutable.py 1239
444     def test_check_all_bad_blocks(self):
445-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
446+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
447         # the Checker won't notice this.. it doesn't look at actual data
448hunk ./src/allmydata/test/test_mutable.py 1241
449-        d = self._fn.check(Monitor())
450+        d.addCallback(lambda ignored:
451+            self._fn.check(Monitor()))
452         d.addCallback(self.check_good, "test_check_all_bad_blocks")
453         return d
454 
455hunk ./src/allmydata/test/test_mutable.py 1252
456         return d
457 
458     def test_verify_all_bad_sig(self):
459-        corrupt(None, self._storage, 1) # bad sig
460-        d = self._fn.check(Monitor(), verify=True)
461+        d = corrupt(None, self._storage, 1) # bad sig
462+        d.addCallback(lambda ignored:
463+            self._fn.check(Monitor(), verify=True))
464         d.addCallback(self.check_bad, "test_verify_all_bad_sig")
465         return d
466 
467hunk ./src/allmydata/test/test_mutable.py 1259
468     def test_verify_one_bad_sig(self):
469-        corrupt(None, self._storage, 1, [9]) # bad sig
470-        d = self._fn.check(Monitor(), verify=True)
471+        d = corrupt(None, self._storage, 1, [9]) # bad sig
472+        d.addCallback(lambda ignored:
473+            self._fn.check(Monitor(), verify=True))
474         d.addCallback(self.check_bad, "test_verify_one_bad_sig")
475         return d
476 
477hunk ./src/allmydata/test/test_mutable.py 1266
478     def test_verify_one_bad_block(self):
479-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
480+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
481         # the Verifier *will* notice this, since it examines every byte
482hunk ./src/allmydata/test/test_mutable.py 1268
483-        d = self._fn.check(Monitor(), verify=True)
484+        d.addCallback(lambda ignored:
485+            self._fn.check(Monitor(), verify=True))
486         d.addCallback(self.check_bad, "test_verify_one_bad_block")
487         d.addCallback(self.check_expected_failure,
488                       CorruptShareError, "block hash tree failure",
489hunk ./src/allmydata/test/test_mutable.py 1277
490         return d
491 
492     def test_verify_one_bad_sharehash(self):
493-        corrupt(None, self._storage, "share_hash_chain", [9], 5)
494-        d = self._fn.check(Monitor(), verify=True)
495+        d = corrupt(None, self._storage, "share_hash_chain", [9], 5)
496+        d.addCallback(lambda ignored:
497+            self._fn.check(Monitor(), verify=True))
498         d.addCallback(self.check_bad, "test_verify_one_bad_sharehash")
499         d.addCallback(self.check_expected_failure,
500                       CorruptShareError, "corrupt hashes",
501hunk ./src/allmydata/test/test_mutable.py 1287
502         return d
503 
504     def test_verify_one_bad_encprivkey(self):
505-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
506-        d = self._fn.check(Monitor(), verify=True)
507+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
508+        d.addCallback(lambda ignored:
509+            self._fn.check(Monitor(), verify=True))
510         d.addCallback(self.check_bad, "test_verify_one_bad_encprivkey")
511         d.addCallback(self.check_expected_failure,
512                       CorruptShareError, "invalid privkey",
513hunk ./src/allmydata/test/test_mutable.py 1297
514         return d
515 
516     def test_verify_one_bad_encprivkey_uncheckable(self):
517-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
518+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
519         readonly_fn = self._fn.get_readonly()
520         # a read-only node has no way to validate the privkey
521hunk ./src/allmydata/test/test_mutable.py 1300
522-        d = readonly_fn.check(Monitor(), verify=True)
523+        d.addCallback(lambda ignored:
524+            readonly_fn.check(Monitor(), verify=True))
525         d.addCallback(self.check_good,
526                       "test_verify_one_bad_encprivkey_uncheckable")
527         return d
528}
529[Alter the ServermapUpdater to find MDMF files
530Kevan Carstensen <kevan@isnotajoke.com>**20100626234118
531 Ignore-this: 25f6278209c2983ba8f307cfe0fde0
532 
533 The servermapupdater should find MDMF files on a grid in the same way
534 that it finds SDMF files. This patch makes it do that.
535] {
536hunk ./src/allmydata/mutable/servermap.py 7
537 from itertools import count
538 from twisted.internet import defer
539 from twisted.python import failure
540-from foolscap.api import DeadReferenceError, RemoteException, eventually
541+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
542+                         fireEventually
543 from allmydata.util import base32, hashutil, idlib, log
544 from allmydata.storage.server import si_b2a
545 from allmydata.interfaces import IServermapUpdaterStatus
546hunk ./src/allmydata/mutable/servermap.py 17
547 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
548      DictOfSets, CorruptShareError, NeedMoreDataError
549 from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
550-     SIGNED_PREFIX_LENGTH
551+     SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
552 
553 class UpdateStatus:
554     implements(IServermapUpdaterStatus)
555hunk ./src/allmydata/mutable/servermap.py 254
556         """Return a set of versionids, one for each version that is currently
557         recoverable."""
558         versionmap = self.make_versionmap()
559-
560         recoverable_versions = set()
561         for (verinfo, shares) in versionmap.items():
562             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
563hunk ./src/allmydata/mutable/servermap.py 366
564         self._servers_responded = set()
565 
566         # how much data should we read?
567+        # SDMF:
568         #  * if we only need the checkstring, then [0:75]
569         #  * if we need to validate the checkstring sig, then [543ish:799ish]
570         #  * if we need the verification key, then [107:436ish]
571hunk ./src/allmydata/mutable/servermap.py 374
572         #  * if we need the encrypted private key, we want [-1216ish:]
573         #   * but we can't read from negative offsets
574         #   * the offset table tells us the 'ish', also the positive offset
575-        # A future version of the SMDF slot format should consider using
576-        # fixed-size slots so we can retrieve less data. For now, we'll just
577-        # read 2000 bytes, which also happens to read enough actual data to
578-        # pre-fetch a 9-entry dirnode.
579+        # MDMF:
580+        #  * Checkstring? [0:72]
581+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
582+        #    the offset table will tell us for sure.
583+        #  * If we need the verification key, we have to consult the offset
584+        #    table as well.
585+        # At this point, we don't know which we are. Our filenode can
586+        # tell us, but it might be lying -- in some cases, we're
587+        # responsible for telling it which kind of file it is.
588         self._read_size = 4000
589         if mode == MODE_CHECK:
590             # we use unpack_prefix_and_signature, so we need 1k
591hunk ./src/allmydata/mutable/servermap.py 432
592         self._queries_completed = 0
593 
594         sb = self._storage_broker
595+        # All of the peers, permuted by the storage index, as usual.
596         full_peerlist = sb.get_servers_for_index(self._storage_index)
597         self.full_peerlist = full_peerlist # for use later, immutable
598         self.extra_peers = full_peerlist[:] # peers are removed as we use them
599hunk ./src/allmydata/mutable/servermap.py 439
600         self._good_peers = set() # peers who had some shares
601         self._empty_peers = set() # peers who don't have any shares
602         self._bad_peers = set() # peers to whom our queries failed
603+        self._readers = {} # peerid -> dict(sharewriters), filled in
604+                           # after responses come in.
605 
606         k = self._node.get_required_shares()
607hunk ./src/allmydata/mutable/servermap.py 443
608+        # For what cases can these conditions work?
609         if k is None:
610             # make a guess
611             k = 3
612hunk ./src/allmydata/mutable/servermap.py 456
613         self.num_peers_to_query = k + self.EPSILON
614 
615         if self.mode == MODE_CHECK:
616+            # We want to query all of the peers.
617             initial_peers_to_query = dict(full_peerlist)
618             must_query = set(initial_peers_to_query.keys())
619             self.extra_peers = []
620hunk ./src/allmydata/mutable/servermap.py 464
621             # we're planning to replace all the shares, so we want a good
622             # chance of finding them all. We will keep searching until we've
623             # seen epsilon that don't have a share.
624+            # We don't query all of the peers because that could take a while.
625             self.num_peers_to_query = N + self.EPSILON
626             initial_peers_to_query, must_query = self._build_initial_querylist()
627             self.required_num_empty_peers = self.EPSILON
628hunk ./src/allmydata/mutable/servermap.py 474
629             # might also avoid the round trip required to read the encrypted
630             # private key.
631 
632-        else:
633+        else: # MODE_READ, MODE_ANYTHING
634+            # 2k peers is good enough.
635             initial_peers_to_query, must_query = self._build_initial_querylist()
636 
637         # this is a set of peers that we are required to get responses from:
638hunk ./src/allmydata/mutable/servermap.py 490
639         # before we can consider ourselves finished, and self.extra_peers
640         # contains the overflow (peers that we should tap if we don't get
641         # enough responses)
642+        # I guess that self._must_query is a subset of
643+        # initial_peers_to_query?
644+        assert set(must_query).issubset(set(initial_peers_to_query))
645 
646         self._send_initial_requests(initial_peers_to_query)
647         self._status.timings["initial_queries"] = time.time() - self._started
648hunk ./src/allmydata/mutable/servermap.py 549
649         # errors that aren't handled by _query_failed (and errors caused by
650         # _query_failed) get logged, but we still want to check for doneness.
651         d.addErrback(log.err)
652-        d.addBoth(self._check_for_done)
653         d.addErrback(self._fatal_error)
654hunk ./src/allmydata/mutable/servermap.py 550
655+        d.addCallback(self._check_for_done)
656         return d
657 
658     def _do_read(self, ss, peerid, storage_index, shnums, readv):
659hunk ./src/allmydata/mutable/servermap.py 569
660         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
661         return d
662 
663+
664+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
665+        """
666+        I am called when a remote server returns a corrupt share in
667+        response to one of our queries. By corrupt, I mean a share
668+        without a valid signature. I then record the failure, notify the
669+        server of the corruption, and record the share as bad.
670+        """
671+        f = failure.Failure(e)
672+        self.log(format="bad share: %(f_value)s", f_value=str(f),
673+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
674+        # Notify the server that its share is corrupt.
675+        self.notify_server_corruption(peerid, shnum, str(e))
676+        # By flagging this as a bad peer, we won't count any of
677+        # the other shares on that peer as valid, though if we
678+        # happen to find a valid version string amongst those
679+        # shares, we'll keep track of it so that we don't need
680+        # to validate the signature on those again.
681+        self._bad_peers.add(peerid)
682+        self._last_failure = f
683+        # XXX: Use the reader for this?
684+        checkstring = data[:SIGNED_PREFIX_LENGTH]
685+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
686+        self._servermap.problems.append(f)
687+
688+
689+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
690+        """
691+        If one of my queries returns successfully (which means that we
692+        were able to and successfully did validate the signature), I
693+        cache the data that we initially fetched from the storage
694+        server. This will help reduce the number of roundtrips that need
695+        to occur when the file is downloaded, or when the file is
696+        updated.
697+        """
698+        self._node._add_to_cache(verinfo, shnum, 0, data, now)
699+
700+
701     def _got_results(self, datavs, peerid, readsize, stuff, started):
702         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
703                       peerid=idlib.shortnodeid_b2a(peerid),
704hunk ./src/allmydata/mutable/servermap.py 630
705         else:
706             self._empty_peers.add(peerid)
707 
708-        last_verinfo = None
709-        last_shnum = None
710+        ss, storage_index = stuff
711+        ds = []
712+
713         for shnum,datav in datavs.items():
714             data = datav[0]
715hunk ./src/allmydata/mutable/servermap.py 635
716-            try:
717-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
718-                last_verinfo = verinfo
719-                last_shnum = shnum
720-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
721-            except CorruptShareError, e:
722-                # log it and give the other shares a chance to be processed
723-                f = failure.Failure()
724-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
725-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
726-                self.notify_server_corruption(peerid, shnum, str(e))
727-                self._bad_peers.add(peerid)
728-                self._last_failure = f
729-                checkstring = data[:SIGNED_PREFIX_LENGTH]
730-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
731-                self._servermap.problems.append(f)
732-                pass
733-
734-        self._status.timings["cumulative_verify"] += (time.time() - now)
735+            reader = MDMFSlotReadProxy(ss,
736+                                       storage_index,
737+                                       shnum,
738+                                       data)
739+            self._readers.setdefault(peerid, dict())[shnum] = reader
740+            # our goal, with each response, is to validate the version
741+            # information and share data as best we can at this point --
742+            # we do this by validating the signature. To do this, we
743+            # need to do the following:
744+            #   - If we don't already have the public key, fetch the
745+            #     public key. We use this to validate the signature.
746+            if not self._node.get_pubkey():
747+                # fetch and set the public key.
748+                d = reader.get_verification_key()
749+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
750+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
751+                # XXX: Make self._pubkey_query_failed?
752+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
753+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
754+            else:
755+                # we already have the public key.
756+                d = defer.succeed(None)
757+            # Neither of these two branches return anything of
758+            # consequence, so the first entry in our deferredlist will
759+            # be None.
760 
761hunk ./src/allmydata/mutable/servermap.py 661
762-        if self._need_privkey and last_verinfo:
763-            # send them a request for the privkey. We send one request per
764-            # server.
765-            lp2 = self.log("sending privkey request",
766-                           parent=lp, level=log.NOISY)
767-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
768-             offsets_tuple) = last_verinfo
769-            o = dict(offsets_tuple)
770+            # - Next, we need the version information. We almost
771+            #   certainly got this by reading the first thousand or so
772+            #   bytes of the share on the storage server, so we
773+            #   shouldn't need to fetch anything at this step.
774+            d2 = reader.get_verinfo()
775+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
776+                self._got_corrupt_share(error, shnum, peerid, data, lp))
777+            # - Next, we need the signature. For an SDMF share, it is
778+            #   likely that we fetched this when doing our initial fetch
779+            #   to get the version information. In MDMF, this lives at
780+            #   the end of the share, so unless the file is quite small,
781+            #   we'll need to do a remote fetch to get it.
782+            d3 = reader.get_signature()
783+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
784+                self._got_corrupt_share(error, shnum, peerid, data, lp))
785+            #  Once we have all three of these responses, we can move on
786+            #  to validating the signature
787 
788hunk ./src/allmydata/mutable/servermap.py 679
789-            self._queries_outstanding.add(peerid)
790-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
791-            ss = self._servermap.connections[peerid]
792-            privkey_started = time.time()
793-            d = self._do_read(ss, peerid, self._storage_index,
794-                              [last_shnum], readv)
795-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
796-                          privkey_started, lp2)
797-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
798-            d.addErrback(log.err)
799-            d.addCallback(self._check_for_done)
800-            d.addErrback(self._fatal_error)
801+            # Does the node already have a privkey? If not, we'll try to
802+            # fetch it here.
803+            if self._need_privkey:
804+                d4 = reader.get_encprivkey()
805+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
806+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
807+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
808+                    self._privkey_query_failed(error, shnum, data, lp))
809+            else:
810+                d4 = defer.succeed(None)
811 
812hunk ./src/allmydata/mutable/servermap.py 690
813+            dl = defer.DeferredList([d, d2, d3, d4])
814+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
815+                self._got_signature_one_share(results, shnum, peerid, lp))
816+            dl.addErrback(lambda error, shnum=shnum, data=data:
817+               self._got_corrupt_share(error, shnum, peerid, data, lp))
818+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
819+                self._cache_good_sharedata(verinfo, shnum, now, data))
820+            ds.append(dl)
821+        # dl is a deferred list that will fire when all of the shares
822+        # that we found on this peer are done processing. When dl fires,
823+        # we know that processing is done, so we can decrement the
824+        # semaphore-like thing that we incremented earlier.
825+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
826+        # Are we done? Done means that there are no more queries to
827+        # send, that there are no outstanding queries, and that we
828+        # haven't received any queries that are still processing. If we
829+        # are done, self._check_for_done will cause the done deferred
830+        # that we returned to our caller to fire, which tells them that
831+        # they have a complete servermap, and that we won't be touching
832+        # the servermap anymore.
833+        dl.addCallback(self._check_for_done)
834+        dl.addErrback(self._fatal_error)
835         # all done!
836         self.log("_got_results done", parent=lp, level=log.NOISY)
837hunk ./src/allmydata/mutable/servermap.py 714
838+        return dl
839+
840+
841+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
842+        if self._node.get_pubkey():
843+            return # don't go through this again if we don't have to
844+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
845+        assert len(fingerprint) == 32
846+        if fingerprint != self._node.get_fingerprint():
847+            raise CorruptShareError(peerid, shnum,
848+                                "pubkey doesn't match fingerprint")
849+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
850+        assert self._node.get_pubkey()
851+
852 
853     def notify_server_corruption(self, peerid, shnum, reason):
854         ss = self._servermap.connections[peerid]
855hunk ./src/allmydata/mutable/servermap.py 734
856         ss.callRemoteOnly("advise_corrupt_share",
857                           "mutable", self._storage_index, shnum, reason)
858 
859-    def _got_results_one_share(self, shnum, data, peerid, lp):
860+
861+    def _got_signature_one_share(self, results, shnum, peerid, lp):
862+        # It is our job to give versioninfo to our caller. We need to
863+        # raise CorruptShareError if the share is corrupt for any
864+        # reason, something that our caller will handle.
865         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
866                  shnum=shnum,
867                  peerid=idlib.shortnodeid_b2a(peerid),
868hunk ./src/allmydata/mutable/servermap.py 744
869                  level=log.NOISY,
870                  parent=lp)
871-
872-        # this might raise NeedMoreDataError, if the pubkey and signature
873-        # live at some weird offset. That shouldn't happen, so I'm going to
874-        # treat it as a bad share.
875-        (seqnum, root_hash, IV, k, N, segsize, datalength,
876-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
877-
878-        if not self._node.get_pubkey():
879-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
880-            assert len(fingerprint) == 32
881-            if fingerprint != self._node.get_fingerprint():
882-                raise CorruptShareError(peerid, shnum,
883-                                        "pubkey doesn't match fingerprint")
884-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
885-
886-        if self._need_privkey:
887-            self._try_to_extract_privkey(data, peerid, shnum, lp)
888-
889-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
890-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
891+        _, verinfo, signature, __ = results
892+        (seqnum,
893+         root_hash,
894+         saltish,
895+         segsize,
896+         datalen,
897+         k,
898+         n,
899+         prefix,
900+         offsets) = verinfo[1]
901         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
902 
903hunk ./src/allmydata/mutable/servermap.py 756
904-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
905+        # XXX: This should be done for us in the method, so
906+        # presumably you can go in there and fix it.
907+        verinfo = (seqnum,
908+                   root_hash,
909+                   saltish,
910+                   segsize,
911+                   datalen,
912+                   k,
913+                   n,
914+                   prefix,
915                    offsets_tuple)
916hunk ./src/allmydata/mutable/servermap.py 767
917+        # This tuple uniquely identifies a share on the grid; we use it
918+        # to keep track of the ones that we've already seen.
919 
920         if verinfo not in self._valid_versions:
921hunk ./src/allmydata/mutable/servermap.py 771
922-            # it's a new pair. Verify the signature.
923-            valid = self._node.get_pubkey().verify(prefix, signature)
924+            # This is a new version tuple, and we need to validate it
925+            # against the public key before keeping track of it.
926+            assert self._node.get_pubkey()
927+            valid = self._node.get_pubkey().verify(prefix, signature[1])
928             if not valid:
929hunk ./src/allmydata/mutable/servermap.py 776
930-                raise CorruptShareError(peerid, shnum, "signature is invalid")
931+                raise CorruptShareError(peerid, shnum,
932+                                        "signature is invalid")
933 
934hunk ./src/allmydata/mutable/servermap.py 779
935-            # ok, it's a valid verinfo. Add it to the list of validated
936-            # versions.
937-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
938-                     % (seqnum, base32.b2a(root_hash)[:4],
939-                        idlib.shortnodeid_b2a(peerid), shnum,
940-                        k, N, segsize, datalength),
941-                     parent=lp)
942-            self._valid_versions.add(verinfo)
943-        # We now know that this is a valid candidate verinfo.
944+        # ok, it's a valid verinfo. Add it to the list of validated
945+        # versions.
946+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
947+                 % (seqnum, base32.b2a(root_hash)[:4],
948+                    idlib.shortnodeid_b2a(peerid), shnum,
949+                    k, n, segsize, datalen),
950+                    parent=lp)
951+        self._valid_versions.add(verinfo)
952+        # We now know that this is a valid candidate verinfo. Whether or
953+        # not this instance of it is valid is a matter for the next
954+        # statement; at this point, we just know that if we see this
955+        # version info again, that its signature checks out and that
956+        # we're okay to skip the signature-checking step.
957 
958hunk ./src/allmydata/mutable/servermap.py 793
959+        # (peerid, shnum) are bound in the method invocation.
960         if (peerid, shnum) in self._servermap.bad_shares:
961             # we've been told that the rest of the data in this share is
962             # unusable, so don't add it to the servermap.
963hunk ./src/allmydata/mutable/servermap.py 808
964         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
965         return verinfo
966 
967+
968     def _deserialize_pubkey(self, pubkey_s):
969         verifier = rsa.create_verifying_key_from_string(pubkey_s)
970         return verifier
971hunk ./src/allmydata/mutable/servermap.py 813
972 
973-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
974-        try:
975-            r = unpack_share(data)
976-        except NeedMoreDataError, e:
977-            # this share won't help us. oh well.
978-            offset = e.encprivkey_offset
979-            length = e.encprivkey_length
980-            self.log("shnum %d on peerid %s: share was too short (%dB) "
981-                     "to get the encprivkey; [%d:%d] ought to hold it" %
982-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
983-                      offset, offset+length),
984-                     parent=lp)
985-            # NOTE: if uncoordinated writes are taking place, someone might
986-            # change the share (and most probably move the encprivkey) before
987-            # we get a chance to do one of these reads and fetch it. This
988-            # will cause us to see a NotEnoughSharesError(unable to fetch
989-            # privkey) instead of an UncoordinatedWriteError . This is a
990-            # nuisance, but it will go away when we move to DSA-based mutable
991-            # files (since the privkey will be small enough to fit in the
992-            # write cap).
993-
994-            return
995-
996-        (seqnum, root_hash, IV, k, N, segsize, datalen,
997-         pubkey, signature, share_hash_chain, block_hash_tree,
998-         share_data, enc_privkey) = r
999-
1000-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1001 
1002     def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
1003hunk ./src/allmydata/mutable/servermap.py 815
1004-
1005+        """
1006+        Given a writekey from a remote server, I validate it against the
1007+        writekey stored in my node. If it is valid, then I set the
1008+        privkey and encprivkey properties of the node.
1009+        """
1010         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
1011         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
1012         if alleged_writekey != self._node.get_writekey():
1013hunk ./src/allmydata/mutable/servermap.py 892
1014         self._queries_completed += 1
1015         self._last_failure = f
1016 
1017-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
1018-        now = time.time()
1019-        elapsed = now - started
1020-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
1021-        self._queries_outstanding.discard(peerid)
1022-        if not self._need_privkey:
1023-            return
1024-        if shnum not in datavs:
1025-            self.log("privkey wasn't there when we asked it",
1026-                     level=log.WEIRD, umid="VA9uDQ")
1027-            return
1028-        datav = datavs[shnum]
1029-        enc_privkey = datav[0]
1030-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1031 
1032     def _privkey_query_failed(self, f, peerid, shnum, lp):
1033         self._queries_outstanding.discard(peerid)
1034hunk ./src/allmydata/mutable/servermap.py 906
1035         self._servermap.problems.append(f)
1036         self._last_failure = f
1037 
1038+
1039     def _check_for_done(self, res):
1040         # exit paths:
1041         #  return self._send_more_queries(outstanding) : send some more queries
1042hunk ./src/allmydata/mutable/servermap.py 912
1043         #  return self._done() : all done
1044         #  return : keep waiting, no new queries
1045-
1046         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
1047                               "%(outstanding)d queries outstanding, "
1048                               "%(extra)d extra peers available, "
1049hunk ./src/allmydata/mutable/servermap.py 1117
1050         self._servermap.last_update_time = self._started
1051         # the servermap will not be touched after this
1052         self.log("servermap: %s" % self._servermap.summarize_versions())
1053+
1054         eventually(self._done_deferred.callback, self._servermap)
1055 
1056     def _fatal_error(self, f):
1057hunk ./src/allmydata/test/test_mutable.py 637
1058         d.addCallback(_created)
1059         return d
1060 
1061-    def publish_multiple(self):
1062+    def publish_mdmf(self):
1063+        # like publish_one, except that the result is guaranteed to be
1064+        # an MDMF file.
1065+        # self.CONTENTS should have more than one segment.
1066+        self.CONTENTS = "This is an MDMF file" * 100000
1067+        self._storage = FakeStorage()
1068+        self._nodemaker = make_nodemaker(self._storage)
1069+        self._storage_broker = self._nodemaker.storage_broker
1070+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
1071+        def _created(node):
1072+            self._fn = node
1073+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1074+        d.addCallback(_created)
1075+        return d
1076+
1077+
1078+    def publish_sdmf(self):
1079+        # like publish_one, except that the result is guaranteed to be
1080+        # an SDMF file
1081+        self.CONTENTS = "This is an SDMF file" * 1000
1082+        self._storage = FakeStorage()
1083+        self._nodemaker = make_nodemaker(self._storage)
1084+        self._storage_broker = self._nodemaker.storage_broker
1085+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
1086+        def _created(node):
1087+            self._fn = node
1088+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1089+        d.addCallback(_created)
1090+        return d
1091+
1092+
1093+    def publish_multiple(self, version=0):
1094         self.CONTENTS = ["Contents 0",
1095                          "Contents 1",
1096                          "Contents 2",
1097hunk ./src/allmydata/test/test_mutable.py 677
1098         self._copied_shares = {}
1099         self._storage = FakeStorage()
1100         self._nodemaker = make_nodemaker(self._storage)
1101-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
1102+        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
1103         def _created(node):
1104             self._fn = node
1105             # now create multiple versions of the same file, and accumulate
1106hunk ./src/allmydata/test/test_mutable.py 906
1107         return d
1108 
1109 
1110+    def test_servermapupdater_finds_mdmf_files(self):
1111+        # setUp already published an MDMF file for us. We just need to
1112+        # make sure that when we run the ServermapUpdater, the file is
1113+        # reported to have one recoverable version.
1114+        d = defer.succeed(None)
1115+        d.addCallback(lambda ignored:
1116+            self.publish_mdmf())
1117+        d.addCallback(lambda ignored:
1118+            self.make_servermap(mode=MODE_CHECK))
1119+        # Calling make_servermap also updates the servermap in the mode
1120+        # that we specify, so we just need to see what it says.
1121+        def _check_servermap(sm):
1122+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
1123+        d.addCallback(_check_servermap)
1124+        return d
1125+
1126+
1127+    def test_servermapupdater_finds_sdmf_files(self):
1128+        d = defer.succeed(None)
1129+        d.addCallback(lambda ignored:
1130+            self.publish_sdmf())
1131+        d.addCallback(lambda ignored:
1132+            self.make_servermap(mode=MODE_CHECK))
1133+        d.addCallback(lambda servermap:
1134+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
1135+        return d
1136+
1137 
1138 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
1139     def setUp(self):
1140hunk ./src/allmydata/test/test_mutable.py 1050
1141         return d
1142     test_no_servers_download.timeout = 15
1143 
1144+
1145     def _test_corrupt_all(self, offset, substring,
1146                           should_succeed=False, corrupt_early=True,
1147                           failure_checker=None):
1148}
1149[Make a segmented mutable uploader
1150Kevan Carstensen <kevan@isnotajoke.com>**20100626234204
1151 Ignore-this: d199af8ab0bc64d8ed2bc19c5437bfba
1152 
1153 The mutable file uploader should be able to publish files with one
1154 segment and files with multiple segments. This patch makes it do that.
1155 This is still incomplete, and rather ugly -- I need to flesh out error
1156 handling, I need to write tests, and I need to remove some of the uglier
1157 kludges in the process before I can call this done.
1158] {
1159hunk ./src/allmydata/mutable/publish.py 8
1160 from zope.interface import implements
1161 from twisted.internet import defer
1162 from twisted.python import failure
1163-from allmydata.interfaces import IPublishStatus
1164+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
1165 from allmydata.util import base32, hashutil, mathutil, idlib, log
1166 from allmydata import hashtree, codec
1167 from allmydata.storage.server import si_b2a
1168hunk ./src/allmydata/mutable/publish.py 19
1169      UncoordinatedWriteError, NotEnoughServersError
1170 from allmydata.mutable.servermap import ServerMap
1171 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
1172-     unpack_checkstring, SIGNED_PREFIX
1173+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
1174+
1175+KiB = 1024
1176+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
1177 
1178 class PublishStatus:
1179     implements(IPublishStatus)
1180hunk ./src/allmydata/mutable/publish.py 112
1181         self._status.set_helper(False)
1182         self._status.set_progress(0.0)
1183         self._status.set_active(True)
1184+        # We use this to control how the file is written.
1185+        version = self._node.get_version()
1186+        assert version in (SDMF_VERSION, MDMF_VERSION)
1187+        self._version = version
1188 
1189     def get_status(self):
1190         return self._status
1191hunk ./src/allmydata/mutable/publish.py 134
1192         simultaneous write.
1193         """
1194 
1195-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
1196-        # 2: perform peer selection, get candidate servers
1197-        #  2a: send queries to n+epsilon servers, to determine current shares
1198-        #  2b: based upon responses, create target map
1199-        # 3: send slot_testv_and_readv_and_writev messages
1200-        # 4: as responses return, update share-dispatch table
1201-        # 4a: may need to run recovery algorithm
1202-        # 5: when enough responses are back, we're done
1203+        # 0. Setup encoding parameters, encoder, and other such things.
1204+        # 1. Encrypt, encode, and publish segments.
1205 
1206         self.log("starting publish, datalen is %s" % len(newdata))
1207         self._status.set_size(len(newdata))
1208hunk ./src/allmydata/mutable/publish.py 187
1209         self.bad_peers = set() # peerids who have errbacked/refused requests
1210 
1211         self.newdata = newdata
1212-        self.salt = os.urandom(16)
1213 
1214hunk ./src/allmydata/mutable/publish.py 188
1215+        # This will set self.segment_size, self.num_segments, and
1216+        # self.fec.
1217         self.setup_encoding_parameters()
1218 
1219         # if we experience any surprises (writes which were rejected because
1220hunk ./src/allmydata/mutable/publish.py 238
1221             self.bad_share_checkstrings[key] = old_checkstring
1222             self.connections[peerid] = self._servermap.connections[peerid]
1223 
1224-        # create the shares. We'll discard these as they are delivered. SDMF:
1225-        # we're allowed to hold everything in memory.
1226+        # Now, the process dovetails -- if this is an SDMF file, we need
1227+        # to write an SDMF file. Otherwise, we need to write an MDMF
1228+        # file.
1229+        if self._version == MDMF_VERSION:
1230+            return self._publish_mdmf()
1231+        else:
1232+            return self._publish_sdmf()
1233+        #return self.done_deferred
1234+
1235+    def _publish_mdmf(self):
1236+        # Next, we find homes for all of the shares that we don't have
1237+        # homes for yet.
1238+        # TODO: Make this part do peer selection.
1239+        self.update_goal()
1240+        self.writers = {}
1241+        # For each (peerid, shnum) in self.goal, we make an
1242+        # MDMFSlotWriteProxy for that peer. We'll use this to write
1243+        # shares to the peer.
1244+        for key in self.goal:
1245+            peerid, shnum = key
1246+            write_enabler = self._node.get_write_enabler(peerid)
1247+            renew_secret = self._node.get_renewal_secret(peerid)
1248+            cancel_secret = self._node.get_cancel_secret(peerid)
1249+            secrets = (write_enabler, renew_secret, cancel_secret)
1250+
1251+            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
1252+                                                      self.connections[peerid],
1253+                                                      self._storage_index,
1254+                                                      secrets,
1255+                                                      self._new_seqnum,
1256+                                                      self.required_shares,
1257+                                                      self.total_shares,
1258+                                                      self.segment_size,
1259+                                                      len(self.newdata))
1260+            if (peerid, shnum) in self._servermap.servermap:
1261+                old_versionid, old_timestamp = self._servermap.servermap[key]
1262+                (old_seqnum, old_root_hash, old_salt, old_segsize,
1263+                 old_datalength, old_k, old_N, old_prefix,
1264+                 old_offsets_tuple) = old_versionid
1265+                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
1266+
1267+        # Now, we start pushing shares.
1268+        self._status.timings["setup"] = time.time() - self._started
1269+        def _start_pushing(res):
1270+            self._started_pushing = time.time()
1271+            return res
1272+
1273+        # First, we encrypt, encode, and publish the shares that we need
1274+        # to encrypt, encode, and publish.
1275+
1276+        # This will eventually hold the block hash chain for each share
1277+        # that we publish. We define it this way so that empty publishes
1278+        # will still have something to write to the remote slot.
1279+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
1280+        self.sharehash_leaves = None # eventually [sharehashes]
1281+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
1282+                              # validate the share]
1283 
1284hunk ./src/allmydata/mutable/publish.py 296
1285+        d = defer.succeed(None)
1286+        self.log("Starting push")
1287+        for i in xrange(self.num_segments - 1):
1288+            d.addCallback(lambda ignored, i=i:
1289+                self.push_segment(i))
1290+            d.addCallback(self._turn_barrier)
1291+        # We have at least one segment, so we will have a tail segment
1292+        if self.num_segments > 0:
1293+            d.addCallback(lambda ignored:
1294+                self.push_tail_segment())
1295+
1296+        d.addCallback(lambda ignored:
1297+            self.push_encprivkey())
1298+        d.addCallback(lambda ignored:
1299+            self.push_blockhashes())
1300+        d.addCallback(lambda ignored:
1301+            self.push_sharehashes())
1302+        d.addCallback(lambda ignored:
1303+            self.push_toplevel_hashes_and_signature())
1304+        d.addCallback(lambda ignored:
1305+            self.finish_publishing())
1306+        return d
1307+
1308+
1309+    def _publish_sdmf(self):
1310         self._status.timings["setup"] = time.time() - self._started
1311hunk ./src/allmydata/mutable/publish.py 322
1312+        self.salt = os.urandom(16)
1313+
1314         d = self._encrypt_and_encode()
1315         d.addCallback(self._generate_shares)
1316         def _start_pushing(res):
1317hunk ./src/allmydata/mutable/publish.py 335
1318 
1319         return self.done_deferred
1320 
1321+
1322     def setup_encoding_parameters(self):
1323hunk ./src/allmydata/mutable/publish.py 337
1324-        segment_size = len(self.newdata)
1325+        if self._version == MDMF_VERSION:
1326+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
1327+        else:
1328+            segment_size = len(self.newdata) # SDMF is only one segment
1329         # this must be a multiple of self.required_shares
1330         segment_size = mathutil.next_multiple(segment_size,
1331                                               self.required_shares)
1332hunk ./src/allmydata/mutable/publish.py 350
1333                                                   segment_size)
1334         else:
1335             self.num_segments = 0
1336-        assert self.num_segments in [0, 1,] # SDMF restrictions
1337+        if self._version == SDMF_VERSION:
1338+            assert self.num_segments in (0, 1) # SDMF
1339+            return
1340+        # calculate the tail segment size.
1341+        self.tail_segment_size = len(self.newdata) % segment_size
1342+
1343+        if self.tail_segment_size == 0:
1344+            # The tail segment is the same size as the other segments.
1345+            self.tail_segment_size = segment_size
1346+
1347+        # We'll make an encoder ahead-of-time for the normal-sized
1348+        # segments (defined as any segment of segment_size size.
1349+        # (the part of the code that puts the tail segment will make its
1350+        #  own encoder for that part)
1351+        fec = codec.CRSEncoder()
1352+        fec.set_params(self.segment_size,
1353+                       self.required_shares, self.total_shares)
1354+        self.piece_size = fec.get_block_size()
1355+        self.fec = fec
1356+
1357+
1358+    def push_segment(self, segnum):
1359+        started = time.time()
1360+        segsize = self.segment_size
1361+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
1362+        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
1363+        assert len(data) == segsize
1364+
1365+        salt = os.urandom(16)
1366+
1367+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1368+        enc = AES(key)
1369+        crypttext = enc.process(data)
1370+        assert len(crypttext) == len(data)
1371+
1372+        now = time.time()
1373+        self._status.timings["encrypt"] = now - started
1374+        started = now
1375+
1376+        # now apply FEC
1377+
1378+        self._status.set_status("Encoding")
1379+        crypttext_pieces = [None] * self.required_shares
1380+        piece_size = self.piece_size
1381+        for i in range(len(crypttext_pieces)):
1382+            offset = i * piece_size
1383+            piece = crypttext[offset:offset+piece_size]
1384+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1385+            crypttext_pieces[i] = piece
1386+            assert len(piece) == piece_size
1387+        d = self.fec.encode(crypttext_pieces)
1388+        def _done_encoding(res):
1389+            elapsed = time.time() - started
1390+            self._status.timings["encode"] = elapsed
1391+            return res
1392+        d.addCallback(_done_encoding)
1393+
1394+        def _push_shares_and_salt(results):
1395+            shares, shareids = results
1396+            dl = []
1397+            for i in xrange(len(shares)):
1398+                sharedata = shares[i]
1399+                shareid = shareids[i]
1400+                block_hash = hashutil.block_hash(salt + sharedata)
1401+                self.blockhashes[shareid].append(block_hash)
1402+
1403+                # find the writer for this share
1404+                d = self.writers[shareid].put_block(sharedata, segnum, salt)
1405+                dl.append(d)
1406+            # TODO: Naturally, we need to check on the results of these.
1407+            return defer.DeferredList(dl)
1408+        d.addCallback(_push_shares_and_salt)
1409+        return d
1410+
1411+
1412+    def push_tail_segment(self):
1413+        # This is essentially the same as push_segment, except that we
1414+        # don't use the cached encoder that we use elsewhere.
1415+        self.log("Pushing tail segment")
1416+        started = time.time()
1417+        segsize = self.segment_size
1418+        data = self.newdata[segsize * (self.num_segments-1):]
1419+        assert len(data) == self.tail_segment_size
1420+        salt = os.urandom(16)
1421+
1422+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1423+        enc = AES(key)
1424+        crypttext = enc.process(data)
1425+        assert len(crypttext) == len(data)
1426+
1427+        now = time.time()
1428+        self._status.timings['encrypt'] = now - started
1429+        started = now
1430+
1431+        self._status.set_status("Encoding")
1432+        tail_fec = codec.CRSEncoder()
1433+        tail_fec.set_params(self.tail_segment_size,
1434+                            self.required_shares,
1435+                            self.total_shares)
1436+
1437+        crypttext_pieces = [None] * self.required_shares
1438+        piece_size = tail_fec.get_block_size()
1439+        for i in range(len(crypttext_pieces)):
1440+            offset = i * piece_size
1441+            piece = crypttext[offset:offset+piece_size]
1442+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1443+            crypttext_pieces[i] = piece
1444+            assert len(piece) == piece_size
1445+        d = tail_fec.encode(crypttext_pieces)
1446+        def _push_shares_and_salt(results):
1447+            shares, shareids = results
1448+            dl = []
1449+            for i in xrange(len(shares)):
1450+                sharedata = shares[i]
1451+                shareid = shareids[i]
1452+                block_hash = hashutil.block_hash(salt + sharedata)
1453+                self.blockhashes[shareid].append(block_hash)
1454+                # find the writer for this share
1455+                d = self.writers[shareid].put_block(sharedata,
1456+                                                    self.num_segments - 1,
1457+                                                    salt)
1458+                dl.append(d)
1459+            # TODO: Naturally, we need to check on the results of these.
1460+            return defer.DeferredList(dl)
1461+        d.addCallback(_push_shares_and_salt)
1462+        return d
1463+
1464+
1465+    def push_encprivkey(self):
1466+        started = time.time()
1467+        encprivkey = self._encprivkey
1468+        dl = []
1469+        def _spy_on_writer(results):
1470+            print results
1471+            return results
1472+        for shnum, writer in self.writers.iteritems():
1473+            d = writer.put_encprivkey(encprivkey)
1474+            dl.append(d)
1475+        d = defer.DeferredList(dl)
1476+        return d
1477+
1478+
1479+    def push_blockhashes(self):
1480+        started = time.time()
1481+        dl = []
1482+        def _spy_on_results(results):
1483+            print results
1484+            return results
1485+        self.sharehash_leaves = [None] * len(self.blockhashes)
1486+        for shnum, blockhashes in self.blockhashes.iteritems():
1487+            t = hashtree.HashTree(blockhashes)
1488+            self.blockhashes[shnum] = list(t)
1489+            # set the leaf for future use.
1490+            self.sharehash_leaves[shnum] = t[0]
1491+            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
1492+            dl.append(d)
1493+        d = defer.DeferredList(dl)
1494+        return d
1495+
1496+
1497+    def push_sharehashes(self):
1498+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
1499+        share_hash_chain = {}
1500+        ds = []
1501+        def _spy_on_results(results):
1502+            print results
1503+            return results
1504+        for shnum in xrange(len(self.sharehash_leaves)):
1505+            needed_indices = share_hash_tree.needed_hashes(shnum)
1506+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
1507+                                             for i in needed_indices] )
1508+            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
1509+            ds.append(d)
1510+        self.root_hash = share_hash_tree[0]
1511+        d = defer.DeferredList(ds)
1512+        return d
1513+
1514+
1515+    def push_toplevel_hashes_and_signature(self):
1516+        # We need to to three things here:
1517+        #   - Push the root hash and salt hash
1518+        #   - Get the checkstring of the resulting layout; sign that.
1519+        #   - Push the signature
1520+        ds = []
1521+        def _spy_on_results(results):
1522+            print results
1523+            return results
1524+        for shnum in xrange(self.total_shares):
1525+            d = self.writers[shnum].put_root_hash(self.root_hash)
1526+            ds.append(d)
1527+        d = defer.DeferredList(ds)
1528+        def _make_and_place_signature(ignored):
1529+            signable = self.writers[0].get_signable()
1530+            self.signature = self._privkey.sign(signable)
1531+
1532+            ds = []
1533+            for (shnum, writer) in self.writers.iteritems():
1534+                d = writer.put_signature(self.signature)
1535+                ds.append(d)
1536+            return defer.DeferredList(ds)
1537+        d.addCallback(_make_and_place_signature)
1538+        return d
1539+
1540+
1541+    def finish_publishing(self):
1542+        # We're almost done -- we just need to put the verification key
1543+        # and the offsets
1544+        ds = []
1545+        verification_key = self._pubkey.serialize()
1546+
1547+        def _spy_on_results(results):
1548+            print results
1549+            return results
1550+        for (shnum, writer) in self.writers.iteritems():
1551+            d = writer.put_verification_key(verification_key)
1552+            d.addCallback(lambda ignored, writer=writer:
1553+                writer.finish_publishing())
1554+            ds.append(d)
1555+        return defer.DeferredList(ds)
1556+
1557+
1558+    def _turn_barrier(self, res):
1559+        # putting this method in a Deferred chain imposes a guaranteed
1560+        # reactor turn between the pre- and post- portions of that chain.
1561+        # This can be useful to limit memory consumption: since Deferreds do
1562+        # not do tail recursion, code which uses defer.succeed(result) for
1563+        # consistency will cause objects to live for longer than you might
1564+        # normally expect.
1565+        return fireEventually(res)
1566+
1567 
1568     def _fatal_error(self, f):
1569         self.log("error during loop", failure=f, level=log.UNUSUAL)
1570hunk ./src/allmydata/mutable/publish.py 716
1571             self.log_goal(self.goal, "after update: ")
1572 
1573 
1574-
1575     def _encrypt_and_encode(self):
1576         # this returns a Deferred that fires with a list of (sharedata,
1577         # sharenum) tuples. TODO: cache the ciphertext, only produce the
1578hunk ./src/allmydata/mutable/publish.py 757
1579         d.addCallback(_done_encoding)
1580         return d
1581 
1582+
1583     def _generate_shares(self, shares_and_shareids):
1584         # this sets self.shares and self.root_hash
1585         self.log("_generate_shares")
1586hunk ./src/allmydata/mutable/publish.py 1145
1587             self._status.set_progress(1.0)
1588         eventually(self.done_deferred.callback, res)
1589 
1590-
1591hunk ./src/allmydata/test/test_mutable.py 248
1592         d.addCallback(_created)
1593         return d
1594 
1595+
1596+    def test_create_mdmf(self):
1597+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
1598+        def _created(n):
1599+            self.failUnless(isinstance(n, MutableFileNode))
1600+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
1601+            sb = self.nodemaker.storage_broker
1602+            peer0 = sorted(sb.get_all_serverids())[0]
1603+            shnums = self._storage._peers[peer0].keys()
1604+            self.failUnlessEqual(len(shnums), 1)
1605+        d.addCallback(_created)
1606+        return d
1607+
1608+
1609     def test_serialize(self):
1610         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
1611         calls = []
1612hunk ./src/allmydata/test/test_mutable.py 334
1613         d.addCallback(_created)
1614         return d
1615 
1616+
1617+    def test_create_mdmf_with_initial_contents(self):
1618+        initial_contents = "foobarbaz" * 131072 # 900KiB
1619+        d = self.nodemaker.create_mutable_file(initial_contents,
1620+                                               version=MDMF_VERSION)
1621+        def _created(n):
1622+            d = n.download_best_version()
1623+            d.addCallback(lambda data:
1624+                self.failUnlessEqual(data, initial_contents))
1625+            d.addCallback(lambda ignored:
1626+                n.overwrite(initial_contents + "foobarbaz"))
1627+            d.addCallback(lambda ignored:
1628+                n.download_best_version())
1629+            d.addCallback(lambda data:
1630+                self.failUnlessEqual(data, initial_contents +
1631+                                           "foobarbaz"))
1632+            return d
1633+        d.addCallback(_created)
1634+        return d
1635+
1636+
1637     def test_create_with_initial_contents_function(self):
1638         data = "initial contents"
1639         def _make_contents(n):
1640hunk ./src/allmydata/test/test_mutable.py 370
1641         d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
1642         return d
1643 
1644+
1645+    def test_create_mdmf_with_initial_contents_function(self):
1646+        data = "initial contents" * 100000
1647+        def _make_contents(n):
1648+            self.failUnless(isinstance(n, MutableFileNode))
1649+            key = n.get_writekey()
1650+            self.failUnless(isinstance(key, str), key)
1651+            self.failUnlessEqual(len(key), 16)
1652+            return data
1653+        d = self.nodemaker.create_mutable_file(_make_contents,
1654+                                               version=MDMF_VERSION)
1655+        d.addCallback(lambda n:
1656+            n.download_best_version())
1657+        d.addCallback(lambda data2:
1658+            self.failUnlessEqual(data2, data))
1659+        return d
1660+
1661+
1662     def test_create_with_too_large_contents(self):
1663         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
1664         d = self.nodemaker.create_mutable_file(BIG)
1665}
1666[Write a segmented mutable downloader
1667Kevan Carstensen <kevan@isnotajoke.com>**20100626234314
1668 Ignore-this: d2bef531cde1b5c38f2eb28afdd4b17c
1669 
1670 The segmented mutable downloader can deal with MDMF files (files with
1671 one or more segments in MDMF format) and SDMF files (files with one
1672 segment in SDMF format). It is backwards compatible with the old
1673 file format.
1674 
1675 This patch also contains tests for the segmented mutable downloader.
1676] {
1677hunk ./src/allmydata/mutable/retrieve.py 8
1678 from twisted.internet import defer
1679 from twisted.python import failure
1680 from foolscap.api import DeadReferenceError, eventually, fireEventually
1681-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
1682-from allmydata.util import hashutil, idlib, log
1683+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
1684+                                 MDMF_VERSION, SDMF_VERSION
1685+from allmydata.util import hashutil, idlib, log, mathutil
1686 from allmydata import hashtree, codec
1687 from allmydata.storage.server import si_b2a
1688 from pycryptopp.cipher.aes import AES
1689hunk ./src/allmydata/mutable/retrieve.py 17
1690 from pycryptopp.publickey import rsa
1691 
1692 from allmydata.mutable.common import DictOfSets, CorruptShareError, UncoordinatedWriteError
1693-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
1694+from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data, \
1695+                                     MDMFSlotReadProxy
1696 
1697 class RetrieveStatus:
1698     implements(IRetrieveStatus)
1699hunk ./src/allmydata/mutable/retrieve.py 104
1700         self.verinfo = verinfo
1701         # during repair, we may be called upon to grab the private key, since
1702         # it wasn't picked up during a verify=False checker run, and we'll
1703-        # need it for repair to generate the a new version.
1704+        # need it for repair to generate a new version.
1705         self._need_privkey = fetch_privkey
1706         if self._node.get_privkey():
1707             self._need_privkey = False
1708hunk ./src/allmydata/mutable/retrieve.py 109
1709 
1710+        if self._need_privkey:
1711+            # TODO: Evaluate the need for this. We'll use it if we want
1712+            # to limit how many queries are on the wire for the privkey
1713+            # at once.
1714+            self._privkey_query_markers = [] # one Marker for each time we've
1715+                                             # tried to get the privkey.
1716+
1717         self._status = RetrieveStatus()
1718         self._status.set_storage_index(self._storage_index)
1719         self._status.set_helper(False)
1720hunk ./src/allmydata/mutable/retrieve.py 125
1721          offsets_tuple) = self.verinfo
1722         self._status.set_size(datalength)
1723         self._status.set_encoding(k, N)
1724+        self.readers = {}
1725 
1726     def get_status(self):
1727         return self._status
1728hunk ./src/allmydata/mutable/retrieve.py 149
1729         self.remaining_sharemap = DictOfSets()
1730         for (shnum, peerid, timestamp) in shares:
1731             self.remaining_sharemap.add(shnum, peerid)
1732+            # If the servermap update fetched anything, it fetched at least 1
1733+            # KiB, so we ask for that much.
1734+            # TODO: Change the cache methods to allow us to fetch all of the
1735+            # data that they have, then change this method to do that.
1736+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
1737+                                                               shnum,
1738+                                                               0,
1739+                                                               1000)
1740+            ss = self.servermap.connections[peerid]
1741+            reader = MDMFSlotReadProxy(ss,
1742+                                       self._storage_index,
1743+                                       shnum,
1744+                                       any_cache)
1745+            reader.peerid = peerid
1746+            self.readers[shnum] = reader
1747+
1748 
1749         self.shares = {} # maps shnum to validated blocks
1750hunk ./src/allmydata/mutable/retrieve.py 167
1751+        self._active_readers = [] # list of active readers for this dl.
1752+        self._validated_readers = set() # set of readers that we have
1753+                                        # validated the prefix of
1754+        self._block_hash_trees = {} # shnum => hashtree
1755+        # TODO: Make this into a file-backed consumer or something to
1756+        # conserve memory.
1757+        self._plaintext = ""
1758 
1759         # how many shares do we need?
1760hunk ./src/allmydata/mutable/retrieve.py 176
1761-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1762+        (seqnum,
1763+         root_hash,
1764+         IV,
1765+         segsize,
1766+         datalength,
1767+         k,
1768+         N,
1769+         prefix,
1770          offsets_tuple) = self.verinfo
1771hunk ./src/allmydata/mutable/retrieve.py 185
1772-        assert len(self.remaining_sharemap) >= k
1773-        # we start with the lowest shnums we have available, since FEC is
1774-        # faster if we're using "primary shares"
1775-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
1776-        for shnum in self.active_shnums:
1777-            # we use an arbitrary peer who has the share. If shares are
1778-            # doubled up (more than one share per peer), we could make this
1779-            # run faster by spreading the load among multiple peers. But the
1780-            # algorithm to do that is more complicated than I want to write
1781-            # right now, and a well-provisioned grid shouldn't have multiple
1782-            # shares per peer.
1783-            peerid = list(self.remaining_sharemap[shnum])[0]
1784-            self.get_data(shnum, peerid)
1785 
1786hunk ./src/allmydata/mutable/retrieve.py 186
1787-        # control flow beyond this point: state machine. Receiving responses
1788-        # from queries is the input. We might send out more queries, or we
1789-        # might produce a result.
1790 
1791hunk ./src/allmydata/mutable/retrieve.py 187
1792+        # We need one share hash tree for the entire file; its leaves
1793+        # are the roots of the block hash trees for the shares that
1794+        # comprise it, and its root is in the verinfo.
1795+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
1796+        self.share_hash_tree.set_hashes({0: root_hash})
1797+
1798+        # This will set up both the segment decoder and the tail segment
1799+        # decoder, as well as a variety of other instance variables that
1800+        # the download process will use.
1801+        self._setup_encoding_parameters()
1802+        assert len(self.remaining_sharemap) >= k
1803+
1804+        self.log("starting download")
1805+        self._add_active_peers()
1806+        # The download process beyond this is a state machine.
1807+        # _add_active_peers will select the peers that we want to use
1808+        # for the download, and then attempt to start downloading. After
1809+        # each segment, it will check for doneness, reacting to broken
1810+        # peers and corrupt shares as necessary. If it runs out of good
1811+        # peers before downloading all of the segments, _done_deferred
1812+        # will errback.  Otherwise, it will eventually callback with the
1813+        # contents of the mutable file.
1814         return self._done_deferred
1815 
1816hunk ./src/allmydata/mutable/retrieve.py 211
1817-    def get_data(self, shnum, peerid):
1818-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
1819-                 shnum=shnum,
1820-                 peerid=idlib.shortnodeid_b2a(peerid),
1821-                 level=log.NOISY)
1822-        ss = self.servermap.connections[peerid]
1823-        started = time.time()
1824-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1825+
1826+    def _setup_encoding_parameters(self):
1827+        """
1828+        I set up the encoding parameters, including k, n, the number
1829+        of segments associated with this file, and the segment decoder.
1830+        """
1831+        (seqnum,
1832+         root_hash,
1833+         IV,
1834+         segsize,
1835+         datalength,
1836+         k,
1837+         n,
1838+         known_prefix,
1839          offsets_tuple) = self.verinfo
1840hunk ./src/allmydata/mutable/retrieve.py 226
1841-        offsets = dict(offsets_tuple)
1842+        self._required_shares = k
1843+        self._total_shares = n
1844+        self._segment_size = segsize
1845+        self._data_length = datalength
1846+
1847+        if not IV:
1848+            self._version = MDMF_VERSION
1849+        else:
1850+            self._version = SDMF_VERSION
1851+
1852+        if datalength and segsize:
1853+            self._num_segments = mathutil.div_ceil(datalength, segsize)
1854+            self._tail_data_size = datalength % segsize
1855+        else:
1856+            self._num_segments = 0
1857+            self._tail_data_size = 0
1858 
1859hunk ./src/allmydata/mutable/retrieve.py 243
1860-        # we read the checkstring, to make sure that the data we grab is from
1861-        # the right version.
1862-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
1863+        self._segment_decoder = codec.CRSDecoder()
1864+        self._segment_decoder.set_params(segsize, k, n)
1865+        self._current_segment = 0
1866 
1867hunk ./src/allmydata/mutable/retrieve.py 247
1868-        # We also read the data, and the hashes necessary to validate them
1869-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
1870-        # signature or the pubkey, since that was handled during the
1871-        # servermap phase, and we'll be comparing the share hash chain
1872-        # against the roothash that was validated back then.
1873+        if  not self._tail_data_size:
1874+            self._tail_data_size = segsize
1875 
1876hunk ./src/allmydata/mutable/retrieve.py 250
1877-        readv.append( (offsets['share_hash_chain'],
1878-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
1879+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
1880+                                                         self._required_shares)
1881+        if self._tail_segment_size == self._segment_size:
1882+            self._tail_decoder = self._segment_decoder
1883+        else:
1884+            self._tail_decoder = codec.CRSDecoder()
1885+            self._tail_decoder.set_params(self._tail_segment_size,
1886+                                          self._required_shares,
1887+                                          self._total_shares)
1888 
1889hunk ./src/allmydata/mutable/retrieve.py 260
1890-        # if we need the private key (for repair), we also fetch that
1891-        if self._need_privkey:
1892-            readv.append( (offsets['enc_privkey'],
1893-                           offsets['EOF'] - offsets['enc_privkey']) )
1894+        self.log("got encoding parameters: "
1895+                 "k: %d "
1896+                 "n: %d "
1897+                 "%d segments of %d bytes each (%d byte tail segment)" % \
1898+                 (k, n, self._num_segments, self._segment_size,
1899+                  self._tail_segment_size))
1900 
1901hunk ./src/allmydata/mutable/retrieve.py 267
1902-        m = Marker()
1903-        self._outstanding_queries[m] = (peerid, shnum, started)
1904+        for i in xrange(self._total_shares):
1905+            # So we don't have to do this later.
1906+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
1907 
1908hunk ./src/allmydata/mutable/retrieve.py 271
1909-        # ask the cache first
1910-        got_from_cache = False
1911-        datavs = []
1912-        for (offset, length) in readv:
1913-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
1914-                                                            offset, length)
1915-            if data is not None:
1916-                datavs.append(data)
1917-        if len(datavs) == len(readv):
1918-            self.log("got data from cache")
1919-            got_from_cache = True
1920-            d = fireEventually({shnum: datavs})
1921-            # datavs is a dict mapping shnum to a pair of strings
1922-        else:
1923-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
1924-        self.remaining_sharemap.discard(shnum, peerid)
1925+        # If we have more than one segment, we are an SDMF file, which
1926+        # means that we need to validate the salts as we receive them.
1927+        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
1928+        self._salt_hash_tree[0] = IV # from the prefix.
1929 
1930hunk ./src/allmydata/mutable/retrieve.py 276
1931-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
1932-        d.addErrback(self._query_failed, m, peerid)
1933-        # errors that aren't handled by _query_failed (and errors caused by
1934-        # _query_failed) get logged, but we still want to check for doneness.
1935-        def _oops(f):
1936-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
1937-                     shnum=shnum,
1938-                     peerid=idlib.shortnodeid_b2a(peerid),
1939-                     failure=f,
1940-                     level=log.WEIRD, umid="W0xnQA")
1941-        d.addErrback(_oops)
1942-        d.addBoth(self._check_for_done)
1943-        # any error during _check_for_done means the download fails. If the
1944-        # download is successful, _check_for_done will fire _done by itself.
1945-        d.addErrback(self._done)
1946-        d.addErrback(log.err)
1947-        return d # purely for testing convenience
1948 
1949hunk ./src/allmydata/mutable/retrieve.py 277
1950-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
1951-        # isolate the callRemote to a separate method, so tests can subclass
1952-        # Publish and override it
1953-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
1954-        return d
1955+    def _add_active_peers(self):
1956+        """
1957+        I populate self._active_readers with enough active readers to
1958+        retrieve the contents of this mutable file. I am called before
1959+        downloading starts, and (eventually) after each validation
1960+        error, connection error, or other problem in the download.
1961+        """
1962+        # TODO: It would be cool to investigate other heuristics for
1963+        # reader selection. For instance, the cost (in time the user
1964+        # spends waiting for their file) of selecting a really slow peer
1965+        # that happens to have a primary share is probably more than
1966+        # selecting a really fast peer that doesn't have a primary
1967+        # share. Maybe the servermap could be extended to provide this
1968+        # information; it could keep track of latency information while
1969+        # it gathers more important data, and then this routine could
1970+        # use that to select active readers.
1971+        #
1972+        # (these and other questions would be easier to answer with a
1973+        #  robust, configurable tahoe-lafs simulator, which modeled node
1974+        #  failures, differences in node speed, and other characteristics
1975+        #  that we expect storage servers to have.  You could have
1976+        #  presets for really stable grids (like allmydata.com),
1977+        #  friendnets, make it easy to configure your own settings, and
1978+        #  then simulate the effect of big changes on these use cases
1979+        #  instead of just reasoning about what the effect might be. Out
1980+        #  of scope for MDMF, though.)
1981 
1982hunk ./src/allmydata/mutable/retrieve.py 304
1983-    def remove_peer(self, peerid):
1984-        for shnum in list(self.remaining_sharemap.keys()):
1985-            self.remaining_sharemap.discard(shnum, peerid)
1986+        # We need at least self._required_shares readers to download a
1987+        # segment.
1988+        needed = self._required_shares - len(self._active_readers)
1989+        # XXX: Why don't format= log messages work here?
1990+        self.log("adding %d peers to the active peers list" % needed)
1991 
1992hunk ./src/allmydata/mutable/retrieve.py 310
1993-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
1994-        now = time.time()
1995-        elapsed = now - started
1996-        if not got_from_cache:
1997-            self._status.add_fetch_timing(peerid, elapsed)
1998-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
1999-                 shares=len(datavs),
2000-                 peerid=idlib.shortnodeid_b2a(peerid),
2001-                 level=log.NOISY)
2002-        self._outstanding_queries.pop(marker, None)
2003-        if not self._running:
2004-            return
2005+        # We favor lower numbered shares, since FEC is faster with
2006+        # primary shares than with other shares, and lower-numbered
2007+        # shares are more likely to be primary than higher numbered
2008+        # shares.
2009+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
2010+        # We shouldn't consider adding shares that we already have; this
2011+        # will cause problems later.
2012+        active_shnums -= set([reader.shnum for reader in self._active_readers])
2013+        active_shnums = list(active_shnums)[:needed]
2014+        if len(active_shnums) < needed:
2015+            # We don't have enough readers to retrieve the file; fail.
2016+            return self._failed()
2017 
2018hunk ./src/allmydata/mutable/retrieve.py 323
2019-        # note that we only ask for a single share per query, so we only
2020-        # expect a single share back. On the other hand, we use the extra
2021-        # shares if we get them.. seems better than an assert().
2022+        for shnum in active_shnums:
2023+            self._active_readers.append(self.readers[shnum])
2024+            self.log("added reader for share %d" % shnum)
2025+        assert len(self._active_readers) == self._required_shares
2026+        # Conceptually, this is part of the _add_active_peers step. It
2027+        # validates the prefixes of newly added readers to make sure
2028+        # that they match what we are expecting for self.verinfo. If
2029+        # validation is successful, _validate_active_prefixes will call
2030+        # _download_current_segment for us. If validation is
2031+        # unsuccessful, then _validate_prefixes will remove the peer and
2032+        # call _add_active_peers again, where we will attempt to rectify
2033+        # the problem by choosing another peer.
2034+        return self._validate_active_prefixes()
2035 
2036hunk ./src/allmydata/mutable/retrieve.py 337
2037-        for shnum,datav in datavs.items():
2038-            (prefix, hash_and_data) = datav[:2]
2039-            try:
2040-                self._got_results_one_share(shnum, peerid,
2041-                                            prefix, hash_and_data)
2042-            except CorruptShareError, e:
2043-                # log it and give the other shares a chance to be processed
2044-                f = failure.Failure()
2045-                self.log(format="bad share: %(f_value)s",
2046-                         f_value=str(f.value), failure=f,
2047-                         level=log.WEIRD, umid="7fzWZw")
2048-                self.notify_server_corruption(peerid, shnum, str(e))
2049-                self.remove_peer(peerid)
2050-                self.servermap.mark_bad_share(peerid, shnum, prefix)
2051-                self._bad_shares.add( (peerid, shnum) )
2052-                self._status.problems[peerid] = f
2053-                self._last_failure = f
2054-                pass
2055-            if self._need_privkey and len(datav) > 2:
2056-                lp = None
2057-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
2058-        # all done!
2059 
2060hunk ./src/allmydata/mutable/retrieve.py 338
2061-    def notify_server_corruption(self, peerid, shnum, reason):
2062-        ss = self.servermap.connections[peerid]
2063-        ss.callRemoteOnly("advise_corrupt_share",
2064-                          "mutable", self._storage_index, shnum, reason)
2065+    def _validate_active_prefixes(self):
2066+        """
2067+        I check to make sure that the prefixes on the peers that I am
2068+        currently reading from match the prefix that we want to see, as
2069+        said in self.verinfo.
2070 
2071hunk ./src/allmydata/mutable/retrieve.py 344
2072-    def _got_results_one_share(self, shnum, peerid,
2073-                               got_prefix, got_hash_and_data):
2074-        self.log("_got_results: got shnum #%d from peerid %s"
2075-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
2076-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2077+        If I find that all of the active peers have acceptable prefixes,
2078+        I pass control to _download_current_segment, which will use
2079+        those peers to do cool things. If I find that some of the active
2080+        peers have unacceptable prefixes, I will remove them from active
2081+        peers (and from further consideration) and call
2082+        _add_active_peers to attempt to rectify the situation. I keep
2083+        track of which peers I have already validated so that I don't
2084+        need to do so again.
2085+        """
2086+        assert self._active_readers, "No more active readers"
2087+
2088+        ds = []
2089+        new_readers = set(self._active_readers) - self._validated_readers
2090+        self.log('validating %d newly-added active readers' % len(new_readers))
2091+
2092+        for reader in new_readers:
2093+            # We force a remote read here -- otherwise, we are relying
2094+            # on cached data that we already verified as valid, and we
2095+            # won't detect an uncoordinated write that has occurred
2096+            # since the last servermap update.
2097+            d = reader.get_prefix(force_remote=True)
2098+            d.addCallback(self._try_to_validate_prefix, reader)
2099+            ds.append(d)
2100+        dl = defer.DeferredList(ds, consumeErrors=True)
2101+        def _check_results(results):
2102+            # Each result in results will be of the form (success, msg).
2103+            # We don't care about msg, but success will tell us whether
2104+            # or not the checkstring validated. If it didn't, we need to
2105+            # remove the offending (peer,share) from our active readers,
2106+            # and ensure that active readers is again populated.
2107+            bad_readers = []
2108+            for i, result in enumerate(results):
2109+                if not result[0]:
2110+                    reader = self._active_readers[i]
2111+                    f = result[1]
2112+                    assert isinstance(f, failure.Failure)
2113+
2114+                    self.log("The reader %s failed to "
2115+                             "properly validate: %s" % \
2116+                             (reader, str(f.value)))
2117+                    bad_readers.append((reader, f))
2118+                else:
2119+                    reader = self._active_readers[i]
2120+                    self.log("the reader %s checks out, so we'll use it" % \
2121+                             reader)
2122+                    self._validated_readers.add(reader)
2123+                    # Each time we validate a reader, we check to see if
2124+                    # we need the private key. If we do, we politely ask
2125+                    # for it and then continue computing. If we find
2126+                    # that we haven't gotten it at the end of
2127+                    # segment decoding, then we'll take more drastic
2128+                    # measures.
2129+                    if self._need_privkey:
2130+                        d = reader.get_encprivkey()
2131+                        d.addCallback(self._try_to_validate_privkey, reader)
2132+            if bad_readers:
2133+                # We do them all at once, or else we screw up list indexing.
2134+                for (reader, f) in bad_readers:
2135+                    self._mark_bad_share(reader, f)
2136+                return self._add_active_peers()
2137+            else:
2138+                return self._download_current_segment()
2139+            # The next step will assert that it has enough active
2140+            # readers to fetch shares; we just need to remove it.
2141+        dl.addCallback(_check_results)
2142+        return dl
2143+
2144+
2145+    def _try_to_validate_prefix(self, prefix, reader):
2146+        """
2147+        I check that the prefix returned by a candidate server for
2148+        retrieval matches the prefix that the servermap knows about
2149+        (and, hence, the prefix that was validated earlier). If it does,
2150+        I return True, which means that I approve of the use of the
2151+        candidate server for segment retrieval. If it doesn't, I return
2152+        False, which means that another server must be chosen.
2153+        """
2154+        (seqnum,
2155+         root_hash,
2156+         IV,
2157+         segsize,
2158+         datalength,
2159+         k,
2160+         N,
2161+         known_prefix,
2162          offsets_tuple) = self.verinfo
2163hunk ./src/allmydata/mutable/retrieve.py 430
2164-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
2165-        if got_prefix != prefix:
2166-            msg = "someone wrote to the data since we read the servermap: prefix changed"
2167-            raise UncoordinatedWriteError(msg)
2168-        (share_hash_chain, block_hash_tree,
2169-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
2170+        if known_prefix != prefix:
2171+            self.log("prefix from share %d doesn't match" % reader.shnum)
2172+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
2173+                                          "indicate an uncoordinated write")
2174+        # Otherwise, we're okay -- no issues.
2175 
2176hunk ./src/allmydata/mutable/retrieve.py 436
2177-        assert isinstance(share_data, str)
2178-        # build the block hash tree. SDMF has only one leaf.
2179-        leaves = [hashutil.block_hash(share_data)]
2180-        t = hashtree.HashTree(leaves)
2181-        if list(t) != block_hash_tree:
2182-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
2183-        share_hash_leaf = t[0]
2184-        t2 = hashtree.IncompleteHashTree(N)
2185-        # root_hash was checked by the signature
2186-        t2.set_hashes({0: root_hash})
2187-        try:
2188-            t2.set_hashes(hashes=share_hash_chain,
2189-                          leaves={shnum: share_hash_leaf})
2190-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
2191-                IndexError), e:
2192-            msg = "corrupt hashes: %s" % (e,)
2193-            raise CorruptShareError(peerid, shnum, msg)
2194-        self.log(" data valid! len=%d" % len(share_data))
2195-        # each query comes down to this: placing validated share data into
2196-        # self.shares
2197-        self.shares[shnum] = share_data
2198 
2199hunk ./src/allmydata/mutable/retrieve.py 437
2200-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
2201+    def _remove_reader(self, reader):
2202+        """
2203+        At various points, we will wish to remove a peer from
2204+        consideration and/or use. These include, but are not necessarily
2205+        limited to:
2206 
2207hunk ./src/allmydata/mutable/retrieve.py 443
2208-        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2209-        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2210-        if alleged_writekey != self._node.get_writekey():
2211-            self.log("invalid privkey from %s shnum %d" %
2212-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
2213-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
2214-            return
2215+            - A connection error.
2216+            - A mismatched prefix (that is, a prefix that does not match
2217+              our conception of the version information string).
2218+            - A failing block hash, salt hash, or share hash, which can
2219+              indicate disk failure/bit flips, or network trouble.
2220 
2221hunk ./src/allmydata/mutable/retrieve.py 449
2222-        # it's good
2223-        self.log("got valid privkey from shnum %d on peerid %s" %
2224-                 (shnum, idlib.shortnodeid_b2a(peerid)),
2225-                 parent=lp)
2226-        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2227-        self._node._populate_encprivkey(enc_privkey)
2228-        self._node._populate_privkey(privkey)
2229-        self._need_privkey = False
2230+        This method will do that. I will make sure that the
2231+        (shnum,reader) combination represented by my reader argument is
2232+        not used for anything else during this download. I will not
2233+        advise the reader of any corruption, something that my callers
2234+        may wish to do on their own.
2235+        """
2236+        # TODO: When you're done writing this, see if this is ever
2237+        # actually used for something that _mark_bad_share isn't. I have
2238+        # a feeling that they will be used for very similar things, and
2239+        # that having them both here is just going to be an epic amount
2240+        # of code duplication.
2241+        #
2242+        # (well, okay, not epic, but meaningful)
2243+        self.log("removing reader %s" % reader)
2244+        # Remove the reader from _active_readers
2245+        self._active_readers.remove(reader)
2246+        # TODO: self.readers.remove(reader)?
2247+        for shnum in list(self.remaining_sharemap.keys()):
2248+            self.remaining_sharemap.discard(shnum, reader.peerid)
2249 
2250hunk ./src/allmydata/mutable/retrieve.py 469
2251-    def _query_failed(self, f, marker, peerid):
2252-        self.log(format="query to [%(peerid)s] failed",
2253-                 peerid=idlib.shortnodeid_b2a(peerid),
2254-                 level=log.NOISY)
2255-        self._status.problems[peerid] = f
2256-        self._outstanding_queries.pop(marker, None)
2257-        if not self._running:
2258-            return
2259-        self._last_failure = f
2260-        self.remove_peer(peerid)
2261-        level = log.WEIRD
2262-        if f.check(DeadReferenceError):
2263-            level = log.UNUSUAL
2264-        self.log(format="error during query: %(f_value)s",
2265-                 f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
2266 
2267hunk ./src/allmydata/mutable/retrieve.py 470
2268-    def _check_for_done(self, res):
2269-        # exit paths:
2270-        #  return : keep waiting, no new queries
2271-        #  return self._send_more_queries(outstanding) : send some more queries
2272-        #  fire self._done(plaintext) : download successful
2273-        #  raise exception : download fails
2274+    def _mark_bad_share(self, reader, f):
2275+        """
2276+        I mark the (peerid, shnum) encapsulated by my reader argument as
2277+        a bad share, which means that it will not be used anywhere else.
2278 
2279hunk ./src/allmydata/mutable/retrieve.py 475
2280-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
2281-                 running=self._running, decoding=self._decoding,
2282-                 level=log.NOISY)
2283-        if not self._running:
2284-            return
2285-        if self._decoding:
2286-            return
2287-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2288-         offsets_tuple) = self.verinfo
2289+        There are several reasons to want to mark something as a bad
2290+        share. These include:
2291 
2292hunk ./src/allmydata/mutable/retrieve.py 478
2293-        if len(self.shares) < k:
2294-            # we don't have enough shares yet
2295-            return self._maybe_send_more_queries(k)
2296-        if self._need_privkey:
2297-            # we got k shares, but none of them had a valid privkey. TODO:
2298-            # look further. Adding code to do this is a bit complicated, and
2299-            # I want to avoid that complication, and this should be pretty
2300-            # rare (k shares with bitflips in the enc_privkey but not in the
2301-            # data blocks). If we actually do get here, the subsequent repair
2302-            # will fail for lack of a privkey.
2303-            self.log("got k shares but still need_privkey, bummer",
2304-                     level=log.WEIRD, umid="MdRHPA")
2305+            - A connection error to the peer.
2306+            - A mismatched prefix (that is, a prefix that does not match
2307+              our local conception of the version information string).
2308+            - A failing block hash, salt hash, share hash, or other
2309+              integrity check.
2310 
2311hunk ./src/allmydata/mutable/retrieve.py 484
2312-        # we have enough to finish. All the shares have had their hashes
2313-        # checked, so if something fails at this point, we don't know how
2314-        # to fix it, so the download will fail.
2315+        This method will ensure that readers that we wish to mark bad
2316+        (for these reasons or other reasons) are not used for the rest
2317+        of the download. Additionally, it will attempt to tell the
2318+        remote peer (with no guarantee of success) that its share is
2319+        corrupt.
2320+        """
2321+        self.log("marking share %d on server %s as bad" % \
2322+                 (reader.shnum, reader))
2323+        self._remove_reader(reader)
2324+        self._bad_shares.add((reader.peerid, reader.shnum))
2325+        self._status.problems[reader.peerid] = f
2326+        self._last_failure = f
2327+        self.notify_server_corruption(reader.peerid, reader.shnum,
2328+                                      str(f.value))
2329 
2330hunk ./src/allmydata/mutable/retrieve.py 499
2331-        self._decoding = True # avoid reentrancy
2332-        self._status.set_status("decoding")
2333-        now = time.time()
2334-        elapsed = now - self._started
2335-        self._status.timings["fetch"] = elapsed
2336 
2337hunk ./src/allmydata/mutable/retrieve.py 500
2338-        d = defer.maybeDeferred(self._decode)
2339-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
2340-        d.addBoth(self._done)
2341-        return d # purely for test convenience
2342+    def _download_current_segment(self):
2343+        """
2344+        I download, validate, decode, decrypt, and assemble the segment
2345+        that this Retrieve is currently responsible for downloading.
2346+        """
2347+        assert len(self._active_readers) >= self._required_shares
2348+        if self._current_segment < self._num_segments:
2349+            d = self._process_segment(self._current_segment)
2350+        else:
2351+            d = defer.succeed(None)
2352+        d.addCallback(self._check_for_done)
2353+        return d
2354 
2355hunk ./src/allmydata/mutable/retrieve.py 513
2356-    def _maybe_send_more_queries(self, k):
2357-        # we don't have enough shares yet. Should we send out more queries?
2358-        # There are some number of queries outstanding, each for a single
2359-        # share. If we can generate 'needed_shares' additional queries, we do
2360-        # so. If we can't, then we know this file is a goner, and we raise
2361-        # NotEnoughSharesError.
2362-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
2363-                         "outstanding=%(outstanding)d"),
2364-                 have=len(self.shares), k=k,
2365-                 outstanding=len(self._outstanding_queries),
2366-                 level=log.NOISY)
2367 
2368hunk ./src/allmydata/mutable/retrieve.py 514
2369-        remaining_shares = k - len(self.shares)
2370-        needed = remaining_shares - len(self._outstanding_queries)
2371-        if not needed:
2372-            # we have enough queries in flight already
2373+    def _process_segment(self, segnum):
2374+        """
2375+        I download, validate, decode, and decrypt one segment of the
2376+        file that this Retrieve is retrieving. This means coordinating
2377+        the process of getting k blocks of that file, validating them,
2378+        assembling them into one segment with the decoder, and then
2379+        decrypting them.
2380+        """
2381+        self.log("processing segment %d" % segnum)
2382 
2383hunk ./src/allmydata/mutable/retrieve.py 524
2384-            # TODO: but if they've been in flight for a long time, and we
2385-            # have reason to believe that new queries might respond faster
2386-            # (i.e. we've seen other queries come back faster, then consider
2387-            # sending out new queries. This could help with peers which have
2388-            # silently gone away since the servermap was updated, for which
2389-            # we're still waiting for the 15-minute TCP disconnect to happen.
2390-            self.log("enough queries are in flight, no more are needed",
2391-                     level=log.NOISY)
2392-            return
2393+        # TODO: The old code uses a marker. Should this code do that
2394+        # too? What did the Marker do?
2395+        assert len(self._active_readers) >= self._required_shares
2396+
2397+        # We need to ask each of our active readers for its block and
2398+        # salt. We will then validate those. If validation is
2399+        # successful, we will assemble the results into plaintext.
2400+        ds = []
2401+        for reader in self._active_readers:
2402+            d = reader.get_block_and_salt(segnum, queue=True)
2403+            d2 = self._get_needed_hashes(reader, segnum)
2404+            dl = defer.DeferredList([d, d2], consumeErrors=True)
2405+            dl.addCallback(self._validate_block, segnum, reader)
2406+            dl.addErrback(self._validation_or_decoding_failed, [reader])
2407+            ds.append(dl)
2408+            reader.flush()
2409+        dl = defer.DeferredList(ds)
2410+        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
2411+        return dl
2412 
2413hunk ./src/allmydata/mutable/retrieve.py 544
2414-        outstanding_shnums = set([shnum
2415-                                  for (peerid, shnum, started)
2416-                                  in self._outstanding_queries.values()])
2417-        # prefer low-numbered shares, they are more likely to be primary
2418-        available_shnums = sorted(self.remaining_sharemap.keys())
2419-        for shnum in available_shnums:
2420-            if shnum in outstanding_shnums:
2421-                # skip ones that are already in transit
2422-                continue
2423-            if shnum not in self.remaining_sharemap:
2424-                # no servers for that shnum. note that DictOfSets removes
2425-                # empty sets from the dict for us.
2426-                continue
2427-            peerid = list(self.remaining_sharemap[shnum])[0]
2428-            # get_data will remove that peerid from the sharemap, and add the
2429-            # query to self._outstanding_queries
2430-            self._status.set_status("Retrieving More Shares")
2431-            self.get_data(shnum, peerid)
2432-            needed -= 1
2433-            if not needed:
2434+
2435+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
2436+        """
2437+        I take the results of fetching and validating the blocks from a
2438+        callback chain in another method. If the results are such that
2439+        they tell me that validation and fetching succeeded without
2440+        incident, I will proceed with decoding and decryption.
2441+        Otherwise, I will do nothing.
2442+        """
2443+        self.log("trying to decode and decrypt segment %d" % segnum)
2444+        failures = False
2445+        for block_and_salt in blocks_and_salts:
2446+            if not block_and_salt[0] or block_and_salt[1] == None:
2447+                self.log("some validation operations failed; not proceeding")
2448+                failures = True
2449                 break
2450hunk ./src/allmydata/mutable/retrieve.py 560
2451+        if not failures:
2452+            self.log("everything looks ok, building segment %d" % segnum)
2453+            d = self._decode_blocks(blocks_and_salts, segnum)
2454+            d.addCallback(self._decrypt_segment)
2455+            d.addErrback(self._validation_or_decoding_failed,
2456+                         self._active_readers)
2457+            d.addCallback(self._set_segment)
2458+            return d
2459+        else:
2460+            return defer.succeed(None)
2461+
2462+
2463+    def _set_segment(self, segment):
2464+        """
2465+        Given a plaintext segment, I register that segment with the
2466+        target that is handling the file download.
2467+        """
2468+        self.log("got plaintext for segment %d" % self._current_segment)
2469+        self._plaintext += segment
2470+        self._current_segment += 1
2471 
2472hunk ./src/allmydata/mutable/retrieve.py 581
2473-        # at this point, we have as many outstanding queries as we can. If
2474-        # needed!=0 then we might not have enough to recover the file.
2475-        if needed:
2476-            format = ("ran out of peers: "
2477-                      "have %(have)d shares (k=%(k)d), "
2478-                      "%(outstanding)d queries in flight, "
2479-                      "need %(need)d more, "
2480-                      "found %(bad)d bad shares")
2481-            args = {"have": len(self.shares),
2482-                    "k": k,
2483-                    "outstanding": len(self._outstanding_queries),
2484-                    "need": needed,
2485-                    "bad": len(self._bad_shares),
2486-                    }
2487-            self.log(format=format,
2488-                     level=log.WEIRD, umid="ezTfjw", **args)
2489-            err = NotEnoughSharesError("%s, last failure: %s" %
2490-                                      (format % args, self._last_failure))
2491-            if self._bad_shares:
2492-                self.log("We found some bad shares this pass. You should "
2493-                         "update the servermap and try again to check "
2494-                         "more peers",
2495-                         level=log.WEIRD, umid="EFkOlA")
2496-                err.servermap = self.servermap
2497-            raise err
2498 
2499hunk ./src/allmydata/mutable/retrieve.py 582
2500+    def _validation_or_decoding_failed(self, f, readers):
2501+        """
2502+        I am called when a block or a salt fails to correctly validate, or when
2503+        the decryption or decoding operation fails for some reason.  I react to
2504+        this failure by notifying the remote server of corruption, and then
2505+        removing the remote peer from further activity.
2506+        """
2507+        assert isinstance(readers, list)
2508+        bad_shnums = [reader.shnum for reader in readers]
2509+
2510+        self.log("validation or decoding failed on share(s) %s, peer(s) %s "
2511+                 ", segment %d: %s" % \
2512+                 (bad_shnums, readers, self._current_segment, str(f)))
2513+        for reader in readers:
2514+            self._mark_bad_share(reader, f)
2515         return
2516 
2517hunk ./src/allmydata/mutable/retrieve.py 599
2518-    def _decode(self):
2519-        started = time.time()
2520-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2521-         offsets_tuple) = self.verinfo
2522 
2523hunk ./src/allmydata/mutable/retrieve.py 600
2524-        # shares_dict is a dict mapping shnum to share data, but the codec
2525-        # wants two lists.
2526-        shareids = []; shares = []
2527-        for shareid, share in self.shares.items():
2528+    def _validate_block(self, results, segnum, reader):
2529+        """
2530+        I validate a block from one share on a remote server.
2531+        """
2532+        # Grab the part of the block hash tree that is necessary to
2533+        # validate this block, then generate the block hash root.
2534+        self.log("validating share %d for segment %d" % (reader.shnum,
2535+                                                             segnum))
2536+        # Did we fail to fetch either of the things that we were
2537+        # supposed to? Fail if so.
2538+        if not results[0][0] and results[1][0]:
2539+            # handled by the errback handler.
2540+
2541+            # These all get batched into one query, so the resulting
2542+            # failure should be the same for all of them, so we can just
2543+            # use the first one.
2544+            assert isinstance(results[0][1], failure.Failure)
2545+
2546+            f = results[0][1]
2547+            raise CorruptShareError(reader.peerid,
2548+                                    reader.shnum,
2549+                                    "Connection error: %s" % str(f))
2550+
2551+        block_and_salt, block_and_sharehashes = results
2552+        block, salt = block_and_salt[1]
2553+        blockhashes, sharehashes = block_and_sharehashes[1]
2554+
2555+        blockhashes = dict(enumerate(blockhashes[1]))
2556+        self.log("the reader gave me the following blockhashes: %s" % \
2557+                 blockhashes.keys())
2558+        self.log("the reader gave me the following sharehashes: %s" % \
2559+                 sharehashes[1].keys())
2560+        bht = self._block_hash_trees[reader.shnum]
2561+
2562+        if bht.needed_hashes(segnum, include_leaf=True):
2563+            try:
2564+                bht.set_hashes(blockhashes)
2565+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2566+                    IndexError), e:
2567+                raise CorruptShareError(reader.peerid,
2568+                                        reader.shnum,
2569+                                        "block hash tree failure: %s" % e)
2570+
2571+        if self._version == MDMF_VERSION:
2572+            blockhash = hashutil.block_hash(salt + block)
2573+        else:
2574+            blockhash = hashutil.block_hash(block)
2575+        # If this works without an error, then validation is
2576+        # successful.
2577+        try:
2578+           bht.set_hashes(leaves={segnum: blockhash})
2579+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2580+                IndexError), e:
2581+            raise CorruptShareError(reader.peerid,
2582+                                    reader.shnum,
2583+                                    "block hash tree failure: %s" % e)
2584+
2585+        # Reaching this point means that we know that this segment
2586+        # is correct. Now we need to check to see whether the share
2587+        # hash chain is also correct.
2588+        # SDMF wrote share hash chains that didn't contain the
2589+        # leaves, which would be produced from the block hash tree.
2590+        # So we need to validate the block hash tree first. If
2591+        # successful, then bht[0] will contain the root for the
2592+        # shnum, which will be a leaf in the share hash tree, which
2593+        # will allow us to validate the rest of the tree.
2594+        if self.share_hash_tree.needed_hashes(reader.shnum,
2595+                                               include_leaf=True):
2596+            try:
2597+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
2598+                                            leaves={reader.shnum: bht[0]})
2599+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2600+                    IndexError), e:
2601+                raise CorruptShareError(reader.peerid,
2602+                                        reader.shnum,
2603+                                        "corrupt hashes: %s" % e)
2604+
2605+        # TODO: Validate the salt, too.
2606+        self.log('share %d is valid for segment %d' % (reader.shnum,
2607+                                                       segnum))
2608+        return {reader.shnum: (block, salt)}
2609+
2610+
2611+    def _get_needed_hashes(self, reader, segnum):
2612+        """
2613+        I get the hashes needed to validate segnum from the reader, then return
2614+        to my caller when this is done.
2615+        """
2616+        bht = self._block_hash_trees[reader.shnum]
2617+        needed = bht.needed_hashes(segnum, include_leaf=True)
2618+        # The root of the block hash tree is also a leaf in the share
2619+        # hash tree. So we don't need to fetch it from the remote
2620+        # server. In the case of files with one segment, this means that
2621+        # we won't fetch any block hash tree from the remote server,
2622+        # since the hash of each share of the file is the entire block
2623+        # hash tree, and is a leaf in the share hash tree. This is fine,
2624+        # since any share corruption will be detected in the share hash
2625+        # tree.
2626+        #needed.discard(0)
2627+        self.log("getting blockhashes for segment %d, share %d: %s" % \
2628+                 (segnum, reader.shnum, str(needed)))
2629+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
2630+        if self.share_hash_tree.needed_hashes(reader.shnum):
2631+            need = self.share_hash_tree.needed_hashes(reader.shnum)
2632+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
2633+                                                                 str(need)))
2634+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
2635+        else:
2636+            d2 = defer.succeed({}) # the logic in the next method
2637+                                   # expects a dict
2638+        dl = defer.DeferredList([d1, d2], consumeErrors=True)
2639+        return dl
2640+
2641+
2642+    def _decode_blocks(self, blocks_and_salts, segnum):
2643+        """
2644+        I take a list of k blocks and salts, and decode that into a
2645+        single encrypted segment.
2646+        """
2647+        d = {}
2648+        # We want to merge our dictionaries to the form
2649+        # {shnum: blocks_and_salts}
2650+        #
2651+        # The dictionaries come from validate block that way, so we just
2652+        # need to merge them.
2653+        for block_and_salt in blocks_and_salts:
2654+            d.update(block_and_salt[1])
2655+
2656+        # All of these blocks should have the same salt; in SDMF, it is
2657+        # the file-wide IV, while in MDMF it is the per-segment salt. In
2658+        # either case, we just need to get one of them and use it.
2659+        #
2660+        # d.items()[0] is like (shnum, (block, salt))
2661+        # d.items()[0][1] is like (block, salt)
2662+        # d.items()[0][1][1] is the salt.
2663+        salt = d.items()[0][1][1]
2664+        # Next, extract just the blocks from the dict. We'll use the
2665+        # salt in the next step.
2666+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
2667+        d2 = dict(share_and_shareids)
2668+        shareids = []
2669+        shares = []
2670+        for shareid, share in d2.items():
2671             shareids.append(shareid)
2672             shares.append(share)
2673 
2674hunk ./src/allmydata/mutable/retrieve.py 746
2675-        assert len(shareids) >= k, len(shareids)
2676+        assert len(shareids) >= self._required_shares, len(shareids)
2677         # zfec really doesn't want extra shares
2678hunk ./src/allmydata/mutable/retrieve.py 748
2679-        shareids = shareids[:k]
2680-        shares = shares[:k]
2681-
2682-        fec = codec.CRSDecoder()
2683-        fec.set_params(segsize, k, N)
2684-
2685-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
2686-        self.log("about to decode, shareids=%s" % (shareids,))
2687-        d = defer.maybeDeferred(fec.decode, shares, shareids)
2688-        def _done(buffers):
2689-            self._status.timings["decode"] = time.time() - started
2690-            self.log(" decode done, %d buffers" % len(buffers))
2691+        shareids = shareids[:self._required_shares]
2692+        shares = shares[:self._required_shares]
2693+        self.log("decoding segment %d" % segnum)
2694+        if segnum == self._num_segments - 1:
2695+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
2696+        else:
2697+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
2698+        def _process(buffers):
2699             segment = "".join(buffers)
2700hunk ./src/allmydata/mutable/retrieve.py 757
2701+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
2702+                     segnum=segnum,
2703+                     numsegs=self._num_segments,
2704+                     level=log.NOISY)
2705             self.log(" joined length %d, datalength %d" %
2706hunk ./src/allmydata/mutable/retrieve.py 762
2707-                     (len(segment), datalength))
2708-            segment = segment[:datalength]
2709+                     (len(segment), self._data_length))
2710+            if segnum == self._num_segments - 1:
2711+                size_to_use = self._tail_data_size
2712+            else:
2713+                size_to_use = self._segment_size
2714+            segment = segment[:size_to_use]
2715             self.log(" segment len=%d" % len(segment))
2716hunk ./src/allmydata/mutable/retrieve.py 769
2717-            return segment
2718-        def _err(f):
2719-            self.log(" decode failed: %s" % f)
2720-            return f
2721-        d.addCallback(_done)
2722-        d.addErrback(_err)
2723+            return segment, salt
2724+        d.addCallback(_process)
2725         return d
2726 
2727hunk ./src/allmydata/mutable/retrieve.py 773
2728-    def _decrypt(self, crypttext, IV, readkey):
2729+
2730+    def _decrypt_segment(self, segment_and_salt):
2731+        """
2732+        I take a single segment and its salt, and decrypt it. I return
2733+        the plaintext of the segment that is in my argument.
2734+        """
2735+        segment, salt = segment_and_salt
2736         self._status.set_status("decrypting")
2737hunk ./src/allmydata/mutable/retrieve.py 781
2738+        self.log("decrypting segment %d" % self._current_segment)
2739         started = time.time()
2740hunk ./src/allmydata/mutable/retrieve.py 783
2741-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
2742+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
2743         decryptor = AES(key)
2744hunk ./src/allmydata/mutable/retrieve.py 785
2745-        plaintext = decryptor.process(crypttext)
2746+        plaintext = decryptor.process(segment)
2747         self._status.timings["decrypt"] = time.time() - started
2748         return plaintext
2749 
2750hunk ./src/allmydata/mutable/retrieve.py 789
2751-    def _done(self, res):
2752-        if not self._running:
2753+
2754+    def notify_server_corruption(self, peerid, shnum, reason):
2755+        ss = self.servermap.connections[peerid]
2756+        ss.callRemoteOnly("advise_corrupt_share",
2757+                          "mutable", self._storage_index, shnum, reason)
2758+
2759+
2760+    def _try_to_validate_privkey(self, enc_privkey, reader):
2761+
2762+        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2763+        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2764+        if alleged_writekey != self._node.get_writekey():
2765+            self.log("invalid privkey from %s shnum %d" %
2766+                     (reader, reader.shnum),
2767+                     level=log.WEIRD, umid="YIw4tA")
2768             return
2769hunk ./src/allmydata/mutable/retrieve.py 805
2770-        self._running = False
2771-        self._status.set_active(False)
2772-        self._status.timings["total"] = time.time() - self._started
2773-        # res is either the new contents, or a Failure
2774-        if isinstance(res, failure.Failure):
2775-            self.log("Retrieve done, with failure", failure=res,
2776-                     level=log.UNUSUAL)
2777-            self._status.set_status("Failed")
2778-        else:
2779-            self.log("Retrieve done, success!")
2780-            self._status.set_status("Finished")
2781-            self._status.set_progress(1.0)
2782-            # remember the encoding parameters, use them again next time
2783-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2784-             offsets_tuple) = self.verinfo
2785-            self._node._populate_required_shares(k)
2786-            self._node._populate_total_shares(N)
2787-        eventually(self._done_deferred.callback, res)
2788 
2789hunk ./src/allmydata/mutable/retrieve.py 806
2790+        # it's good
2791+        self.log("got valid privkey from shnum %d on reader %s" %
2792+                 (reader.shnum, reader))
2793+        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2794+        self._node._populate_encprivkey(enc_privkey)
2795+        self._node._populate_privkey(privkey)
2796+        self._need_privkey = False
2797+
2798+
2799+    def _check_for_done(self, res):
2800+        """
2801+        I check to see if this Retrieve object has successfully finished
2802+        its work.
2803+
2804+        I can exit in the following ways:
2805+            - If there are no more segments to download, then I exit by
2806+              causing self._done_deferred to fire with the plaintext
2807+              content requested by the caller.
2808+            - If there are still segments to be downloaded, and there
2809+              are enough active readers (readers which have not broken
2810+              and have not given us corrupt data) to continue
2811+              downloading, I send control back to
2812+              _download_current_segment.
2813+            - If there are still segments to be downloaded but there are
2814+              not enough active peers to download them, I ask
2815+              _add_active_peers to add more peers. If it is successful,
2816+              it will call _download_current_segment. If there are not
2817+              enough peers to retrieve the file, then that will cause
2818+              _done_deferred to errback.
2819+        """
2820+        self.log("checking for doneness")
2821+        if self._current_segment == self._num_segments:
2822+            # No more segments to download, we're done.
2823+            self.log("got plaintext, done")
2824+            return self._done()
2825+
2826+        if len(self._active_readers) >= self._required_shares:
2827+            # More segments to download, but we have enough good peers
2828+            # in self._active_readers that we can do that without issue,
2829+            # so go nab the next segment.
2830+            self.log("not done yet: on segment %d of %d" % \
2831+                     (self._current_segment + 1, self._num_segments))
2832+            return self._download_current_segment()
2833+
2834+        self.log("not done yet: on segment %d of %d, need to add peers" % \
2835+                 (self._current_segment + 1, self._num_segments))
2836+        return self._add_active_peers()
2837+
2838+
2839+    def _done(self):
2840+        """
2841+        I am called by _check_for_done when the download process has
2842+        finished successfully. After making some useful logging
2843+        statements, I return the decrypted contents to the owner of this
2844+        Retrieve object through self._done_deferred.
2845+        """
2846+        eventually(self._done_deferred.callback, self._plaintext)
2847+
2848+
2849+    def _failed(self):
2850+        """
2851+        I am called by _add_active_peers when there are not enough
2852+        active peers left to complete the download. After making some
2853+        useful logging statements, I return an exception to that effect
2854+        to the caller of this Retrieve object through
2855+        self._done_deferred.
2856+        """
2857+        format = ("ran out of peers: "
2858+                  "have %(have)d of %(total)d segments "
2859+                  "found %(bad)d bad shares "
2860+                  "encoding %(k)d-of-%(n)d")
2861+        args = {"have": self._current_segment,
2862+                "total": self._num_segments,
2863+                "k": self._required_shares,
2864+                "n": self._total_shares,
2865+                "bad": len(self._bad_shares)}
2866+        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
2867+                                                        str(self._last_failure)))
2868+        f = failure.Failure(e)
2869+        eventually(self._done_deferred.callback, f)
2870hunk ./src/allmydata/test/test_mutable.py 12
2871 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
2872      ssk_pubkey_fingerprint_hash
2873 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
2874-     NotEnoughSharesError
2875+     NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
2876 from allmydata.monitor import Monitor
2877 from allmydata.test.common import ShouldFailMixin
2878 from allmydata.test.no_network import GridTestMixin
2879hunk ./src/allmydata/test/test_mutable.py 28
2880 from allmydata.mutable.retrieve import Retrieve
2881 from allmydata.mutable.publish import Publish
2882 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
2883-from allmydata.mutable.layout import unpack_header, unpack_share
2884+from allmydata.mutable.layout import unpack_header, unpack_share, \
2885+                                     MDMFSlotReadProxy
2886 from allmydata.mutable.repairer import MustForceRepairError
2887 
2888 import allmydata.test.common_util as testutil
2889hunk ./src/allmydata/test/test_mutable.py 104
2890         d = fireEventually()
2891         d.addCallback(lambda res: _call())
2892         return d
2893+
2894     def callRemoteOnly(self, methname, *args, **kwargs):
2895         d = self.callRemote(methname, *args, **kwargs)
2896         d.addBoth(lambda ignore: None)
2897hunk ./src/allmydata/test/test_mutable.py 163
2898 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
2899     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
2900     # list of shnums to corrupt.
2901+    ds = []
2902     for peerid in s._peers:
2903         shares = s._peers[peerid]
2904         for shnum in shares:
2905hunk ./src/allmydata/test/test_mutable.py 190
2906                 else:
2907                     offset1 = offset
2908                     offset2 = 0
2909-                if offset1 == "pubkey":
2910+                if offset1 == "pubkey" and IV:
2911                     real_offset = 107
2912hunk ./src/allmydata/test/test_mutable.py 192
2913+                elif offset1 == "share_data" and not IV:
2914+                    real_offset = 104
2915                 elif offset1 in o:
2916                     real_offset = o[offset1]
2917                 else:
2918hunk ./src/allmydata/test/test_mutable.py 327
2919         d.addCallback(_created)
2920         return d
2921 
2922+
2923+    def test_upload_and_download_mdmf(self):
2924+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
2925+        def _created(n):
2926+            d = defer.succeed(None)
2927+            d.addCallback(lambda ignored:
2928+                n.get_servermap(MODE_READ))
2929+            def _then(servermap):
2930+                dumped = servermap.dump(StringIO())
2931+                self.failUnlessIn("3-of-10", dumped.getvalue())
2932+            d.addCallback(_then)
2933+            # Now overwrite the contents with some new contents. We want
2934+            # to make them big enough to force the file to be uploaded
2935+            # in more than one segment.
2936+            big_contents = "contents1" * 100000 # about 900 KiB
2937+            d.addCallback(lambda ignored:
2938+                n.overwrite(big_contents))
2939+            d.addCallback(lambda ignored:
2940+                n.download_best_version())
2941+            d.addCallback(lambda data:
2942+                self.failUnlessEqual(data, big_contents))
2943+            # Overwrite the contents again with some new contents. As
2944+            # before, they need to be big enough to force multiple
2945+            # segments, so that we make the downloader deal with
2946+            # multiple segments.
2947+            bigger_contents = "contents2" * 1000000 # about 9MiB
2948+            d.addCallback(lambda ignored:
2949+                n.overwrite(bigger_contents))
2950+            d.addCallback(lambda ignored:
2951+                n.download_best_version())
2952+            d.addCallback(lambda data:
2953+                self.failUnlessEqual(data, bigger_contents))
2954+            return d
2955+        d.addCallback(_created)
2956+        return d
2957+
2958+
2959     def test_create_with_initial_contents(self):
2960         d = self.nodemaker.create_mutable_file("contents 1")
2961         def _created(n):
2962hunk ./src/allmydata/test/test_mutable.py 1147
2963 
2964 
2965     def _test_corrupt_all(self, offset, substring,
2966-                          should_succeed=False, corrupt_early=True,
2967-                          failure_checker=None):
2968+                          should_succeed=False,
2969+                          corrupt_early=True,
2970+                          failure_checker=None,
2971+                          fetch_privkey=False):
2972         d = defer.succeed(None)
2973         if corrupt_early:
2974             d.addCallback(corrupt, self._storage, offset)
2975hunk ./src/allmydata/test/test_mutable.py 1167
2976                     self.failUnlessIn(substring, "".join(allproblems))
2977                 return servermap
2978             if should_succeed:
2979-                d1 = self._fn.download_version(servermap, ver)
2980+                d1 = self._fn.download_version(servermap, ver,
2981+                                               fetch_privkey)
2982                 d1.addCallback(lambda new_contents:
2983                                self.failUnlessEqual(new_contents, self.CONTENTS))
2984             else:
2985hunk ./src/allmydata/test/test_mutable.py 1175
2986                 d1 = self.shouldFail(NotEnoughSharesError,
2987                                      "_corrupt_all(offset=%s)" % (offset,),
2988                                      substring,
2989-                                     self._fn.download_version, servermap, ver)
2990+                                     self._fn.download_version, servermap,
2991+                                                                ver,
2992+                                                                fetch_privkey)
2993             if failure_checker:
2994                 d1.addCallback(failure_checker)
2995             d1.addCallback(lambda res: servermap)
2996hunk ./src/allmydata/test/test_mutable.py 1186
2997         return d
2998 
2999     def test_corrupt_all_verbyte(self):
3000-        # when the version byte is not 0, we hit an UnknownVersionError error
3001-        # in unpack_share().
3002+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
3003+        # error in unpack_share().
3004         d = self._test_corrupt_all(0, "UnknownVersionError")
3005         def _check_servermap(servermap):
3006             # and the dump should mention the problems
3007hunk ./src/allmydata/test/test_mutable.py 1193
3008             s = StringIO()
3009             dump = servermap.dump(s).getvalue()
3010-            self.failUnless("10 PROBLEMS" in dump, dump)
3011+            self.failUnless("30 PROBLEMS" in dump, dump)
3012         d.addCallback(_check_servermap)
3013         return d
3014 
3015hunk ./src/allmydata/test/test_mutable.py 1263
3016         return self._test_corrupt_all("enc_privkey", None, should_succeed=True)
3017 
3018 
3019+    def test_corrupt_all_encprivkey_late(self):
3020+        # this should work for the same reason as above, but we corrupt
3021+        # after the servermap update to exercise the error handling
3022+        # code.
3023+        # We need to remove the privkey from the node, or the retrieve
3024+        # process won't know to update it.
3025+        self._fn._privkey = None
3026+        return self._test_corrupt_all("enc_privkey",
3027+                                      None, # this shouldn't fail
3028+                                      should_succeed=True,
3029+                                      corrupt_early=False,
3030+                                      fetch_privkey=True)
3031+
3032+
3033     def test_corrupt_all_seqnum_late(self):
3034         # corrupting the seqnum between mapupdate and retrieve should result
3035         # in NotEnoughSharesError, since each share will look invalid
3036hunk ./src/allmydata/test/test_mutable.py 1283
3037         def _check(res):
3038             f = res[0]
3039             self.failUnless(f.check(NotEnoughSharesError))
3040-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
3041+            self.failUnless("uncoordinated write" in str(f))
3042         return self._test_corrupt_all(1, "ran out of peers",
3043                                       corrupt_early=False,
3044                                       failure_checker=_check)
3045hunk ./src/allmydata/test/test_mutable.py 1333
3046                       self.failUnlessEqual(new_contents, self.CONTENTS))
3047         return d
3048 
3049-    def test_corrupt_some(self):
3050-        # corrupt the data of first five shares (so the servermap thinks
3051-        # they're good but retrieve marks them as bad), so that the
3052-        # MODE_READ set of 6 will be insufficient, forcing node.download to
3053-        # retry with more servers.
3054-        corrupt(None, self._storage, "share_data", range(5))
3055-        d = self.make_servermap()
3056+
3057+    def _test_corrupt_some(self, offset, mdmf=False):
3058+        if mdmf:
3059+            d = self.publish_mdmf()
3060+        else:
3061+            d = defer.succeed(None)
3062+        d.addCallback(lambda ignored:
3063+            corrupt(None, self._storage, offset, range(5)))
3064+        d.addCallback(lambda ignored:
3065+            self.make_servermap())
3066         def _do_retrieve(servermap):
3067             ver = servermap.best_recoverable_version()
3068             self.failUnless(ver)
3069hunk ./src/allmydata/test/test_mutable.py 1349
3070             return self._fn.download_best_version()
3071         d.addCallback(_do_retrieve)
3072         d.addCallback(lambda new_contents:
3073-                      self.failUnlessEqual(new_contents, self.CONTENTS))
3074+            self.failUnlessEqual(new_contents, self.CONTENTS))
3075         return d
3076 
3077hunk ./src/allmydata/test/test_mutable.py 1352
3078+
3079+    def test_corrupt_some(self):
3080+        # corrupt the data of first five shares (so the servermap thinks
3081+        # they're good but retrieve marks them as bad), so that the
3082+        # MODE_READ set of 6 will be insufficient, forcing node.download to
3083+        # retry with more servers.
3084+        return self._test_corrupt_some("share_data")
3085+
3086+
3087     def test_download_fails(self):
3088         d = corrupt(None, self._storage, "signature")
3089         d.addCallback(lambda ignored:
3090hunk ./src/allmydata/test/test_mutable.py 1366
3091             self.shouldFail(UnrecoverableFileError, "test_download_anyway",
3092                             "no recoverable versions",
3093-                            self._fn.download_best_version)
3094+                            self._fn.download_best_version))
3095         return d
3096 
3097 
3098hunk ./src/allmydata/test/test_mutable.py 1370
3099+
3100+    def test_corrupt_mdmf_block_hash_tree(self):
3101+        d = self.publish_mdmf()
3102+        d.addCallback(lambda ignored:
3103+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3104+                                   "block hash tree failure",
3105+                                   corrupt_early=False,
3106+                                   should_succeed=False))
3107+        return d
3108+
3109+
3110+    def test_corrupt_mdmf_block_hash_tree_late(self):
3111+        d = self.publish_mdmf()
3112+        d.addCallback(lambda ignored:
3113+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3114+                                   "block hash tree failure",
3115+                                   corrupt_early=True,
3116+                                   should_succeed=False))
3117+        return d
3118+
3119+
3120+    def test_corrupt_mdmf_share_data(self):
3121+        d = self.publish_mdmf()
3122+        d.addCallback(lambda ignored:
3123+            # TODO: Find out what the block size is and corrupt a
3124+            # specific block, rather than just guessing.
3125+            self._test_corrupt_all(("share_data", 12 * 40),
3126+                                    "block hash tree failure",
3127+                                    corrupt_early=True,
3128+                                    should_succeed=False))
3129+        return d
3130+
3131+
3132+    def test_corrupt_some_mdmf(self):
3133+        return self._test_corrupt_some(("share_data", 12 * 40),
3134+                                       mdmf=True)
3135+
3136+
3137 class CheckerMixin:
3138     def check_good(self, r, where):
3139         self.failUnless(r.is_healthy(), where)
3140hunk ./src/allmydata/test/test_mutable.py 2116
3141             d.addCallback(lambda res:
3142                           self.shouldFail(NotEnoughSharesError,
3143                                           "test_retrieve_surprise",
3144-                                          "ran out of peers: have 0 shares (k=3)",
3145+                                          "ran out of peers: have 0 of 1",
3146                                           n.download_version,
3147                                           self.old_map,
3148                                           self.old_map.best_recoverable_version(),
3149hunk ./src/allmydata/test/test_mutable.py 2125
3150         d.addCallback(_created)
3151         return d
3152 
3153+
3154     def test_unexpected_shares(self):
3155         # upload the file, take a servermap, shut down one of the servers,
3156         # upload it again (causing shares to appear on a new server), then
3157hunk ./src/allmydata/test/test_mutable.py 2329
3158         self.basedir = "mutable/Problems/test_privkey_query_missing"
3159         self.set_up_grid(num_servers=20)
3160         nm = self.g.clients[0].nodemaker
3161-        LARGE = "These are Larger contents" * 2000 # about 50KB
3162+        LARGE = "These are Larger contents" * 2000 # about 50KiB
3163         nm._node_cache = DevNullDictionary() # disable the nodecache
3164 
3165         d = nm.create_mutable_file(LARGE)
3166hunk ./src/allmydata/test/test_mutable.py 2342
3167         d.addCallback(_created)
3168         d.addCallback(lambda res: self.n2.get_servermap(MODE_WRITE))
3169         return d
3170+
3171+
3172+    def test_block_and_hash_query_error(self):
3173+        # This tests for what happens when a query to a remote server
3174+        # fails in either the hash validation step or the block getting
3175+        # step (because of batching, this is the same actual query).
3176+        # We need to have the storage server persist up until the point
3177+        # that its prefix is validated, then suddenly die. This
3178+        # exercises some exception handling code in Retrieve.
3179+        self.basedir = "mutable/Problems/test_block_and_hash_query_error"
3180+        self.set_up_grid(num_servers=20)
3181+        nm = self.g.clients[0].nodemaker
3182+        CONTENTS = "contents" * 2000
3183+        d = nm.create_mutable_file(CONTENTS)
3184+        def _created(node):
3185+            self._node = node
3186+        d.addCallback(_created)
3187+        d.addCallback(lambda ignored:
3188+            self._node.get_servermap(MODE_READ))
3189+        def _then(servermap):
3190+            # we have our servermap. Now we set up the servers like the
3191+            # tests above -- the first one that gets a read call should
3192+            # start throwing errors, but only after returning its prefix
3193+            # for validation. Since we'll download without fetching the
3194+            # private key, the next query to the remote server will be
3195+            # for either a block and salt or for hashes, either of which
3196+            # will exercise the error handling code.
3197+            killer = FirstServerGetsKilled()
3198+            for (serverid, ss) in nm.storage_broker.get_all_servers():
3199+                ss.post_call_notifier = killer.notify
3200+            ver = servermap.best_recoverable_version()
3201+            assert ver
3202+            return self._node.download_version(servermap, ver)
3203+        d.addCallback(_then)
3204+        d.addCallback(lambda data:
3205+            self.failUnlessEqual(data, CONTENTS))
3206+        return d
3207}
3208[mutable/checker.py: check MDMF files
3209Kevan Carstensen <kevan@isnotajoke.com>**20100628225048
3210 Ignore-this: fb697b36285d60552df6ca5ac6a37629
3211 
3212 This patch adapts the mutable file checker and verifier to check and
3213 verify MDMF files. It does this by using the new segmented downloader,
3214 which is trained to perform verification operations on request. This
3215 removes some code duplication.
3216] {
3217hunk ./src/allmydata/mutable/checker.py 12
3218 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
3219 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
3220 from allmydata.mutable.layout import unpack_share, SIGNED_PREFIX_LENGTH
3221+from allmydata.mutable.retrieve import Retrieve # for verifying
3222 
3223 class MutableChecker:
3224 
3225hunk ./src/allmydata/mutable/checker.py 29
3226 
3227     def check(self, verify=False, add_lease=False):
3228         servermap = ServerMap()
3229+        # Updating the servermap in MODE_CHECK will stand a good chance
3230+        # of finding all of the shares, and getting a good idea of
3231+        # recoverability, etc, without verifying.
3232         u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
3233                              servermap, MODE_CHECK, add_lease=add_lease)
3234         if self._history:
3235hunk ./src/allmydata/mutable/checker.py 55
3236         if num_recoverable:
3237             self.best_version = servermap.best_recoverable_version()
3238 
3239+        # The file is unhealthy and needs to be repaired if:
3240+        # - There are unrecoverable versions.
3241         if servermap.unrecoverable_versions():
3242             self.need_repair = True
3243hunk ./src/allmydata/mutable/checker.py 59
3244+        # - There isn't a recoverable version.
3245         if num_recoverable != 1:
3246             self.need_repair = True
3247hunk ./src/allmydata/mutable/checker.py 62
3248+        # - The best recoverable version is missing some shares.
3249         if self.best_version:
3250             available_shares = servermap.shares_available()
3251             (num_distinct_shares, k, N) = available_shares[self.best_version]
3252hunk ./src/allmydata/mutable/checker.py 73
3253 
3254     def _verify_all_shares(self, servermap):
3255         # read every byte of each share
3256+        #
3257+        # This logic is going to be very nearly the same as the
3258+        # downloader. I bet we could pass the downloader a flag that
3259+        # makes it do this, and piggyback onto that instead of
3260+        # duplicating a bunch of code.
3261+        #
3262+        # Like:
3263+        #  r = Retrieve(blah, blah, blah, verify=True)
3264+        #  d = r.download()
3265+        #  (wait, wait, wait, d.callback)
3266+        # 
3267+        #  Then, when it has finished, we can check the servermap (which
3268+        #  we provided to Retrieve) to figure out which shares are bad,
3269+        #  since the Retrieve process will have updated the servermap as
3270+        #  it went along.
3271+        #
3272+        #  By passing the verify=True flag to the constructor, we are
3273+        #  telling the downloader a few things.
3274+        #
3275+        #  1. It needs to download all N shares, not just K shares.
3276+        #  2. It doesn't need to decrypt or decode the shares, only
3277+        #     verify them.
3278         if not self.best_version:
3279             return
3280hunk ./src/allmydata/mutable/checker.py 97
3281-        versionmap = servermap.make_versionmap()
3282-        shares = versionmap[self.best_version]
3283-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3284-         offsets_tuple) = self.best_version
3285-        offsets = dict(offsets_tuple)
3286-        readv = [ (0, offsets["EOF"]) ]
3287-        dl = []
3288-        for (shnum, peerid, timestamp) in shares:
3289-            ss = servermap.connections[peerid]
3290-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
3291-            d.addCallback(self._got_answer, peerid, servermap)
3292-            dl.append(d)
3293-        return defer.DeferredList(dl, fireOnOneErrback=True, consumeErrors=True)
3294 
3295hunk ./src/allmydata/mutable/checker.py 98
3296-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
3297-        # isolate the callRemote to a separate method, so tests can subclass
3298-        # Publish and override it
3299-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
3300+        r = Retrieve(self._node, servermap, self.best_version, verify=True)
3301+        d = r.download()
3302+        d.addCallback(self._process_bad_shares)
3303         return d
3304 
3305hunk ./src/allmydata/mutable/checker.py 103
3306-    def _got_answer(self, datavs, peerid, servermap):
3307-        for shnum,datav in datavs.items():
3308-            data = datav[0]
3309-            try:
3310-                self._got_results_one_share(shnum, peerid, data)
3311-            except CorruptShareError:
3312-                f = failure.Failure()
3313-                self.need_repair = True
3314-                self.bad_shares.append( (peerid, shnum, f) )
3315-                prefix = data[:SIGNED_PREFIX_LENGTH]
3316-                servermap.mark_bad_share(peerid, shnum, prefix)
3317-                ss = servermap.connections[peerid]
3318-                self.notify_server_corruption(ss, shnum, str(f.value))
3319-
3320-    def check_prefix(self, peerid, shnum, data):
3321-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3322-         offsets_tuple) = self.best_version
3323-        got_prefix = data[:SIGNED_PREFIX_LENGTH]
3324-        if got_prefix != prefix:
3325-            raise CorruptShareError(peerid, shnum,
3326-                                    "prefix mismatch: share changed while we were reading it")
3327-
3328-    def _got_results_one_share(self, shnum, peerid, data):
3329-        self.check_prefix(peerid, shnum, data)
3330-
3331-        # the [seqnum:signature] pieces are validated by _compare_prefix,
3332-        # which checks their signature against the pubkey known to be
3333-        # associated with this file.
3334 
3335hunk ./src/allmydata/mutable/checker.py 104
3336-        (seqnum, root_hash, IV, k, N, segsize, datalen, pubkey, signature,
3337-         share_hash_chain, block_hash_tree, share_data,
3338-         enc_privkey) = unpack_share(data)
3339-
3340-        # validate [share_hash_chain,block_hash_tree,share_data]
3341-
3342-        leaves = [hashutil.block_hash(share_data)]
3343-        t = hashtree.HashTree(leaves)
3344-        if list(t) != block_hash_tree:
3345-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
3346-        share_hash_leaf = t[0]
3347-        t2 = hashtree.IncompleteHashTree(N)
3348-        # root_hash was checked by the signature
3349-        t2.set_hashes({0: root_hash})
3350-        try:
3351-            t2.set_hashes(hashes=share_hash_chain,
3352-                          leaves={shnum: share_hash_leaf})
3353-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
3354-                IndexError), e:
3355-            msg = "corrupt hashes: %s" % (e,)
3356-            raise CorruptShareError(peerid, shnum, msg)
3357-
3358-        # validate enc_privkey: only possible if we have a write-cap
3359-        if not self._node.is_readonly():
3360-            alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3361-            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3362-            if alleged_writekey != self._node.get_writekey():
3363-                raise CorruptShareError(peerid, shnum, "invalid privkey")
3364+    def _process_bad_shares(self, bad_shares):
3365+        if bad_shares:
3366+            self.need_repair = True
3367+        self.bad_shares = bad_shares
3368 
3369hunk ./src/allmydata/mutable/checker.py 109
3370-    def notify_server_corruption(self, ss, shnum, reason):
3371-        ss.callRemoteOnly("advise_corrupt_share",
3372-                          "mutable", self._storage_index, shnum, reason)
3373 
3374     def _count_shares(self, smap, version):
3375         available_shares = smap.shares_available()
3376hunk ./src/allmydata/test/test_mutable.py 193
3377                 if offset1 == "pubkey" and IV:
3378                     real_offset = 107
3379                 elif offset1 == "share_data" and not IV:
3380-                    real_offset = 104
3381+                    real_offset = 107
3382                 elif offset1 in o:
3383                     real_offset = o[offset1]
3384                 else:
3385hunk ./src/allmydata/test/test_mutable.py 395
3386             return d
3387         d.addCallback(_created)
3388         return d
3389+    test_create_mdmf_with_initial_contents.timeout = 20
3390 
3391 
3392     def test_create_with_initial_contents_function(self):
3393hunk ./src/allmydata/test/test_mutable.py 700
3394                                            k, N, segsize, datalen)
3395                 self.failUnless(p._pubkey.verify(sig_material, signature))
3396                 #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
3397-                self.failUnless(isinstance(share_hash_chain, dict))
3398-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3399+                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3400                 for shnum,share_hash in share_hash_chain.items():
3401                     self.failUnless(isinstance(shnum, int))
3402                     self.failUnless(isinstance(share_hash, str))
3403hunk ./src/allmydata/test/test_mutable.py 820
3404                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
3405 
3406 
3407+
3408+
3409 class Servermap(unittest.TestCase, PublishMixin):
3410     def setUp(self):
3411         return self.publish_one()
3412hunk ./src/allmydata/test/test_mutable.py 951
3413         self._storage._peers = {} # delete all shares
3414         ms = self.make_servermap
3415         d = defer.succeed(None)
3416-
3417+#
3418         d.addCallback(lambda res: ms(mode=MODE_CHECK))
3419         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
3420 
3421hunk ./src/allmydata/test/test_mutable.py 1440
3422         d.addCallback(self.check_good, "test_check_good")
3423         return d
3424 
3425+    def test_check_mdmf_good(self):
3426+        d = self.publish_mdmf()
3427+        d.addCallback(lambda ignored:
3428+            self._fn.check(Monitor()))
3429+        d.addCallback(self.check_good, "test_check_mdmf_good")
3430+        return d
3431+
3432     def test_check_no_shares(self):
3433         for shares in self._storage._peers.values():
3434             shares.clear()
3435hunk ./src/allmydata/test/test_mutable.py 1454
3436         d.addCallback(self.check_bad, "test_check_no_shares")
3437         return d
3438 
3439+    def test_check_mdmf_no_shares(self):
3440+        d = self.publish_mdmf()
3441+        def _then(ignored):
3442+            for share in self._storage._peers.values():
3443+                share.clear()
3444+        d.addCallback(_then)
3445+        d.addCallback(lambda ignored:
3446+            self._fn.check(Monitor()))
3447+        d.addCallback(self.check_bad, "test_check_mdmf_no_shares")
3448+        return d
3449+
3450     def test_check_not_enough_shares(self):
3451         for shares in self._storage._peers.values():
3452             for shnum in shares.keys():
3453hunk ./src/allmydata/test/test_mutable.py 1474
3454         d.addCallback(self.check_bad, "test_check_not_enough_shares")
3455         return d
3456 
3457+    def test_check_mdmf_not_enough_shares(self):
3458+        d = self.publish_mdmf()
3459+        def _then(ignored):
3460+            for shares in self._storage._peers.values():
3461+                for shnum in shares.keys():
3462+                    if shnum > 0:
3463+                        del shares[shnum]
3464+        d.addCallback(_then)
3465+        d.addCallback(lambda ignored:
3466+            self._fn.check(Monitor()))
3467+        d.addCallback(self.check_bad, "test_check_mdmf_not_enougH_shares")
3468+        return d
3469+
3470+
3471     def test_check_all_bad_sig(self):
3472         d = corrupt(None, self._storage, 1) # bad sig
3473         d.addCallback(lambda ignored:
3474hunk ./src/allmydata/test/test_mutable.py 1495
3475         d.addCallback(self.check_bad, "test_check_all_bad_sig")
3476         return d
3477 
3478+    def test_check_mdmf_all_bad_sig(self):
3479+        d = self.publish_mdmf()
3480+        d.addCallback(lambda ignored:
3481+            corrupt(None, self._storage, 1))
3482+        d.addCallback(lambda ignored:
3483+            self._fn.check(Monitor()))
3484+        d.addCallback(self.check_bad, "test_check_mdmf_all_bad_sig")
3485+        return d
3486+
3487     def test_check_all_bad_blocks(self):
3488         d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
3489         # the Checker won't notice this.. it doesn't look at actual data
3490hunk ./src/allmydata/test/test_mutable.py 1512
3491         d.addCallback(self.check_good, "test_check_all_bad_blocks")
3492         return d
3493 
3494+
3495+    def test_check_mdmf_all_bad_blocks(self):
3496+        d = self.publish_mdmf()
3497+        d.addCallback(lambda ignored:
3498+            corrupt(None, self._storage, "share_data"))
3499+        d.addCallback(lambda ignored:
3500+            self._fn.check(Monitor()))
3501+        d.addCallback(self.check_good, "test_check_mdmf_all_bad_blocks")
3502+        return d
3503+
3504     def test_verify_good(self):
3505         d = self._fn.check(Monitor(), verify=True)
3506         d.addCallback(self.check_good, "test_verify_good")
3507hunk ./src/allmydata/test/test_mutable.py 1582
3508                       "test_verify_one_bad_encprivkey_uncheckable")
3509         return d
3510 
3511+
3512+    def test_verify_mdmf_good(self):
3513+        d = self.publish_mdmf()
3514+        d.addCallback(lambda ignored:
3515+            self._fn.check(Monitor(), verify=True))
3516+        d.addCallback(self.check_good, "test_verify_mdmf_good")
3517+        return d
3518+
3519+
3520+    def test_verify_mdmf_one_bad_block(self):
3521+        d = self.publish_mdmf()
3522+        d.addCallback(lambda ignored:
3523+            corrupt(None, self._storage, "share_data", [1]))
3524+        d.addCallback(lambda ignored:
3525+            self._fn.check(Monitor(), verify=True))
3526+        # We should find one bad block here
3527+        d.addCallback(self.check_bad, "test_verify_mdmf_one_bad_block")
3528+        d.addCallback(self.check_expected_failure,
3529+                      CorruptShareError, "block hash tree failure",
3530+                      "test_verify_mdmf_one_bad_block")
3531+        return d
3532+
3533+
3534+    def test_verify_mdmf_bad_encprivkey(self):
3535+        d = self.publish_mdmf()
3536+        d.addCallback(lambda ignored:
3537+            corrupt(None, self._storage, "enc_privkey", [1]))
3538+        d.addCallback(lambda ignored:
3539+            self._fn.check(Monitor(), verify=True))
3540+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
3541+        d.addCallback(self.check_expected_failure,
3542+                      CorruptShareError, "privkey",
3543+                      "test_verify_mdmf_bad_encprivkey")
3544+        return d
3545+
3546+
3547+    def test_verify_mdmf_bad_sig(self):
3548+        d = self.publish_mdmf()
3549+        d.addCallback(lambda ignored:
3550+            corrupt(None, self._storage, 1, [1]))
3551+        d.addCallback(lambda ignored:
3552+            self._fn.check(Monitor(), verify=True))
3553+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_sig")
3554+        return d
3555+
3556+
3557+    def test_verify_mdmf_bad_encprivkey_uncheckable(self):
3558+        d = self.publish_mdmf()
3559+        d.addCallback(lambda ignored:
3560+            corrupt(None, self._storage, "enc_privkey", [1]))
3561+        d.addCallback(lambda ignored:
3562+            self._fn.get_readonly())
3563+        d.addCallback(lambda fn:
3564+            fn.check(Monitor(), verify=True))
3565+        d.addCallback(self.check_good,
3566+                      "test_verify_mdmf_bad_encprivkey_uncheckable")
3567+        return d
3568+
3569+
3570 class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
3571 
3572     def get_shares(self, s):
3573hunk ./src/allmydata/test/test_mutable.py 1706
3574         current_shares = self.old_shares[-1]
3575         self.failUnlessEqual(old_shares, current_shares)
3576 
3577+
3578     def test_unrepairable_0shares(self):
3579         d = self.publish_one()
3580         def _delete_all_shares(ign):
3581hunk ./src/allmydata/test/test_mutable.py 1721
3582         d.addCallback(_check)
3583         return d
3584 
3585+    def test_mdmf_unrepairable_0shares(self):
3586+        d = self.publish_mdmf()
3587+        def _delete_all_shares(ign):
3588+            shares = self._storage._peers
3589+            for peerid in shares:
3590+                shares[peerid] = {}
3591+        d.addCallback(_delete_all_shares)
3592+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3593+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3594+        d.addCallback(lambda crr: self.failIf(crr.get_successful()))
3595+        return d
3596+
3597+
3598     def test_unrepairable_1share(self):
3599         d = self.publish_one()
3600         def _delete_all_shares(ign):
3601hunk ./src/allmydata/test/test_mutable.py 1750
3602         d.addCallback(_check)
3603         return d
3604 
3605+    def test_mdmf_unrepairable_1share(self):
3606+        d = self.publish_mdmf()
3607+        def _delete_all_shares(ign):
3608+            shares = self._storage._peers
3609+            for peerid in shares:
3610+                for shnum in list(shares[peerid]):
3611+                    if shnum > 0:
3612+                        del shares[peerid][shnum]
3613+        d.addCallback(_delete_all_shares)
3614+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3615+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3616+        def _check(crr):
3617+            self.failUnlessEqual(crr.get_successful(), False)
3618+        d.addCallback(_check)
3619+        return d
3620+
3621+    def test_repairable_5shares(self):
3622+        d = self.publish_mdmf()
3623+        def _delete_all_shares(ign):
3624+            shares = self._storage._peers
3625+            for peerid in shares:
3626+                for shnum in list(shares[peerid]):
3627+                    if shnum > 4:
3628+                        del shares[peerid][shnum]
3629+        d.addCallback(_delete_all_shares)
3630+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3631+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3632+        def _check(crr):
3633+            self.failUnlessEqual(crr.get_successful(), True)
3634+        d.addCallback(_check)
3635+        return d
3636+
3637+    def test_mdmf_repairable_5shares(self):
3638+        d = self.publish_mdmf()
3639+        def _delete_all_shares(ign):
3640+            shares = self._storage._peers
3641+            for peerid in shares:
3642+                for shnum in list(shares[peerid]):
3643+                    if shnum > 5:
3644+                        del shares[peerid][shnum]
3645+        d.addCallback(_delete_all_shares)
3646+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3647+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3648+        def _check(crr):
3649+            self.failUnlessEqual(crr.get_successful(), True)
3650+        d.addCallback(_check)
3651+        return d
3652+
3653+
3654     def test_merge(self):
3655         self.old_shares = []
3656         d = self.publish_multiple()
3657}
3658[mutable/retrieve.py: learn how to verify mutable files
3659Kevan Carstensen <kevan@isnotajoke.com>**20100628225201
3660 Ignore-this: 989af7800c47589620918461ec989483
3661] {
3662hunk ./src/allmydata/mutable/retrieve.py 86
3663     # Retrieve object will remain tied to a specific version of the file, and
3664     # will use a single ServerMap instance.
3665 
3666-    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False):
3667+    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
3668+                 verify=False):
3669         self._node = filenode
3670         assert self._node.get_pubkey()
3671         self._storage_index = filenode.get_storage_index()
3672hunk ./src/allmydata/mutable/retrieve.py 106
3673         # during repair, we may be called upon to grab the private key, since
3674         # it wasn't picked up during a verify=False checker run, and we'll
3675         # need it for repair to generate a new version.
3676-        self._need_privkey = fetch_privkey
3677-        if self._node.get_privkey():
3678+        self._need_privkey = fetch_privkey or verify
3679+        if self._node.get_privkey() and not verify:
3680             self._need_privkey = False
3681 
3682         if self._need_privkey:
3683hunk ./src/allmydata/mutable/retrieve.py 117
3684             self._privkey_query_markers = [] # one Marker for each time we've
3685                                              # tried to get the privkey.
3686 
3687+        # verify means that we are using the downloader logic to verify all
3688+        # of our shares. This tells the downloader a few things.
3689+        #
3690+        # 1. We need to download all of the shares.
3691+        # 2. We don't need to decode or decrypt the shares, since our
3692+        #    caller doesn't care about the plaintext, only the
3693+        #    information about which shares are or are not valid.
3694+        # 3. When we are validating readers, we need to validate the
3695+        #    signature on the prefix. Do we? We already do this in the
3696+        #    servermap update?
3697+        #
3698+        # (just work on 1 and 2 for now, I guess)
3699+        self._verify = False
3700+        if verify:
3701+            self._verify = True
3702+
3703         self._status = RetrieveStatus()
3704         self._status.set_storage_index(self._storage_index)
3705         self._status.set_helper(False)
3706hunk ./src/allmydata/mutable/retrieve.py 323
3707 
3708         # We need at least self._required_shares readers to download a
3709         # segment.
3710-        needed = self._required_shares - len(self._active_readers)
3711+        if self._verify:
3712+            needed = self._total_shares
3713+        else:
3714+            needed = self._required_shares - len(self._active_readers)
3715         # XXX: Why don't format= log messages work here?
3716         self.log("adding %d peers to the active peers list" % needed)
3717 
3718hunk ./src/allmydata/mutable/retrieve.py 339
3719         # will cause problems later.
3720         active_shnums -= set([reader.shnum for reader in self._active_readers])
3721         active_shnums = list(active_shnums)[:needed]
3722-        if len(active_shnums) < needed:
3723+        if len(active_shnums) < needed and not self._verify:
3724             # We don't have enough readers to retrieve the file; fail.
3725             return self._failed()
3726 
3727hunk ./src/allmydata/mutable/retrieve.py 346
3728         for shnum in active_shnums:
3729             self._active_readers.append(self.readers[shnum])
3730             self.log("added reader for share %d" % shnum)
3731-        assert len(self._active_readers) == self._required_shares
3732+        assert len(self._active_readers) >= self._required_shares
3733         # Conceptually, this is part of the _add_active_peers step. It
3734         # validates the prefixes of newly added readers to make sure
3735         # that they match what we are expecting for self.verinfo. If
3736hunk ./src/allmydata/mutable/retrieve.py 416
3737                     # that we haven't gotten it at the end of
3738                     # segment decoding, then we'll take more drastic
3739                     # measures.
3740-                    if self._need_privkey:
3741+                    if self._need_privkey and not self._node.is_readonly():
3742                         d = reader.get_encprivkey()
3743                         d.addCallback(self._try_to_validate_privkey, reader)
3744             if bad_readers:
3745hunk ./src/allmydata/mutable/retrieve.py 423
3746                 # We do them all at once, or else we screw up list indexing.
3747                 for (reader, f) in bad_readers:
3748                     self._mark_bad_share(reader, f)
3749-                return self._add_active_peers()
3750+                if self._verify:
3751+                    if len(self._active_readers) >= self._required_shares:
3752+                        return self._download_current_segment()
3753+                    else:
3754+                        return self._failed()
3755+                else:
3756+                    return self._add_active_peers()
3757             else:
3758                 return self._download_current_segment()
3759             # The next step will assert that it has enough active
3760hunk ./src/allmydata/mutable/retrieve.py 518
3761         """
3762         self.log("marking share %d on server %s as bad" % \
3763                  (reader.shnum, reader))
3764+        prefix = self.verinfo[-2]
3765+        self.servermap.mark_bad_share(reader.peerid,
3766+                                      reader.shnum,
3767+                                      prefix)
3768         self._remove_reader(reader)
3769hunk ./src/allmydata/mutable/retrieve.py 523
3770-        self._bad_shares.add((reader.peerid, reader.shnum))
3771+        self._bad_shares.add((reader.peerid, reader.shnum, f))
3772         self._status.problems[reader.peerid] = f
3773         self._last_failure = f
3774         self.notify_server_corruption(reader.peerid, reader.shnum,
3775hunk ./src/allmydata/mutable/retrieve.py 571
3776             ds.append(dl)
3777             reader.flush()
3778         dl = defer.DeferredList(ds)
3779-        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3780+        if self._verify:
3781+            dl.addCallback(lambda ignored: "")
3782+            dl.addCallback(self._set_segment)
3783+        else:
3784+            dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3785         return dl
3786 
3787 
3788hunk ./src/allmydata/mutable/retrieve.py 701
3789         # shnum, which will be a leaf in the share hash tree, which
3790         # will allow us to validate the rest of the tree.
3791         if self.share_hash_tree.needed_hashes(reader.shnum,
3792-                                               include_leaf=True):
3793+                                              include_leaf=True) or \
3794+                                              self._verify:
3795             try:
3796                 self.share_hash_tree.set_hashes(hashes=sharehashes[1],
3797                                             leaves={reader.shnum: bht[0]})
3798hunk ./src/allmydata/mutable/retrieve.py 832
3799 
3800 
3801     def _try_to_validate_privkey(self, enc_privkey, reader):
3802-
3803         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3804         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3805         if alleged_writekey != self._node.get_writekey():
3806hunk ./src/allmydata/mutable/retrieve.py 838
3807             self.log("invalid privkey from %s shnum %d" %
3808                      (reader, reader.shnum),
3809                      level=log.WEIRD, umid="YIw4tA")
3810+            if self._verify:
3811+                self.servermap.mark_bad_share(reader.peerid, reader.shnum,
3812+                                              self.verinfo[-2])
3813+                e = CorruptShareError(reader.peerid,
3814+                                      reader.shnum,
3815+                                      "invalid privkey")
3816+                f = failure.Failure(e)
3817+                self._bad_shares.add((reader.peerid, reader.shnum, f))
3818             return
3819 
3820         # it's good
3821hunk ./src/allmydata/mutable/retrieve.py 904
3822         statements, I return the decrypted contents to the owner of this
3823         Retrieve object through self._done_deferred.
3824         """
3825-        eventually(self._done_deferred.callback, self._plaintext)
3826+        if self._verify:
3827+            ret = list(self._bad_shares)
3828+            self.log("done verifying, found %d bad shares" % len(ret))
3829+        else:
3830+            ret = self._plaintext
3831+        eventually(self._done_deferred.callback, ret)
3832 
3833 
3834     def _failed(self):
3835hunk ./src/allmydata/mutable/retrieve.py 920
3836         to the caller of this Retrieve object through
3837         self._done_deferred.
3838         """
3839-        format = ("ran out of peers: "
3840-                  "have %(have)d of %(total)d segments "
3841-                  "found %(bad)d bad shares "
3842-                  "encoding %(k)d-of-%(n)d")
3843-        args = {"have": self._current_segment,
3844-                "total": self._num_segments,
3845-                "k": self._required_shares,
3846-                "n": self._total_shares,
3847-                "bad": len(self._bad_shares)}
3848-        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
3849-                                                        str(self._last_failure)))
3850-        f = failure.Failure(e)
3851-        eventually(self._done_deferred.callback, f)
3852+        if self._verify:
3853+            ret = list(self._bad_shares)
3854+        else:
3855+            format = ("ran out of peers: "
3856+                      "have %(have)d of %(total)d segments "
3857+                      "found %(bad)d bad shares "
3858+                      "encoding %(k)d-of-%(n)d")
3859+            args = {"have": self._current_segment,
3860+                    "total": self._num_segments,
3861+                    "k": self._required_shares,
3862+                    "n": self._total_shares,
3863+                    "bad": len(self._bad_shares)}
3864+            e = NotEnoughSharesError("%s, last failure: %s" % \
3865+                                     (format % args, str(self._last_failure)))
3866+            f = failure.Failure(e)
3867+            ret = f
3868+        eventually(self._done_deferred.callback, ret)
3869}
3870[interfaces.py: add IMutableSlotWriter
3871Kevan Carstensen <kevan@isnotajoke.com>**20100630183305
3872 Ignore-this: ff9dca96ef1a009ae85485682f81ea5
3873] hunk ./src/allmydata/interfaces.py 418
3874         """
3875 
3876 
3877+class IMutableSlotWriter(Interface):
3878+    """
3879+    The interface for a writer around a mutable slot on a remote server.
3880+    """
3881+    def set_checkstring(checkstring, *args):
3882+        """
3883+        Set the checkstring that I will pass to the remote server when
3884+        writing.
3885+
3886+            @param checkstring A packed checkstring to use.
3887+
3888+        Note that implementations can differ in which semantics they
3889+        wish to support for set_checkstring -- they can, for example,
3890+        build the checkstring themselves from its constituents, or
3891+        some other thing.
3892+        """
3893+
3894+    def get_checkstring():
3895+        """
3896+        Get the checkstring that I think currently exists on the remote
3897+        server.
3898+        """
3899+
3900+    def put_block(data, segnum, salt):
3901+        """
3902+        Add a block and salt to the share.
3903+        """
3904+
3905+    def put_encprivey(encprivkey):
3906+        """
3907+        Add the encrypted private key to the share.
3908+        """
3909+
3910+    def put_blockhashes(blockhashes=list):
3911+        """
3912+        Add the block hash tree to the share.
3913+        """
3914+
3915+    def put_sharehashes(sharehashes=dict):
3916+        """
3917+        Add the share hash chain to the share.
3918+        """
3919+
3920+    def get_signable():
3921+        """
3922+        Return the part of the share that needs to be signed.
3923+        """
3924+
3925+    def put_signature(signature):
3926+        """
3927+        Add the signature to the share.
3928+        """
3929+
3930+    def put_verification_key(verification_key):
3931+        """
3932+        Add the verification key to the share.
3933+        """
3934+
3935+    def finish_publishing():
3936+        """
3937+        Do anything necessary to finish writing the share to a remote
3938+        server. I require that no further publishing needs to take place
3939+        after this method has been called.
3940+        """
3941+
3942+
3943 class IURI(Interface):
3944     def init_from_string(uri):
3945         """Accept a string (as created by my to_string() method) and populate
3946[test/test_mutable.py: temporarily disable two tests that are now irrelevant
3947Kevan Carstensen <kevan@isnotajoke.com>**20100701232806
3948 Ignore-this: 701e143567f3954812ca6960af1d6ac7
3949] {
3950hunk ./src/allmydata/test/test_mutable.py 651
3951             self.failUnlessEqual(len(share_ids), 10)
3952         d.addCallback(_done)
3953         return d
3954+    test_encrypt.todo = "Write an equivalent of this for the new uploader"
3955 
3956     def test_generate(self):
3957         nm = make_nodemaker()
3958hunk ./src/allmydata/test/test_mutable.py 713
3959                 self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
3960         d.addCallback(_generated)
3961         return d
3962+    test_generate.todo = "Write an equivalent of this for the new uploader"
3963 
3964     # TODO: when we publish to 20 peers, we should get one share per peer on 10
3965     # when we publish to 3 peers, we should get either 3 or 4 shares per peer
3966}
3967[Add MDMF reader and writer, and SDMF writer
3968Kevan Carstensen <kevan@isnotajoke.com>**20100702225531
3969 Ignore-this: bf6276a91d27dcb4e779b0eb82ea1843
3970 
3971 The MDMF/SDMF reader MDMF writer, and SDMF writer are similar to the
3972 object proxies that exist for immutable files. They abstract away
3973 details of connection, state, and caching from their callers (in this
3974 case, the download, servermap updater, and uploader), and expose methods
3975 to get and set information on the remote server.
3976 
3977 MDMFSlotReadProxy reads a mutable file from the server, doing the right
3978 thing (in most cases) regardless of whether the file is MDMF or SDMF. It
3979 allows callers to tell it how to batch and flush reads.
3980 
3981 MDMFSlotWriteProxy writes an MDMF mutable file to a server.
3982 
3983 SDMFSlotWriteProxy writes an SDMF mutable file to a server.
3984 
3985 This patch also includes tests for MDMFSlotReadProxy,
3986 SDMFSlotWriteProxy, and MDMFSlotWriteProxy.
3987] {
3988hunk ./src/allmydata/mutable/layout.py 4
3989 
3990 import struct
3991 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
3992+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
3993+                                 MDMF_VERSION, IMutableSlotWriter
3994+from allmydata.util import mathutil, observer
3995+from twisted.python import failure
3996+from twisted.internet import defer
3997+from zope.interface import implements
3998+
3999+
4000+# These strings describe the format of the packed structs they help process
4001+# Here's what they mean:
4002+#
4003+#  PREFIX:
4004+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4005+#    B: The version information; an 8 bit version identifier. Stored as
4006+#       an unsigned char. This is currently 00 00 00 00; our modifications
4007+#       will turn it into 00 00 00 01.
4008+#    Q: The sequence number; this is sort of like a revision history for
4009+#       mutable files; they start at 1 and increase as they are changed after
4010+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4011+#       length.
4012+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4013+#       characters = 32 bytes to store the value.
4014+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4015+#       16 characters.
4016+#
4017+#  SIGNED_PREFIX additions, things that are covered by the signature:
4018+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4019+#       which is convenient because our erasure coding scheme cannot
4020+#       encode if you ask for more than 255 pieces.
4021+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4022+#       same reasons as above.
4023+#    Q: The segment size of the uploaded file. This will essentially be the
4024+#       length of the file in SDMF. An unsigned long long, so we can store
4025+#       files of quite large size.
4026+#    Q: The data length of the uploaded file. Modulo padding, this will be
4027+#       the same of the data length field. Like the data length field, it is
4028+#       an unsigned long long and can be quite large.
4029+#
4030+#   HEADER additions:
4031+#     L: The offset of the signature of this. An unsigned long.
4032+#     L: The offset of the share hash chain. An unsigned long.
4033+#     L: The offset of the block hash tree. An unsigned long.
4034+#     L: The offset of the share data. An unsigned long.
4035+#     Q: The offset of the encrypted private key. An unsigned long long, to
4036+#        account for the possibility of a lot of share data.
4037+#     Q: The offset of the EOF. An unsigned long long, to account for the
4038+#        possibility of a lot of share data.
4039+#
4040+#  After all of these, we have the following:
4041+#    - The verification key: Occupies the space between the end of the header
4042+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4043+#    - The signature, which goes from the signature offset to the share hash
4044+#      chain offset.
4045+#    - The share hash chain, which goes from the share hash chain offset to
4046+#      the block hash tree offset.
4047+#    - The share data, which goes from the share data offset to the encrypted
4048+#      private key offset.
4049+#    - The encrypted private key offset, which goes until the end of the file.
4050+#
4051+#  The block hash tree in this encoding has only one share, so the offset of
4052+#  the share data will be 32 bits more than the offset of the block hash tree.
4053+#  Given this, we may need to check to see how many bytes a reasonably sized
4054+#  block hash tree will take up.
4055 
4056 PREFIX = ">BQ32s16s" # each version has a different prefix
4057 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4058hunk ./src/allmydata/mutable/layout.py 73
4059 SIGNED_PREFIX_LENGTH = struct.calcsize(SIGNED_PREFIX)
4060 HEADER = ">BQ32s16s BBQQ LLLLQQ" # includes offsets
4061 HEADER_LENGTH = struct.calcsize(HEADER)
4062+OFFSETS = ">LLLLQQ"
4063+OFFSETS_LENGTH = struct.calcsize(OFFSETS)
4064 
4065 def unpack_header(data):
4066     o = {}
4067hunk ./src/allmydata/mutable/layout.py 194
4068     return (share_hash_chain, block_hash_tree, share_data)
4069 
4070 
4071-def pack_checkstring(seqnum, root_hash, IV):
4072+def pack_checkstring(seqnum, root_hash, IV, version=0):
4073     return struct.pack(PREFIX,
4074hunk ./src/allmydata/mutable/layout.py 196
4075-                       0, # version,
4076+                       version,
4077                        seqnum,
4078                        root_hash,
4079                        IV)
4080hunk ./src/allmydata/mutable/layout.py 269
4081                            encprivkey])
4082     return final_share
4083 
4084+def pack_prefix(seqnum, root_hash, IV,
4085+                required_shares, total_shares,
4086+                segment_size, data_length):
4087+    prefix = struct.pack(SIGNED_PREFIX,
4088+                         0, # version,
4089+                         seqnum,
4090+                         root_hash,
4091+                         IV,
4092+                         required_shares,
4093+                         total_shares,
4094+                         segment_size,
4095+                         data_length,
4096+                         )
4097+    return prefix
4098+
4099+
4100+class SDMFSlotWriteProxy:
4101+    implements(IMutableSlotWriter)
4102+    """
4103+    I represent a remote write slot for an SDMF mutable file. I build a
4104+    share in memory, and then write it in one piece to the remote
4105+    server. This mimics how SDMF shares were built before MDMF (and the
4106+    new MDMF uploader), but provides that functionality in a way that
4107+    allows the MDMF uploader to be built without much special-casing for
4108+    file format, which makes the uploader code more readable.
4109+    """
4110+    def __init__(self,
4111+                 shnum,
4112+                 rref, # a remote reference to a storage server
4113+                 storage_index,
4114+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4115+                 seqnum, # the sequence number of the mutable file
4116+                 required_shares,
4117+                 total_shares,
4118+                 segment_size,
4119+                 data_length): # the length of the original file
4120+        self.shnum = shnum
4121+        self._rref = rref
4122+        self._storage_index = storage_index
4123+        self._secrets = secrets
4124+        self._seqnum = seqnum
4125+        self._required_shares = required_shares
4126+        self._total_shares = total_shares
4127+        self._segment_size = segment_size
4128+        self._data_length = data_length
4129+
4130+        # This is an SDMF file, so it should have only one segment, so,
4131+        # modulo padding of the data length, the segment size and the
4132+        # data length should be the same.
4133+        expected_segment_size = mathutil.next_multiple(data_length,
4134+                                                       self._required_shares)
4135+        assert expected_segment_size == segment_size
4136+
4137+        self._block_size = self._segment_size / self._required_shares
4138+
4139+        # This is meant to mimic how SDMF files were built before MDMF
4140+        # entered the picture: we generate each share in its entirety,
4141+        # then push it off to the storage server in one write. When
4142+        # callers call set_*, they are just populating this dict.
4143+        # finish_publishing will stitch these pieces together into a
4144+        # coherent share, and then write the coherent share to the
4145+        # storage server.
4146+        self._share_pieces = {}
4147+
4148+        # This tells the write logic what checkstring to use when
4149+        # writing remote shares.
4150+        self._testvs = []
4151+
4152+        self._readvs = [(0, struct.calcsize(PREFIX))]
4153+
4154+
4155+    def set_checkstring(self, checkstring_or_seqnum,
4156+                              root_hash=None,
4157+                              salt=None):
4158+        """
4159+        Set the checkstring that I will pass to the remote server when
4160+        writing.
4161+
4162+            @param checkstring_or_seqnum: A packed checkstring to use,
4163+                   or a sequence number. I will treat this as a checkstr
4164+
4165+        Note that implementations can differ in which semantics they
4166+        wish to support for set_checkstring -- they can, for example,
4167+        build the checkstring themselves from its constituents, or
4168+        some other thing.
4169+        """
4170+        if root_hash and salt:
4171+            checkstring = struct.pack(PREFIX,
4172+                                      0,
4173+                                      checkstring_or_seqnum,
4174+                                      root_hash,
4175+                                      salt)
4176+        else:
4177+            checkstring = checkstring_or_seqnum
4178+        self._testvs = [(0, len(checkstring), "eq", checkstring)]
4179+
4180+
4181+    def get_checkstring(self):
4182+        """
4183+        Get the checkstring that I think currently exists on the remote
4184+        server.
4185+        """
4186+        if self._testvs:
4187+            return self._testvs[0][3]
4188+        return ""
4189+
4190+
4191+    def put_block(self, data, segnum, salt):
4192+        """
4193+        Add a block and salt to the share.
4194+        """
4195+        # SDMF files have only one segment
4196+        assert segnum == 0
4197+        assert len(data) == self._block_size
4198+        assert len(salt) == SALT_SIZE
4199+
4200+        self._share_pieces['sharedata'] = data
4201+        self._share_pieces['salt'] = salt
4202+
4203+        # TODO: Figure out something intelligent to return.
4204+        return defer.succeed(None)
4205+
4206+
4207+    def put_encprivkey(self, encprivkey):
4208+        """
4209+        Add the encrypted private key to the share.
4210+        """
4211+        self._share_pieces['encprivkey'] = encprivkey
4212+
4213+        return defer.succeed(None)
4214+
4215+
4216+    def put_blockhashes(self, blockhashes):
4217+        """
4218+        Add the block hash tree to the share.
4219+        """
4220+        assert isinstance(blockhashes, list)
4221+        for h in blockhashes:
4222+            assert len(h) == HASH_SIZE
4223+
4224+        # serialize the blockhashes, then set them.
4225+        blockhashes_s = "".join(blockhashes)
4226+        self._share_pieces['block_hash_tree'] = blockhashes_s
4227+
4228+        return defer.succeed(None)
4229+
4230+
4231+    def put_sharehashes(self, sharehashes):
4232+        """
4233+        Add the share hash chain to the share.
4234+        """
4235+        assert isinstance(sharehashes, dict)
4236+        for h in sharehashes.itervalues():
4237+            assert len(h) == HASH_SIZE
4238+
4239+        # serialize the sharehashes, then set them.
4240+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4241+                                 for i in sorted(sharehashes.keys())])
4242+        self._share_pieces['share_hash_chain'] = sharehashes_s
4243+
4244+        return defer.succeed(None)
4245+
4246+
4247+    def put_root_hash(self, root_hash):
4248+        """
4249+        Add the root hash to the share.
4250+        """
4251+        assert len(root_hash) == HASH_SIZE
4252+
4253+        self._share_pieces['root_hash'] = root_hash
4254+
4255+        return defer.succeed(None)
4256+
4257+
4258+    def put_salt(self, salt):
4259+        """
4260+        Add a salt to an empty SDMF file.
4261+        """
4262+        assert len(salt) == SALT_SIZE
4263+
4264+        self._share_pieces['salt'] = salt
4265+        self._share_pieces['sharedata'] = ""
4266+
4267+
4268+    def get_signable(self):
4269+        """
4270+        Return the part of the share that needs to be signed.
4271+
4272+        SDMF writers need to sign the packed representation of the
4273+        first eight fields of the remote share, that is:
4274+            - version number (0)
4275+            - sequence number
4276+            - root of the share hash tree
4277+            - salt
4278+            - k
4279+            - n
4280+            - segsize
4281+            - datalen
4282+
4283+        This method is responsible for returning that to callers.
4284+        """
4285+        return struct.pack(SIGNED_PREFIX,
4286+                           0,
4287+                           self._seqnum,
4288+                           self._share_pieces['root_hash'],
4289+                           self._share_pieces['salt'],
4290+                           self._required_shares,
4291+                           self._total_shares,
4292+                           self._segment_size,
4293+                           self._data_length)
4294+
4295+
4296+    def put_signature(self, signature):
4297+        """
4298+        Add the signature to the share.
4299+        """
4300+        self._share_pieces['signature'] = signature
4301+
4302+        return defer.succeed(None)
4303+
4304+
4305+    def put_verification_key(self, verification_key):
4306+        """
4307+        Add the verification key to the share.
4308+        """
4309+        self._share_pieces['verification_key'] = verification_key
4310+
4311+        return defer.succeed(None)
4312+
4313+
4314+    def get_verinfo(self):
4315+        """
4316+        I return my verinfo tuple. This is used by the ServermapUpdater
4317+        to keep track of versions of mutable files.
4318+
4319+        The verinfo tuple for MDMF files contains:
4320+            - seqnum
4321+            - root hash
4322+            - a blank (nothing)
4323+            - segsize
4324+            - datalen
4325+            - k
4326+            - n
4327+            - prefix (the thing that you sign)
4328+            - a tuple of offsets
4329+
4330+        We include the nonce in MDMF to simplify processing of version
4331+        information tuples.
4332+
4333+        The verinfo tuple for SDMF files is the same, but contains a
4334+        16-byte IV instead of a hash of salts.
4335+        """
4336+        return (self._seqnum,
4337+                self._share_pieces['root_hash'],
4338+                self._share_pieces['salt'],
4339+                self._segment_size,
4340+                self._data_length,
4341+                self._required_shares,
4342+                self._total_shares,
4343+                self.get_signable(),
4344+                self._get_offsets_tuple())
4345+
4346+    def _get_offsets_dict(self):
4347+        post_offset = HEADER_LENGTH
4348+        offsets = {}
4349+
4350+        verification_key_length = len(self._share_pieces['verification_key'])
4351+        o1 = offsets['signature'] = post_offset + verification_key_length
4352+
4353+        signature_length = len(self._share_pieces['signature'])
4354+        o2 = offsets['share_hash_chain'] = o1 + signature_length
4355+
4356+        share_hash_chain_length = len(self._share_pieces['share_hash_chain'])
4357+        o3 = offsets['block_hash_tree'] = o2 + share_hash_chain_length
4358+
4359+        block_hash_tree_length = len(self._share_pieces['block_hash_tree'])
4360+        o4 = offsets['share_data'] = o3 + block_hash_tree_length
4361+
4362+        share_data_length = len(self._share_pieces['sharedata'])
4363+        o5 = offsets['enc_privkey'] = o4 + share_data_length
4364+
4365+        encprivkey_length = len(self._share_pieces['encprivkey'])
4366+        offsets['EOF'] = o5 + encprivkey_length
4367+        return offsets
4368+
4369+
4370+    def _get_offsets_tuple(self):
4371+        offsets = self._get_offsets_dict()
4372+        return tuple([(key, value) for key, value in offsets.items()])
4373+
4374+
4375+    def _pack_offsets(self):
4376+        offsets = self._get_offsets_dict()
4377+        return struct.pack(">LLLLQQ",
4378+                           offsets['signature'],
4379+                           offsets['share_hash_chain'],
4380+                           offsets['block_hash_tree'],
4381+                           offsets['share_data'],
4382+                           offsets['enc_privkey'],
4383+                           offsets['EOF'])
4384+
4385+
4386+    def finish_publishing(self):
4387+        """
4388+        Do anything necessary to finish writing the share to a remote
4389+        server. I require that no further publishing needs to take place
4390+        after this method has been called.
4391+        """
4392+        for k in ["sharedata", "encprivkey", "signature", "verification_key",
4393+                  "share_hash_chain", "block_hash_tree"]:
4394+            assert k in self._share_pieces
4395+        # This is the only method that actually writes something to the
4396+        # remote server.
4397+        # First, we need to pack the share into data that we can write
4398+        # to the remote server in one write.
4399+        offsets = self._pack_offsets()
4400+        prefix = self.get_signable()
4401+        final_share = "".join([prefix,
4402+                               offsets,
4403+                               self._share_pieces['verification_key'],
4404+                               self._share_pieces['signature'],
4405+                               self._share_pieces['share_hash_chain'],
4406+                               self._share_pieces['block_hash_tree'],
4407+                               self._share_pieces['sharedata'],
4408+                               self._share_pieces['encprivkey']])
4409+
4410+        # Our only data vector is going to be writing the final share,
4411+        # in its entirely.
4412+        datavs = [(0, final_share)]
4413+
4414+        if not self._testvs:
4415+            # Our caller has not provided us with another checkstring
4416+            # yet, so we assume that we are writing a new share, and set
4417+            # a test vector that will allow a new share to be written.
4418+            self._testvs = []
4419+            self._testvs.append(tuple([0, 1, "eq", ""]))
4420+            new_share = True
4421+
4422+        tw_vectors = {}
4423+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
4424+        return self._rref.callRemote("slot_testv_and_readv_and_writev",
4425+                                     self._storage_index,
4426+                                     self._secrets,
4427+                                     tw_vectors,
4428+                                     # TODO is it useful to read something?
4429+                                     self._readvs)
4430+
4431+
4432+MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
4433+MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
4434+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4435+MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4436+MDMFCHECKSTRING = ">BQ32s"
4437+MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
4438+MDMFOFFSETS = ">QQQQQQ"
4439+MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
4440+
4441+class MDMFSlotWriteProxy:
4442+    implements(IMutableSlotWriter)
4443+
4444+    """
4445+    I represent a remote write slot for an MDMF mutable file.
4446+
4447+    I abstract away from my caller the details of block and salt
4448+    management, and the implementation of the on-disk format for MDMF
4449+    shares.
4450+    """
4451+    # Expected layout, MDMF:
4452+    # offset:     size:       name:
4453+    #-- signed part --
4454+    # 0           1           version number (01)
4455+    # 1           8           sequence number
4456+    # 9           32          share tree root hash
4457+    # 41          1           The "k" encoding parameter
4458+    # 42          1           The "N" encoding parameter
4459+    # 43          8           The segment size of the uploaded file
4460+    # 51          8           The data length of the original plaintext
4461+    #-- end signed part --
4462+    # 59          8           The offset of the encrypted private key
4463+    # 67          8           The offset of the block hash tree
4464+    # 75          8           The offset of the share hash chain
4465+    # 83          8           The offset of the signature
4466+    # 91          8           The offset of the verification key
4467+    # 99          8           The offset of the EOF
4468+    #
4469+    # followed by salts and share data, the encrypted private key, the
4470+    # block hash tree, the salt hash tree, the share hash chain, a
4471+    # signature over the first eight fields, and a verification key.
4472+    #
4473+    # The checkstring is the first three fields -- the version number,
4474+    # sequence number, root hash and root salt hash. This is consistent
4475+    # in meaning to what we have with SDMF files, except now instead of
4476+    # using the literal salt, we use a value derived from all of the
4477+    # salts -- the share hash root.
4478+    #
4479+    # The salt is stored before the block for each segment. The block
4480+    # hash tree is computed over the combination of block and salt for
4481+    # each segment. In this way, we get integrity checking for both
4482+    # block and salt with the current block hash tree arrangement.
4483+    #
4484+    # The ordering of the offsets is different to reflect the dependencies
4485+    # that we'll run into with an MDMF file. The expected write flow is
4486+    # something like this:
4487+    #
4488+    #   0: Initialize with the sequence number, encoding parameters and
4489+    #      data length. From this, we can deduce the number of segments,
4490+    #      and where they should go.. We can also figure out where the
4491+    #      encrypted private key should go, because we can figure out how
4492+    #      big the share data will be.
4493+    #
4494+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4495+    #      like
4496+    #
4497+    #       put_block(data, segnum, salt)
4498+    #
4499+    #      to write a block and a salt to the disk. We can do both of
4500+    #      these operations now because we have enough of the offsets to
4501+    #      know where to put them.
4502+    #
4503+    #   2: Put the encrypted private key. Use:
4504+    #
4505+    #        put_encprivkey(encprivkey)
4506+    #
4507+    #      Now that we know the length of the private key, we can fill
4508+    #      in the offset for the block hash tree.
4509+    #
4510+    #   3: We're now in a position to upload the block hash tree for
4511+    #      a share. Put that using something like:
4512+    #       
4513+    #        put_blockhashes(block_hash_tree)
4514+    #
4515+    #      Note that block_hash_tree is a list of hashes -- we'll take
4516+    #      care of the details of serializing that appropriately. When
4517+    #      we get the block hash tree, we are also in a position to
4518+    #      calculate the offset for the share hash chain, and fill that
4519+    #      into the offsets table.
4520+    #
4521+    #   4: At the same time, we're in a position to upload the salt hash
4522+    #      tree. This is a Merkle tree over all of the salts. We use a
4523+    #      Merkle tree so that we can validate each block,salt pair as
4524+    #      we download them later. We do this using
4525+    #
4526+    #        put_salthashes(salt_hash_tree)
4527+    #
4528+    #      When you do this, I automatically put the root of the tree
4529+    #      (the hash at index 0 of the list) in its appropriate slot in
4530+    #      the signed prefix of the share.
4531+    #
4532+    #   5: We're now in a position to upload the share hash chain for
4533+    #      a share. Do that with something like:
4534+    #     
4535+    #        put_sharehashes(share_hash_chain)
4536+    #
4537+    #      share_hash_chain should be a dictionary mapping shnums to
4538+    #      32-byte hashes -- the wrapper handles serialization.
4539+    #      We'll know where to put the signature at this point, also.
4540+    #      The root of this tree will be put explicitly in the next
4541+    #      step.
4542+    #
4543+    #      TODO: Why? Why not just include it in the tree here?
4544+    #
4545+    #   6: Before putting the signature, we must first put the
4546+    #      root_hash. Do this with:
4547+    #
4548+    #        put_root_hash(root_hash).
4549+    #     
4550+    #      In terms of knowing where to put this value, it was always
4551+    #      possible to place it, but it makes sense semantically to
4552+    #      place it after the share hash tree, so that's why you do it
4553+    #      in this order.
4554+    #
4555+    #   6: With the root hash put, we can now sign the header. Use:
4556+    #
4557+    #        get_signable()
4558+    #
4559+    #      to get the part of the header that you want to sign, and use:
4560+    #       
4561+    #        put_signature(signature)
4562+    #
4563+    #      to write your signature to the remote server.
4564+    #
4565+    #   6: Add the verification key, and finish. Do:
4566+    #
4567+    #        put_verification_key(key)
4568+    #
4569+    #      and
4570+    #
4571+    #        finish_publish()
4572+    #
4573+    # Checkstring management:
4574+    #
4575+    # To write to a mutable slot, we have to provide test vectors to ensure
4576+    # that we are writing to the same data that we think we are. These
4577+    # vectors allow us to detect uncoordinated writes; that is, writes
4578+    # where both we and some other shareholder are writing to the
4579+    # mutable slot, and to report those back to the parts of the program
4580+    # doing the writing.
4581+    #
4582+    # With SDMF, this was easy -- all of the share data was written in
4583+    # one go, so it was easy to detect uncoordinated writes, and we only
4584+    # had to do it once. With MDMF, not all of the file is written at
4585+    # once.
4586+    #
4587+    # If a share is new, we write out as much of the header as we can
4588+    # before writing out anything else. This gives other writers a
4589+    # canary that they can use to detect uncoordinated writes, and, if
4590+    # they do the same thing, gives us the same canary. We them update
4591+    # the share. We won't be able to write out two fields of the header
4592+    # -- the share tree hash and the salt hash -- until we finish
4593+    # writing out the share. We only require the writer to provide the
4594+    # initial checkstring, and keep track of what it should be after
4595+    # updates ourselves.
4596+    #
4597+    # If we haven't written anything yet, then on the first write (which
4598+    # will probably be a block + salt of a share), we'll also write out
4599+    # the header. On subsequent passes, we'll expect to see the header.
4600+    # This changes in two places:
4601+    #
4602+    #   - When we write out the salt hash
4603+    #   - When we write out the root of the share hash tree
4604+    #
4605+    # since these values will change the header. It is possible that we
4606+    # can just make those be written in one operation to minimize
4607+    # disruption.
4608+    def __init__(self,
4609+                 shnum,
4610+                 rref, # a remote reference to a storage server
4611+                 storage_index,
4612+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4613+                 seqnum, # the sequence number of the mutable file
4614+                 required_shares,
4615+                 total_shares,
4616+                 segment_size,
4617+                 data_length): # the length of the original file
4618+        self.shnum = shnum
4619+        self._rref = rref
4620+        self._storage_index = storage_index
4621+        self._seqnum = seqnum
4622+        self._required_shares = required_shares
4623+        assert self.shnum >= 0 and self.shnum < total_shares
4624+        self._total_shares = total_shares
4625+        # We build up the offset table as we write things. It is the
4626+        # last thing we write to the remote server.
4627+        self._offsets = {}
4628+        self._testvs = []
4629+        self._secrets = secrets
4630+        # The segment size needs to be a multiple of the k parameter --
4631+        # any padding should have been carried out by the publisher
4632+        # already.
4633+        assert segment_size % required_shares == 0
4634+        self._segment_size = segment_size
4635+        self._data_length = data_length
4636+
4637+        # These are set later -- we define them here so that we can
4638+        # check for their existence easily
4639+
4640+        # This is the root of the share hash tree -- the Merkle tree
4641+        # over the roots of the block hash trees computed for shares in
4642+        # this upload.
4643+        self._root_hash = None
4644+
4645+        # We haven't yet written anything to the remote bucket. By
4646+        # setting this, we tell the _write method as much. The write
4647+        # method will then know that it also needs to add a write vector
4648+        # for the checkstring (or what we have of it) to the first write
4649+        # request. We'll then record that value for future use.  If
4650+        # we're expecting something to be there already, we need to call
4651+        # set_checkstring before we write anything to tell the first
4652+        # write about that.
4653+        self._written = False
4654+
4655+        # When writing data to the storage servers, we get a read vector
4656+        # for free. We'll read the checkstring, which will help us
4657+        # figure out what's gone wrong if a write fails.
4658+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
4659+
4660+        # We calculate the number of segments because it tells us
4661+        # where the salt part of the file ends/share segment begins,
4662+        # and also because it provides a useful amount of bounds checking.
4663+        self._num_segments = mathutil.div_ceil(self._data_length,
4664+                                               self._segment_size)
4665+        self._block_size = self._segment_size / self._required_shares
4666+        # We also calculate the share size, to help us with block
4667+        # constraints later.
4668+        tail_size = self._data_length % self._segment_size
4669+        if not tail_size:
4670+            self._tail_block_size = self._block_size
4671+        else:
4672+            self._tail_block_size = mathutil.next_multiple(tail_size,
4673+                                                           self._required_shares)
4674+            self._tail_block_size /= self._required_shares
4675+
4676+        # We already know where the sharedata starts; right after the end
4677+        # of the header (which is defined as the signable part + the offsets)
4678+        # We can also calculate where the encrypted private key begins
4679+        # from what we know know.
4680+        self._actual_block_size = self._block_size + SALT_SIZE
4681+        data_size = self._actual_block_size * (self._num_segments - 1)
4682+        data_size += self._tail_block_size
4683+        data_size += SALT_SIZE
4684+        self._offsets['enc_privkey'] = MDMFHEADERSIZE
4685+        self._offsets['enc_privkey'] += data_size
4686+        # We'll wait for the rest. Callers can now call my "put_block" and
4687+        # "set_checkstring" methods.
4688+
4689+
4690+    def set_checkstring(self,
4691+                        seqnum_or_checkstring,
4692+                        root_hash=None,
4693+                        salt=None):
4694+        """
4695+        Set checkstring checkstring for the given shnum.
4696+
4697+        This can be invoked in one of two ways.
4698+
4699+        With one argument, I assume that you are giving me a literal
4700+        checkstring -- e.g., the output of get_checkstring. I will then
4701+        set that checkstring as it is. This form is used by unit tests.
4702+
4703+        With two arguments, I assume that you are giving me a sequence
4704+        number and root hash to make a checkstring from. In that case, I
4705+        will build a checkstring and set it for you. This form is used
4706+        by the publisher.
4707+
4708+        By default, I assume that I am writing new shares to the grid.
4709+        If you don't explcitly set your own checkstring, I will use
4710+        one that requires that the remote share not exist. You will want
4711+        to use this method if you are updating a share in-place;
4712+        otherwise, writes will fail.
4713+        """
4714+        # You're allowed to overwrite checkstrings with this method;
4715+        # I assume that users know what they are doing when they call
4716+        # it.
4717+        if root_hash:
4718+            checkstring = struct.pack(MDMFCHECKSTRING,
4719+                                      1,
4720+                                      seqnum_or_checkstring,
4721+                                      root_hash)
4722+        else:
4723+            checkstring = seqnum_or_checkstring
4724+
4725+        if checkstring == "":
4726+            # We special-case this, since len("") = 0, but we need
4727+            # length of 1 for the case of an empty share to work on the
4728+            # storage server, which is what a checkstring that is the
4729+            # empty string means.
4730+            self._testvs = []
4731+        else:
4732+            self._testvs = []
4733+            self._testvs.append((0, len(checkstring), "eq", checkstring))
4734+
4735+
4736+    def __repr__(self):
4737+        return "MDMFSlotWriteProxy for share %d" % self.shnum
4738+
4739+
4740+    def get_checkstring(self):
4741+        """
4742+        Given a share number, I return a representation of what the
4743+        checkstring for that share on the server will look like.
4744+
4745+        I am mostly used for tests.
4746+        """
4747+        if self._root_hash:
4748+            roothash = self._root_hash
4749+        else:
4750+            roothash = "\x00" * 32
4751+        return struct.pack(MDMFCHECKSTRING,
4752+                           1,
4753+                           self._seqnum,
4754+                           roothash)
4755+
4756+
4757+    def put_block(self, data, segnum, salt):
4758+        """
4759+        Put the encrypted-and-encoded data segment in the slot, along
4760+        with the salt.
4761+        """
4762+        if segnum >= self._num_segments:
4763+            raise LayoutInvalid("I won't overwrite the private key")
4764+        if len(salt) != SALT_SIZE:
4765+            raise LayoutInvalid("I was given a salt of size %d, but "
4766+                                "I wanted a salt of size %d")
4767+        if segnum + 1 == self._num_segments:
4768+            if len(data) != self._tail_block_size:
4769+                raise LayoutInvalid("I was given the wrong size block to write")
4770+        elif len(data) != self._block_size:
4771+            raise LayoutInvalid("I was given the wrong size block to write")
4772+
4773+        # We want to write at len(MDMFHEADER) + segnum * block_size.
4774+
4775+        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
4776+        data = salt + data
4777+
4778+        datavs = [tuple([offset, data])]
4779+        return self._write(datavs)
4780+
4781+
4782+    def put_encprivkey(self, encprivkey):
4783+        """
4784+        Put the encrypted private key in the remote slot.
4785+        """
4786+        assert self._offsets
4787+        assert self._offsets['enc_privkey']
4788+        # You shouldn't re-write the encprivkey after the block hash
4789+        # tree is written, since that could cause the private key to run
4790+        # into the block hash tree. Before it writes the block hash
4791+        # tree, the block hash tree writing method writes the offset of
4792+        # the salt hash tree. So that's a good indicator of whether or
4793+        # not the block hash tree has been written.
4794+        if "share_hash_chain" in self._offsets:
4795+            raise LayoutInvalid("You must write this before the block hash tree")
4796+
4797+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
4798+        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
4799+        def _on_failure():
4800+            del(self._offsets['block_hash_tree'])
4801+        return self._write(datavs, on_failure=_on_failure)
4802+
4803+
4804+    def put_blockhashes(self, blockhashes):
4805+        """
4806+        Put the block hash tree in the remote slot.
4807+
4808+        The encrypted private key must be put before the block hash
4809+        tree, since we need to know how large it is to know where the
4810+        block hash tree should go. The block hash tree must be put
4811+        before the salt hash tree, since its size determines the
4812+        offset of the share hash chain.
4813+        """
4814+        assert self._offsets
4815+        assert isinstance(blockhashes, list)
4816+        if "block_hash_tree" not in self._offsets:
4817+            raise LayoutInvalid("You must put the encrypted private key "
4818+                                "before you put the block hash tree")
4819+        # If written, the share hash chain causes the signature offset
4820+        # to be defined.
4821+        if "signature" in self._offsets:
4822+            raise LayoutInvalid("You must put the block hash tree before "
4823+                                "you put the share hash chain")
4824+        blockhashes_s = "".join(blockhashes)
4825+        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
4826+        datavs = []
4827+        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
4828+        def _on_failure():
4829+            del(self._offsets['share_hash_chain'])
4830+        return self._write(datavs, on_failure=_on_failure)
4831+
4832+
4833+    def put_sharehashes(self, sharehashes):
4834+        """
4835+        Put the share hash chain in the remote slot.
4836+
4837+        The salt hash tree must be put before the share hash chain,
4838+        since we need to know where the salt hash tree ends before we
4839+        can know where the share hash chain starts. The share hash chain
4840+        must be put before the signature, since the length of the packed
4841+        share hash chain determines the offset of the signature. Also,
4842+        semantically, you must know what the root of the salt hash tree
4843+        is before you can generate a valid signature.
4844+        """
4845+        assert isinstance(sharehashes, dict)
4846+        if "share_hash_chain" not in self._offsets:
4847+            raise LayoutInvalid("You need to put the salt hash tree before "
4848+                                "you can put the share hash chain")
4849+        # The signature comes after the share hash chain. If the
4850+        # signature has already been written, we must not write another
4851+        # share hash chain. The signature writes the verification key
4852+        # offset when it gets sent to the remote server, so we look for
4853+        # that.
4854+        if "verification_key" in self._offsets:
4855+            raise LayoutInvalid("You must write the share hash chain "
4856+                                "before you write the signature")
4857+        datavs = []
4858+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4859+                                  for i in sorted(sharehashes.keys())])
4860+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
4861+        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
4862+        def _on_failure():
4863+            del(self._offsets['signature'])
4864+        return self._write(datavs, on_failure=_on_failure)
4865+
4866+
4867+    def put_root_hash(self, roothash):
4868+        """
4869+        Put the root hash (the root of the share hash tree) in the
4870+        remote slot.
4871+        """
4872+        # It does not make sense to be able to put the root
4873+        # hash without first putting the share hashes, since you need
4874+        # the share hashes to generate the root hash.
4875+        #
4876+        # Signature is defined by the routine that places the share hash
4877+        # chain, so it's a good thing to look for in finding out whether
4878+        # or not the share hash chain exists on the remote server.
4879+        if "signature" not in self._offsets:
4880+            raise LayoutInvalid("You need to put the share hash chain "
4881+                                "before you can put the root share hash")
4882+        if len(roothash) != HASH_SIZE:
4883+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
4884+                                 % HASH_SIZE)
4885+        datavs = []
4886+        self._root_hash = roothash
4887+        # To write both of these values, we update the checkstring on
4888+        # the remote server, which includes them
4889+        checkstring = self.get_checkstring()
4890+        datavs.append(tuple([0, checkstring]))
4891+        # This write, if successful, changes the checkstring, so we need
4892+        # to update our internal checkstring to be consistent with the
4893+        # one on the server.
4894+        def _on_success():
4895+            self._testvs = [(0, len(checkstring), "eq", checkstring)]
4896+        def _on_failure():
4897+            self._root_hash = None
4898+        return self._write(datavs,
4899+                           on_success=_on_success,
4900+                           on_failure=_on_failure)
4901+
4902+
4903+    def get_signable(self):
4904+        """
4905+        Get the first seven fields of the mutable file; the parts that
4906+        are signed.
4907+        """
4908+        if not self._root_hash:
4909+            raise LayoutInvalid("You need to set the root hash "
4910+                                "before getting something to "
4911+                                "sign")
4912+        return struct.pack(MDMFSIGNABLEHEADER,
4913+                           1,
4914+                           self._seqnum,
4915+                           self._root_hash,
4916+                           self._required_shares,
4917+                           self._total_shares,
4918+                           self._segment_size,
4919+                           self._data_length)
4920+
4921+
4922+    def put_signature(self, signature):
4923+        """
4924+        Put the signature field to the remote slot.
4925+
4926+        I require that the root hash and share hash chain have been put
4927+        to the grid before I will write the signature to the grid.
4928+        """
4929+        if "signature" not in self._offsets:
4930+            raise LayoutInvalid("You must put the share hash chain "
4931+        # It does not make sense to put a signature without first
4932+        # putting the root hash and the salt hash (since otherwise
4933+        # the signature would be incomplete), so we don't allow that.
4934+                       "before putting the signature")
4935+        if not self._root_hash:
4936+            raise LayoutInvalid("You must complete the signed prefix "
4937+                                "before computing a signature")
4938+        # If we put the signature after we put the verification key, we
4939+        # could end up running into the verification key, and will
4940+        # probably screw up the offsets as well. So we don't allow that.
4941+        # The method that writes the verification key defines the EOF
4942+        # offset before writing the verification key, so look for that.
4943+        if "EOF" in self._offsets:
4944+            raise LayoutInvalid("You must write the signature before the verification key")
4945+
4946+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
4947+        datavs = []
4948+        datavs.append(tuple([self._offsets['signature'], signature]))
4949+        def _on_failure():
4950+            del(self._offsets['verification_key'])
4951+        return self._write(datavs, on_failure=_on_failure)
4952+
4953+
4954+    def put_verification_key(self, verification_key):
4955+        """
4956+        Put the verification key into the remote slot.
4957+
4958+        I require that the signature have been written to the storage
4959+        server before I allow the verification key to be written to the
4960+        remote server.
4961+        """
4962+        if "verification_key" not in self._offsets:
4963+            raise LayoutInvalid("You must put the signature before you "
4964+                                "can put the verification key")
4965+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
4966+        datavs = []
4967+        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
4968+        def _on_failure():
4969+            del(self._offsets['EOF'])
4970+        return self._write(datavs, on_failure=_on_failure)
4971+
4972+    def _get_offsets_tuple(self):
4973+        return tuple([(key, value) for key, value in self._offsets.items()])
4974+
4975+    def get_verinfo(self):
4976+        return (self._seqnum,
4977+                self._root_hash,
4978+                self._required_shares,
4979+                self._total_shares,
4980+                self._segment_size,
4981+                self._data_length,
4982+                self.get_signable(),
4983+                self._get_offsets_tuple())
4984+
4985+
4986+    def finish_publishing(self):
4987+        """
4988+        Write the offset table and encoding parameters to the remote
4989+        slot, since that's the only thing we have yet to publish at this
4990+        point.
4991+        """
4992+        if "EOF" not in self._offsets:
4993+            raise LayoutInvalid("You must put the verification key before "
4994+                                "you can publish the offsets")
4995+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4996+        offsets = struct.pack(MDMFOFFSETS,
4997+                              self._offsets['enc_privkey'],
4998+                              self._offsets['block_hash_tree'],
4999+                              self._offsets['share_hash_chain'],
5000+                              self._offsets['signature'],
5001+                              self._offsets['verification_key'],
5002+                              self._offsets['EOF'])
5003+        datavs = []
5004+        datavs.append(tuple([offsets_offset, offsets]))
5005+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5006+        params = struct.pack(">BBQQ",
5007+                             self._required_shares,
5008+                             self._total_shares,
5009+                             self._segment_size,
5010+                             self._data_length)
5011+        datavs.append(tuple([encoding_parameters_offset, params]))
5012+        return self._write(datavs)
5013+
5014+
5015+    def _write(self, datavs, on_failure=None, on_success=None):
5016+        """I write the data vectors in datavs to the remote slot."""
5017+        tw_vectors = {}
5018+        new_share = False
5019+        if not self._testvs:
5020+            self._testvs = []
5021+            self._testvs.append(tuple([0, 1, "eq", ""]))
5022+            new_share = True
5023+        if not self._written:
5024+            # Write a new checkstring to the share when we write it, so
5025+            # that we have something to check later.
5026+            new_checkstring = self.get_checkstring()
5027+            datavs.append((0, new_checkstring))
5028+            def _first_write():
5029+                self._written = True
5030+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5031+            on_success = _first_write
5032+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
5033+        datalength = sum([len(x[1]) for x in datavs])
5034+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5035+                                  self._storage_index,
5036+                                  self._secrets,
5037+                                  tw_vectors,
5038+                                  self._readv)
5039+        def _result(results):
5040+            if isinstance(results, failure.Failure) or not results[0]:
5041+                # Do nothing; the write was unsuccessful.
5042+                if on_failure: on_failure()
5043+            else:
5044+                if on_success: on_success()
5045+            return results
5046+        d.addCallback(_result)
5047+        return d
5048+
5049+
5050+class MDMFSlotReadProxy:
5051+    """
5052+    I read from a mutable slot filled with data written in the MDMF data
5053+    format (which is described above).
5054+
5055+    I can be initialized with some amount of data, which I will use (if
5056+    it is valid) to eliminate some of the need to fetch it from servers.
5057+    """
5058+    def __init__(self,
5059+                 rref,
5060+                 storage_index,
5061+                 shnum,
5062+                 data=""):
5063+        # Start the initialization process.
5064+        self._rref = rref
5065+        self._storage_index = storage_index
5066+        self.shnum = shnum
5067+
5068+        # Before doing anything, the reader is probably going to want to
5069+        # verify that the signature is correct. To do that, they'll need
5070+        # the verification key, and the signature. To get those, we'll
5071+        # need the offset table. So fetch the offset table on the
5072+        # assumption that that will be the first thing that a reader is
5073+        # going to do.
5074+
5075+        # The fact that these encoding parameters are None tells us
5076+        # that we haven't yet fetched them from the remote share, so we
5077+        # should. We could just not set them, but the checks will be
5078+        # easier to read if we don't have to use hasattr.
5079+        self._version_number = None
5080+        self._sequence_number = None
5081+        self._root_hash = None
5082+        # Filled in if we're dealing with an SDMF file. Unused
5083+        # otherwise.
5084+        self._salt = None
5085+        self._required_shares = None
5086+        self._total_shares = None
5087+        self._segment_size = None
5088+        self._data_length = None
5089+        self._offsets = None
5090+
5091+        # If the user has chosen to initialize us with some data, we'll
5092+        # try to satisfy subsequent data requests with that data before
5093+        # asking the storage server for it. If
5094+        self._data = data
5095+        # The way callers interact with cache in the filenode returns
5096+        # None if there isn't any cached data, but the way we index the
5097+        # cached data requires a string, so convert None to "".
5098+        if self._data == None:
5099+            self._data = ""
5100+
5101+        self._queue_observers = observer.ObserverList()
5102+        self._queue_errbacks = observer.ObserverList()
5103+        self._readvs = []
5104+
5105+
5106+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5107+        """
5108+        I fetch the offset table and the header from the remote slot if
5109+        I don't already have them. If I do have them, I do nothing and
5110+        return an empty Deferred.
5111+        """
5112+        if self._offsets:
5113+            return defer.succeed(None)
5114+        # At this point, we may be either SDMF or MDMF. Fetching 107
5115+        # bytes will be enough to get header and offsets for both SDMF and
5116+        # MDMF, though we'll be left with 4 more bytes than we
5117+        # need if this ends up being MDMF. This is probably less
5118+        # expensive than the cost of a second roundtrip.
5119+        readvs = [(0, 107)]
5120+        d = self._read(readvs, force_remote)
5121+        d.addCallback(self._process_encoding_parameters)
5122+        d.addCallback(self._process_offsets)
5123+        return d
5124+
5125+
5126+    def _process_encoding_parameters(self, encoding_parameters):
5127+        assert self.shnum in encoding_parameters
5128+        encoding_parameters = encoding_parameters[self.shnum][0]
5129+        # The first byte is the version number. It will tell us what
5130+        # to do next.
5131+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5132+        if verno == MDMF_VERSION:
5133+            read_size = MDMFHEADERWITHOUTOFFSETSSIZE
5134+            (verno,
5135+             seqnum,
5136+             root_hash,
5137+             k,
5138+             n,
5139+             segsize,
5140+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5141+                                      encoding_parameters[:read_size])
5142+            if segsize == 0 and datalen == 0:
5143+                # Empty file, no segments.
5144+                self._num_segments = 0
5145+            else:
5146+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5147+
5148+        elif verno == SDMF_VERSION:
5149+            read_size = SIGNED_PREFIX_LENGTH
5150+            (verno,
5151+             seqnum,
5152+             root_hash,
5153+             salt,
5154+             k,
5155+             n,
5156+             segsize,
5157+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5158+                                encoding_parameters[:SIGNED_PREFIX_LENGTH])
5159+            self._salt = salt
5160+            if segsize == 0 and datalen == 0:
5161+                # empty file
5162+                self._num_segments = 0
5163+            else:
5164+                # non-empty SDMF files have one segment.
5165+                self._num_segments = 1
5166+        else:
5167+            raise UnknownVersionError("You asked me to read mutable file "
5168+                                      "version %d, but I only understand "
5169+                                      "%d and %d" % (verno, SDMF_VERSION,
5170+                                                     MDMF_VERSION))
5171+
5172+        self._version_number = verno
5173+        self._sequence_number = seqnum
5174+        self._root_hash = root_hash
5175+        self._required_shares = k
5176+        self._total_shares = n
5177+        self._segment_size = segsize
5178+        self._data_length = datalen
5179+
5180+        self._block_size = self._segment_size / self._required_shares
5181+        # We can upload empty files, and need to account for this fact
5182+        # so as to avoid zero-division and zero-modulo errors.
5183+        if datalen > 0:
5184+            tail_size = self._data_length % self._segment_size
5185+        else:
5186+            tail_size = 0
5187+        if not tail_size:
5188+            self._tail_block_size = self._block_size
5189+        else:
5190+            self._tail_block_size = mathutil.next_multiple(tail_size,
5191+                                                    self._required_shares)
5192+            self._tail_block_size /= self._required_shares
5193+
5194+        return encoding_parameters
5195+
5196+
5197+    def _process_offsets(self, offsets):
5198+        if self._version_number == 0:
5199+            read_size = OFFSETS_LENGTH
5200+            read_offset = SIGNED_PREFIX_LENGTH
5201+            end = read_size + read_offset
5202+            (signature,
5203+             share_hash_chain,
5204+             block_hash_tree,
5205+             share_data,
5206+             enc_privkey,
5207+             EOF) = struct.unpack(">LLLLQQ",
5208+                                  offsets[read_offset:end])
5209+            self._offsets = {}
5210+            self._offsets['signature'] = signature
5211+            self._offsets['share_data'] = share_data
5212+            self._offsets['block_hash_tree'] = block_hash_tree
5213+            self._offsets['share_hash_chain'] = share_hash_chain
5214+            self._offsets['enc_privkey'] = enc_privkey
5215+            self._offsets['EOF'] = EOF
5216+
5217+        elif self._version_number == 1:
5218+            read_offset = MDMFHEADERWITHOUTOFFSETSSIZE
5219+            read_length = MDMFOFFSETS_LENGTH
5220+            end = read_offset + read_length
5221+            (encprivkey,
5222+             blockhashes,
5223+             sharehashes,
5224+             signature,
5225+             verification_key,
5226+             eof) = struct.unpack(MDMFOFFSETS,
5227+                                  offsets[read_offset:end])
5228+            self._offsets = {}
5229+            self._offsets['enc_privkey'] = encprivkey
5230+            self._offsets['block_hash_tree'] = blockhashes
5231+            self._offsets['share_hash_chain'] = sharehashes
5232+            self._offsets['signature'] = signature
5233+            self._offsets['verification_key'] = verification_key
5234+            self._offsets['EOF'] = eof
5235+
5236+
5237+    def get_block_and_salt(self, segnum, queue=False):
5238+        """
5239+        I return (block, salt), where block is the block data and
5240+        salt is the salt used to encrypt that segment.
5241+        """
5242+        d = self._maybe_fetch_offsets_and_header()
5243+        def _then(ignored):
5244+            if self._version_number == 1:
5245+                base_share_offset = MDMFHEADERSIZE
5246+            else:
5247+                base_share_offset = self._offsets['share_data']
5248+
5249+            if segnum + 1 > self._num_segments:
5250+                raise LayoutInvalid("Not a valid segment number")
5251+
5252+            if self._version_number == 0:
5253+                share_offset = base_share_offset + self._block_size * segnum
5254+            else:
5255+                share_offset = base_share_offset + (self._block_size + \
5256+                                                    SALT_SIZE) * segnum
5257+            if segnum + 1 == self._num_segments:
5258+                data = self._tail_block_size
5259+            else:
5260+                data = self._block_size
5261+
5262+            if self._version_number == 1:
5263+                data += SALT_SIZE
5264+
5265+            readvs = [(share_offset, data)]
5266+            return readvs
5267+        d.addCallback(_then)
5268+        d.addCallback(lambda readvs:
5269+            self._read(readvs, queue=queue))
5270+        def _process_results(results):
5271+            assert self.shnum in results
5272+            if self._version_number == 0:
5273+                # We only read the share data, but we know the salt from
5274+                # when we fetched the header
5275+                data = results[self.shnum]
5276+                if not data:
5277+                    data = ""
5278+                else:
5279+                    assert len(data) == 1
5280+                    data = data[0]
5281+                salt = self._salt
5282+            else:
5283+                data = results[self.shnum]
5284+                if not data:
5285+                    salt = data = ""
5286+                else:
5287+                    salt_and_data = results[self.shnum][0]
5288+                    salt = salt_and_data[:SALT_SIZE]
5289+                    data = salt_and_data[SALT_SIZE:]
5290+            return data, salt
5291+        d.addCallback(_process_results)
5292+        return d
5293+
5294+
5295+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5296+        """
5297+        I return the block hash tree
5298+
5299+        I take an optional argument, needed, which is a set of indices
5300+        correspond to hashes that I should fetch. If this argument is
5301+        missing, I will fetch the entire block hash tree; otherwise, I
5302+        may attempt to fetch fewer hashes, based on what needed says
5303+        that I should do. Note that I may fetch as many hashes as I
5304+        want, so long as the set of hashes that I do fetch is a superset
5305+        of the ones that I am asked for, so callers should be prepared
5306+        to tolerate additional hashes.
5307+        """
5308+        # TODO: Return only the parts of the block hash tree necessary
5309+        # to validate the blocknum provided?
5310+        # This is a good idea, but it is hard to implement correctly. It
5311+        # is bad to fetch any one block hash more than once, so we
5312+        # probably just want to fetch the whole thing at once and then
5313+        # serve it.
5314+        if needed == set([]):
5315+            return defer.succeed([])
5316+        d = self._maybe_fetch_offsets_and_header()
5317+        def _then(ignored):
5318+            blockhashes_offset = self._offsets['block_hash_tree']
5319+            if self._version_number == 1:
5320+                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
5321+            else:
5322+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5323+            readvs = [(blockhashes_offset, blockhashes_length)]
5324+            return readvs
5325+        d.addCallback(_then)
5326+        d.addCallback(lambda readvs:
5327+            self._read(readvs, queue=queue, force_remote=force_remote))
5328+        def _build_block_hash_tree(results):
5329+            assert self.shnum in results
5330+
5331+            rawhashes = results[self.shnum][0]
5332+            results = [rawhashes[i:i+HASH_SIZE]
5333+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5334+            return results
5335+        d.addCallback(_build_block_hash_tree)
5336+        return d
5337+
5338+
5339+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5340+        """
5341+        I return the part of the share hash chain placed to validate
5342+        this share.
5343+
5344+        I take an optional argument, needed. Needed is a set of indices
5345+        that correspond to the hashes that I should fetch. If needed is
5346+        not present, I will fetch and return the entire share hash
5347+        chain. Otherwise, I may fetch and return any part of the share
5348+        hash chain that is a superset of the part that I am asked to
5349+        fetch. Callers should be prepared to deal with more hashes than
5350+        they've asked for.
5351+        """
5352+        if needed == set([]):
5353+            return defer.succeed([])
5354+        d = self._maybe_fetch_offsets_and_header()
5355+
5356+        def _make_readvs(ignored):
5357+            sharehashes_offset = self._offsets['share_hash_chain']
5358+            if self._version_number == 0:
5359+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5360+            else:
5361+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5362+            readvs = [(sharehashes_offset, sharehashes_length)]
5363+            return readvs
5364+        d.addCallback(_make_readvs)
5365+        d.addCallback(lambda readvs:
5366+            self._read(readvs, queue=queue, force_remote=force_remote))
5367+        def _build_share_hash_chain(results):
5368+            assert self.shnum in results
5369+
5370+            sharehashes = results[self.shnum][0]
5371+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5372+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5373+            results = dict([struct.unpack(">H32s", data)
5374+                            for data in results])
5375+            return results
5376+        d.addCallback(_build_share_hash_chain)
5377+        return d
5378+
5379+
5380+    def get_encprivkey(self, queue=False):
5381+        """
5382+        I return the encrypted private key.
5383+        """
5384+        d = self._maybe_fetch_offsets_and_header()
5385+
5386+        def _make_readvs(ignored):
5387+            privkey_offset = self._offsets['enc_privkey']
5388+            if self._version_number == 0:
5389+                privkey_length = self._offsets['EOF'] - privkey_offset
5390+            else:
5391+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5392+            readvs = [(privkey_offset, privkey_length)]
5393+            return readvs
5394+        d.addCallback(_make_readvs)
5395+        d.addCallback(lambda readvs:
5396+            self._read(readvs, queue=queue))
5397+        def _process_results(results):
5398+            assert self.shnum in results
5399+            privkey = results[self.shnum][0]
5400+            return privkey
5401+        d.addCallback(_process_results)
5402+        return d
5403+
5404+
5405+    def get_signature(self, queue=False):
5406+        """
5407+        I return the signature of my share.
5408+        """
5409+        d = self._maybe_fetch_offsets_and_header()
5410+
5411+        def _make_readvs(ignored):
5412+            signature_offset = self._offsets['signature']
5413+            if self._version_number == 1:
5414+                signature_length = self._offsets['verification_key'] - signature_offset
5415+            else:
5416+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5417+            readvs = [(signature_offset, signature_length)]
5418+            return readvs
5419+        d.addCallback(_make_readvs)
5420+        d.addCallback(lambda readvs:
5421+            self._read(readvs, queue=queue))
5422+        def _process_results(results):
5423+            assert self.shnum in results
5424+            signature = results[self.shnum][0]
5425+            return signature
5426+        d.addCallback(_process_results)
5427+        return d
5428+
5429+
5430+    def get_verification_key(self, queue=False):
5431+        """
5432+        I return the verification key.
5433+        """
5434+        d = self._maybe_fetch_offsets_and_header()
5435+
5436+        def _make_readvs(ignored):
5437+            if self._version_number == 1:
5438+                vk_offset = self._offsets['verification_key']
5439+                vk_length = self._offsets['EOF'] - vk_offset
5440+            else:
5441+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5442+                vk_length = self._offsets['signature'] - vk_offset
5443+            readvs = [(vk_offset, vk_length)]
5444+            return readvs
5445+        d.addCallback(_make_readvs)
5446+        d.addCallback(lambda readvs:
5447+            self._read(readvs, queue=queue))
5448+        def _process_results(results):
5449+            assert self.shnum in results
5450+            verification_key = results[self.shnum][0]
5451+            return verification_key
5452+        d.addCallback(_process_results)
5453+        return d
5454+
5455+
5456+    def get_encoding_parameters(self):
5457+        """
5458+        I return (k, n, segsize, datalen)
5459+        """
5460+        d = self._maybe_fetch_offsets_and_header()
5461+        d.addCallback(lambda ignored:
5462+            (self._required_shares,
5463+             self._total_shares,
5464+             self._segment_size,
5465+             self._data_length))
5466+        return d
5467+
5468+
5469+    def get_seqnum(self):
5470+        """
5471+        I return the sequence number for this share.
5472+        """
5473+        d = self._maybe_fetch_offsets_and_header()
5474+        d.addCallback(lambda ignored:
5475+            self._sequence_number)
5476+        return d
5477+
5478+
5479+    def get_root_hash(self):
5480+        """
5481+        I return the root of the block hash tree
5482+        """
5483+        d = self._maybe_fetch_offsets_and_header()
5484+        d.addCallback(lambda ignored: self._root_hash)
5485+        return d
5486+
5487+
5488+    def get_checkstring(self):
5489+        """
5490+        I return the packed representation of the following:
5491+
5492+            - version number
5493+            - sequence number
5494+            - root hash
5495+            - salt hash
5496+
5497+        which my users use as a checkstring to detect other writers.
5498+        """
5499+        d = self._maybe_fetch_offsets_and_header()
5500+        def _build_checkstring(ignored):
5501+            if self._salt:
5502+                checkstring = strut.pack(PREFIX,
5503+                                         self._version_number,
5504+                                         self._sequence_number,
5505+                                         self._root_hash,
5506+                                         self._salt)
5507+            else:
5508+                checkstring = struct.pack(MDMFCHECKSTRING,
5509+                                          self._version_number,
5510+                                          self._sequence_number,
5511+                                          self._root_hash)
5512+
5513+            return checkstring
5514+        d.addCallback(_build_checkstring)
5515+        return d
5516+
5517+
5518+    def get_prefix(self, force_remote):
5519+        d = self._maybe_fetch_offsets_and_header(force_remote)
5520+        d.addCallback(lambda ignored:
5521+            self._build_prefix())
5522+        return d
5523+
5524+
5525+    def _build_prefix(self):
5526+        # The prefix is another name for the part of the remote share
5527+        # that gets signed. It consists of everything up to and
5528+        # including the datalength, packed by struct.
5529+        if self._version_number == SDMF_VERSION:
5530+            return struct.pack(SIGNED_PREFIX,
5531+                           self._version_number,
5532+                           self._sequence_number,
5533+                           self._root_hash,
5534+                           self._salt,
5535+                           self._required_shares,
5536+                           self._total_shares,
5537+                           self._segment_size,
5538+                           self._data_length)
5539+
5540+        else:
5541+            return struct.pack(MDMFSIGNABLEHEADER,
5542+                           self._version_number,
5543+                           self._sequence_number,
5544+                           self._root_hash,
5545+                           self._required_shares,
5546+                           self._total_shares,
5547+                           self._segment_size,
5548+                           self._data_length)
5549+
5550+
5551+    def _get_offsets_tuple(self):
5552+        # The offsets tuple is another component of the version
5553+        # information tuple. It is basically our offsets dictionary,
5554+        # itemized and in a tuple.
5555+        return self._offsets.copy()
5556+
5557+
5558+    def get_verinfo(self):
5559+        """
5560+        I return my verinfo tuple. This is used by the ServermapUpdater
5561+        to keep track of versions of mutable files.
5562+
5563+        The verinfo tuple for MDMF files contains:
5564+            - seqnum
5565+            - root hash
5566+            - a blank (nothing)
5567+            - segsize
5568+            - datalen
5569+            - k
5570+            - n
5571+            - prefix (the thing that you sign)
5572+            - a tuple of offsets
5573+
5574+        We include the nonce in MDMF to simplify processing of version
5575+        information tuples.
5576+
5577+        The verinfo tuple for SDMF files is the same, but contains a
5578+        16-byte IV instead of a hash of salts.
5579+        """
5580+        d = self._maybe_fetch_offsets_and_header()
5581+        def _build_verinfo(ignored):
5582+            if self._version_number == SDMF_VERSION:
5583+                salt_to_use = self._salt
5584+            else:
5585+                salt_to_use = None
5586+            return (self._sequence_number,
5587+                    self._root_hash,
5588+                    salt_to_use,
5589+                    self._segment_size,
5590+                    self._data_length,
5591+                    self._required_shares,
5592+                    self._total_shares,
5593+                    self._build_prefix(),
5594+                    self._get_offsets_tuple())
5595+        d.addCallback(_build_verinfo)
5596+        return d
5597+
5598+
5599+    def flush(self):
5600+        """
5601+        I flush my queue of read vectors.
5602+        """
5603+        d = self._read(self._readvs)
5604+        def _then(results):
5605+            self._readvs = []
5606+            if isinstance(results, failure.Failure):
5607+                self._queue_errbacks.notify(results)
5608+            else:
5609+                self._queue_observers.notify(results)
5610+            self._queue_observers = observer.ObserverList()
5611+            self._queue_errbacks = observer.ObserverList()
5612+        d.addBoth(_then)
5613+
5614+
5615+    def _read(self, readvs, force_remote=False, queue=False):
5616+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
5617+        # TODO: It's entirely possible to tweak this so that it just
5618+        # fulfills the requests that it can, and not demand that all
5619+        # requests are satisfiable before running it.
5620+        if not unsatisfiable and not force_remote:
5621+            results = [self._data[offset:offset+length]
5622+                       for (offset, length) in readvs]
5623+            results = {self.shnum: results}
5624+            return defer.succeed(results)
5625+        else:
5626+            if queue:
5627+                start = len(self._readvs)
5628+                self._readvs += readvs
5629+                end = len(self._readvs)
5630+                def _get_results(results, start, end):
5631+                    if not self.shnum in results:
5632+                        return {self._shnum: [""]}
5633+                    return {self.shnum: results[self.shnum][start:end]}
5634+                d = defer.Deferred()
5635+                d.addCallback(_get_results, start, end)
5636+                self._queue_observers.subscribe(d.callback)
5637+                self._queue_errbacks.subscribe(d.errback)
5638+                return d
5639+            return self._rref.callRemote("slot_readv",
5640+                                         self._storage_index,
5641+                                         [self.shnum],
5642+                                         readvs)
5643+
5644+
5645+    def is_sdmf(self):
5646+        """I tell my caller whether or not my remote file is SDMF or MDMF
5647+        """
5648+        d = self._maybe_fetch_offsets_and_header()
5649+        d.addCallback(lambda ignored:
5650+            self._version_number == 0)
5651+        return d
5652+
5653+
5654+class LayoutInvalid(Exception):
5655+    """
5656+    This isn't a valid MDMF mutable file
5657+    """
5658hunk ./src/allmydata/test/test_storage.py 2
5659 
5660-import time, os.path, stat, re, simplejson, struct
5661+import time, os.path, stat, re, simplejson, struct, shutil
5662 
5663 from twisted.trial import unittest
5664 
5665hunk ./src/allmydata/test/test_storage.py 22
5666 from allmydata.storage.expirer import LeaseCheckingCrawler
5667 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
5668      ReadBucketProxy
5669-from allmydata.interfaces import BadWriteEnablerError
5670-from allmydata.test.common import LoggingServiceParent
5671+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
5672+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
5673+                                     SIGNED_PREFIX, MDMFHEADER, \
5674+                                     MDMFOFFSETS, SDMFSlotWriteProxy
5675+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
5676+                                 SDMF_VERSION
5677+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
5678 from allmydata.test.common_web import WebRenderingMixin
5679 from allmydata.web.storage import StorageStatus, remove_prefix
5680 
5681hunk ./src/allmydata/test/test_storage.py 106
5682 
5683 class RemoteBucket:
5684 
5685+    def __init__(self):
5686+        self.read_count = 0
5687+        self.write_count = 0
5688+
5689     def callRemote(self, methname, *args, **kwargs):
5690         def _call():
5691             meth = getattr(self.target, "remote_" + methname)
5692hunk ./src/allmydata/test/test_storage.py 114
5693             return meth(*args, **kwargs)
5694+
5695+        if methname == "slot_readv":
5696+            self.read_count += 1
5697+        if "writev" in methname:
5698+            self.write_count += 1
5699+
5700         return defer.maybeDeferred(_call)
5701 
5702hunk ./src/allmydata/test/test_storage.py 122
5703+
5704 class BucketProxy(unittest.TestCase):
5705     def make_bucket(self, name, size):
5706         basedir = os.path.join("storage", "BucketProxy", name)
5707hunk ./src/allmydata/test/test_storage.py 1299
5708         self.failUnless(os.path.exists(prefixdir), prefixdir)
5709         self.failIf(os.path.exists(bucketdir), bucketdir)
5710 
5711+
5712+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
5713+    def setUp(self):
5714+        self.sparent = LoggingServiceParent()
5715+        self._lease_secret = itertools.count()
5716+        self.ss = self.create("MDMFProxies storage test server")
5717+        self.rref = RemoteBucket()
5718+        self.rref.target = self.ss
5719+        self.secrets = (self.write_enabler("we_secret"),
5720+                        self.renew_secret("renew_secret"),
5721+                        self.cancel_secret("cancel_secret"))
5722+        self.segment = "aaaaaa"
5723+        self.block = "aa"
5724+        self.salt = "a" * 16
5725+        self.block_hash = "a" * 32
5726+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
5727+        self.share_hash = self.block_hash
5728+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
5729+        self.signature = "foobarbaz"
5730+        self.verification_key = "vvvvvv"
5731+        self.encprivkey = "private"
5732+        self.root_hash = self.block_hash
5733+        self.salt_hash = self.root_hash
5734+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
5735+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
5736+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
5737+        # blockhashes and salt hashes are serialized in the same way,
5738+        # only we lop off the first element and store that in the
5739+        # header.
5740+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
5741+
5742+
5743+    def tearDown(self):
5744+        self.sparent.stopService()
5745+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
5746+
5747+
5748+    def write_enabler(self, we_tag):
5749+        return hashutil.tagged_hash("we_blah", we_tag)
5750+
5751+
5752+    def renew_secret(self, tag):
5753+        return hashutil.tagged_hash("renew_blah", str(tag))
5754+
5755+
5756+    def cancel_secret(self, tag):
5757+        return hashutil.tagged_hash("cancel_blah", str(tag))
5758+
5759+
5760+    def workdir(self, name):
5761+        basedir = os.path.join("storage", "MutableServer", name)
5762+        return basedir
5763+
5764+
5765+    def create(self, name):
5766+        workdir = self.workdir(name)
5767+        ss = StorageServer(workdir, "\x00" * 20)
5768+        ss.setServiceParent(self.sparent)
5769+        return ss
5770+
5771+
5772+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
5773+        # Start with the checkstring
5774+        data = struct.pack(">BQ32s",
5775+                           1,
5776+                           0,
5777+                           self.root_hash)
5778+        self.checkstring = data
5779+        # Next, the encoding parameters
5780+        if tail_segment:
5781+            data += struct.pack(">BBQQ",
5782+                                3,
5783+                                10,
5784+                                6,
5785+                                33)
5786+        elif empty:
5787+            data += struct.pack(">BBQQ",
5788+                                3,
5789+                                10,
5790+                                0,
5791+                                0)
5792+        else:
5793+            data += struct.pack(">BBQQ",
5794+                                3,
5795+                                10,
5796+                                6,
5797+                                36)
5798+        # Now we'll build the offsets.
5799+        sharedata = ""
5800+        if not tail_segment and not empty:
5801+            for i in xrange(6):
5802+                sharedata += self.salt + self.block
5803+        elif tail_segment:
5804+            for i in xrange(5):
5805+                sharedata += self.salt + self.block
5806+            sharedata += self.salt + "a"
5807+
5808+        # The encrypted private key comes after the shares + salts
5809+        offset_size = struct.calcsize(MDMFOFFSETS)
5810+        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
5811+        # The blockhashes come after the private key
5812+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
5813+        # The sharehashes come after the salt hashes
5814+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
5815+        # The signature comes after the share hash chain
5816+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
5817+        # The verification key comes after the signature
5818+        verification_offset = signature_offset + len(self.signature)
5819+        # The EOF comes after the verification key
5820+        eof_offset = verification_offset + len(self.verification_key)
5821+        data += struct.pack(MDMFOFFSETS,
5822+                            encrypted_private_key_offset,
5823+                            blockhashes_offset,
5824+                            sharehashes_offset,
5825+                            signature_offset,
5826+                            verification_offset,
5827+                            eof_offset)
5828+        self.offsets = {}
5829+        self.offsets['enc_privkey'] = encrypted_private_key_offset
5830+        self.offsets['block_hash_tree'] = blockhashes_offset
5831+        self.offsets['share_hash_chain'] = sharehashes_offset
5832+        self.offsets['signature'] = signature_offset
5833+        self.offsets['verification_key'] = verification_offset
5834+        self.offsets['EOF'] = eof_offset
5835+        # Next, we'll add in the salts and share data,
5836+        data += sharedata
5837+        # the private key,
5838+        data += self.encprivkey
5839+        # the block hash tree,
5840+        data += self.block_hash_tree_s
5841+        # the share hash chain,
5842+        data += self.share_hash_chain_s
5843+        # the signature,
5844+        data += self.signature
5845+        # and the verification key
5846+        data += self.verification_key
5847+        return data
5848+
5849+
5850+    def write_test_share_to_server(self,
5851+                                   storage_index,
5852+                                   tail_segment=False,
5853+                                   empty=False):
5854+        """
5855+        I write some data for the read tests to read to self.ss
5856+
5857+        If tail_segment=True, then I will write a share that has a
5858+        smaller tail segment than other segments.
5859+        """
5860+        write = self.ss.remote_slot_testv_and_readv_and_writev
5861+        data = self.build_test_mdmf_share(tail_segment, empty)
5862+        # Finally, we write the whole thing to the storage server in one
5863+        # pass.
5864+        testvs = [(0, 1, "eq", "")]
5865+        tws = {}
5866+        tws[0] = (testvs, [(0, data)], None)
5867+        readv = [(0, 1)]
5868+        results = write(storage_index, self.secrets, tws, readv)
5869+        self.failUnless(results[0])
5870+
5871+
5872+    def build_test_sdmf_share(self, empty=False):
5873+        if empty:
5874+            sharedata = ""
5875+        else:
5876+            sharedata = self.segment * 6
5877+        self.sharedata = sharedata
5878+        blocksize = len(sharedata) / 3
5879+        block = sharedata[:blocksize]
5880+        self.blockdata = block
5881+        prefix = struct.pack(">BQ32s16s BBQQ",
5882+                             0, # version,
5883+                             0,
5884+                             self.root_hash,
5885+                             self.salt,
5886+                             3,
5887+                             10,
5888+                             len(sharedata),
5889+                             len(sharedata),
5890+                            )
5891+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5892+        signature_offset = post_offset + len(self.verification_key)
5893+        sharehashes_offset = signature_offset + len(self.signature)
5894+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
5895+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
5896+        encprivkey_offset = sharedata_offset + len(block)
5897+        eof_offset = encprivkey_offset + len(self.encprivkey)
5898+        offsets = struct.pack(">LLLLQQ",
5899+                              signature_offset,
5900+                              sharehashes_offset,
5901+                              blockhashes_offset,
5902+                              sharedata_offset,
5903+                              encprivkey_offset,
5904+                              eof_offset)
5905+        final_share = "".join([prefix,
5906+                           offsets,
5907+                           self.verification_key,
5908+                           self.signature,
5909+                           self.share_hash_chain_s,
5910+                           self.block_hash_tree_s,
5911+                           block,
5912+                           self.encprivkey])
5913+        self.offsets = {}
5914+        self.offsets['signature'] = signature_offset
5915+        self.offsets['share_hash_chain'] = sharehashes_offset
5916+        self.offsets['block_hash_tree'] = blockhashes_offset
5917+        self.offsets['share_data'] = sharedata_offset
5918+        self.offsets['enc_privkey'] = encprivkey_offset
5919+        self.offsets['EOF'] = eof_offset
5920+        return final_share
5921+
5922+
5923+    def write_sdmf_share_to_server(self,
5924+                                   storage_index,
5925+                                   empty=False):
5926+        # Some tests need SDMF shares to verify that we can still
5927+        # read them. This method writes one, which resembles but is not
5928+        assert self.rref
5929+        write = self.ss.remote_slot_testv_and_readv_and_writev
5930+        share = self.build_test_sdmf_share(empty)
5931+        testvs = [(0, 1, "eq", "")]
5932+        tws = {}
5933+        tws[0] = (testvs, [(0, share)], None)
5934+        readv = []
5935+        results = write(storage_index, self.secrets, tws, readv)
5936+        self.failUnless(results[0])
5937+
5938+
5939+    def test_read(self):
5940+        self.write_test_share_to_server("si1")
5941+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
5942+        # Check that every method equals what we expect it to.
5943+        d = defer.succeed(None)
5944+        def _check_block_and_salt((block, salt)):
5945+            self.failUnlessEqual(block, self.block)
5946+            self.failUnlessEqual(salt, self.salt)
5947+
5948+        for i in xrange(6):
5949+            d.addCallback(lambda ignored, i=i:
5950+                mr.get_block_and_salt(i))
5951+            d.addCallback(_check_block_and_salt)
5952+
5953+        d.addCallback(lambda ignored:
5954+            mr.get_encprivkey())
5955+        d.addCallback(lambda encprivkey:
5956+            self.failUnlessEqual(self.encprivkey, encprivkey))
5957+
5958+        d.addCallback(lambda ignored:
5959+            mr.get_blockhashes())
5960+        d.addCallback(lambda blockhashes:
5961+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
5962+
5963+        d.addCallback(lambda ignored:
5964+            mr.get_sharehashes())
5965+        d.addCallback(lambda sharehashes:
5966+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
5967+
5968+        d.addCallback(lambda ignored:
5969+            mr.get_signature())
5970+        d.addCallback(lambda signature:
5971+            self.failUnlessEqual(signature, self.signature))
5972+
5973+        d.addCallback(lambda ignored:
5974+            mr.get_verification_key())
5975+        d.addCallback(lambda verification_key:
5976+            self.failUnlessEqual(verification_key, self.verification_key))
5977+
5978+        d.addCallback(lambda ignored:
5979+            mr.get_seqnum())
5980+        d.addCallback(lambda seqnum:
5981+            self.failUnlessEqual(seqnum, 0))
5982+
5983+        d.addCallback(lambda ignored:
5984+            mr.get_root_hash())
5985+        d.addCallback(lambda root_hash:
5986+            self.failUnlessEqual(self.root_hash, root_hash))
5987+
5988+        d.addCallback(lambda ignored:
5989+            mr.get_seqnum())
5990+        d.addCallback(lambda seqnum:
5991+            self.failUnlessEqual(0, seqnum))
5992+
5993+        d.addCallback(lambda ignored:
5994+            mr.get_encoding_parameters())
5995+        def _check_encoding_parameters((k, n, segsize, datalen)):
5996+            self.failUnlessEqual(k, 3)
5997+            self.failUnlessEqual(n, 10)
5998+            self.failUnlessEqual(segsize, 6)
5999+            self.failUnlessEqual(datalen, 36)
6000+        d.addCallback(_check_encoding_parameters)
6001+
6002+        d.addCallback(lambda ignored:
6003+            mr.get_checkstring())
6004+        d.addCallback(lambda checkstring:
6005+            self.failUnlessEqual(checkstring, checkstring))
6006+        return d
6007+
6008+
6009+    def test_read_with_different_tail_segment_size(self):
6010+        self.write_test_share_to_server("si1", tail_segment=True)
6011+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6012+        d = mr.get_block_and_salt(5)
6013+        def _check_tail_segment(results):
6014+            block, salt = results
6015+            self.failUnlessEqual(len(block), 1)
6016+            self.failUnlessEqual(block, "a")
6017+        d.addCallback(_check_tail_segment)
6018+        return d
6019+
6020+
6021+    def test_get_block_with_invalid_segnum(self):
6022+        self.write_test_share_to_server("si1")
6023+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6024+        d = defer.succeed(None)
6025+        d.addCallback(lambda ignored:
6026+            self.shouldFail(LayoutInvalid, "test invalid segnum",
6027+                            None,
6028+                            mr.get_block_and_salt, 7))
6029+        return d
6030+
6031+
6032+    def test_get_encoding_parameters_first(self):
6033+        self.write_test_share_to_server("si1")
6034+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6035+        d = mr.get_encoding_parameters()
6036+        def _check_encoding_parameters((k, n, segment_size, datalen)):
6037+            self.failUnlessEqual(k, 3)
6038+            self.failUnlessEqual(n, 10)
6039+            self.failUnlessEqual(segment_size, 6)
6040+            self.failUnlessEqual(datalen, 36)
6041+        d.addCallback(_check_encoding_parameters)
6042+        return d
6043+
6044+
6045+    def test_get_seqnum_first(self):
6046+        self.write_test_share_to_server("si1")
6047+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6048+        d = mr.get_seqnum()
6049+        d.addCallback(lambda seqnum:
6050+            self.failUnlessEqual(seqnum, 0))
6051+        return d
6052+
6053+
6054+    def test_get_root_hash_first(self):
6055+        self.write_test_share_to_server("si1")
6056+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6057+        d = mr.get_root_hash()
6058+        d.addCallback(lambda root_hash:
6059+            self.failUnlessEqual(root_hash, self.root_hash))
6060+        return d
6061+
6062+
6063+    def test_get_checkstring_first(self):
6064+        self.write_test_share_to_server("si1")
6065+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6066+        d = mr.get_checkstring()
6067+        d.addCallback(lambda checkstring:
6068+            self.failUnlessEqual(checkstring, self.checkstring))
6069+        return d
6070+
6071+
6072+    def test_write_read_vectors(self):
6073+        # When writing for us, the storage server will return to us a
6074+        # read vector, along with its result. If a write fails because
6075+        # the test vectors failed, this read vector can help us to
6076+        # diagnose the problem. This test ensures that the read vector
6077+        # is working appropriately.
6078+        mw = self._make_new_mw("si1", 0)
6079+        d = defer.succeed(None)
6080+
6081+        # Write one share. This should return a checkstring of nothing,
6082+        # since there is no data there.
6083+        d.addCallback(lambda ignored:
6084+            mw.put_block(self.block, 0, self.salt))
6085+        def _check_first_write(results):
6086+            result, readvs = results
6087+            self.failUnless(result)
6088+            self.failIf(readvs)
6089+        d.addCallback(_check_first_write)
6090+        # Now, there should be a different checkstring returned when
6091+        # we write other shares
6092+        d.addCallback(lambda ignored:
6093+            mw.put_block(self.block, 1, self.salt))
6094+        def _check_next_write(results):
6095+            result, readvs = results
6096+            self.failUnless(result)
6097+            self.expected_checkstring = mw.get_checkstring()
6098+            self.failUnlessIn(0, readvs)
6099+            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
6100+        d.addCallback(_check_next_write)
6101+        # Add the other four shares
6102+        for i in xrange(2, 6):
6103+            d.addCallback(lambda ignored, i=i:
6104+                mw.put_block(self.block, i, self.salt))
6105+            d.addCallback(_check_next_write)
6106+        # Add the encrypted private key
6107+        d.addCallback(lambda ignored:
6108+            mw.put_encprivkey(self.encprivkey))
6109+        d.addCallback(_check_next_write)
6110+        # Add the block hash tree and share hash tree
6111+        d.addCallback(lambda ignored:
6112+            mw.put_blockhashes(self.block_hash_tree))
6113+        d.addCallback(_check_next_write)
6114+        d.addCallback(lambda ignored:
6115+            mw.put_sharehashes(self.share_hash_chain))
6116+        d.addCallback(_check_next_write)
6117+        # Add the root hash and the salt hash. This should change the
6118+        # checkstring, but not in a way that we'll be able to see right
6119+        # now, since the read vectors are applied before the write
6120+        # vectors.
6121+        d.addCallback(lambda ignored:
6122+            mw.put_root_hash(self.root_hash))
6123+        def _check_old_testv_after_new_one_is_written(results):
6124+            result, readvs = results
6125+            self.failUnless(result)
6126+            self.failUnlessIn(0, readvs)
6127+            self.failUnlessEqual(self.expected_checkstring,
6128+                                 readvs[0][0])
6129+            new_checkstring = mw.get_checkstring()
6130+            self.failIfEqual(new_checkstring,
6131+                             readvs[0][0])
6132+        d.addCallback(_check_old_testv_after_new_one_is_written)
6133+        # Now add the signature. This should succeed, meaning that the
6134+        # data gets written and the read vector matches what the writer
6135+        # thinks should be there.
6136+        d.addCallback(lambda ignored:
6137+            mw.put_signature(self.signature))
6138+        d.addCallback(_check_next_write)
6139+        # The checkstring remains the same for the rest of the process.
6140+        return d
6141+
6142+
6143+    def test_blockhashes_after_share_hash_chain(self):
6144+        mw = self._make_new_mw("si1", 0)
6145+        d = defer.succeed(None)
6146+        # Put everything up to and including the share hash chain
6147+        for i in xrange(6):
6148+            d.addCallback(lambda ignored, i=i:
6149+                mw.put_block(self.block, i, self.salt))
6150+        d.addCallback(lambda ignored:
6151+            mw.put_encprivkey(self.encprivkey))
6152+        d.addCallback(lambda ignored:
6153+            mw.put_blockhashes(self.block_hash_tree))
6154+        d.addCallback(lambda ignored:
6155+            mw.put_sharehashes(self.share_hash_chain))
6156+
6157+        # Now try to put the block hash tree again.
6158+        d.addCallback(lambda ignored:
6159+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
6160+                            None,
6161+                            mw.put_blockhashes, self.block_hash_tree))
6162+        return d
6163+
6164+
6165+    def test_encprivkey_after_blockhashes(self):
6166+        mw = self._make_new_mw("si1", 0)
6167+        d = defer.succeed(None)
6168+        # Put everything up to and including the block hash tree
6169+        for i in xrange(6):
6170+            d.addCallback(lambda ignored, i=i:
6171+                mw.put_block(self.block, i, self.salt))
6172+        d.addCallback(lambda ignored:
6173+            mw.put_encprivkey(self.encprivkey))
6174+        d.addCallback(lambda ignored:
6175+            mw.put_blockhashes(self.block_hash_tree))
6176+        d.addCallback(lambda ignored:
6177+            self.shouldFail(LayoutInvalid, "out of order private key",
6178+                            None,
6179+                            mw.put_encprivkey, self.encprivkey))
6180+        return d
6181+
6182+
6183+    def test_share_hash_chain_after_signature(self):
6184+        mw = self._make_new_mw("si1", 0)
6185+        d = defer.succeed(None)
6186+        # Put everything up to and including the signature
6187+        for i in xrange(6):
6188+            d.addCallback(lambda ignored, i=i:
6189+                mw.put_block(self.block, i, self.salt))
6190+        d.addCallback(lambda ignored:
6191+            mw.put_encprivkey(self.encprivkey))
6192+        d.addCallback(lambda ignored:
6193+            mw.put_blockhashes(self.block_hash_tree))
6194+        d.addCallback(lambda ignored:
6195+            mw.put_sharehashes(self.share_hash_chain))
6196+        d.addCallback(lambda ignored:
6197+            mw.put_root_hash(self.root_hash))
6198+        d.addCallback(lambda ignored:
6199+            mw.put_signature(self.signature))
6200+        # Now try to put the share hash chain again. This should fail
6201+        d.addCallback(lambda ignored:
6202+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
6203+                            None,
6204+                            mw.put_sharehashes, self.share_hash_chain))
6205+        return d
6206+
6207+
6208+    def test_signature_after_verification_key(self):
6209+        mw = self._make_new_mw("si1", 0)
6210+        d = defer.succeed(None)
6211+        # Put everything up to and including the verification key.
6212+        for i in xrange(6):
6213+            d.addCallback(lambda ignored, i=i:
6214+                mw.put_block(self.block, i, self.salt))
6215+        d.addCallback(lambda ignored:
6216+            mw.put_encprivkey(self.encprivkey))
6217+        d.addCallback(lambda ignored:
6218+            mw.put_blockhashes(self.block_hash_tree))
6219+        d.addCallback(lambda ignored:
6220+            mw.put_sharehashes(self.share_hash_chain))
6221+        d.addCallback(lambda ignored:
6222+            mw.put_root_hash(self.root_hash))
6223+        d.addCallback(lambda ignored:
6224+            mw.put_signature(self.signature))
6225+        d.addCallback(lambda ignored:
6226+            mw.put_verification_key(self.verification_key))
6227+        # Now try to put the signature again. This should fail
6228+        d.addCallback(lambda ignored:
6229+            self.shouldFail(LayoutInvalid, "signature after verification",
6230+                            None,
6231+                            mw.put_signature, self.signature))
6232+        return d
6233+
6234+
6235+    def test_uncoordinated_write(self):
6236+        # Make two mutable writers, both pointing to the same storage
6237+        # server, both at the same storage index, and try writing to the
6238+        # same share.
6239+        mw1 = self._make_new_mw("si1", 0)
6240+        mw2 = self._make_new_mw("si1", 0)
6241+        d = defer.succeed(None)
6242+        def _check_success(results):
6243+            result, readvs = results
6244+            self.failUnless(result)
6245+
6246+        def _check_failure(results):
6247+            result, readvs = results
6248+            self.failIf(result)
6249+
6250+        d.addCallback(lambda ignored:
6251+            mw1.put_block(self.block, 0, self.salt))
6252+        d.addCallback(_check_success)
6253+        d.addCallback(lambda ignored:
6254+            mw2.put_block(self.block, 0, self.salt))
6255+        d.addCallback(_check_failure)
6256+        return d
6257+
6258+
6259+    def test_invalid_salt_size(self):
6260+        # Salts need to be 16 bytes in size. Writes that attempt to
6261+        # write more or less than this should be rejected.
6262+        mw = self._make_new_mw("si1", 0)
6263+        invalid_salt = "a" * 17 # 17 bytes
6264+        another_invalid_salt = "b" * 15 # 15 bytes
6265+        d = defer.succeed(None)
6266+        d.addCallback(lambda ignored:
6267+            self.shouldFail(LayoutInvalid, "salt too big",
6268+                            None,
6269+                            mw.put_block, self.block, 0, invalid_salt))
6270+        d.addCallback(lambda ignored:
6271+            self.shouldFail(LayoutInvalid, "salt too small",
6272+                            None,
6273+                            mw.put_block, self.block, 0,
6274+                            another_invalid_salt))
6275+        return d
6276+
6277+
6278+    def test_write_test_vectors(self):
6279+        # If we give the write proxy a bogus test vector at
6280+        # any point during the process, it should fail to write.
6281+        mw = self._make_new_mw("si1", 0)
6282+        mw.set_checkstring("this is a lie")
6283+        # The initial write should be expecting to find the improbable
6284+        # checkstring above in place; finding nothing, it should fail.
6285+        d = defer.succeed(None)
6286+        d.addCallback(lambda ignored:
6287+            mw.put_block(self.block, 0, self.salt))
6288+        def _check_failure(results):
6289+            result, readv = results
6290+            self.failIf(result)
6291+        d.addCallback(_check_failure)
6292+        # Now set the checkstring to the empty string, which
6293+        # indicates that no share is there.
6294+        d.addCallback(lambda ignored:
6295+            mw.set_checkstring(""))
6296+        d.addCallback(lambda ignored:
6297+            mw.put_block(self.block, 0, self.salt))
6298+        def _check_success(results):
6299+            result, readv = results
6300+            self.failUnless(result)
6301+        d.addCallback(_check_success)
6302+        # Now set the checkstring to something wrong
6303+        d.addCallback(lambda ignored:
6304+            mw.set_checkstring("something wrong"))
6305+        # This should fail to do anything
6306+        d.addCallback(lambda ignored:
6307+            mw.put_block(self.block, 1, self.salt))
6308+        d.addCallback(_check_failure)
6309+        # Now set it back to what it should be.
6310+        d.addCallback(lambda ignored:
6311+            mw.set_checkstring(mw.get_checkstring()))
6312+        for i in xrange(1, 6):
6313+            d.addCallback(lambda ignored, i=i:
6314+                mw.put_block(self.block, i, self.salt))
6315+            d.addCallback(_check_success)
6316+        d.addCallback(lambda ignored:
6317+            mw.put_encprivkey(self.encprivkey))
6318+        d.addCallback(_check_success)
6319+        d.addCallback(lambda ignored:
6320+            mw.put_blockhashes(self.block_hash_tree))
6321+        d.addCallback(_check_success)
6322+        d.addCallback(lambda ignored:
6323+            mw.put_sharehashes(self.share_hash_chain))
6324+        d.addCallback(_check_success)
6325+        def _keep_old_checkstring(ignored):
6326+            self.old_checkstring = mw.get_checkstring()
6327+            mw.set_checkstring("foobarbaz")
6328+        d.addCallback(_keep_old_checkstring)
6329+        d.addCallback(lambda ignored:
6330+            mw.put_root_hash(self.root_hash))
6331+        d.addCallback(_check_failure)
6332+        d.addCallback(lambda ignored:
6333+            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
6334+        def _restore_old_checkstring(ignored):
6335+            mw.set_checkstring(self.old_checkstring)
6336+        d.addCallback(_restore_old_checkstring)
6337+        d.addCallback(lambda ignored:
6338+            mw.put_root_hash(self.root_hash))
6339+        d.addCallback(_check_success)
6340+        # The checkstring should have been set appropriately for us on
6341+        # the last write; if we try to change it to something else,
6342+        # that change should cause the verification key step to fail.
6343+        d.addCallback(lambda ignored:
6344+            mw.set_checkstring("something else"))
6345+        d.addCallback(lambda ignored:
6346+            mw.put_signature(self.signature))
6347+        d.addCallback(_check_failure)
6348+        d.addCallback(lambda ignored:
6349+            mw.set_checkstring(mw.get_checkstring()))
6350+        d.addCallback(lambda ignored:
6351+            mw.put_signature(self.signature))
6352+        d.addCallback(_check_success)
6353+        d.addCallback(lambda ignored:
6354+            mw.put_verification_key(self.verification_key))
6355+        d.addCallback(_check_success)
6356+        return d
6357+
6358+
6359+    def test_offset_only_set_on_success(self):
6360+        # The write proxy should be smart enough to detect when a write
6361+        # has failed, and to temper its definition of progress based on
6362+        # that.
6363+        mw = self._make_new_mw("si1", 0)
6364+        d = defer.succeed(None)
6365+        for i in xrange(1, 6):
6366+            d.addCallback(lambda ignored, i=i:
6367+                mw.put_block(self.block, i, self.salt))
6368+        def _break_checkstring(ignored):
6369+            self._old_checkstring = mw.get_checkstring()
6370+            mw.set_checkstring("foobarbaz")
6371+
6372+        def _fix_checkstring(ignored):
6373+            mw.set_checkstring(self._old_checkstring)
6374+
6375+        d.addCallback(_break_checkstring)
6376+
6377+        # Setting the encrypted private key shouldn't work now, which is
6378+        # to be expected and is tested elsewhere. We also want to make
6379+        # sure that we can't add the block hash tree after a failed
6380+        # write of this sort.
6381+        d.addCallback(lambda ignored:
6382+            mw.put_encprivkey(self.encprivkey))
6383+        d.addCallback(lambda ignored:
6384+            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
6385+                            None,
6386+                            mw.put_blockhashes, self.block_hash_tree))
6387+        d.addCallback(_fix_checkstring)
6388+        d.addCallback(lambda ignored:
6389+            mw.put_encprivkey(self.encprivkey))
6390+        d.addCallback(_break_checkstring)
6391+        d.addCallback(lambda ignored:
6392+            mw.put_blockhashes(self.block_hash_tree))
6393+        d.addCallback(lambda ignored:
6394+            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
6395+                            None,
6396+                            mw.put_sharehashes, self.share_hash_chain))
6397+        d.addCallback(_fix_checkstring)
6398+        d.addCallback(lambda ignored:
6399+            mw.put_blockhashes(self.block_hash_tree))
6400+        d.addCallback(_break_checkstring)
6401+        d.addCallback(lambda ignored:
6402+            mw.put_sharehashes(self.share_hash_chain))
6403+        d.addCallback(lambda ignored:
6404+            self.shouldFail(LayoutInvalid, "out-of-order root hash",
6405+                            None,
6406+                            mw.put_root_hash, self.root_hash))
6407+        d.addCallback(_fix_checkstring)
6408+        d.addCallback(lambda ignored:
6409+            mw.put_sharehashes(self.share_hash_chain))
6410+        d.addCallback(_break_checkstring)
6411+        d.addCallback(lambda ignored:
6412+            mw.put_root_hash(self.root_hash))
6413+        d.addCallback(lambda ignored:
6414+            self.shouldFail(LayoutInvalid, "out-of-order signature",
6415+                            None,
6416+                            mw.put_signature, self.signature))
6417+        d.addCallback(_fix_checkstring)
6418+        d.addCallback(lambda ignored:
6419+            mw.put_root_hash(self.root_hash))
6420+        d.addCallback(_break_checkstring)
6421+        d.addCallback(lambda ignored:
6422+            mw.put_signature(self.signature))
6423+        d.addCallback(lambda ignored:
6424+            self.shouldFail(LayoutInvalid, "out-of-order verification key",
6425+                            None,
6426+                            mw.put_verification_key,
6427+                            self.verification_key))
6428+        d.addCallback(_fix_checkstring)
6429+        d.addCallback(lambda ignored:
6430+            mw.put_signature(self.signature))
6431+        d.addCallback(_break_checkstring)
6432+        d.addCallback(lambda ignored:
6433+            mw.put_verification_key(self.verification_key))
6434+        d.addCallback(lambda ignored:
6435+            self.shouldFail(LayoutInvalid, "out-of-order finish",
6436+                            None,
6437+                            mw.finish_publishing))
6438+        return d
6439+
6440+
6441+    def serialize_blockhashes(self, blockhashes):
6442+        return "".join(blockhashes)
6443+
6444+
6445+    def serialize_sharehashes(self, sharehashes):
6446+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
6447+                        for i in sorted(sharehashes.keys())])
6448+        return ret
6449+
6450+
6451+    def test_write(self):
6452+        # This translates to a file with 6 6-byte segments, and with 2-byte
6453+        # blocks.
6454+        mw = self._make_new_mw("si1", 0)
6455+        mw2 = self._make_new_mw("si1", 1)
6456+        # Test writing some blocks.
6457+        read = self.ss.remote_slot_readv
6458+        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
6459+        written_block_size = 2 + len(self.salt)
6460+        written_block = self.block + self.salt
6461+        def _check_block_write(i, share):
6462+            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
6463+                                {share: [written_block]})
6464+        d = defer.succeed(None)
6465+        for i in xrange(6):
6466+            d.addCallback(lambda ignored, i=i:
6467+                mw.put_block(self.block, i, self.salt))
6468+            d.addCallback(lambda ignored, i=i:
6469+                _check_block_write(i, 0))
6470+        # Now try the same thing, but with share 1 instead of share 0.
6471+        for i in xrange(6):
6472+            d.addCallback(lambda ignored, i=i:
6473+                mw2.put_block(self.block, i, self.salt))
6474+            d.addCallback(lambda ignored, i=i:
6475+                _check_block_write(i, 1))
6476+
6477+        # Next, we make a fake encrypted private key, and put it onto the
6478+        # storage server.
6479+        d.addCallback(lambda ignored:
6480+            mw.put_encprivkey(self.encprivkey))
6481+        expected_private_key_offset = expected_sharedata_offset + \
6482+                                      len(written_block) * 6
6483+        self.failUnlessEqual(len(self.encprivkey), 7)
6484+        d.addCallback(lambda ignored:
6485+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6486+                                 {0: [self.encprivkey]}))
6487+
6488+        # Next, we put a fake block hash tree.
6489+        d.addCallback(lambda ignored:
6490+            mw.put_blockhashes(self.block_hash_tree))
6491+        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6492+        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6493+        d.addCallback(lambda ignored:
6494+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6495+                                 {0: [self.block_hash_tree_s]}))
6496+
6497+        # Next, put a fake share hash chain
6498+        d.addCallback(lambda ignored:
6499+            mw.put_sharehashes(self.share_hash_chain))
6500+        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6501+        d.addCallback(lambda ignored:
6502+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6503+                                 {0: [self.share_hash_chain_s]}))
6504+
6505+        # Next, we put what is supposed to be the root hash of
6506+        # our share hash tree but isn't       
6507+        d.addCallback(lambda ignored:
6508+            mw.put_root_hash(self.root_hash))
6509+        # The root hash gets inserted at byte 9 (its position is in the header,
6510+        # and is fixed).
6511+        def _check(ignored):
6512+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
6513+                                 {0: [self.root_hash]})
6514+        d.addCallback(_check)
6515+
6516+        # Next, we put a signature of the header block.
6517+        d.addCallback(lambda ignored:
6518+            mw.put_signature(self.signature))
6519+        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6520+        self.failUnlessEqual(len(self.signature), 9)
6521+        d.addCallback(lambda ignored:
6522+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6523+                                 {0: [self.signature]}))
6524+
6525+        # Next, we put the verification key
6526+        d.addCallback(lambda ignored:
6527+            mw.put_verification_key(self.verification_key))
6528+        expected_verification_key_offset = expected_signature_offset + len(self.signature)
6529+        self.failUnlessEqual(len(self.verification_key), 6)
6530+        d.addCallback(lambda ignored:
6531+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6532+                                 {0: [self.verification_key]}))
6533+
6534+        def _check_signable(ignored):
6535+            # Make sure that the signable is what we think it should be.
6536+            signable = mw.get_signable()
6537+            verno, seq, roothash, k, n, segsize, datalen = \
6538+                                            struct.unpack(">BQ32sBBQQ",
6539+                                                          signable)
6540+            self.failUnlessEqual(verno, 1)
6541+            self.failUnlessEqual(seq, 0)
6542+            self.failUnlessEqual(roothash, self.root_hash)
6543+            self.failUnlessEqual(k, 3)
6544+            self.failUnlessEqual(n, 10)
6545+            self.failUnlessEqual(segsize, 6)
6546+            self.failUnlessEqual(datalen, 36)
6547+        d.addCallback(_check_signable)
6548+        # Next, we cause the offset table to be published.
6549+        d.addCallback(lambda ignored:
6550+            mw.finish_publishing())
6551+        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6552+
6553+        def _check_offsets(ignored):
6554+            # Check the version number to make sure that it is correct.
6555+            expected_version_number = struct.pack(">B", 1)
6556+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
6557+                                 {0: [expected_version_number]})
6558+            # Check the sequence number to make sure that it is correct
6559+            expected_sequence_number = struct.pack(">Q", 0)
6560+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
6561+                                 {0: [expected_sequence_number]})
6562+            # Check that the encoding parameters (k, N, segement size, data
6563+            # length) are what they should be. These are  3, 10, 6, 36
6564+            expected_k = struct.pack(">B", 3)
6565+            self.failUnlessEqual(read("si1", [0], [(41, 1)]),
6566+                                 {0: [expected_k]})
6567+            expected_n = struct.pack(">B", 10)
6568+            self.failUnlessEqual(read("si1", [0], [(42, 1)]),
6569+                                 {0: [expected_n]})
6570+            expected_segment_size = struct.pack(">Q", 6)
6571+            self.failUnlessEqual(read("si1", [0], [(43, 8)]),
6572+                                 {0: [expected_segment_size]})
6573+            expected_data_length = struct.pack(">Q", 36)
6574+            self.failUnlessEqual(read("si1", [0], [(51, 8)]),
6575+                                 {0: [expected_data_length]})
6576+            expected_offset = struct.pack(">Q", expected_private_key_offset)
6577+            self.failUnlessEqual(read("si1", [0], [(59, 8)]),
6578+                                 {0: [expected_offset]})
6579+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
6580+            self.failUnlessEqual(read("si1", [0], [(67, 8)]),
6581+                                 {0: [expected_offset]})
6582+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6583+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
6584+                                 {0: [expected_offset]})
6585+            expected_offset = struct.pack(">Q", expected_signature_offset)
6586+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6587+                                 {0: [expected_offset]})
6588+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6589+            self.failUnlessEqual(read("si1", [0], [(91, 8)]),
6590+                                 {0: [expected_offset]})
6591+            expected_offset = struct.pack(">Q", expected_eof_offset)
6592+            self.failUnlessEqual(read("si1", [0], [(99, 8)]),
6593+                                 {0: [expected_offset]})
6594+        d.addCallback(_check_offsets)
6595+        return d
6596+
6597+    def _make_new_mw(self, si, share, datalength=36):
6598+        # This is a file of size 36 bytes. Since it has a segment
6599+        # size of 6, we know that it has 6 byte segments, which will
6600+        # be split into blocks of 2 bytes because our FEC k
6601+        # parameter is 3.
6602+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
6603+                                6, datalength)
6604+        return mw
6605+
6606+
6607+    def test_write_rejected_with_too_many_blocks(self):
6608+        mw = self._make_new_mw("si0", 0)
6609+
6610+        # Try writing too many blocks. We should not be able to write
6611+        # more than 6
6612+        # blocks into each share.
6613+        d = defer.succeed(None)
6614+        for i in xrange(6):
6615+            d.addCallback(lambda ignored, i=i:
6616+                mw.put_block(self.block, i, self.salt))
6617+        d.addCallback(lambda ignored:
6618+            self.shouldFail(LayoutInvalid, "too many blocks",
6619+                            None,
6620+                            mw.put_block, self.block, 7, self.salt))
6621+        return d
6622+
6623+
6624+    def test_write_rejected_with_invalid_salt(self):
6625+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
6626+        # less should cause an error.
6627+        mw = self._make_new_mw("si1", 0)
6628+        bad_salt = "a" * 17 # 17 bytes
6629+        d = defer.succeed(None)
6630+        d.addCallback(lambda ignored:
6631+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
6632+                            None, mw.put_block, self.block, 7, bad_salt))
6633+        return d
6634+
6635+
6636+    def test_write_rejected_with_invalid_root_hash(self):
6637+        # Try writing an invalid root hash. This should be SHA256d, and
6638+        # 32 bytes long as a result.
6639+        mw = self._make_new_mw("si2", 0)
6640+        # 17 bytes != 32 bytes
6641+        invalid_root_hash = "a" * 17
6642+        d = defer.succeed(None)
6643+        # Before this test can work, we need to put some blocks + salts,
6644+        # a block hash tree, and a share hash tree. Otherwise, we'll see
6645+        # failures that match what we are looking for, but are caused by
6646+        # the constraints imposed on operation ordering.
6647+        for i in xrange(6):
6648+            d.addCallback(lambda ignored, i=i:
6649+                mw.put_block(self.block, i, self.salt))
6650+        d.addCallback(lambda ignored:
6651+            mw.put_encprivkey(self.encprivkey))
6652+        d.addCallback(lambda ignored:
6653+            mw.put_blockhashes(self.block_hash_tree))
6654+        d.addCallback(lambda ignored:
6655+            mw.put_sharehashes(self.share_hash_chain))
6656+        d.addCallback(lambda ignored:
6657+            self.shouldFail(LayoutInvalid, "invalid root hash",
6658+                            None, mw.put_root_hash, invalid_root_hash))
6659+        return d
6660+
6661+
6662+    def test_write_rejected_with_invalid_blocksize(self):
6663+        # The blocksize implied by the writer that we get from
6664+        # _make_new_mw is 2bytes -- any more or any less than this
6665+        # should be cause for failure, unless it is the tail segment, in
6666+        # which case it may not be failure.
6667+        invalid_block = "a"
6668+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
6669+                                             # one byte blocks
6670+        # 1 bytes != 2 bytes
6671+        d = defer.succeed(None)
6672+        d.addCallback(lambda ignored, invalid_block=invalid_block:
6673+            self.shouldFail(LayoutInvalid, "test blocksize too small",
6674+                            None, mw.put_block, invalid_block, 0,
6675+                            self.salt))
6676+        invalid_block = invalid_block * 3
6677+        # 3 bytes != 2 bytes
6678+        d.addCallback(lambda ignored:
6679+            self.shouldFail(LayoutInvalid, "test blocksize too large",
6680+                            None,
6681+                            mw.put_block, invalid_block, 0, self.salt))
6682+        for i in xrange(5):
6683+            d.addCallback(lambda ignored, i=i:
6684+                mw.put_block(self.block, i, self.salt))
6685+        # Try to put an invalid tail segment
6686+        d.addCallback(lambda ignored:
6687+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
6688+                            None,
6689+                            mw.put_block, self.block, 5, self.salt))
6690+        valid_block = "a"
6691+        d.addCallback(lambda ignored:
6692+            mw.put_block(valid_block, 5, self.salt))
6693+        return d
6694+
6695+
6696+    def test_write_enforces_order_constraints(self):
6697+        # We require that the MDMFSlotWriteProxy be interacted with in a
6698+        # specific way.
6699+        # That way is:
6700+        # 0: __init__
6701+        # 1: write blocks and salts
6702+        # 2: Write the encrypted private key
6703+        # 3: Write the block hashes
6704+        # 4: Write the share hashes
6705+        # 5: Write the root hash and salt hash
6706+        # 6: Write the signature and verification key
6707+        # 7: Write the file.
6708+        #
6709+        # Some of these can be performed out-of-order, and some can't.
6710+        # The dependencies that I want to test here are:
6711+        #  - Private key before block hashes
6712+        #  - share hashes and block hashes before root hash
6713+        #  - root hash before signature
6714+        #  - signature before verification key
6715+        mw0 = self._make_new_mw("si0", 0)
6716+        # Write some shares
6717+        d = defer.succeed(None)
6718+        for i in xrange(6):
6719+            d.addCallback(lambda ignored, i=i:
6720+                mw0.put_block(self.block, i, self.salt))
6721+        # Try to write the block hashes before writing the encrypted
6722+        # private key
6723+        d.addCallback(lambda ignored:
6724+            self.shouldFail(LayoutInvalid, "block hashes before key",
6725+                            None, mw0.put_blockhashes,
6726+                            self.block_hash_tree))
6727+
6728+        # Write the private key.
6729+        d.addCallback(lambda ignored:
6730+            mw0.put_encprivkey(self.encprivkey))
6731+
6732+
6733+        # Try to write the share hash chain without writing the block
6734+        # hash tree
6735+        d.addCallback(lambda ignored:
6736+            self.shouldFail(LayoutInvalid, "share hash chain before "
6737+                                           "salt hash tree",
6738+                            None,
6739+                            mw0.put_sharehashes, self.share_hash_chain))
6740+
6741+        # Try to write the root hash and without writing either the
6742+        # block hashes or the or the share hashes
6743+        d.addCallback(lambda ignored:
6744+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6745+                            None,
6746+                            mw0.put_root_hash, self.root_hash))
6747+
6748+        # Now write the block hashes and try again
6749+        d.addCallback(lambda ignored:
6750+            mw0.put_blockhashes(self.block_hash_tree))
6751+
6752+        d.addCallback(lambda ignored:
6753+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6754+                            None, mw0.put_root_hash, self.root_hash))
6755+
6756+        # We haven't yet put the root hash on the share, so we shouldn't
6757+        # be able to sign it.
6758+        d.addCallback(lambda ignored:
6759+            self.shouldFail(LayoutInvalid, "signature before root hash",
6760+                            None, mw0.put_signature, self.signature))
6761+
6762+        d.addCallback(lambda ignored:
6763+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
6764+
6765+        # ..and, since that fails, we also shouldn't be able to put the
6766+        # verification key.
6767+        d.addCallback(lambda ignored:
6768+            self.shouldFail(LayoutInvalid, "key before signature",
6769+                            None, mw0.put_verification_key,
6770+                            self.verification_key))
6771+
6772+        # Now write the share hashes.
6773+        d.addCallback(lambda ignored:
6774+            mw0.put_sharehashes(self.share_hash_chain))
6775+        # We should be able to write the root hash now too
6776+        d.addCallback(lambda ignored:
6777+            mw0.put_root_hash(self.root_hash))
6778+
6779+        # We should still be unable to put the verification key
6780+        d.addCallback(lambda ignored:
6781+            self.shouldFail(LayoutInvalid, "key before signature",
6782+                            None, mw0.put_verification_key,
6783+                            self.verification_key))
6784+
6785+        d.addCallback(lambda ignored:
6786+            mw0.put_signature(self.signature))
6787+
6788+        # We shouldn't be able to write the offsets to the remote server
6789+        # until the offset table is finished; IOW, until we have written
6790+        # the verification key.
6791+        d.addCallback(lambda ignored:
6792+            self.shouldFail(LayoutInvalid, "offsets before verification key",
6793+                            None,
6794+                            mw0.finish_publishing))
6795+
6796+        d.addCallback(lambda ignored:
6797+            mw0.put_verification_key(self.verification_key))
6798+        return d
6799+
6800+
6801+    def test_end_to_end(self):
6802+        mw = self._make_new_mw("si1", 0)
6803+        # Write a share using the mutable writer, and make sure that the
6804+        # reader knows how to read everything back to us.
6805+        d = defer.succeed(None)
6806+        for i in xrange(6):
6807+            d.addCallback(lambda ignored, i=i:
6808+                mw.put_block(self.block, i, self.salt))
6809+        d.addCallback(lambda ignored:
6810+            mw.put_encprivkey(self.encprivkey))
6811+        d.addCallback(lambda ignored:
6812+            mw.put_blockhashes(self.block_hash_tree))
6813+        d.addCallback(lambda ignored:
6814+            mw.put_sharehashes(self.share_hash_chain))
6815+        d.addCallback(lambda ignored:
6816+            mw.put_root_hash(self.root_hash))
6817+        d.addCallback(lambda ignored:
6818+            mw.put_signature(self.signature))
6819+        d.addCallback(lambda ignored:
6820+            mw.put_verification_key(self.verification_key))
6821+        d.addCallback(lambda ignored:
6822+            mw.finish_publishing())
6823+
6824+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6825+        def _check_block_and_salt((block, salt)):
6826+            self.failUnlessEqual(block, self.block)
6827+            self.failUnlessEqual(salt, self.salt)
6828+
6829+        for i in xrange(6):
6830+            d.addCallback(lambda ignored, i=i:
6831+                mr.get_block_and_salt(i))
6832+            d.addCallback(_check_block_and_salt)
6833+
6834+        d.addCallback(lambda ignored:
6835+            mr.get_encprivkey())
6836+        d.addCallback(lambda encprivkey:
6837+            self.failUnlessEqual(self.encprivkey, encprivkey))
6838+
6839+        d.addCallback(lambda ignored:
6840+            mr.get_blockhashes())
6841+        d.addCallback(lambda blockhashes:
6842+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6843+
6844+        d.addCallback(lambda ignored:
6845+            mr.get_sharehashes())
6846+        d.addCallback(lambda sharehashes:
6847+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6848+
6849+        d.addCallback(lambda ignored:
6850+            mr.get_signature())
6851+        d.addCallback(lambda signature:
6852+            self.failUnlessEqual(signature, self.signature))
6853+
6854+        d.addCallback(lambda ignored:
6855+            mr.get_verification_key())
6856+        d.addCallback(lambda verification_key:
6857+            self.failUnlessEqual(verification_key, self.verification_key))
6858+
6859+        d.addCallback(lambda ignored:
6860+            mr.get_seqnum())
6861+        d.addCallback(lambda seqnum:
6862+            self.failUnlessEqual(seqnum, 0))
6863+
6864+        d.addCallback(lambda ignored:
6865+            mr.get_root_hash())
6866+        d.addCallback(lambda root_hash:
6867+            self.failUnlessEqual(self.root_hash, root_hash))
6868+
6869+        d.addCallback(lambda ignored:
6870+            mr.get_encoding_parameters())
6871+        def _check_encoding_parameters((k, n, segsize, datalen)):
6872+            self.failUnlessEqual(k, 3)
6873+            self.failUnlessEqual(n, 10)
6874+            self.failUnlessEqual(segsize, 6)
6875+            self.failUnlessEqual(datalen, 36)
6876+        d.addCallback(_check_encoding_parameters)
6877+
6878+        d.addCallback(lambda ignored:
6879+            mr.get_checkstring())
6880+        d.addCallback(lambda checkstring:
6881+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
6882+        return d
6883+
6884+
6885+    def test_is_sdmf(self):
6886+        # The MDMFSlotReadProxy should also know how to read SDMF files,
6887+        # since it will encounter them on the grid. Callers use the
6888+        # is_sdmf method to test this.
6889+        self.write_sdmf_share_to_server("si1")
6890+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6891+        d = mr.is_sdmf()
6892+        d.addCallback(lambda issdmf:
6893+            self.failUnless(issdmf))
6894+        return d
6895+
6896+
6897+    def test_reads_sdmf(self):
6898+        # The slot read proxy should, naturally, know how to tell us
6899+        # about data in the SDMF format
6900+        self.write_sdmf_share_to_server("si1")
6901+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6902+        d = defer.succeed(None)
6903+        d.addCallback(lambda ignored:
6904+            mr.is_sdmf())
6905+        d.addCallback(lambda issdmf:
6906+            self.failUnless(issdmf))
6907+
6908+        # What do we need to read?
6909+        #  - The sharedata
6910+        #  - The salt
6911+        d.addCallback(lambda ignored:
6912+            mr.get_block_and_salt(0))
6913+        def _check_block_and_salt(results):
6914+            block, salt = results
6915+            # Our original file is 36 bytes long. Then each share is 12
6916+            # bytes in size. The share is composed entirely of the
6917+            # letter a. self.block contains 2 as, so 6 * self.block is
6918+            # what we are looking for.
6919+            self.failUnlessEqual(block, self.block * 6)
6920+            self.failUnlessEqual(salt, self.salt)
6921+        d.addCallback(_check_block_and_salt)
6922+
6923+        #  - The blockhashes
6924+        d.addCallback(lambda ignored:
6925+            mr.get_blockhashes())
6926+        d.addCallback(lambda blockhashes:
6927+            self.failUnlessEqual(self.block_hash_tree,
6928+                                 blockhashes,
6929+                                 blockhashes))
6930+        #  - The sharehashes
6931+        d.addCallback(lambda ignored:
6932+            mr.get_sharehashes())
6933+        d.addCallback(lambda sharehashes:
6934+            self.failUnlessEqual(self.share_hash_chain,
6935+                                 sharehashes))
6936+        #  - The keys
6937+        d.addCallback(lambda ignored:
6938+            mr.get_encprivkey())
6939+        d.addCallback(lambda encprivkey:
6940+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
6941+        d.addCallback(lambda ignored:
6942+            mr.get_verification_key())
6943+        d.addCallback(lambda verification_key:
6944+            self.failUnlessEqual(verification_key,
6945+                                 self.verification_key,
6946+                                 verification_key))
6947+        #  - The signature
6948+        d.addCallback(lambda ignored:
6949+            mr.get_signature())
6950+        d.addCallback(lambda signature:
6951+            self.failUnlessEqual(signature, self.signature, signature))
6952+
6953+        #  - The sequence number
6954+        d.addCallback(lambda ignored:
6955+            mr.get_seqnum())
6956+        d.addCallback(lambda seqnum:
6957+            self.failUnlessEqual(seqnum, 0, seqnum))
6958+
6959+        #  - The root hash
6960+        d.addCallback(lambda ignored:
6961+            mr.get_root_hash())
6962+        d.addCallback(lambda root_hash:
6963+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
6964+        return d
6965+
6966+
6967+    def test_only_reads_one_segment_sdmf(self):
6968+        # SDMF shares have only one segment, so it doesn't make sense to
6969+        # read more segments than that. The reader should know this and
6970+        # complain if we try to do that.
6971+        self.write_sdmf_share_to_server("si1")
6972+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6973+        d = defer.succeed(None)
6974+        d.addCallback(lambda ignored:
6975+            mr.is_sdmf())
6976+        d.addCallback(lambda issdmf:
6977+            self.failUnless(issdmf))
6978+        d.addCallback(lambda ignored:
6979+            self.shouldFail(LayoutInvalid, "test bad segment",
6980+                            None,
6981+                            mr.get_block_and_salt, 1))
6982+        return d
6983+
6984+
6985+    def test_read_with_prefetched_mdmf_data(self):
6986+        # The MDMFSlotReadProxy will prefill certain fields if you pass
6987+        # it data that you have already fetched. This is useful for
6988+        # cases like the Servermap, which prefetches ~2kb of data while
6989+        # finding out which shares are on the remote peer so that it
6990+        # doesn't waste round trips.
6991+        mdmf_data = self.build_test_mdmf_share()
6992+        self.write_test_share_to_server("si1")
6993+        def _make_mr(ignored, length):
6994+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
6995+            return mr
6996+
6997+        d = defer.succeed(None)
6998+        # This should be enough to fill in both the encoding parameters
6999+        # and the table of offsets, which will complete the version
7000+        # information tuple.
7001+        d.addCallback(_make_mr, 107)
7002+        d.addCallback(lambda mr:
7003+            mr.get_verinfo())
7004+        def _check_verinfo(verinfo):
7005+            self.failUnless(verinfo)
7006+            self.failUnlessEqual(len(verinfo), 9)
7007+            (seqnum,
7008+             root_hash,
7009+             salt_hash,
7010+             segsize,
7011+             datalen,
7012+             k,
7013+             n,
7014+             prefix,
7015+             offsets) = verinfo
7016+            self.failUnlessEqual(seqnum, 0)
7017+            self.failUnlessEqual(root_hash, self.root_hash)
7018+            self.failUnlessEqual(segsize, 6)
7019+            self.failUnlessEqual(datalen, 36)
7020+            self.failUnlessEqual(k, 3)
7021+            self.failUnlessEqual(n, 10)
7022+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
7023+                                          1,
7024+                                          seqnum,
7025+                                          root_hash,
7026+                                          k,
7027+                                          n,
7028+                                          segsize,
7029+                                          datalen)
7030+            self.failUnlessEqual(expected_prefix, prefix)
7031+            self.failUnlessEqual(self.rref.read_count, 0)
7032+        d.addCallback(_check_verinfo)
7033+        # This is not enough data to read a block and a share, so the
7034+        # wrapper should attempt to read this from the remote server.
7035+        d.addCallback(_make_mr, 107)
7036+        d.addCallback(lambda mr:
7037+            mr.get_block_and_salt(0))
7038+        def _check_block_and_salt((block, salt)):
7039+            self.failUnlessEqual(block, self.block)
7040+            self.failUnlessEqual(salt, self.salt)
7041+            self.failUnlessEqual(self.rref.read_count, 1)
7042+        # This should be enough data to read one block.
7043+        d.addCallback(_make_mr, 249)
7044+        d.addCallback(lambda mr:
7045+            mr.get_block_and_salt(0))
7046+        d.addCallback(_check_block_and_salt)
7047+        return d
7048+
7049+
7050+    def test_read_with_prefetched_sdmf_data(self):
7051+        sdmf_data = self.build_test_sdmf_share()
7052+        self.write_sdmf_share_to_server("si1")
7053+        def _make_mr(ignored, length):
7054+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
7055+            return mr
7056+
7057+        d = defer.succeed(None)
7058+        # This should be enough to get us the encoding parameters,
7059+        # offset table, and everything else we need to build a verinfo
7060+        # string.
7061+        d.addCallback(_make_mr, 107)
7062+        d.addCallback(lambda mr:
7063+            mr.get_verinfo())
7064+        def _check_verinfo(verinfo):
7065+            self.failUnless(verinfo)
7066+            self.failUnlessEqual(len(verinfo), 9)
7067+            (seqnum,
7068+             root_hash,
7069+             salt,
7070+             segsize,
7071+             datalen,
7072+             k,
7073+             n,
7074+             prefix,
7075+             offsets) = verinfo
7076+            self.failUnlessEqual(seqnum, 0)
7077+            self.failUnlessEqual(root_hash, self.root_hash)
7078+            self.failUnlessEqual(salt, self.salt)
7079+            self.failUnlessEqual(segsize, 36)
7080+            self.failUnlessEqual(datalen, 36)
7081+            self.failUnlessEqual(k, 3)
7082+            self.failUnlessEqual(n, 10)
7083+            expected_prefix = struct.pack(SIGNED_PREFIX,
7084+                                          0,
7085+                                          seqnum,
7086+                                          root_hash,
7087+                                          salt,
7088+                                          k,
7089+                                          n,
7090+                                          segsize,
7091+                                          datalen)
7092+            self.failUnlessEqual(expected_prefix, prefix)
7093+            self.failUnlessEqual(self.rref.read_count, 0)
7094+        d.addCallback(_check_verinfo)
7095+        # This shouldn't be enough to read any share data.
7096+        d.addCallback(_make_mr, 107)
7097+        d.addCallback(lambda mr:
7098+            mr.get_block_and_salt(0))
7099+        def _check_block_and_salt((block, salt)):
7100+            self.failUnlessEqual(block, self.block * 6)
7101+            self.failUnlessEqual(salt, self.salt)
7102+            # TODO: Fix the read routine so that it reads only the data
7103+            #       that it has cached if it can't read all of it.
7104+            self.failUnlessEqual(self.rref.read_count, 2)
7105+
7106+        # This should be enough to read share data.
7107+        d.addCallback(_make_mr, self.offsets['share_data'])
7108+        d.addCallback(lambda mr:
7109+            mr.get_block_and_salt(0))
7110+        d.addCallback(_check_block_and_salt)
7111+        return d
7112+
7113+
7114+    def test_read_with_empty_mdmf_file(self):
7115+        # Some tests upload a file with no contents to test things
7116+        # unrelated to the actual handling of the content of the file.
7117+        # The reader should behave intelligently in these cases.
7118+        self.write_test_share_to_server("si1", empty=True)
7119+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7120+        # We should be able to get the encoding parameters, and they
7121+        # should be correct.
7122+        d = defer.succeed(None)
7123+        d.addCallback(lambda ignored:
7124+            mr.get_encoding_parameters())
7125+        def _check_encoding_parameters(params):
7126+            self.failUnlessEqual(len(params), 4)
7127+            k, n, segsize, datalen = params
7128+            self.failUnlessEqual(k, 3)
7129+            self.failUnlessEqual(n, 10)
7130+            self.failUnlessEqual(segsize, 0)
7131+            self.failUnlessEqual(datalen, 0)
7132+        d.addCallback(_check_encoding_parameters)
7133+
7134+        # We should not be able to fetch a block, since there are no
7135+        # blocks to fetch
7136+        d.addCallback(lambda ignored:
7137+            self.shouldFail(LayoutInvalid, "get block on empty file",
7138+                            None,
7139+                            mr.get_block_and_salt, 0))
7140+        return d
7141+
7142+
7143+    def test_read_with_empty_sdmf_file(self):
7144+        self.write_sdmf_share_to_server("si1", empty=True)
7145+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7146+        # We should be able to get the encoding parameters, and they
7147+        # should be correct
7148+        d = defer.succeed(None)
7149+        d.addCallback(lambda ignored:
7150+            mr.get_encoding_parameters())
7151+        def _check_encoding_parameters(params):
7152+            self.failUnlessEqual(len(params), 4)
7153+            k, n, segsize, datalen = params
7154+            self.failUnlessEqual(k, 3)
7155+            self.failUnlessEqual(n, 10)
7156+            self.failUnlessEqual(segsize, 0)
7157+            self.failUnlessEqual(datalen, 0)
7158+        d.addCallback(_check_encoding_parameters)
7159+
7160+        # It does not make sense to get a block in this format, so we
7161+        # should not be able to.
7162+        d.addCallback(lambda ignored:
7163+            self.shouldFail(LayoutInvalid, "get block on an empty file",
7164+                            None,
7165+                            mr.get_block_and_salt, 0))
7166+        return d
7167+
7168+
7169+    def test_verinfo_with_sdmf_file(self):
7170+        self.write_sdmf_share_to_server("si1")
7171+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7172+        # We should be able to get the version information.
7173+        d = defer.succeed(None)
7174+        d.addCallback(lambda ignored:
7175+            mr.get_verinfo())
7176+        def _check_verinfo(verinfo):
7177+            self.failUnless(verinfo)
7178+            self.failUnlessEqual(len(verinfo), 9)
7179+            (seqnum,
7180+             root_hash,
7181+             salt,
7182+             segsize,
7183+             datalen,
7184+             k,
7185+             n,
7186+             prefix,
7187+             offsets) = verinfo
7188+            self.failUnlessEqual(seqnum, 0)
7189+            self.failUnlessEqual(root_hash, self.root_hash)
7190+            self.failUnlessEqual(salt, self.salt)
7191+            self.failUnlessEqual(segsize, 36)
7192+            self.failUnlessEqual(datalen, 36)
7193+            self.failUnlessEqual(k, 3)
7194+            self.failUnlessEqual(n, 10)
7195+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
7196+                                          0,
7197+                                          seqnum,
7198+                                          root_hash,
7199+                                          salt,
7200+                                          k,
7201+                                          n,
7202+                                          segsize,
7203+                                          datalen)
7204+            self.failUnlessEqual(prefix, expected_prefix)
7205+            self.failUnlessEqual(offsets, self.offsets)
7206+        d.addCallback(_check_verinfo)
7207+        return d
7208+
7209+
7210+    def test_verinfo_with_mdmf_file(self):
7211+        self.write_test_share_to_server("si1")
7212+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7213+        d = defer.succeed(None)
7214+        d.addCallback(lambda ignored:
7215+            mr.get_verinfo())
7216+        def _check_verinfo(verinfo):
7217+            self.failUnless(verinfo)
7218+            self.failUnlessEqual(len(verinfo), 9)
7219+            (seqnum,
7220+             root_hash,
7221+             IV,
7222+             segsize,
7223+             datalen,
7224+             k,
7225+             n,
7226+             prefix,
7227+             offsets) = verinfo
7228+            self.failUnlessEqual(seqnum, 0)
7229+            self.failUnlessEqual(root_hash, self.root_hash)
7230+            self.failIf(IV)
7231+            self.failUnlessEqual(segsize, 6)
7232+            self.failUnlessEqual(datalen, 36)
7233+            self.failUnlessEqual(k, 3)
7234+            self.failUnlessEqual(n, 10)
7235+            expected_prefix = struct.pack(">BQ32s BBQQ",
7236+                                          1,
7237+                                          seqnum,
7238+                                          root_hash,
7239+                                          k,
7240+                                          n,
7241+                                          segsize,
7242+                                          datalen)
7243+            self.failUnlessEqual(prefix, expected_prefix)
7244+            self.failUnlessEqual(offsets, self.offsets)
7245+        d.addCallback(_check_verinfo)
7246+        return d
7247+
7248+
7249+    def test_reader_queue(self):
7250+        self.write_test_share_to_server('si1')
7251+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7252+        d1 = mr.get_block_and_salt(0, queue=True)
7253+        d2 = mr.get_blockhashes(queue=True)
7254+        d3 = mr.get_sharehashes(queue=True)
7255+        d4 = mr.get_signature(queue=True)
7256+        d5 = mr.get_verification_key(queue=True)
7257+        dl = defer.DeferredList([d1, d2, d3, d4, d5])
7258+        mr.flush()
7259+        def _print(results):
7260+            self.failUnlessEqual(len(results), 5)
7261+            # We have one read for version information and offsets, and
7262+            # one for everything else.
7263+            self.failUnlessEqual(self.rref.read_count, 2)
7264+            block, salt = results[0][1] # results[0] is a boolean that says
7265+                                           # whether or not the operation
7266+                                           # worked.
7267+            self.failUnlessEqual(self.block, block)
7268+            self.failUnlessEqual(self.salt, salt)
7269+
7270+            blockhashes = results[1][1]
7271+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
7272+
7273+            sharehashes = results[2][1]
7274+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
7275+
7276+            signature = results[3][1]
7277+            self.failUnlessEqual(self.signature, signature)
7278+
7279+            verification_key = results[4][1]
7280+            self.failUnlessEqual(self.verification_key, verification_key)
7281+        dl.addCallback(_print)
7282+        return dl
7283+
7284+
7285+    def test_sdmf_writer(self):
7286+        # Go through the motions of writing an SDMF share to the storage
7287+        # server. Then read the storage server to see that the share got
7288+        # written in the way that we think it should have.
7289+
7290+        # We do this first so that the necessary instance variables get
7291+        # set the way we want them for the tests below.
7292+        data = self.build_test_sdmf_share()
7293+        sdmfr = SDMFSlotWriteProxy(0,
7294+                                   self.rref,
7295+                                   "si1",
7296+                                   self.secrets,
7297+                                   0, 3, 10, 36, 36)
7298+        # Put the block and salt.
7299+        sdmfr.put_block(self.blockdata, 0, self.salt)
7300+
7301+        # Put the encprivkey
7302+        sdmfr.put_encprivkey(self.encprivkey)
7303+
7304+        # Put the block and share hash chains
7305+        sdmfr.put_blockhashes(self.block_hash_tree)
7306+        sdmfr.put_sharehashes(self.share_hash_chain)
7307+        sdmfr.put_root_hash(self.root_hash)
7308+
7309+        # Put the signature
7310+        sdmfr.put_signature(self.signature)
7311+
7312+        # Put the verification key
7313+        sdmfr.put_verification_key(self.verification_key)
7314+
7315+        # Now check to make sure that nothing has been written yet.
7316+        self.failUnlessEqual(self.rref.write_count, 0)
7317+
7318+        # Now finish publishing
7319+        d = sdmfr.finish_publishing()
7320+        def _then(ignored):
7321+            self.failUnlessEqual(self.rref.write_count, 1)
7322+            read = self.ss.remote_slot_readv
7323+            self.failUnlessEqual(read("si1", [0], [(0, len(data))]),
7324+                                 {0: [data]})
7325+        d.addCallback(_then)
7326+        return d
7327+
7328+
7329+    def test_sdmf_writer_preexisting_share(self):
7330+        data = self.build_test_sdmf_share()
7331+        self.write_sdmf_share_to_server("si1")
7332+
7333+        # Now there is a share on the storage server. To successfully
7334+        # write, we need to set the checkstring correctly. When we
7335+        # don't, no write should occur.
7336+        sdmfw = SDMFSlotWriteProxy(0,
7337+                                   self.rref,
7338+                                   "si1",
7339+                                   self.secrets,
7340+                                   1, 3, 10, 36, 36)
7341+        sdmfw.put_block(self.blockdata, 0, self.salt)
7342+
7343+        # Put the encprivkey
7344+        sdmfw.put_encprivkey(self.encprivkey)
7345+
7346+        # Put the block and share hash chains
7347+        sdmfw.put_blockhashes(self.block_hash_tree)
7348+        sdmfw.put_sharehashes(self.share_hash_chain)
7349+
7350+        # Put the root hash
7351+        sdmfw.put_root_hash(self.root_hash)
7352+
7353+        # Put the signature
7354+        sdmfw.put_signature(self.signature)
7355+
7356+        # Put the verification key
7357+        sdmfw.put_verification_key(self.verification_key)
7358+
7359+        # We shouldn't have a checkstring yet
7360+        self.failUnlessEqual(sdmfw.get_checkstring(), "")
7361+
7362+        d = sdmfw.finish_publishing()
7363+        def _then(results):
7364+            self.failIf(results[0])
7365+            # this is the correct checkstring
7366+            self._expected_checkstring = results[1][0][0]
7367+            return self._expected_checkstring
7368+
7369+        d.addCallback(_then)
7370+        d.addCallback(sdmfw.set_checkstring)
7371+        d.addCallback(lambda ignored:
7372+            sdmfw.get_checkstring())
7373+        d.addCallback(lambda checkstring:
7374+            self.failUnlessEqual(checkstring, self._expected_checkstring))
7375+        d.addCallback(lambda ignored:
7376+            sdmfw.finish_publishing())
7377+        def _then_again(results):
7378+            self.failUnless(results[0])
7379+            read = self.ss.remote_slot_readv
7380+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
7381+                                 {0: [struct.pack(">Q", 1)]})
7382+            self.failUnlessEqual(read("si1", [0], [(9, len(data) - 9)]),
7383+                                 {0: [data[9:]]})
7384+        d.addCallback(_then_again)
7385+        return d
7386+
7387+
7388 class Stats(unittest.TestCase):
7389 
7390     def setUp(self):
7391}
7392[mutable/publish.py: cleanup + simplification
7393Kevan Carstensen <kevan@isnotajoke.com>**20100702225554
7394 Ignore-this: 36a58424ceceffb1ddc55cc5934399e2
7395] {
7396hunk ./src/allmydata/mutable/publish.py 19
7397      UncoordinatedWriteError, NotEnoughServersError
7398 from allmydata.mutable.servermap import ServerMap
7399 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
7400-     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
7401+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy, \
7402+     SDMFSlotWriteProxy
7403 
7404 KiB = 1024
7405 DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
7406hunk ./src/allmydata/mutable/publish.py 24
7407+PUSHING_BLOCKS_STATE = 0
7408+PUSHING_EVERYTHING_ELSE_STATE = 1
7409+DONE_STATE = 2
7410 
7411 class PublishStatus:
7412     implements(IPublishStatus)
7413hunk ./src/allmydata/mutable/publish.py 229
7414 
7415         self.bad_share_checkstrings = {}
7416 
7417+        # This is set at the last step of the publishing process.
7418+        self.versioninfo = ""
7419+
7420         # we use the servermap to populate the initial goal: this way we will
7421         # try to update each existing share in place.
7422         for (peerid, shnum) in self._servermap.servermap:
7423hunk ./src/allmydata/mutable/publish.py 245
7424             self.bad_share_checkstrings[key] = old_checkstring
7425             self.connections[peerid] = self._servermap.connections[peerid]
7426 
7427-        # Now, the process dovetails -- if this is an SDMF file, we need
7428-        # to write an SDMF file. Otherwise, we need to write an MDMF
7429-        # file.
7430-        if self._version == MDMF_VERSION:
7431-            return self._publish_mdmf()
7432-        else:
7433-            return self._publish_sdmf()
7434-        #return self.done_deferred
7435-
7436-    def _publish_mdmf(self):
7437-        # Next, we find homes for all of the shares that we don't have
7438-        # homes for yet.
7439         # TODO: Make this part do peer selection.
7440         self.update_goal()
7441         self.writers = {}
7442hunk ./src/allmydata/mutable/publish.py 248
7443-        # For each (peerid, shnum) in self.goal, we make an
7444-        # MDMFSlotWriteProxy for that peer. We'll use this to write
7445+        if self._version == MDMF_VERSION:
7446+            writer_class = MDMFSlotWriteProxy
7447+        else:
7448+            writer_class = SDMFSlotWriteProxy
7449+
7450+        # For each (peerid, shnum) in self.goal, we make a
7451+        # write proxy for that peer. We'll use this to write
7452         # shares to the peer.
7453         for key in self.goal:
7454             peerid, shnum = key
7455hunk ./src/allmydata/mutable/publish.py 263
7456             cancel_secret = self._node.get_cancel_secret(peerid)
7457             secrets = (write_enabler, renew_secret, cancel_secret)
7458 
7459-            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
7460-                                                      self.connections[peerid],
7461-                                                      self._storage_index,
7462-                                                      secrets,
7463-                                                      self._new_seqnum,
7464-                                                      self.required_shares,
7465-                                                      self.total_shares,
7466-                                                      self.segment_size,
7467-                                                      len(self.newdata))
7468+            self.writers[shnum] =  writer_class(shnum,
7469+                                                self.connections[peerid],
7470+                                                self._storage_index,
7471+                                                secrets,
7472+                                                self._new_seqnum,
7473+                                                self.required_shares,
7474+                                                self.total_shares,
7475+                                                self.segment_size,
7476+                                                len(self.newdata))
7477+            self.writers[shnum].peerid = peerid
7478             if (peerid, shnum) in self._servermap.servermap:
7479                 old_versionid, old_timestamp = self._servermap.servermap[key]
7480                 (old_seqnum, old_root_hash, old_salt, old_segsize,
7481hunk ./src/allmydata/mutable/publish.py 278
7482                  old_datalength, old_k, old_N, old_prefix,
7483                  old_offsets_tuple) = old_versionid
7484-                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
7485+                self.writers[shnum].set_checkstring(old_seqnum,
7486+                                                    old_root_hash,
7487+                                                    old_salt)
7488+            elif (peerid, shnum) in self.bad_share_checkstrings:
7489+                old_checkstring = self.bad_share_checkstrings[(peerid, shnum)]
7490+                self.writers[shnum].set_checkstring(old_checkstring)
7491+
7492+        # Our remote shares will not have a complete checkstring until
7493+        # after we are done writing share data and have started to write
7494+        # blocks. In the meantime, we need to know what to look for when
7495+        # writing, so that we can detect UncoordinatedWriteErrors.
7496+        self._checkstring = self.writers.values()[0].get_checkstring()
7497 
7498         # Now, we start pushing shares.
7499         self._status.timings["setup"] = time.time() - self._started
7500hunk ./src/allmydata/mutable/publish.py 293
7501-        def _start_pushing(res):
7502-            self._started_pushing = time.time()
7503-            return res
7504-
7505         # First, we encrypt, encode, and publish the shares that we need
7506         # to encrypt, encode, and publish.
7507 
7508hunk ./src/allmydata/mutable/publish.py 306
7509 
7510         d = defer.succeed(None)
7511         self.log("Starting push")
7512-        for i in xrange(self.num_segments - 1):
7513-            d.addCallback(lambda ignored, i=i:
7514-                self.push_segment(i))
7515-            d.addCallback(self._turn_barrier)
7516-        # We have at least one segment, so we will have a tail segment
7517-        if self.num_segments > 0:
7518-            d.addCallback(lambda ignored:
7519-                self.push_tail_segment())
7520-
7521-        d.addCallback(lambda ignored:
7522-            self.push_encprivkey())
7523-        d.addCallback(lambda ignored:
7524-            self.push_blockhashes())
7525-        d.addCallback(lambda ignored:
7526-            self.push_sharehashes())
7527-        d.addCallback(lambda ignored:
7528-            self.push_toplevel_hashes_and_signature())
7529-        d.addCallback(lambda ignored:
7530-            self.finish_publishing())
7531-        return d
7532-
7533-
7534-    def _publish_sdmf(self):
7535-        self._status.timings["setup"] = time.time() - self._started
7536-        self.salt = os.urandom(16)
7537 
7538hunk ./src/allmydata/mutable/publish.py 307
7539-        d = self._encrypt_and_encode()
7540-        d.addCallback(self._generate_shares)
7541-        def _start_pushing(res):
7542-            self._started_pushing = time.time()
7543-            return res
7544-        d.addCallback(_start_pushing)
7545-        d.addCallback(self.loop) # trigger delivery
7546-        d.addErrback(self._fatal_error)
7547+        self._state = PUSHING_BLOCKS_STATE
7548+        self._push()
7549 
7550         return self.done_deferred
7551 
7552hunk ./src/allmydata/mutable/publish.py 327
7553                                                   segment_size)
7554         else:
7555             self.num_segments = 0
7556+
7557+        self.log("building encoding parameters for file")
7558+        self.log("got segsize %d" % self.segment_size)
7559+        self.log("got %d segments" % self.num_segments)
7560+
7561         if self._version == SDMF_VERSION:
7562             assert self.num_segments in (0, 1) # SDMF
7563hunk ./src/allmydata/mutable/publish.py 334
7564-            return
7565         # calculate the tail segment size.
7566hunk ./src/allmydata/mutable/publish.py 335
7567-        self.tail_segment_size = len(self.newdata) % segment_size
7568 
7569hunk ./src/allmydata/mutable/publish.py 336
7570-        if self.tail_segment_size == 0:
7571+        if segment_size and self.newdata:
7572+            self.tail_segment_size = len(self.newdata) % segment_size
7573+        else:
7574+            self.tail_segment_size = 0
7575+
7576+        if self.tail_segment_size == 0 and segment_size:
7577             # The tail segment is the same size as the other segments.
7578             self.tail_segment_size = segment_size
7579 
7580hunk ./src/allmydata/mutable/publish.py 345
7581-        # We'll make an encoder ahead-of-time for the normal-sized
7582-        # segments (defined as any segment of segment_size size.
7583-        # (the part of the code that puts the tail segment will make its
7584-        #  own encoder for that part)
7585+        # Make FEC encoders
7586         fec = codec.CRSEncoder()
7587         fec.set_params(self.segment_size,
7588                        self.required_shares, self.total_shares)
7589hunk ./src/allmydata/mutable/publish.py 352
7590         self.piece_size = fec.get_block_size()
7591         self.fec = fec
7592 
7593+        if self.tail_segment_size == self.segment_size:
7594+            self.tail_fec = self.fec
7595+        else:
7596+            tail_fec = codec.CRSEncoder()
7597+            tail_fec.set_params(self.tail_segment_size,
7598+                                self.required_shares,
7599+                                self.total_shares)
7600+            self.tail_fec = tail_fec
7601+
7602+        self._current_segment = 0
7603+
7604+
7605+    def _push(self, ignored=None):
7606+        """
7607+        I manage state transitions. In particular, I see that we still
7608+        have a good enough number of writers to complete the upload
7609+        successfully.
7610+        """
7611+        # Can we still successfully publish this file?
7612+        # TODO: Keep track of outstanding queries before aborting the
7613+        #       process.
7614+        if len(self.writers) <= self.required_shares or self.surprised:
7615+            return self._failure()
7616+
7617+        # Figure out what we need to do next. Each of these needs to
7618+        # return a deferred so that we don't block execution when this
7619+        # is first called in the upload method.
7620+        if self._state == PUSHING_BLOCKS_STATE:
7621+            return self.push_segment(self._current_segment)
7622+
7623+        # XXX: Do we want more granularity in states? Is that useful at
7624+        #      all?
7625+        #      Yes -- quicker reaction to UCW.
7626+        elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
7627+            return self.push_everything_else()
7628+
7629+        # If we make it to this point, we were successful in placing the
7630+        # file.
7631+        return self._done(None)
7632+
7633 
7634     def push_segment(self, segnum):
7635hunk ./src/allmydata/mutable/publish.py 394
7636+        if self.num_segments == 0 and self._version == SDMF_VERSION:
7637+            self._add_dummy_salts()
7638+
7639+        if segnum == self.num_segments:
7640+            # We don't have any more segments to push.
7641+            self._state = PUSHING_EVERYTHING_ELSE_STATE
7642+            return self._push()
7643+
7644+        d = self._encode_segment(segnum)
7645+        d.addCallback(self._push_segment, segnum)
7646+        def _increment_segnum(ign):
7647+            self._current_segment += 1
7648+        # XXX: I don't think we need to do addBoth here -- any errBacks
7649+        # should be handled within push_segment.
7650+        d.addBoth(_increment_segnum)
7651+        d.addBoth(self._push)
7652+
7653+
7654+    def _add_dummy_salts(self):
7655+        """
7656+        SDMF files need a salt even if they're empty, or the signature
7657+        won't make sense. This method adds a dummy salt to each of our
7658+        SDMF writers so that they can write the signature later.
7659+        """
7660+        salt = os.urandom(16)
7661+        assert self._version == SDMF_VERSION
7662+
7663+        for writer in self.writers.itervalues():
7664+            writer.put_salt(salt)
7665+
7666+
7667+    def _encode_segment(self, segnum):
7668+        """
7669+        I encrypt and encode the segment segnum.
7670+        """
7671         started = time.time()
7672hunk ./src/allmydata/mutable/publish.py 430
7673-        segsize = self.segment_size
7674+
7675+        if segnum + 1 == self.num_segments:
7676+            segsize = self.tail_segment_size
7677+        else:
7678+            segsize = self.segment_size
7679+
7680+
7681+        offset = self.segment_size * segnum
7682+        length = segsize + offset
7683         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
7684hunk ./src/allmydata/mutable/publish.py 440
7685-        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
7686+        data = self.newdata[offset:length]
7687         assert len(data) == segsize
7688 
7689         salt = os.urandom(16)
7690hunk ./src/allmydata/mutable/publish.py 455
7691         started = now
7692 
7693         # now apply FEC
7694+        if segnum + 1 == self.num_segments:
7695+            fec = self.tail_fec
7696+        else:
7697+            fec = self.fec
7698 
7699         self._status.set_status("Encoding")
7700         crypttext_pieces = [None] * self.required_shares
7701hunk ./src/allmydata/mutable/publish.py 462
7702-        piece_size = self.piece_size
7703+        piece_size = fec.get_block_size()
7704         for i in range(len(crypttext_pieces)):
7705             offset = i * piece_size
7706             piece = crypttext[offset:offset+piece_size]
7707hunk ./src/allmydata/mutable/publish.py 469
7708             piece = piece + "\x00"*(piece_size - len(piece)) # padding
7709             crypttext_pieces[i] = piece
7710             assert len(piece) == piece_size
7711-        d = self.fec.encode(crypttext_pieces)
7712+        d = fec.encode(crypttext_pieces)
7713         def _done_encoding(res):
7714             elapsed = time.time() - started
7715             self._status.timings["encode"] = elapsed
7716hunk ./src/allmydata/mutable/publish.py 473
7717-            return res
7718+            return (res, salt)
7719         d.addCallback(_done_encoding)
7720hunk ./src/allmydata/mutable/publish.py 475
7721-
7722-        def _push_shares_and_salt(results):
7723-            shares, shareids = results
7724-            dl = []
7725-            for i in xrange(len(shares)):
7726-                sharedata = shares[i]
7727-                shareid = shareids[i]
7728-                block_hash = hashutil.block_hash(salt + sharedata)
7729-                self.blockhashes[shareid].append(block_hash)
7730-
7731-                # find the writer for this share
7732-                d = self.writers[shareid].put_block(sharedata, segnum, salt)
7733-                dl.append(d)
7734-            # TODO: Naturally, we need to check on the results of these.
7735-            return defer.DeferredList(dl)
7736-        d.addCallback(_push_shares_and_salt)
7737         return d
7738 
7739 
7740hunk ./src/allmydata/mutable/publish.py 478
7741-    def push_tail_segment(self):
7742-        # This is essentially the same as push_segment, except that we
7743-        # don't use the cached encoder that we use elsewhere.
7744-        self.log("Pushing tail segment")
7745+    def _push_segment(self, encoded_and_salt, segnum):
7746+        """
7747+        I push (data, salt) as segment number segnum.
7748+        """
7749+        results, salt = encoded_and_salt
7750+        shares, shareids = results
7751         started = time.time()
7752hunk ./src/allmydata/mutable/publish.py 485
7753-        segsize = self.segment_size
7754-        data = self.newdata[segsize * (self.num_segments-1):]
7755-        assert len(data) == self.tail_segment_size
7756-        salt = os.urandom(16)
7757-
7758-        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
7759-        enc = AES(key)
7760-        crypttext = enc.process(data)
7761-        assert len(crypttext) == len(data)
7762+        dl = []
7763+        for i in xrange(len(shares)):
7764+            sharedata = shares[i]
7765+            shareid = shareids[i]
7766+            if self._version == MDMF_VERSION:
7767+                hashed = salt + sharedata
7768+            else:
7769+                hashed = sharedata
7770+            block_hash = hashutil.block_hash(hashed)
7771+            self.blockhashes[shareid].append(block_hash)
7772 
7773hunk ./src/allmydata/mutable/publish.py 496
7774-        now = time.time()
7775-        self._status.timings['encrypt'] = now - started
7776-        started = now
7777+            # find the writer for this share
7778+            writer = self.writers[shareid]
7779+            d = writer.put_block(sharedata, segnum, salt)
7780+            d.addCallback(self._got_write_answer, writer, started)
7781+            d.addErrback(self._connection_problem, writer)
7782+            dl.append(d)
7783+            # TODO: Naturally, we need to check on the results of these.
7784+        return defer.DeferredList(dl)
7785 
7786hunk ./src/allmydata/mutable/publish.py 505
7787-        self._status.set_status("Encoding")
7788-        tail_fec = codec.CRSEncoder()
7789-        tail_fec.set_params(self.tail_segment_size,
7790-                            self.required_shares,
7791-                            self.total_shares)
7792 
7793hunk ./src/allmydata/mutable/publish.py 506
7794-        crypttext_pieces = [None] * self.required_shares
7795-        piece_size = tail_fec.get_block_size()
7796-        for i in range(len(crypttext_pieces)):
7797-            offset = i * piece_size
7798-            piece = crypttext[offset:offset+piece_size]
7799-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
7800-            crypttext_pieces[i] = piece
7801-            assert len(piece) == piece_size
7802-        d = tail_fec.encode(crypttext_pieces)
7803-        def _push_shares_and_salt(results):
7804-            shares, shareids = results
7805-            dl = []
7806-            for i in xrange(len(shares)):
7807-                sharedata = shares[i]
7808-                shareid = shareids[i]
7809-                block_hash = hashutil.block_hash(salt + sharedata)
7810-                self.blockhashes[shareid].append(block_hash)
7811-                # find the writer for this share
7812-                d = self.writers[shareid].put_block(sharedata,
7813-                                                    self.num_segments - 1,
7814-                                                    salt)
7815-                dl.append(d)
7816-            # TODO: Naturally, we need to check on the results of these.
7817-            return defer.DeferredList(dl)
7818-        d.addCallback(_push_shares_and_salt)
7819+    def push_everything_else(self):
7820+        """
7821+        I put everything else associated with a share.
7822+        """
7823+        encprivkey = self._encprivkey
7824+        d = self.push_encprivkey()
7825+        d.addCallback(self.push_blockhashes)
7826+        d.addCallback(self.push_sharehashes)
7827+        d.addCallback(self.push_toplevel_hashes_and_signature)
7828+        d.addCallback(self.finish_publishing)
7829+        def _change_state(ignored):
7830+            self._state = DONE_STATE
7831+        d.addCallback(_change_state)
7832+        d.addCallback(self._push)
7833         return d
7834 
7835 
7836hunk ./src/allmydata/mutable/publish.py 527
7837         started = time.time()
7838         encprivkey = self._encprivkey
7839         dl = []
7840-        def _spy_on_writer(results):
7841-            print results
7842-            return results
7843-        for shnum, writer in self.writers.iteritems():
7844+        for writer in self.writers.itervalues():
7845             d = writer.put_encprivkey(encprivkey)
7846hunk ./src/allmydata/mutable/publish.py 529
7847+            d.addCallback(self._got_write_answer, writer, started)
7848+            d.addErrback(self._connection_problem, writer)
7849             dl.append(d)
7850         d = defer.DeferredList(dl)
7851         return d
7852hunk ./src/allmydata/mutable/publish.py 536
7853 
7854 
7855-    def push_blockhashes(self):
7856+    def push_blockhashes(self, ignored):
7857         started = time.time()
7858         dl = []
7859hunk ./src/allmydata/mutable/publish.py 539
7860-        def _spy_on_results(results):
7861-            print results
7862-            return results
7863         self.sharehash_leaves = [None] * len(self.blockhashes)
7864         for shnum, blockhashes in self.blockhashes.iteritems():
7865             t = hashtree.HashTree(blockhashes)
7866hunk ./src/allmydata/mutable/publish.py 545
7867             self.blockhashes[shnum] = list(t)
7868             # set the leaf for future use.
7869             self.sharehash_leaves[shnum] = t[0]
7870-            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
7871+            writer = self.writers[shnum]
7872+            d = writer.put_blockhashes(self.blockhashes[shnum])
7873+            d.addCallback(self._got_write_answer, writer, started)
7874+            d.addErrback(self._connection_problem, self.writers[shnum])
7875             dl.append(d)
7876         d = defer.DeferredList(dl)
7877         return d
7878hunk ./src/allmydata/mutable/publish.py 554
7879 
7880 
7881-    def push_sharehashes(self):
7882+    def push_sharehashes(self, ignored):
7883+        started = time.time()
7884         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
7885         share_hash_chain = {}
7886         ds = []
7887hunk ./src/allmydata/mutable/publish.py 559
7888-        def _spy_on_results(results):
7889-            print results
7890-            return results
7891         for shnum in xrange(len(self.sharehash_leaves)):
7892             needed_indices = share_hash_tree.needed_hashes(shnum)
7893             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
7894hunk ./src/allmydata/mutable/publish.py 563
7895                                              for i in needed_indices] )
7896-            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
7897+            writer = self.writers[shnum]
7898+            d = writer.put_sharehashes(self.sharehashes[shnum])
7899+            d.addCallback(self._got_write_answer, writer, started)
7900+            d.addErrback(self._connection_problem, writer)
7901             ds.append(d)
7902         self.root_hash = share_hash_tree[0]
7903         d = defer.DeferredList(ds)
7904hunk ./src/allmydata/mutable/publish.py 573
7905         return d
7906 
7907 
7908-    def push_toplevel_hashes_and_signature(self):
7909+    def push_toplevel_hashes_and_signature(self, ignored):
7910         # We need to to three things here:
7911         #   - Push the root hash and salt hash
7912         #   - Get the checkstring of the resulting layout; sign that.
7913hunk ./src/allmydata/mutable/publish.py 578
7914         #   - Push the signature
7915+        started = time.time()
7916         ds = []
7917hunk ./src/allmydata/mutable/publish.py 580
7918-        def _spy_on_results(results):
7919-            print results
7920-            return results
7921         for shnum in xrange(self.total_shares):
7922hunk ./src/allmydata/mutable/publish.py 581
7923-            d = self.writers[shnum].put_root_hash(self.root_hash)
7924+            writer = self.writers[shnum]
7925+            d = writer.put_root_hash(self.root_hash)
7926+            d.addCallback(self._got_write_answer, writer, started)
7927             ds.append(d)
7928         d = defer.DeferredList(ds)
7929hunk ./src/allmydata/mutable/publish.py 586
7930-        def _make_and_place_signature(ignored):
7931-            signable = self.writers[0].get_signable()
7932-            self.signature = self._privkey.sign(signable)
7933-
7934-            ds = []
7935-            for (shnum, writer) in self.writers.iteritems():
7936-                d = writer.put_signature(self.signature)
7937-                ds.append(d)
7938-            return defer.DeferredList(ds)
7939-        d.addCallback(_make_and_place_signature)
7940+        d.addCallback(self._update_checkstring)
7941+        d.addCallback(self._make_and_place_signature)
7942         return d
7943 
7944 
7945hunk ./src/allmydata/mutable/publish.py 591
7946-    def finish_publishing(self):
7947+    def _update_checkstring(self, ignored):
7948+        """
7949+        After putting the root hash, MDMF files will have the
7950+        checkstring written to the storage server. This means that we
7951+        can update our copy of the checkstring so we can detect
7952+        uncoordinated writes. SDMF files will have the same checkstring,
7953+        so we need not do anything.
7954+        """
7955+        self._checkstring = self.writers.values()[0].get_checkstring()
7956+
7957+
7958+    def _make_and_place_signature(self, ignored):
7959+        """
7960+        I create and place the signature.
7961+        """
7962+        started = time.time()
7963+        signable = self.writers[0].get_signable()
7964+        self.signature = self._privkey.sign(signable)
7965+
7966+        ds = []
7967+        for (shnum, writer) in self.writers.iteritems():
7968+            d = writer.put_signature(self.signature)
7969+            d.addCallback(self._got_write_answer, writer, started)
7970+            d.addErrback(self._connection_problem, writer)
7971+            ds.append(d)
7972+        return defer.DeferredList(ds)
7973+
7974+
7975+    def finish_publishing(self, ignored):
7976         # We're almost done -- we just need to put the verification key
7977         # and the offsets
7978hunk ./src/allmydata/mutable/publish.py 622
7979+        started = time.time()
7980         ds = []
7981         verification_key = self._pubkey.serialize()
7982 
7983hunk ./src/allmydata/mutable/publish.py 626
7984-        def _spy_on_results(results):
7985-            print results
7986-            return results
7987+
7988+        # TODO: Bad, since we remove from this same dict. We need to
7989+        # make a copy, or just use a non-iterated value.
7990         for (shnum, writer) in self.writers.iteritems():
7991             d = writer.put_verification_key(verification_key)
7992hunk ./src/allmydata/mutable/publish.py 631
7993+            d.addCallback(self._got_write_answer, writer, started)
7994+            d.addCallback(self._record_verinfo)
7995             d.addCallback(lambda ignored, writer=writer:
7996                 writer.finish_publishing())
7997hunk ./src/allmydata/mutable/publish.py 635
7998+            d.addCallback(self._got_write_answer, writer, started)
7999+            d.addErrback(self._connection_problem, writer)
8000             ds.append(d)
8001         return defer.DeferredList(ds)
8002 
8003hunk ./src/allmydata/mutable/publish.py 641
8004 
8005-    def _turn_barrier(self, res):
8006-        # putting this method in a Deferred chain imposes a guaranteed
8007-        # reactor turn between the pre- and post- portions of that chain.
8008-        # This can be useful to limit memory consumption: since Deferreds do
8009-        # not do tail recursion, code which uses defer.succeed(result) for
8010-        # consistency will cause objects to live for longer than you might
8011-        # normally expect.
8012-        return fireEventually(res)
8013+    def _record_verinfo(self, ignored):
8014+        self.versioninfo = self.writers.values()[0].get_verinfo()
8015 
8016 
8017hunk ./src/allmydata/mutable/publish.py 645
8018-    def _fatal_error(self, f):
8019-        self.log("error during loop", failure=f, level=log.UNUSUAL)
8020-        self._done(f)
8021+    def _connection_problem(self, f, writer):
8022+        """
8023+        We ran into a connection problem while working with writer, and
8024+        need to deal with that.
8025+        """
8026+        self.log("found problem: %s" % str(f))
8027+        self._last_failure = f
8028+        del(self.writers[writer.shnum])
8029 
8030hunk ./src/allmydata/mutable/publish.py 654
8031-    def _update_status(self):
8032-        self._status.set_status("Sending Shares: %d placed out of %d, "
8033-                                "%d messages outstanding" %
8034-                                (len(self.placed),
8035-                                 len(self.goal),
8036-                                 len(self.outstanding)))
8037-        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
8038 
8039     def loop(self, ignored=None):
8040         self.log("entering loop", level=log.NOISY)
8041hunk ./src/allmydata/mutable/publish.py 778
8042             self.log_goal(self.goal, "after update: ")
8043 
8044 
8045-    def _encrypt_and_encode(self):
8046-        # this returns a Deferred that fires with a list of (sharedata,
8047-        # sharenum) tuples. TODO: cache the ciphertext, only produce the
8048-        # shares that we care about.
8049-        self.log("_encrypt_and_encode")
8050-
8051-        self._status.set_status("Encrypting")
8052-        started = time.time()
8053+    def _got_write_answer(self, answer, writer, started):
8054+        if not answer:
8055+            # SDMF writers only pretend to write when readers set their
8056+            # blocks, salts, and so on -- they actually just write once,
8057+            # at the end of the upload process. In fake writes, they
8058+            # return defer.succeed(None). If we see that, we shouldn't
8059+            # bother checking it.
8060+            return
8061 
8062hunk ./src/allmydata/mutable/publish.py 787
8063-        key = hashutil.ssk_readkey_data_hash(self.salt, self.readkey)
8064-        enc = AES(key)
8065-        crypttext = enc.process(self.newdata)
8066-        assert len(crypttext) == len(self.newdata)
8067+        peerid = writer.peerid
8068+        lp = self.log("_got_write_answer from %s, share %d" %
8069+                      (idlib.shortnodeid_b2a(peerid), writer.shnum))
8070 
8071         now = time.time()
8072hunk ./src/allmydata/mutable/publish.py 792
8073-        self._status.timings["encrypt"] = now - started
8074-        started = now
8075-
8076-        # now apply FEC
8077-
8078-        self._status.set_status("Encoding")
8079-        fec = codec.CRSEncoder()
8080-        fec.set_params(self.segment_size,
8081-                       self.required_shares, self.total_shares)
8082-        piece_size = fec.get_block_size()
8083-        crypttext_pieces = [None] * self.required_shares
8084-        for i in range(len(crypttext_pieces)):
8085-            offset = i * piece_size
8086-            piece = crypttext[offset:offset+piece_size]
8087-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
8088-            crypttext_pieces[i] = piece
8089-            assert len(piece) == piece_size
8090-
8091-        d = fec.encode(crypttext_pieces)
8092-        def _done_encoding(res):
8093-            elapsed = time.time() - started
8094-            self._status.timings["encode"] = elapsed
8095-            return res
8096-        d.addCallback(_done_encoding)
8097-        return d
8098-
8099-
8100-    def _generate_shares(self, shares_and_shareids):
8101-        # this sets self.shares and self.root_hash
8102-        self.log("_generate_shares")
8103-        self._status.set_status("Generating Shares")
8104-        started = time.time()
8105-
8106-        # we should know these by now
8107-        privkey = self._privkey
8108-        encprivkey = self._encprivkey
8109-        pubkey = self._pubkey
8110-
8111-        (shares, share_ids) = shares_and_shareids
8112-
8113-        assert len(shares) == len(share_ids)
8114-        assert len(shares) == self.total_shares
8115-        all_shares = {}
8116-        block_hash_trees = {}
8117-        share_hash_leaves = [None] * len(shares)
8118-        for i in range(len(shares)):
8119-            share_data = shares[i]
8120-            shnum = share_ids[i]
8121-            all_shares[shnum] = share_data
8122-
8123-            # build the block hash tree. SDMF has only one leaf.
8124-            leaves = [hashutil.block_hash(share_data)]
8125-            t = hashtree.HashTree(leaves)
8126-            block_hash_trees[shnum] = list(t)
8127-            share_hash_leaves[shnum] = t[0]
8128-        for leaf in share_hash_leaves:
8129-            assert leaf is not None
8130-        share_hash_tree = hashtree.HashTree(share_hash_leaves)
8131-        share_hash_chain = {}
8132-        for shnum in range(self.total_shares):
8133-            needed_hashes = share_hash_tree.needed_hashes(shnum)
8134-            share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i])
8135-                                              for i in needed_hashes ] )
8136-        root_hash = share_hash_tree[0]
8137-        assert len(root_hash) == 32
8138-        self.log("my new root_hash is %s" % base32.b2a(root_hash))
8139-        self._new_version_info = (self._new_seqnum, root_hash, self.salt)
8140-
8141-        prefix = pack_prefix(self._new_seqnum, root_hash, self.salt,
8142-                             self.required_shares, self.total_shares,
8143-                             self.segment_size, len(self.newdata))
8144-
8145-        # now pack the beginning of the share. All shares are the same up
8146-        # to the signature, then they have divergent share hash chains,
8147-        # then completely different block hash trees + salt + share data,
8148-        # then they all share the same encprivkey at the end. The sizes
8149-        # of everything are the same for all shares.
8150-
8151-        sign_started = time.time()
8152-        signature = privkey.sign(prefix)
8153-        self._status.timings["sign"] = time.time() - sign_started
8154-
8155-        verification_key = pubkey.serialize()
8156-
8157-        final_shares = {}
8158-        for shnum in range(self.total_shares):
8159-            final_share = pack_share(prefix,
8160-                                     verification_key,
8161-                                     signature,
8162-                                     share_hash_chain[shnum],
8163-                                     block_hash_trees[shnum],
8164-                                     all_shares[shnum],
8165-                                     encprivkey)
8166-            final_shares[shnum] = final_share
8167-        elapsed = time.time() - started
8168-        self._status.timings["pack"] = elapsed
8169-        self.shares = final_shares
8170-        self.root_hash = root_hash
8171-
8172-        # we also need to build up the version identifier for what we're
8173-        # pushing. Extract the offsets from one of our shares.
8174-        assert final_shares
8175-        offsets = unpack_header(final_shares.values()[0])[-1]
8176-        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
8177-        verinfo = (self._new_seqnum, root_hash, self.salt,
8178-                   self.segment_size, len(self.newdata),
8179-                   self.required_shares, self.total_shares,
8180-                   prefix, offsets_tuple)
8181-        self.versioninfo = verinfo
8182-
8183-
8184-
8185-    def _send_shares(self, needed):
8186-        self.log("_send_shares")
8187-
8188-        # we're finally ready to send out our shares. If we encounter any
8189-        # surprises here, it's because somebody else is writing at the same
8190-        # time. (Note: in the future, when we remove the _query_peers() step
8191-        # and instead speculate about [or remember] which shares are where,
8192-        # surprises here are *not* indications of UncoordinatedWriteError,
8193-        # and we'll need to respond to them more gracefully.)
8194-
8195-        # needed is a set of (peerid, shnum) tuples. The first thing we do is
8196-        # organize it by peerid.
8197-
8198-        peermap = DictOfSets()
8199-        for (peerid, shnum) in needed:
8200-            peermap.add(peerid, shnum)
8201-
8202-        # the next thing is to build up a bunch of test vectors. The
8203-        # semantics of Publish are that we perform the operation if the world
8204-        # hasn't changed since the ServerMap was constructed (more or less).
8205-        # For every share we're trying to place, we create a test vector that
8206-        # tests to see if the server*share still corresponds to the
8207-        # map.
8208-
8209-        all_tw_vectors = {} # maps peerid to tw_vectors
8210-        sm = self._servermap.servermap
8211-
8212-        for key in needed:
8213-            (peerid, shnum) = key
8214-
8215-            if key in sm:
8216-                # an old version of that share already exists on the
8217-                # server, according to our servermap. We will create a
8218-                # request that attempts to replace it.
8219-                old_versionid, old_timestamp = sm[key]
8220-                (old_seqnum, old_root_hash, old_salt, old_segsize,
8221-                 old_datalength, old_k, old_N, old_prefix,
8222-                 old_offsets_tuple) = old_versionid
8223-                old_checkstring = pack_checkstring(old_seqnum,
8224-                                                   old_root_hash,
8225-                                                   old_salt)
8226-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8227-
8228-            elif key in self.bad_share_checkstrings:
8229-                old_checkstring = self.bad_share_checkstrings[key]
8230-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8231-
8232-            else:
8233-                # add a testv that requires the share not exist
8234-
8235-                # Unfortunately, foolscap-0.2.5 has a bug in the way inbound
8236-                # constraints are handled. If the same object is referenced
8237-                # multiple times inside the arguments, foolscap emits a
8238-                # 'reference' token instead of a distinct copy of the
8239-                # argument. The bug is that these 'reference' tokens are not
8240-                # accepted by the inbound constraint code. To work around
8241-                # this, we need to prevent python from interning the
8242-                # (constant) tuple, by creating a new copy of this vector
8243-                # each time.
8244-
8245-                # This bug is fixed in foolscap-0.2.6, and even though this
8246-                # version of Tahoe requires foolscap-0.3.1 or newer, we are
8247-                # supposed to be able to interoperate with older versions of
8248-                # Tahoe which are allowed to use older versions of foolscap,
8249-                # including foolscap-0.2.5 . In addition, I've seen other
8250-                # foolscap problems triggered by 'reference' tokens (see #541
8251-                # for details). So we must keep this workaround in place.
8252-
8253-                #testv = (0, 1, 'eq', "")
8254-                testv = tuple([0, 1, 'eq', ""])
8255-
8256-            testvs = [testv]
8257-            # the write vector is simply the share
8258-            writev = [(0, self.shares[shnum])]
8259-
8260-            if peerid not in all_tw_vectors:
8261-                all_tw_vectors[peerid] = {}
8262-                # maps shnum to (testvs, writevs, new_length)
8263-            assert shnum not in all_tw_vectors[peerid]
8264-
8265-            all_tw_vectors[peerid][shnum] = (testvs, writev, None)
8266-
8267-        # we read the checkstring back from each share, however we only use
8268-        # it to detect whether there was a new share that we didn't know
8269-        # about. The success or failure of the write will tell us whether
8270-        # there was a collision or not. If there is a collision, the first
8271-        # thing we'll do is update the servermap, which will find out what
8272-        # happened. We could conceivably reduce a roundtrip by using the
8273-        # readv checkstring to populate the servermap, but really we'd have
8274-        # to read enough data to validate the signatures too, so it wouldn't
8275-        # be an overall win.
8276-        read_vector = [(0, struct.calcsize(SIGNED_PREFIX))]
8277-
8278-        # ok, send the messages!
8279-        self.log("sending %d shares" % len(all_tw_vectors), level=log.NOISY)
8280-        started = time.time()
8281-        for (peerid, tw_vectors) in all_tw_vectors.items():
8282-
8283-            write_enabler = self._node.get_write_enabler(peerid)
8284-            renew_secret = self._node.get_renewal_secret(peerid)
8285-            cancel_secret = self._node.get_cancel_secret(peerid)
8286-            secrets = (write_enabler, renew_secret, cancel_secret)
8287-            shnums = tw_vectors.keys()
8288-
8289-            for shnum in shnums:
8290-                self.outstanding.add( (peerid, shnum) )
8291-
8292-            d = self._do_testreadwrite(peerid, secrets,
8293-                                       tw_vectors, read_vector)
8294-            d.addCallbacks(self._got_write_answer, self._got_write_error,
8295-                           callbackArgs=(peerid, shnums, started),
8296-                           errbackArgs=(peerid, shnums, started))
8297-            # tolerate immediate errback, like with DeadReferenceError
8298-            d.addBoth(fireEventually)
8299-            d.addCallback(self.loop)
8300-            d.addErrback(self._fatal_error)
8301-
8302-        self._update_status()
8303-        self.log("%d shares sent" % len(all_tw_vectors), level=log.NOISY)
8304+        elapsed = now - started
8305 
8306hunk ./src/allmydata/mutable/publish.py 794
8307-    def _do_testreadwrite(self, peerid, secrets,
8308-                          tw_vectors, read_vector):
8309-        storage_index = self._storage_index
8310-        ss = self.connections[peerid]
8311+        self._status.add_per_server_time(peerid, elapsed)
8312 
8313hunk ./src/allmydata/mutable/publish.py 796
8314-        #print "SS[%s] is %s" % (idlib.shortnodeid_b2a(peerid), ss), ss.tracker.interfaceName
8315-        d = ss.callRemote("slot_testv_and_readv_and_writev",
8316-                          storage_index,
8317-                          secrets,
8318-                          tw_vectors,
8319-                          read_vector)
8320-        return d
8321+        wrote, read_data = answer
8322 
8323hunk ./src/allmydata/mutable/publish.py 798
8324-    def _got_write_answer(self, answer, peerid, shnums, started):
8325-        lp = self.log("_got_write_answer from %s" %
8326-                      idlib.shortnodeid_b2a(peerid))
8327-        for shnum in shnums:
8328-            self.outstanding.discard( (peerid, shnum) )
8329+        surprise_shares = set(read_data.keys()) - set([writer.shnum])
8330 
8331hunk ./src/allmydata/mutable/publish.py 800
8332-        now = time.time()
8333-        elapsed = now - started
8334-        self._status.add_per_server_time(peerid, elapsed)
8335+        # We need to remove from surprise_shares any shares that we are
8336+        # knowingly also writing to that peer from other writers.
8337 
8338hunk ./src/allmydata/mutable/publish.py 803
8339-        wrote, read_data = answer
8340+        # TODO: Precompute this.
8341+        known_shnums = [x.shnum for x in self.writers.values()
8342+                        if x.peerid == peerid]
8343+        surprise_shares -= set(known_shnums)
8344+        self.log("found the following surprise shares: %s" %
8345+                 str(surprise_shares))
8346 
8347hunk ./src/allmydata/mutable/publish.py 810
8348-        surprise_shares = set(read_data.keys()) - set(shnums)
8349+        # Now surprise shares contains all of the shares that we did not
8350+        # expect to be there.
8351 
8352         surprised = False
8353         for shnum in surprise_shares:
8354hunk ./src/allmydata/mutable/publish.py 817
8355             # read_data is a dict mapping shnum to checkstring (SIGNED_PREFIX)
8356             checkstring = read_data[shnum][0]
8357-            their_version_info = unpack_checkstring(checkstring)
8358-            if their_version_info == self._new_version_info:
8359+            # What we want to do here is to see if their (seqnum,
8360+            # roothash, salt) is the same as our (seqnum, roothash,
8361+            # salt), or the equivalent for MDMF. The best way to do this
8362+            # is to store a packed representation of our checkstring
8363+            # somewhere, then not bother unpacking the other
8364+            # checkstring.
8365+            if checkstring == self._checkstring:
8366                 # they have the right share, somehow
8367 
8368                 if (peerid,shnum) in self.goal:
8369hunk ./src/allmydata/mutable/publish.py 902
8370             self.log("our testv failed, so the write did not happen",
8371                      parent=lp, level=log.WEIRD, umid="8sc26g")
8372             self.surprised = True
8373-            self.bad_peers.add(peerid) # don't ask them again
8374+            # TODO: This needs to
8375+            self.bad_peers.add(writer) # don't ask them again
8376             # use the checkstring to add information to the log message
8377             for (shnum,readv) in read_data.items():
8378                 checkstring = readv[0]
8379hunk ./src/allmydata/mutable/publish.py 928
8380             # self.loop() will take care of finding new homes
8381             return
8382 
8383-        for shnum in shnums:
8384-            self.placed.add( (peerid, shnum) )
8385-            # and update the servermap
8386-            self._servermap.add_new_share(peerid, shnum,
8387+        # and update the servermap
8388+        # self.versioninfo is set during the last phase of publishing.
8389+        # If we get there, we know that responses correspond to placed
8390+        # shares, and can safely execute these statements.
8391+        if self.versioninfo:
8392+            self.log("wrote successfully: adding new share to servermap")
8393+            self._servermap.add_new_share(peerid, writer.shnum,
8394                                           self.versioninfo, started)
8395hunk ./src/allmydata/mutable/publish.py 936
8396-
8397-        # self.loop() will take care of checking to see if we're done
8398-        return
8399+            self.placed.add( (peerid, writer.shnum) )
8400 
8401hunk ./src/allmydata/mutable/publish.py 938
8402-    def _got_write_error(self, f, peerid, shnums, started):
8403-        for shnum in shnums:
8404-            self.outstanding.discard( (peerid, shnum) )
8405-        self.bad_peers.add(peerid)
8406-        if self._first_write_error is None:
8407-            self._first_write_error = f
8408-        self.log(format="error while writing shares %(shnums)s to peerid %(peerid)s",
8409-                 shnums=list(shnums), peerid=idlib.shortnodeid_b2a(peerid),
8410-                 failure=f,
8411-                 level=log.UNUSUAL)
8412         # self.loop() will take care of checking to see if we're done
8413         return
8414 
8415hunk ./src/allmydata/mutable/publish.py 949
8416         now = time.time()
8417         self._status.timings["total"] = now - self._started
8418         self._status.set_active(False)
8419-        if isinstance(res, failure.Failure):
8420-            self.log("Publish done, with failure", failure=res,
8421-                     level=log.WEIRD, umid="nRsR9Q")
8422-            self._status.set_status("Failed")
8423-        elif self.surprised:
8424-            self.log("Publish done, UncoordinatedWriteError", level=log.UNUSUAL)
8425-            self._status.set_status("UncoordinatedWriteError")
8426-            # deliver a failure
8427-            res = failure.Failure(UncoordinatedWriteError())
8428-            # TODO: recovery
8429-        else:
8430-            self.log("Publish done, success")
8431-            self._status.set_status("Finished")
8432-            self._status.set_progress(1.0)
8433+        self.log("Publish done, success")
8434+        self._status.set_status("Finished")
8435+        self._status.set_progress(1.0)
8436         eventually(self.done_deferred.callback, res)
8437 
8438hunk ./src/allmydata/mutable/publish.py 954
8439+    def _failure(self):
8440+
8441+        if not self.surprised:
8442+            # We ran out of servers
8443+            self.log("Publish ran out of good servers, "
8444+                     "last failure was: %s" % str(self._last_failure))
8445+            e = NotEnoughServersError("Ran out of non-bad servers, "
8446+                                      "last failure was %s" %
8447+                                      str(self._last_failure))
8448+        else:
8449+            # We ran into shares that we didn't recognize, which means
8450+            # that we need to return an UncoordinatedWriteError.
8451+            self.log("Publish failed with UncoordinatedWriteError")
8452+            e = UncoordinatedWriteError()
8453+        f = failure.Failure(e)
8454+        eventually(self.done_deferred.callback, f)
8455}
8456[test/test_mutable.py: remove tests that are no longer relevant
8457Kevan Carstensen <kevan@isnotajoke.com>**20100702225710
8458 Ignore-this: 90a26b4cc4b2e190a635474ba7097e21
8459] hunk ./src/allmydata/test/test_mutable.py 627
8460         return d
8461 
8462 
8463-class MakeShares(unittest.TestCase):
8464-    def test_encrypt(self):
8465-        nm = make_nodemaker()
8466-        CONTENTS = "some initial contents"
8467-        d = nm.create_mutable_file(CONTENTS)
8468-        def _created(fn):
8469-            p = Publish(fn, nm.storage_broker, None)
8470-            p.salt = "SALT" * 4
8471-            p.readkey = "\x00" * 16
8472-            p.newdata = CONTENTS
8473-            p.required_shares = 3
8474-            p.total_shares = 10
8475-            p.setup_encoding_parameters()
8476-            return p._encrypt_and_encode()
8477-        d.addCallback(_created)
8478-        def _done(shares_and_shareids):
8479-            (shares, share_ids) = shares_and_shareids
8480-            self.failUnlessEqual(len(shares), 10)
8481-            for sh in shares:
8482-                self.failUnless(isinstance(sh, str))
8483-                self.failUnlessEqual(len(sh), 7)
8484-            self.failUnlessEqual(len(share_ids), 10)
8485-        d.addCallback(_done)
8486-        return d
8487-    test_encrypt.todo = "Write an equivalent of this for the new uploader"
8488-
8489-    def test_generate(self):
8490-        nm = make_nodemaker()
8491-        CONTENTS = "some initial contents"
8492-        d = nm.create_mutable_file(CONTENTS)
8493-        def _created(fn):
8494-            self._fn = fn
8495-            p = Publish(fn, nm.storage_broker, None)
8496-            self._p = p
8497-            p.newdata = CONTENTS
8498-            p.required_shares = 3
8499-            p.total_shares = 10
8500-            p.setup_encoding_parameters()
8501-            p._new_seqnum = 3
8502-            p.salt = "SALT" * 4
8503-            # make some fake shares
8504-            shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
8505-            p._privkey = fn.get_privkey()
8506-            p._encprivkey = fn.get_encprivkey()
8507-            p._pubkey = fn.get_pubkey()
8508-            return p._generate_shares(shares_and_ids)
8509-        d.addCallback(_created)
8510-        def _generated(res):
8511-            p = self._p
8512-            final_shares = p.shares
8513-            root_hash = p.root_hash
8514-            self.failUnlessEqual(len(root_hash), 32)
8515-            self.failUnless(isinstance(final_shares, dict))
8516-            self.failUnlessEqual(len(final_shares), 10)
8517-            self.failUnlessEqual(sorted(final_shares.keys()), range(10))
8518-            for i,sh in final_shares.items():
8519-                self.failUnless(isinstance(sh, str))
8520-                # feed the share through the unpacker as a sanity-check
8521-                pieces = unpack_share(sh)
8522-                (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
8523-                 pubkey, signature, share_hash_chain, block_hash_tree,
8524-                 share_data, enc_privkey) = pieces
8525-                self.failUnlessEqual(u_seqnum, 3)
8526-                self.failUnlessEqual(u_root_hash, root_hash)
8527-                self.failUnlessEqual(k, 3)
8528-                self.failUnlessEqual(N, 10)
8529-                self.failUnlessEqual(segsize, 21)
8530-                self.failUnlessEqual(datalen, len(CONTENTS))
8531-                self.failUnlessEqual(pubkey, p._pubkey.serialize())
8532-                sig_material = struct.pack(">BQ32s16s BBQQ",
8533-                                           0, p._new_seqnum, root_hash, IV,
8534-                                           k, N, segsize, datalen)
8535-                self.failUnless(p._pubkey.verify(sig_material, signature))
8536-                #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
8537-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
8538-                for shnum,share_hash in share_hash_chain.items():
8539-                    self.failUnless(isinstance(shnum, int))
8540-                    self.failUnless(isinstance(share_hash, str))
8541-                    self.failUnlessEqual(len(share_hash), 32)
8542-                self.failUnless(isinstance(block_hash_tree, list))
8543-                self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
8544-                self.failUnlessEqual(IV, "SALT"*4)
8545-                self.failUnlessEqual(len(share_data), len("%07d" % 1))
8546-                self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
8547-        d.addCallback(_generated)
8548-        return d
8549-    test_generate.todo = "Write an equivalent of this for the new uploader"
8550-
8551-    # TODO: when we publish to 20 peers, we should get one share per peer on 10
8552-    # when we publish to 3 peers, we should get either 3 or 4 shares per peer
8553-    # when we publish to zero peers, we should get a NotEnoughSharesError
8554-
8555 class PublishMixin:
8556     def publish_one(self):
8557         # publish a file and create shares, which can then be manipulated
8558[interfaces.py: create IMutableUploadable
8559Kevan Carstensen <kevan@isnotajoke.com>**20100706215217
8560 Ignore-this: bee202ec2bfbd8e41f2d4019cce176c7
8561] hunk ./src/allmydata/interfaces.py 1693
8562         """The upload is finished, and whatever filehandle was in use may be
8563         closed."""
8564 
8565+
8566+class IMutableUploadable(Interface):
8567+    """
8568+    I represent content that is due to be uploaded to a mutable filecap.
8569+    """
8570+    # This is somewhat simpler than the IUploadable interface above
8571+    # because mutable files do not need to be concerned with possibly
8572+    # generating a CHK, nor with per-file keys. It is a subset of the
8573+    # methods in IUploadable, though, so we could just as well implement
8574+    # the mutable uploadables as IUploadables that don't happen to use
8575+    # those methods (with the understanding that the unused methods will
8576+    # never be called on such objects)
8577+    def get_size():
8578+        """
8579+        Returns a Deferred that fires with the size of the content held
8580+        by the uploadable.
8581+        """
8582+
8583+    def read(length):
8584+        """
8585+        Returns a list of strings which, when concatenated, are the next
8586+        length bytes of the file, or fewer if there are fewer bytes
8587+        between the current location and the end of the file.
8588+        """
8589+
8590+    def close():
8591+        """
8592+        The process that used the Uploadable is finished using it, so
8593+        the uploadable may be closed.
8594+        """
8595+
8596 class IUploadResults(Interface):
8597     """I am returned by upload() methods. I contain a number of public
8598     attributes which can be read to determine the results of the upload. Some
8599[mutable/publish.py: add MutableDataHandle and MutableFileHandle
8600Kevan Carstensen <kevan@isnotajoke.com>**20100706215257
8601 Ignore-this: 295ea3bc2a962fd14fb7877fc76c011c
8602] {
8603hunk ./src/allmydata/mutable/publish.py 8
8604 from zope.interface import implements
8605 from twisted.internet import defer
8606 from twisted.python import failure
8607-from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
8608+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
8609+                                 IMutableUploadable
8610 from allmydata.util import base32, hashutil, mathutil, idlib, log
8611 from allmydata import hashtree, codec
8612 from allmydata.storage.server import si_b2a
8613hunk ./src/allmydata/mutable/publish.py 971
8614             e = UncoordinatedWriteError()
8615         f = failure.Failure(e)
8616         eventually(self.done_deferred.callback, f)
8617+
8618+
8619+class MutableFileHandle:
8620+    """
8621+    I am a mutable uploadable built around a filehandle-like object,
8622+    usually either a StringIO instance or a handle to an actual file.
8623+    """
8624+    implements(IMutableUploadable)
8625+
8626+    def __init__(self, filehandle):
8627+        # The filehandle is defined as a generally file-like object that
8628+        # has these two methods. We don't care beyond that.
8629+        assert hasattr(filehandle, "read")
8630+        assert hasattr(filehandle, "close")
8631+
8632+        self._filehandle = filehandle
8633+
8634+
8635+    def get_size(self):
8636+        """
8637+        I return the amount of data in my filehandle.
8638+        """
8639+        if not hasattr(self, "_size"):
8640+            old_position = self._filehandle.tell()
8641+            # Seek to the end of the file by seeking 0 bytes from the
8642+            # file's end
8643+            self._filehandle.seek(0, os.SEEK_END)
8644+            self._size = self._filehandle.tell()
8645+            # Restore the previous position, in case this was called
8646+            # after a read.
8647+            self._filehandle.seek(old_position)
8648+            assert self._filehandle.tell() == old_position
8649+
8650+        assert hasattr(self, "_size")
8651+        return self._size
8652+
8653+
8654+    def read(self, length):
8655+        """
8656+        I return some data (up to length bytes) from my filehandle.
8657+
8658+        In most cases, I return length bytes. If I don't, it is because
8659+        length is longer than the distance between my current position
8660+        in the file that I represent and its end. In that case, I return
8661+        as many bytes as I can before going over the EOF.
8662+        """
8663+        return [self._filehandle.read(length)]
8664+
8665+
8666+    def close(self):
8667+        """
8668+        I close the underlying filehandle. Any further operations on the
8669+        filehandle fail at this point.
8670+        """
8671+        self._filehandle.close()
8672+
8673+
8674+class MutableDataHandle(MutableFileHandle):
8675+    """
8676+    I am a mutable uploadable built around a string, which I then cast
8677+    into a StringIO and treat as a filehandle.
8678+    """
8679+
8680+    def __init__(self, s):
8681+        # Take a string and return a file-like uploadable.
8682+        assert isinstance(s, str)
8683+
8684+        MutableFileHandle.__init__(self, StringIO(s))
8685}
8686[mutable/publish.py: reorganize in preparation of file-like uploadables
8687Kevan Carstensen <kevan@isnotajoke.com>**20100706215541
8688 Ignore-this: 5346c9f919ee5b73807c8f287c64e8ce
8689] {
8690hunk ./src/allmydata/mutable/publish.py 4
8691 
8692 
8693 import os, struct, time
8694+from StringIO import StringIO
8695 from itertools import count
8696 from zope.interface import implements
8697 from twisted.internet import defer
8698hunk ./src/allmydata/mutable/publish.py 118
8699         self._status.set_helper(False)
8700         self._status.set_progress(0.0)
8701         self._status.set_active(True)
8702-        # We use this to control how the file is written.
8703-        version = self._node.get_version()
8704-        assert version in (SDMF_VERSION, MDMF_VERSION)
8705-        self._version = version
8706+        self._version = self._node.get_version()
8707+        assert self._version in (SDMF_VERSION, MDMF_VERSION)
8708+
8709 
8710     def get_status(self):
8711         return self._status
8712hunk ./src/allmydata/mutable/publish.py 141
8713 
8714         # 0. Setup encoding parameters, encoder, and other such things.
8715         # 1. Encrypt, encode, and publish segments.
8716+        self.data = StringIO(newdata)
8717+        self.datalength = len(newdata)
8718 
8719hunk ./src/allmydata/mutable/publish.py 144
8720-        self.log("starting publish, datalen is %s" % len(newdata))
8721-        self._status.set_size(len(newdata))
8722+        self.log("starting publish, datalen is %s" % self.datalength)
8723+        self._status.set_size(self.datalength)
8724         self._status.set_status("Started")
8725         self._started = time.time()
8726 
8727hunk ./src/allmydata/mutable/publish.py 193
8728         self.full_peerlist = full_peerlist # for use later, immutable
8729         self.bad_peers = set() # peerids who have errbacked/refused requests
8730 
8731-        self.newdata = newdata
8732-
8733         # This will set self.segment_size, self.num_segments, and
8734         # self.fec.
8735         self.setup_encoding_parameters()
8736hunk ./src/allmydata/mutable/publish.py 272
8737                                                 self.required_shares,
8738                                                 self.total_shares,
8739                                                 self.segment_size,
8740-                                                len(self.newdata))
8741+                                                self.datalength)
8742             self.writers[shnum].peerid = peerid
8743             if (peerid, shnum) in self._servermap.servermap:
8744                 old_versionid, old_timestamp = self._servermap.servermap[key]
8745hunk ./src/allmydata/mutable/publish.py 318
8746         if self._version == MDMF_VERSION:
8747             segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
8748         else:
8749-            segment_size = len(self.newdata) # SDMF is only one segment
8750+            segment_size = self.datalength # SDMF is only one segment
8751         # this must be a multiple of self.required_shares
8752         segment_size = mathutil.next_multiple(segment_size,
8753                                               self.required_shares)
8754hunk ./src/allmydata/mutable/publish.py 324
8755         self.segment_size = segment_size
8756         if segment_size:
8757-            self.num_segments = mathutil.div_ceil(len(self.newdata),
8758+            self.num_segments = mathutil.div_ceil(self.datalength,
8759                                                   segment_size)
8760         else:
8761             self.num_segments = 0
8762hunk ./src/allmydata/mutable/publish.py 337
8763             assert self.num_segments in (0, 1) # SDMF
8764         # calculate the tail segment size.
8765 
8766-        if segment_size and self.newdata:
8767-            self.tail_segment_size = len(self.newdata) % segment_size
8768+        if segment_size and self.datalength:
8769+            self.tail_segment_size = self.datalength % segment_size
8770         else:
8771             self.tail_segment_size = 0
8772 
8773hunk ./src/allmydata/mutable/publish.py 438
8774             segsize = self.segment_size
8775 
8776 
8777-        offset = self.segment_size * segnum
8778-        length = segsize + offset
8779         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
8780hunk ./src/allmydata/mutable/publish.py 439
8781-        data = self.newdata[offset:length]
8782+        data = self.data.read(segsize)
8783+
8784         assert len(data) == segsize
8785 
8786         salt = os.urandom(16)
8787hunk ./src/allmydata/mutable/publish.py 502
8788             d.addCallback(self._got_write_answer, writer, started)
8789             d.addErrback(self._connection_problem, writer)
8790             dl.append(d)
8791-            # TODO: Naturally, we need to check on the results of these.
8792         return defer.DeferredList(dl)
8793 
8794 
8795}
8796[test/test_mutable.py: write tests for MutableFileHandle and MutableDataHandle
8797Kevan Carstensen <kevan@isnotajoke.com>**20100706215649
8798 Ignore-this: df719a0c52b4bbe9be4fae206c7ab3e7
8799] {
8800hunk ./src/allmydata/test/test_mutable.py 2
8801 
8802-import struct
8803+import struct, os
8804 from cStringIO import StringIO
8805 from twisted.trial import unittest
8806 from twisted.internet import defer, reactor
8807hunk ./src/allmydata/test/test_mutable.py 26
8808      NeedMoreDataError, UnrecoverableFileError, UncoordinatedWriteError, \
8809      NotEnoughServersError, CorruptShareError
8810 from allmydata.mutable.retrieve import Retrieve
8811-from allmydata.mutable.publish import Publish
8812+from allmydata.mutable.publish import Publish, MutableFileHandle, \
8813+                                      MutableDataHandle
8814 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
8815 from allmydata.mutable.layout import unpack_header, unpack_share, \
8816                                      MDMFSlotReadProxy
8817hunk ./src/allmydata/test/test_mutable.py 2465
8818         d.addCallback(lambda data:
8819             self.failUnlessEqual(data, CONTENTS))
8820         return d
8821+
8822+
8823+class FileHandle(unittest.TestCase):
8824+    def setUp(self):
8825+        self.test_data = "Test Data" * 50000
8826+        self.sio = StringIO(self.test_data)
8827+        self.uploadable = MutableFileHandle(self.sio)
8828+
8829+
8830+    def test_filehandle_read(self):
8831+        self.basedir = "mutable/FileHandle/test_filehandle_read"
8832+        chunk_size = 10
8833+        for i in xrange(0, len(self.test_data), chunk_size):
8834+            data = self.uploadable.read(chunk_size)
8835+            data = "".join(data)
8836+            start = i
8837+            end = i + chunk_size
8838+            self.failUnlessEqual(data, self.test_data[start:end])
8839+
8840+
8841+    def test_filehandle_get_size(self):
8842+        self.basedir = "mutable/FileHandle/test_filehandle_get_size"
8843+        actual_size = len(self.test_data)
8844+        size = self.uploadable.get_size()
8845+        self.failUnlessEqual(size, actual_size)
8846+
8847+
8848+    def test_filehandle_get_size_out_of_order(self):
8849+        # We should be able to call get_size whenever we want without
8850+        # disturbing the location of the seek pointer.
8851+        chunk_size = 100
8852+        data = self.uploadable.read(chunk_size)
8853+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8854+
8855+        # Now get the size.
8856+        size = self.uploadable.get_size()
8857+        self.failUnlessEqual(size, len(self.test_data))
8858+
8859+        # Now get more data. We should be right where we left off.
8860+        more_data = self.uploadable.read(chunk_size)
8861+        start = chunk_size
8862+        end = chunk_size * 2
8863+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8864+
8865+
8866+    def test_filehandle_file(self):
8867+        # Make sure that the MutableFileHandle works on a file as well
8868+        # as a StringIO object, since in some cases it will be asked to
8869+        # deal with files.
8870+        self.basedir = self.mktemp()
8871+        # necessary? What am I doing wrong here?
8872+        os.mkdir(self.basedir)
8873+        f_path = os.path.join(self.basedir, "test_file")
8874+        f = open(f_path, "w")
8875+        f.write(self.test_data)
8876+        f.close()
8877+        f = open(f_path, "r")
8878+
8879+        uploadable = MutableFileHandle(f)
8880+
8881+        data = uploadable.read(len(self.test_data))
8882+        self.failUnlessEqual("".join(data), self.test_data)
8883+        size = uploadable.get_size()
8884+        self.failUnlessEqual(size, len(self.test_data))
8885+
8886+
8887+    def test_close(self):
8888+        # Make sure that the MutableFileHandle closes its handle when
8889+        # told to do so.
8890+        self.uploadable.close()
8891+        self.failUnless(self.sio.closed)
8892+
8893+
8894+class DataHandle(unittest.TestCase):
8895+    def setUp(self):
8896+        self.test_data = "Test Data" * 50000
8897+        self.uploadable = MutableDataHandle(self.test_data)
8898+
8899+
8900+    def test_datahandle_read(self):
8901+        chunk_size = 10
8902+        for i in xrange(0, len(self.test_data), chunk_size):
8903+            data = self.uploadable.read(chunk_size)
8904+            data = "".join(data)
8905+            start = i
8906+            end = i + chunk_size
8907+            self.failUnlessEqual(data, self.test_data[start:end])
8908+
8909+
8910+    def test_datahandle_get_size(self):
8911+        actual_size = len(self.test_data)
8912+        size = self.uploadable.get_size()
8913+        self.failUnlessEqual(size, actual_size)
8914+
8915+
8916+    def test_datahandle_get_size_out_of_order(self):
8917+        # We should be able to call get_size whenever we want without
8918+        # disturbing the location of the seek pointer.
8919+        chunk_size = 100
8920+        data = self.uploadable.read(chunk_size)
8921+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8922+
8923+        # Now get the size.
8924+        size = self.uploadable.get_size()
8925+        self.failUnlessEqual(size, len(self.test_data))
8926+
8927+        # Now get more data. We should be right where we left off.
8928+        more_data = self.uploadable.read(chunk_size)
8929+        start = chunk_size
8930+        end = chunk_size * 2
8931+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8932}
8933[Alter tests to work with the new APIs
8934Kevan Carstensen <kevan@isnotajoke.com>**20100708000031
8935 Ignore-this: 1f377904ac61ce40e9a04716fbd2ad95
8936] {
8937hunk ./src/allmydata/test/common.py 12
8938 from allmydata import uri, dirnode, client
8939 from allmydata.introducer.server import IntroducerNode
8940 from allmydata.interfaces import IMutableFileNode, IImmutableFileNode, \
8941-     FileTooLargeError, NotEnoughSharesError, ICheckable
8942+     FileTooLargeError, NotEnoughSharesError, ICheckable, \
8943+     IMutableUploadable
8944 from allmydata.check_results import CheckResults, CheckAndRepairResults, \
8945      DeepCheckResults, DeepCheckAndRepairResults
8946 from allmydata.mutable.common import CorruptShareError
8947hunk ./src/allmydata/test/common.py 18
8948 from allmydata.mutable.layout import unpack_header
8949+from allmydata.mutable.publish import MutableDataHandle
8950 from allmydata.storage.server import storage_index_to_dir
8951 from allmydata.storage.mutable import MutableShareFile
8952 from allmydata.util import hashutil, log, fileutil, pollmixin
8953hunk ./src/allmydata/test/common.py 182
8954         self.init_from_cap(make_mutable_file_cap())
8955     def create(self, contents, key_generator=None, keysize=None):
8956         initial_contents = self._get_initial_contents(contents)
8957-        if len(initial_contents) > self.MUTABLE_SIZELIMIT:
8958+        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
8959             raise FileTooLargeError("SDMF is limited to one segment, and "
8960hunk ./src/allmydata/test/common.py 184
8961-                                    "%d > %d" % (len(initial_contents),
8962+                                    "%d > %d" % (initial_contents.get_size(),
8963                                                  self.MUTABLE_SIZELIMIT))
8964hunk ./src/allmydata/test/common.py 186
8965-        self.all_contents[self.storage_index] = initial_contents
8966+        data = initial_contents.read(initial_contents.get_size())
8967+        data = "".join(data)
8968+        self.all_contents[self.storage_index] = data
8969         return defer.succeed(self)
8970     def _get_initial_contents(self, contents):
8971hunk ./src/allmydata/test/common.py 191
8972-        if isinstance(contents, str):
8973-            return contents
8974         if contents is None:
8975hunk ./src/allmydata/test/common.py 192
8976-            return ""
8977+            return MutableDataHandle("")
8978+
8979+        if IMutableUploadable.providedBy(contents):
8980+            return contents
8981+
8982         assert callable(contents), "%s should be callable, not %s" % \
8983                (contents, type(contents))
8984         return contents(self)
8985hunk ./src/allmydata/test/common.py 309
8986         return defer.succeed(self.all_contents[self.storage_index])
8987 
8988     def overwrite(self, new_contents):
8989-        if len(new_contents) > self.MUTABLE_SIZELIMIT:
8990+        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
8991             raise FileTooLargeError("SDMF is limited to one segment, and "
8992hunk ./src/allmydata/test/common.py 311
8993-                                    "%d > %d" % (len(new_contents),
8994+                                    "%d > %d" % (new_contents.get_size(),
8995                                                  self.MUTABLE_SIZELIMIT))
8996         assert not self.is_readonly()
8997hunk ./src/allmydata/test/common.py 314
8998-        self.all_contents[self.storage_index] = new_contents
8999+        new_data = new_contents.read(new_contents.get_size())
9000+        new_data = "".join(new_data)
9001+        self.all_contents[self.storage_index] = new_data
9002         return defer.succeed(None)
9003     def modify(self, modifier):
9004         # this does not implement FileTooLargeError, but the real one does
9005hunk ./src/allmydata/test/common.py 324
9006     def _modify(self, modifier):
9007         assert not self.is_readonly()
9008         old_contents = self.all_contents[self.storage_index]
9009-        self.all_contents[self.storage_index] = modifier(old_contents, None, True)
9010+        new_data = modifier(old_contents, None, True)
9011+        if new_data is not None:
9012+            new_data = new_data.read(new_data.get_size())
9013+            new_data = "".join(new_data)
9014+        self.all_contents[self.storage_index] = new_data
9015         return None
9016 
9017 def make_mutable_file_cap():
9018hunk ./src/allmydata/test/test_checker.py 11
9019 from allmydata.test.no_network import GridTestMixin
9020 from allmydata.immutable.upload import Data
9021 from allmydata.test.common_web import WebRenderingMixin
9022+from allmydata.mutable.publish import MutableDataHandle
9023 
9024 class FakeClient:
9025     def get_storage_broker(self):
9026hunk ./src/allmydata/test/test_checker.py 291
9027         def _stash_immutable(ur):
9028             self.imm = c0.create_node_from_uri(ur.uri)
9029         d.addCallback(_stash_immutable)
9030-        d.addCallback(lambda ign: c0.create_mutable_file("contents"))
9031+        d.addCallback(lambda ign:
9032+            c0.create_mutable_file(MutableDataHandle("contents")))
9033         def _stash_mutable(node):
9034             self.mut = node
9035         d.addCallback(_stash_mutable)
9036hunk ./src/allmydata/test/test_cli.py 12
9037 from allmydata.util import fileutil, hashutil, base32
9038 from allmydata import uri
9039 from allmydata.immutable import upload
9040+from allmydata.mutable.publish import MutableDataHandle
9041 from allmydata.dirnode import normalize
9042 
9043 # Test that the scripts can be imported -- although the actual tests of their
9044hunk ./src/allmydata/test/test_cli.py 1983
9045         self.set_up_grid()
9046         c0 = self.g.clients[0]
9047         DATA = "data" * 100
9048-        d = c0.create_mutable_file(DATA)
9049+        DATA_uploadable = MutableDataHandle(DATA)
9050+        d = c0.create_mutable_file(DATA_uploadable)
9051         def _stash_uri(n):
9052             self.uri = n.get_uri()
9053         d.addCallback(_stash_uri)
9054hunk ./src/allmydata/test/test_cli.py 2085
9055                                            upload.Data("literal",
9056                                                         convergence="")))
9057         d.addCallback(_stash_uri, "small")
9058-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"1"))
9059+        d.addCallback(lambda ign:
9060+            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
9061         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9062         d.addCallback(_stash_uri, "mutable")
9063 
9064hunk ./src/allmydata/test/test_cli.py 2104
9065         # root/small
9066         # root/mutable
9067 
9068+        # We haven't broken anything yet, so this should all be healthy.
9069         d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
9070                                               self.rooturi))
9071         def _check2((rc, out, err)):
9072hunk ./src/allmydata/test/test_cli.py 2119
9073                             in lines, out)
9074         d.addCallback(_check2)
9075 
9076+        # Similarly, all of these results should be as we expect them to
9077+        # be for a healthy file layout.
9078         d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
9079         def _check_stats((rc, out, err)):
9080             self.failUnlessReallyEqual(err, "")
9081hunk ./src/allmydata/test/test_cli.py 2136
9082             self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
9083         d.addCallback(_check_stats)
9084 
9085+        # Now we break things.
9086         def _clobber_shares(ignored):
9087             shares = self.find_shares(self.uris[u"gööd"])
9088             self.failUnlessReallyEqual(len(shares), 10)
9089hunk ./src/allmydata/test/test_cli.py 2155
9090         d.addCallback(_clobber_shares)
9091 
9092         # root
9093-        # root/gööd  [9 shares]
9094+        # root/gööd  [1 missing share]
9095         # root/small
9096         # root/mutable [1 corrupt share]
9097 
9098hunk ./src/allmydata/test/test_cli.py 2161
9099         d.addCallback(lambda ign:
9100                       self.do_cli("deep-check", "--verbose", self.rooturi))
9101+        # This should reveal the missing share, but not the corrupt
9102+        # share, since we didn't tell the deep check operation to also
9103+        # verify.
9104         def _check3((rc, out, err)):
9105             self.failUnlessReallyEqual(err, "")
9106             self.failUnlessReallyEqual(rc, 0)
9107hunk ./src/allmydata/test/test_cli.py 2212
9108                                   "--verbose", "--verify", "--repair",
9109                                   self.rooturi))
9110         def _check6((rc, out, err)):
9111+            # We've just repaired the directory. There is no reason for
9112+            # that repair to be unsuccessful.
9113             self.failUnlessReallyEqual(err, "")
9114             self.failUnlessReallyEqual(rc, 0)
9115             lines = out.splitlines()
9116hunk ./src/allmydata/test/test_deepcheck.py 9
9117 from twisted.internet import threads # CLI tests use deferToThread
9118 from allmydata.immutable import upload
9119 from allmydata.mutable.common import UnrecoverableFileError
9120+from allmydata.mutable.publish import MutableDataHandle
9121 from allmydata.util import idlib
9122 from allmydata.util import base32
9123 from allmydata.scripts import runner
9124hunk ./src/allmydata/test/test_deepcheck.py 38
9125         self.basedir = "deepcheck/MutableChecker/good"
9126         self.set_up_grid()
9127         CONTENTS = "a little bit of data"
9128-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9129+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9130+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9131         def _created(node):
9132             self.node = node
9133             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9134hunk ./src/allmydata/test/test_deepcheck.py 61
9135         self.basedir = "deepcheck/MutableChecker/corrupt"
9136         self.set_up_grid()
9137         CONTENTS = "a little bit of data"
9138-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9139+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9140+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9141         def _stash_and_corrupt(node):
9142             self.node = node
9143             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9144hunk ./src/allmydata/test/test_deepcheck.py 99
9145         self.basedir = "deepcheck/MutableChecker/delete_share"
9146         self.set_up_grid()
9147         CONTENTS = "a little bit of data"
9148-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9149+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9150+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9151         def _stash_and_delete(node):
9152             self.node = node
9153             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9154hunk ./src/allmydata/test/test_deepcheck.py 223
9155             self.root = n
9156             self.root_uri = n.get_uri()
9157         d.addCallback(_created_root)
9158-        d.addCallback(lambda ign: c0.create_mutable_file("mutable file contents"))
9159+        d.addCallback(lambda ign:
9160+            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
9161         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
9162         def _created_mutable(n):
9163             self.mutable = n
9164hunk ./src/allmydata/test/test_deepcheck.py 965
9165     def create_mangled(self, ignored, name):
9166         nodetype, mangletype = name.split("-", 1)
9167         if nodetype == "mutable":
9168-            d = self.g.clients[0].create_mutable_file("mutable file contents")
9169+            mutable_uploadable = MutableDataHandle("mutable file contents")
9170+            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
9171             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
9172         elif nodetype == "large":
9173             large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
9174hunk ./src/allmydata/test/test_dirnode.py 1281
9175     implements(IMutableFileNode)
9176     counter = 0
9177     def __init__(self, initial_contents=""):
9178-        self.data = self._get_initial_contents(initial_contents)
9179+        data = self._get_initial_contents(initial_contents)
9180+        self.data = data.read(data.get_size())
9181+        self.data = "".join(self.data)
9182+
9183         counter = FakeMutableFile.counter
9184         FakeMutableFile.counter += 1
9185         writekey = hashutil.ssk_writekey_hash(str(counter))
9186hunk ./src/allmydata/test/test_dirnode.py 1331
9187         pass
9188 
9189     def modify(self, modifier):
9190-        self.data = modifier(self.data, None, True)
9191+        data = modifier(self.data, None, True)
9192+        self.data = data.read(data.get_size())
9193+        self.data = "".join(self.data)
9194         return defer.succeed(None)
9195 
9196 class FakeNodeMaker(NodeMaker):
9197hunk ./src/allmydata/test/test_hung_server.py 10
9198 from allmydata.util.consumer import download_to_data
9199 from allmydata.immutable import upload
9200 from allmydata.mutable.common import UnrecoverableFileError
9201+from allmydata.mutable.publish import MutableDataHandle
9202 from allmydata.storage.common import storage_index_to_dir
9203 from allmydata.test.no_network import GridTestMixin
9204 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
9205hunk ./src/allmydata/test/test_hung_server.py 96
9206         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
9207 
9208         if mutable:
9209-            d = nm.create_mutable_file(mutable_plaintext)
9210+            uploadable = MutableDataHandle(mutable_plaintext)
9211+            d = nm.create_mutable_file(uploadable)
9212             def _uploaded_mutable(node):
9213                 self.uri = node.get_uri()
9214                 self.shares = self.find_shares(self.uri)
9215hunk ./src/allmydata/test/test_mutable.py 297
9216             d.addCallback(lambda smap: smap.dump(StringIO()))
9217             d.addCallback(lambda sio:
9218                           self.failUnless("3-of-10" in sio.getvalue()))
9219-            d.addCallback(lambda res: n.overwrite("contents 1"))
9220+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9221             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9222             d.addCallback(lambda res: n.download_best_version())
9223             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9224hunk ./src/allmydata/test/test_mutable.py 304
9225             d.addCallback(lambda res: n.get_size_of_best_version())
9226             d.addCallback(lambda size:
9227                           self.failUnlessEqual(size, len("contents 1")))
9228-            d.addCallback(lambda res: n.overwrite("contents 2"))
9229+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9230             d.addCallback(lambda res: n.download_best_version())
9231             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9232             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9233hunk ./src/allmydata/test/test_mutable.py 308
9234-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9235+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9236             d.addCallback(lambda res: n.download_best_version())
9237             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9238             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9239hunk ./src/allmydata/test/test_mutable.py 320
9240             # mapupdate-to-retrieve data caching (i.e. make the shares larger
9241             # than the default readsize, which is 2000 bytes). A 15kB file
9242             # will have 5kB shares.
9243-            d.addCallback(lambda res: n.overwrite("large size file" * 1000))
9244+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
9245             d.addCallback(lambda res: n.download_best_version())
9246             d.addCallback(lambda res:
9247                           self.failUnlessEqual(res, "large size file" * 1000))
9248hunk ./src/allmydata/test/test_mutable.py 343
9249             # to make them big enough to force the file to be uploaded
9250             # in more than one segment.
9251             big_contents = "contents1" * 100000 # about 900 KiB
9252+            big_contents_uploadable = MutableDataHandle(big_contents)
9253             d.addCallback(lambda ignored:
9254hunk ./src/allmydata/test/test_mutable.py 345
9255-                n.overwrite(big_contents))
9256+                n.overwrite(big_contents_uploadable))
9257             d.addCallback(lambda ignored:
9258                 n.download_best_version())
9259             d.addCallback(lambda data:
9260hunk ./src/allmydata/test/test_mutable.py 355
9261             # segments, so that we make the downloader deal with
9262             # multiple segments.
9263             bigger_contents = "contents2" * 1000000 # about 9MiB
9264+            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
9265             d.addCallback(lambda ignored:
9266hunk ./src/allmydata/test/test_mutable.py 357
9267-                n.overwrite(bigger_contents))
9268+                n.overwrite(bigger_contents_uploadable))
9269             d.addCallback(lambda ignored:
9270                 n.download_best_version())
9271             d.addCallback(lambda data:
9272hunk ./src/allmydata/test/test_mutable.py 368
9273 
9274 
9275     def test_create_with_initial_contents(self):
9276-        d = self.nodemaker.create_mutable_file("contents 1")
9277+        upload1 = MutableDataHandle("contents 1")
9278+        d = self.nodemaker.create_mutable_file(upload1)
9279         def _created(n):
9280             d = n.download_best_version()
9281             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9282hunk ./src/allmydata/test/test_mutable.py 373
9283-            d.addCallback(lambda res: n.overwrite("contents 2"))
9284+            upload2 = MutableDataHandle("contents 2")
9285+            d.addCallback(lambda res: n.overwrite(upload2))
9286             d.addCallback(lambda res: n.download_best_version())
9287             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9288             return d
9289hunk ./src/allmydata/test/test_mutable.py 380
9290         d.addCallback(_created)
9291         return d
9292+    test_create_with_initial_contents.timeout = 15
9293 
9294 
9295     def test_create_mdmf_with_initial_contents(self):
9296hunk ./src/allmydata/test/test_mutable.py 385
9297         initial_contents = "foobarbaz" * 131072 # 900KiB
9298-        d = self.nodemaker.create_mutable_file(initial_contents,
9299+        initial_contents_uploadable = MutableDataHandle(initial_contents)
9300+        d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
9301                                                version=MDMF_VERSION)
9302         def _created(n):
9303             d = n.download_best_version()
9304hunk ./src/allmydata/test/test_mutable.py 392
9305             d.addCallback(lambda data:
9306                 self.failUnlessEqual(data, initial_contents))
9307+            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
9308             d.addCallback(lambda ignored:
9309hunk ./src/allmydata/test/test_mutable.py 394
9310-                n.overwrite(initial_contents + "foobarbaz"))
9311+                n.overwrite(uploadable2))
9312             d.addCallback(lambda ignored:
9313                 n.download_best_version())
9314             d.addCallback(lambda data:
9315hunk ./src/allmydata/test/test_mutable.py 413
9316             key = n.get_writekey()
9317             self.failUnless(isinstance(key, str), key)
9318             self.failUnlessEqual(len(key), 16) # AES key size
9319-            return data
9320+            return MutableDataHandle(data)
9321         d = self.nodemaker.create_mutable_file(_make_contents)
9322         def _created(n):
9323             return n.download_best_version()
9324hunk ./src/allmydata/test/test_mutable.py 429
9325             key = n.get_writekey()
9326             self.failUnless(isinstance(key, str), key)
9327             self.failUnlessEqual(len(key), 16)
9328-            return data
9329+            return MutableDataHandle(data)
9330         d = self.nodemaker.create_mutable_file(_make_contents,
9331                                                version=MDMF_VERSION)
9332         d.addCallback(lambda n:
9333hunk ./src/allmydata/test/test_mutable.py 441
9334 
9335     def test_create_with_too_large_contents(self):
9336         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9337-        d = self.nodemaker.create_mutable_file(BIG)
9338+        BIG_uploadable = MutableDataHandle(BIG)
9339+        d = self.nodemaker.create_mutable_file(BIG_uploadable)
9340         def _created(n):
9341hunk ./src/allmydata/test/test_mutable.py 444
9342-            d = n.overwrite(BIG)
9343+            other_BIG_uploadable = MutableDataHandle(BIG)
9344+            d = n.overwrite(other_BIG_uploadable)
9345             return d
9346         d.addCallback(_created)
9347         return d
9348hunk ./src/allmydata/test/test_mutable.py 459
9349 
9350     def test_modify(self):
9351         def _modifier(old_contents, servermap, first_time):
9352-            return old_contents + "line2"
9353+            new_contents = old_contents + "line2"
9354+            return MutableDataHandle(new_contents)
9355         def _non_modifier(old_contents, servermap, first_time):
9356hunk ./src/allmydata/test/test_mutable.py 462
9357-            return old_contents
9358+            return MutableDataHandle(old_contents)
9359         def _none_modifier(old_contents, servermap, first_time):
9360             return None
9361         def _error_modifier(old_contents, servermap, first_time):
9362hunk ./src/allmydata/test/test_mutable.py 468
9363             raise ValueError("oops")
9364         def _toobig_modifier(old_contents, servermap, first_time):
9365-            return "b" * (self.OLD_MAX_SEGMENT_SIZE+1)
9366+            new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9367+            return MutableDataHandle(new_content)
9368         calls = []
9369         def _ucw_error_modifier(old_contents, servermap, first_time):
9370             # simulate an UncoordinatedWriteError once
9371hunk ./src/allmydata/test/test_mutable.py 476
9372             calls.append(1)
9373             if len(calls) <= 1:
9374                 raise UncoordinatedWriteError("simulated")
9375-            return old_contents + "line3"
9376+            new_contents = old_contents + "line3"
9377+            return MutableDataHandle(new_contents)
9378         def _ucw_error_non_modifier(old_contents, servermap, first_time):
9379             # simulate an UncoordinatedWriteError once, and don't actually
9380             # modify the contents on subsequent invocations
9381hunk ./src/allmydata/test/test_mutable.py 484
9382             calls.append(1)
9383             if len(calls) <= 1:
9384                 raise UncoordinatedWriteError("simulated")
9385-            return old_contents
9386+            return MutableDataHandle(old_contents)
9387 
9388hunk ./src/allmydata/test/test_mutable.py 486
9389-        d = self.nodemaker.create_mutable_file("line1")
9390+        initial_contents = "line1"
9391+        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
9392         def _created(n):
9393             d = n.modify(_modifier)
9394             d.addCallback(lambda res: n.download_best_version())
9395hunk ./src/allmydata/test/test_mutable.py 548
9396 
9397     def test_modify_backoffer(self):
9398         def _modifier(old_contents, servermap, first_time):
9399-            return old_contents + "line2"
9400+            return MutableDataHandle(old_contents + "line2")
9401         calls = []
9402         def _ucw_error_modifier(old_contents, servermap, first_time):
9403             # simulate an UncoordinatedWriteError once
9404hunk ./src/allmydata/test/test_mutable.py 555
9405             calls.append(1)
9406             if len(calls) <= 1:
9407                 raise UncoordinatedWriteError("simulated")
9408-            return old_contents + "line3"
9409+            return MutableDataHandle(old_contents + "line3")
9410         def _always_ucw_error_modifier(old_contents, servermap, first_time):
9411             raise UncoordinatedWriteError("simulated")
9412         def _backoff_stopper(node, f):
9413hunk ./src/allmydata/test/test_mutable.py 570
9414         giveuper._delay = 0.1
9415         giveuper.factor = 1
9416 
9417-        d = self.nodemaker.create_mutable_file("line1")
9418+        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
9419         def _created(n):
9420             d = n.modify(_modifier)
9421             d.addCallback(lambda res: n.download_best_version())
9422hunk ./src/allmydata/test/test_mutable.py 620
9423             d.addCallback(lambda smap: smap.dump(StringIO()))
9424             d.addCallback(lambda sio:
9425                           self.failUnless("3-of-10" in sio.getvalue()))
9426-            d.addCallback(lambda res: n.overwrite("contents 1"))
9427+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9428             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9429             d.addCallback(lambda res: n.download_best_version())
9430             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9431hunk ./src/allmydata/test/test_mutable.py 624
9432-            d.addCallback(lambda res: n.overwrite("contents 2"))
9433+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9434             d.addCallback(lambda res: n.download_best_version())
9435             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9436             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9437hunk ./src/allmydata/test/test_mutable.py 628
9438-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9439+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9440             d.addCallback(lambda res: n.download_best_version())
9441             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9442             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9443hunk ./src/allmydata/test/test_mutable.py 646
9444         # publish a file and create shares, which can then be manipulated
9445         # later.
9446         self.CONTENTS = "New contents go here" * 1000
9447+        self.uploadable = MutableDataHandle(self.CONTENTS)
9448         self._storage = FakeStorage()
9449         self._nodemaker = make_nodemaker(self._storage)
9450         self._storage_broker = self._nodemaker.storage_broker
9451hunk ./src/allmydata/test/test_mutable.py 650
9452-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9453+        d = self._nodemaker.create_mutable_file(self.uploadable)
9454         def _created(node):
9455             self._fn = node
9456             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9457hunk ./src/allmydata/test/test_mutable.py 662
9458         # an MDMF file.
9459         # self.CONTENTS should have more than one segment.
9460         self.CONTENTS = "This is an MDMF file" * 100000
9461+        self.uploadable = MutableDataHandle(self.CONTENTS)
9462         self._storage = FakeStorage()
9463         self._nodemaker = make_nodemaker(self._storage)
9464         self._storage_broker = self._nodemaker.storage_broker
9465hunk ./src/allmydata/test/test_mutable.py 666
9466-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
9467+        d = self._nodemaker.create_mutable_file(self.uploadable, version=MDMF_VERSION)
9468         def _created(node):
9469             self._fn = node
9470             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9471hunk ./src/allmydata/test/test_mutable.py 678
9472         # like publish_one, except that the result is guaranteed to be
9473         # an SDMF file
9474         self.CONTENTS = "This is an SDMF file" * 1000
9475+        self.uploadable = MutableDataHandle(self.CONTENTS)
9476         self._storage = FakeStorage()
9477         self._nodemaker = make_nodemaker(self._storage)
9478         self._storage_broker = self._nodemaker.storage_broker
9479hunk ./src/allmydata/test/test_mutable.py 682
9480-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
9481+        d = self._nodemaker.create_mutable_file(self.uploadable, version=SDMF_VERSION)
9482         def _created(node):
9483             self._fn = node
9484             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9485hunk ./src/allmydata/test/test_mutable.py 696
9486                          "Contents 2",
9487                          "Contents 3a",
9488                          "Contents 3b"]
9489+        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
9490         self._copied_shares = {}
9491         self._storage = FakeStorage()
9492         self._nodemaker = make_nodemaker(self._storage)
9493hunk ./src/allmydata/test/test_mutable.py 700
9494-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
9495+        d = self._nodemaker.create_mutable_file(self.uploadables[0], version=version) # seqnum=1
9496         def _created(node):
9497             self._fn = node
9498             # now create multiple versions of the same file, and accumulate
9499hunk ./src/allmydata/test/test_mutable.py 707
9500             # their shares, so we can mix and match them later.
9501             d = defer.succeed(None)
9502             d.addCallback(self._copy_shares, 0)
9503-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[1])) #s2
9504+            d.addCallback(lambda res: node.overwrite(self.uploadables[1])) #s2
9505             d.addCallback(self._copy_shares, 1)
9506hunk ./src/allmydata/test/test_mutable.py 709
9507-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[2])) #s3
9508+            d.addCallback(lambda res: node.overwrite(self.uploadables[2])) #s3
9509             d.addCallback(self._copy_shares, 2)
9510hunk ./src/allmydata/test/test_mutable.py 711
9511-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[3])) #s4a
9512+            d.addCallback(lambda res: node.overwrite(self.uploadables[3])) #s4a
9513             d.addCallback(self._copy_shares, 3)
9514             # now we replace all the shares with version s3, and upload a new
9515             # version to get s4b.
9516hunk ./src/allmydata/test/test_mutable.py 717
9517             rollback = dict([(i,2) for i in range(10)])
9518             d.addCallback(lambda res: self._set_versions(rollback))
9519-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[4])) #s4b
9520+            d.addCallback(lambda res: node.overwrite(self.uploadables[4])) #s4b
9521             d.addCallback(self._copy_shares, 4)
9522             # we leave the storage in state 4
9523             return d
9524hunk ./src/allmydata/test/test_mutable.py 826
9525         # create a new file, which is large enough to knock the privkey out
9526         # of the early part of the file
9527         LARGE = "These are Larger contents" * 200 # about 5KB
9528-        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE))
9529+        LARGE_uploadable = MutableDataHandle(LARGE)
9530+        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
9531         def _created(large_fn):
9532             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
9533             return self.make_servermap(MODE_WRITE, large_fn2)
9534hunk ./src/allmydata/test/test_mutable.py 1842
9535 class MultipleEncodings(unittest.TestCase):
9536     def setUp(self):
9537         self.CONTENTS = "New contents go here"
9538+        self.uploadable = MutableDataHandle(self.CONTENTS)
9539         self._storage = FakeStorage()
9540         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
9541         self._storage_broker = self._nodemaker.storage_broker
9542hunk ./src/allmydata/test/test_mutable.py 1846
9543-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9544+        d = self._nodemaker.create_mutable_file(self.uploadable)
9545         def _created(node):
9546             self._fn = node
9547         d.addCallback(_created)
9548hunk ./src/allmydata/test/test_mutable.py 1872
9549         s = self._storage
9550         s._peers = {} # clear existing storage
9551         p2 = Publish(fn2, self._storage_broker, None)
9552-        d = p2.publish(data)
9553+        uploadable = MutableDataHandle(data)
9554+        d = p2.publish(uploadable)
9555         def _published(res):
9556             shares = s._peers
9557             s._peers = {}
9558hunk ./src/allmydata/test/test_mutable.py 2049
9559         self._set_versions(target)
9560 
9561         def _modify(oldversion, servermap, first_time):
9562-            return oldversion + " modified"
9563+            return MutableDataHandle(oldversion + " modified")
9564         d = self._fn.modify(_modify)
9565         d.addCallback(lambda res: self._fn.download_best_version())
9566         expected = self.CONTENTS[2] + " modified"
9567hunk ./src/allmydata/test/test_mutable.py 2175
9568         self.basedir = "mutable/Problems/test_publish_surprise"
9569         self.set_up_grid()
9570         nm = self.g.clients[0].nodemaker
9571-        d = nm.create_mutable_file("contents 1")
9572+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9573         def _created(n):
9574             d = defer.succeed(None)
9575             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9576hunk ./src/allmydata/test/test_mutable.py 2185
9577             d.addCallback(_got_smap1)
9578             # then modify the file, leaving the old map untouched
9579             d.addCallback(lambda res: log.msg("starting winning write"))
9580-            d.addCallback(lambda res: n.overwrite("contents 2"))
9581+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9582             # now attempt to modify the file with the old servermap. This
9583             # will look just like an uncoordinated write, in which every
9584             # single share got updated between our mapupdate and our publish
9585hunk ./src/allmydata/test/test_mutable.py 2194
9586                           self.shouldFail(UncoordinatedWriteError,
9587                                           "test_publish_surprise", None,
9588                                           n.upload,
9589-                                          "contents 2a", self.old_map))
9590+                                          MutableDataHandle("contents 2a"), self.old_map))
9591             return d
9592         d.addCallback(_created)
9593         return d
9594hunk ./src/allmydata/test/test_mutable.py 2203
9595         self.basedir = "mutable/Problems/test_retrieve_surprise"
9596         self.set_up_grid()
9597         nm = self.g.clients[0].nodemaker
9598-        d = nm.create_mutable_file("contents 1")
9599+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9600         def _created(n):
9601             d = defer.succeed(None)
9602             d.addCallback(lambda res: n.get_servermap(MODE_READ))
9603hunk ./src/allmydata/test/test_mutable.py 2213
9604             d.addCallback(_got_smap1)
9605             # then modify the file, leaving the old map untouched
9606             d.addCallback(lambda res: log.msg("starting winning write"))
9607-            d.addCallback(lambda res: n.overwrite("contents 2"))
9608+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9609             # now attempt to retrieve the old version with the old servermap.
9610             # This will look like someone has changed the file since we
9611             # updated the servermap.
9612hunk ./src/allmydata/test/test_mutable.py 2241
9613         self.basedir = "mutable/Problems/test_unexpected_shares"
9614         self.set_up_grid()
9615         nm = self.g.clients[0].nodemaker
9616-        d = nm.create_mutable_file("contents 1")
9617+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9618         def _created(n):
9619             d = defer.succeed(None)
9620             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9621hunk ./src/allmydata/test/test_mutable.py 2253
9622                 self.g.remove_server(peer0)
9623                 # then modify the file, leaving the old map untouched
9624                 log.msg("starting winning write")
9625-                return n.overwrite("contents 2")
9626+                return n.overwrite(MutableDataHandle("contents 2"))
9627             d.addCallback(_got_smap1)
9628             # now attempt to modify the file with the old servermap. This
9629             # will look just like an uncoordinated write, in which every
9630hunk ./src/allmydata/test/test_mutable.py 2263
9631                           self.shouldFail(UncoordinatedWriteError,
9632                                           "test_surprise", None,
9633                                           n.upload,
9634-                                          "contents 2a", self.old_map))
9635+                                          MutableDataHandle("contents 2a"), self.old_map))
9636             return d
9637         d.addCallback(_created)
9638         return d
9639hunk ./src/allmydata/test/test_mutable.py 2267
9640+    test_unexpected_shares.timeout = 15
9641 
9642     def test_bad_server(self):
9643         # Break one server, then create the file: the initial publish should
9644hunk ./src/allmydata/test/test_mutable.py 2303
9645         d.addCallback(_break_peer0)
9646         # now "create" the file, using the pre-established key, and let the
9647         # initial publish finally happen
9648-        d.addCallback(lambda res: nm.create_mutable_file("contents 1"))
9649+        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
9650         # that ought to work
9651         def _got_node(n):
9652             d = n.download_best_version()
9653hunk ./src/allmydata/test/test_mutable.py 2312
9654             def _break_peer1(res):
9655                 self.connection1.broken = True
9656             d.addCallback(_break_peer1)
9657-            d.addCallback(lambda res: n.overwrite("contents 2"))
9658+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9659             # that ought to work too
9660             d.addCallback(lambda res: n.download_best_version())
9661             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9662hunk ./src/allmydata/test/test_mutable.py 2344
9663         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
9664         self.g.break_server(peerids[0])
9665 
9666-        d = nm.create_mutable_file("contents 1")
9667+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9668         def _created(n):
9669             d = n.download_best_version()
9670             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9671hunk ./src/allmydata/test/test_mutable.py 2352
9672             def _break_second_server(res):
9673                 self.g.break_server(peerids[1])
9674             d.addCallback(_break_second_server)
9675-            d.addCallback(lambda res: n.overwrite("contents 2"))
9676+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9677             # that ought to work too
9678             d.addCallback(lambda res: n.download_best_version())
9679             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9680hunk ./src/allmydata/test/test_mutable.py 2371
9681         d = self.shouldFail(NotEnoughServersError,
9682                             "test_publish_all_servers_bad",
9683                             "Ran out of non-bad servers",
9684-                            nm.create_mutable_file, "contents")
9685+                            nm.create_mutable_file, MutableDataHandle("contents"))
9686         return d
9687 
9688     def test_publish_no_servers(self):
9689hunk ./src/allmydata/test/test_mutable.py 2383
9690         d = self.shouldFail(NotEnoughServersError,
9691                             "test_publish_no_servers",
9692                             "Ran out of non-bad servers",
9693-                            nm.create_mutable_file, "contents")
9694+                            nm.create_mutable_file, MutableDataHandle("contents"))
9695         return d
9696     test_publish_no_servers.timeout = 30
9697 
9698hunk ./src/allmydata/test/test_mutable.py 2401
9699         # we need some contents that are large enough to push the privkey out
9700         # of the early part of the file
9701         LARGE = "These are Larger contents" * 2000 # about 50KB
9702-        d = nm.create_mutable_file(LARGE)
9703+        LARGE_uploadable = MutableDataHandle(LARGE)
9704+        d = nm.create_mutable_file(LARGE_uploadable)
9705         def _created(n):
9706             self.uri = n.get_uri()
9707             self.n2 = nm.create_from_cap(self.uri)
9708hunk ./src/allmydata/test/test_mutable.py 2438
9709         self.set_up_grid(num_servers=20)
9710         nm = self.g.clients[0].nodemaker
9711         LARGE = "These are Larger contents" * 2000 # about 50KiB
9712+        LARGE_uploadable = MutableDataHandle(LARGE)
9713         nm._node_cache = DevNullDictionary() # disable the nodecache
9714 
9715hunk ./src/allmydata/test/test_mutable.py 2441
9716-        d = nm.create_mutable_file(LARGE)
9717+        d = nm.create_mutable_file(LARGE_uploadable)
9718         def _created(n):
9719             self.uri = n.get_uri()
9720             self.n2 = nm.create_from_cap(self.uri)
9721hunk ./src/allmydata/test/test_mutable.py 2464
9722         self.set_up_grid(num_servers=20)
9723         nm = self.g.clients[0].nodemaker
9724         CONTENTS = "contents" * 2000
9725-        d = nm.create_mutable_file(CONTENTS)
9726+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9727+        d = nm.create_mutable_file(CONTENTS_uploadable)
9728         def _created(node):
9729             self._node = node
9730         d.addCallback(_created)
9731hunk ./src/allmydata/test/test_system.py 22
9732 from allmydata.monitor import Monitor
9733 from allmydata.mutable.common import NotWriteableError
9734 from allmydata.mutable import layout as mutable_layout
9735+from allmydata.mutable.publish import MutableDataHandle
9736 from foolscap.api import DeadReferenceError
9737 from twisted.python.failure import Failure
9738 from twisted.web.client import getPage
9739hunk ./src/allmydata/test/test_system.py 460
9740     def test_mutable(self):
9741         self.basedir = "system/SystemTest/test_mutable"
9742         DATA = "initial contents go here."  # 25 bytes % 3 != 0
9743+        DATA_uploadable = MutableDataHandle(DATA)
9744         NEWDATA = "new contents yay"
9745hunk ./src/allmydata/test/test_system.py 462
9746+        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
9747         NEWERDATA = "this is getting old"
9748hunk ./src/allmydata/test/test_system.py 464
9749+        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
9750 
9751         d = self.set_up_nodes(use_key_generator=True)
9752 
9753hunk ./src/allmydata/test/test_system.py 471
9754         def _create_mutable(res):
9755             c = self.clients[0]
9756             log.msg("starting create_mutable_file")
9757-            d1 = c.create_mutable_file(DATA)
9758+            d1 = c.create_mutable_file(DATA_uploadable)
9759             def _done(res):
9760                 log.msg("DONE: %s" % (res,))
9761                 self._mutable_node_1 = res
9762hunk ./src/allmydata/test/test_system.py 558
9763             self.failUnlessEqual(res, DATA)
9764             # replace the data
9765             log.msg("starting replace1")
9766-            d1 = newnode.overwrite(NEWDATA)
9767+            d1 = newnode.overwrite(NEWDATA_uploadable)
9768             d1.addCallback(lambda res: newnode.download_best_version())
9769             return d1
9770         d.addCallback(_check_download_3)
9771hunk ./src/allmydata/test/test_system.py 572
9772             newnode2 = self.clients[3].create_node_from_uri(uri)
9773             self._newnode3 = self.clients[3].create_node_from_uri(uri)
9774             log.msg("starting replace2")
9775-            d1 = newnode1.overwrite(NEWERDATA)
9776+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
9777             d1.addCallback(lambda res: newnode2.download_best_version())
9778             return d1
9779         d.addCallback(_check_download_4)
9780hunk ./src/allmydata/test/test_system.py 642
9781         def _check_empty_file(res):
9782             # make sure we can create empty files, this usually screws up the
9783             # segsize math
9784-            d1 = self.clients[2].create_mutable_file("")
9785+            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
9786             d1.addCallback(lambda newnode: newnode.download_best_version())
9787             d1.addCallback(lambda res: self.failUnlessEqual("", res))
9788             return d1
9789hunk ./src/allmydata/test/test_system.py 673
9790                                  self.key_generator_svc.key_generator.pool_size + size_delta)
9791 
9792         d.addCallback(check_kg_poolsize, 0)
9793-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
9794+        d.addCallback(lambda junk:
9795+            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
9796         d.addCallback(check_kg_poolsize, -1)
9797         d.addCallback(lambda junk: self.clients[3].create_dirnode())
9798         d.addCallback(check_kg_poolsize, -2)
9799hunk ./src/allmydata/test/test_web.py 3166
9800         def _stash_mutable_uri(n, which):
9801             self.uris[which] = n.get_uri()
9802             assert isinstance(self.uris[which], str)
9803-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9804+        d.addCallback(lambda ign:
9805+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9806         d.addCallback(_stash_mutable_uri, "corrupt")
9807         d.addCallback(lambda ign:
9808                       c0.upload(upload.Data("literal", convergence="")))
9809hunk ./src/allmydata/test/test_web.py 3313
9810         def _stash_mutable_uri(n, which):
9811             self.uris[which] = n.get_uri()
9812             assert isinstance(self.uris[which], str)
9813-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9814+        d.addCallback(lambda ign:
9815+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9816         d.addCallback(_stash_mutable_uri, "corrupt")
9817 
9818         def _compute_fileurls(ignored):
9819hunk ./src/allmydata/test/test_web.py 3976
9820         def _stash_mutable_uri(n, which):
9821             self.uris[which] = n.get_uri()
9822             assert isinstance(self.uris[which], str)
9823-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
9824+        d.addCallback(lambda ign:
9825+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
9826         d.addCallback(_stash_mutable_uri, "mutable")
9827 
9828         def _compute_fileurls(ignored):
9829hunk ./src/allmydata/test/test_web.py 4076
9830                                                         convergence="")))
9831         d.addCallback(_stash_uri, "small")
9832 
9833-        d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
9834+        d.addCallback(lambda ign:
9835+            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
9836         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9837         d.addCallback(_stash_uri, "mutable")
9838 
9839}
9840[Alter mutable files to use file-like objects for publishing instead of strings.
9841Kevan Carstensen <kevan@isnotajoke.com>**20100708000732
9842 Ignore-this: 8dd07d95386b6d540bc21289f981ebd0
9843] {
9844hunk ./src/allmydata/dirnode.py 11
9845 from allmydata.mutable.common import NotWriteableError
9846 from allmydata.mutable.filenode import MutableFileNode
9847 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
9848+from allmydata.mutable.publish import MutableDataHandle
9849 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
9850      IImmutableFileNode, IMutableFileNode, \
9851      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
9852hunk ./src/allmydata/dirnode.py 104
9853 
9854         del children[self.name]
9855         new_contents = self.node._pack_contents(children)
9856-        return new_contents
9857+        uploadable = MutableDataHandle(new_contents)
9858+        return uploadable
9859 
9860 
9861 class MetadataSetter:
9862hunk ./src/allmydata/dirnode.py 130
9863 
9864         children[name] = (child, metadata)
9865         new_contents = self.node._pack_contents(children)
9866-        return new_contents
9867+        uploadable = MutableDataHandle(new_contents)
9868+        return uploadable
9869 
9870 
9871 class Adder:
9872hunk ./src/allmydata/dirnode.py 175
9873 
9874             children[name] = (child, metadata)
9875         new_contents = self.node._pack_contents(children)
9876-        return new_contents
9877+        uploadable = MutableDataHandle(new_contents)
9878+        return uploadable
9879 
9880 
9881 def _encrypt_rw_uri(filenode, rw_uri):
9882hunk ./src/allmydata/mutable/filenode.py 7
9883 from zope.interface import implements
9884 from twisted.internet import defer, reactor
9885 from foolscap.api import eventually
9886-from allmydata.interfaces import IMutableFileNode, \
9887-     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
9888+from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
9889+                                 NotEnoughSharesError, \
9890+                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
9891 from allmydata.util import hashutil, log
9892 from allmydata.util.assertutil import precondition
9893 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
9894hunk ./src/allmydata/mutable/filenode.py 16
9895 from allmydata.monitor import Monitor
9896 from pycryptopp.cipher.aes import AES
9897 
9898-from allmydata.mutable.publish import Publish
9899+from allmydata.mutable.publish import Publish, MutableFileHandle, \
9900+                                      MutableDataHandle
9901 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
9902      ResponseCache, UncoordinatedWriteError
9903 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
9904hunk ./src/allmydata/mutable/filenode.py 133
9905         return self._upload(initial_contents, None)
9906 
9907     def _get_initial_contents(self, contents):
9908-        if isinstance(contents, str):
9909-            return contents
9910         if contents is None:
9911hunk ./src/allmydata/mutable/filenode.py 134
9912-            return ""
9913+            return MutableDataHandle("")
9914+
9915+        if IMutableUploadable.providedBy(contents):
9916+            return contents
9917+
9918         assert callable(contents), "%s should be callable, not %s" % \
9919                (contents, type(contents))
9920         return contents(self)
9921hunk ./src/allmydata/mutable/filenode.py 353
9922     def overwrite(self, new_contents):
9923         return self._do_serialized(self._overwrite, new_contents)
9924     def _overwrite(self, new_contents):
9925+        assert IMutableUploadable.providedBy(new_contents)
9926+
9927         servermap = ServerMap()
9928         d = self._update_servermap(servermap, mode=MODE_WRITE)
9929         d.addCallback(lambda ignored: self._upload(new_contents, servermap))
9930hunk ./src/allmydata/mutable/filenode.py 431
9931                 # recovery when it observes UCWE, we need to do a second
9932                 # publish. See #551 for details. We'll basically loop until
9933                 # we managed an uncontested publish.
9934-                new_contents = old_contents
9935-            precondition(isinstance(new_contents, str),
9936-                         "Modifier function must return a string or None")
9937+                old_uploadable = MutableDataHandle(old_contents)
9938+                new_contents = old_uploadable
9939+            precondition((IMutableUploadable.providedBy(new_contents) or
9940+                          new_contents is None),
9941+                         "Modifier function must return an IMutableUploadable "
9942+                         "or None")
9943             return self._upload(new_contents, servermap)
9944         d.addCallback(_apply)
9945         return d
9946hunk ./src/allmydata/mutable/filenode.py 472
9947         return self._do_serialized(self._upload, new_contents, servermap)
9948     def _upload(self, new_contents, servermap):
9949         assert self._pubkey, "update_servermap must be called before publish"
9950+        assert IMutableUploadable.providedBy(new_contents)
9951+
9952         p = Publish(self, self._storage_broker, servermap)
9953         if self._history:
9954hunk ./src/allmydata/mutable/filenode.py 476
9955-            self._history.notify_publish(p.get_status(), len(new_contents))
9956+            self._history.notify_publish(p.get_status(), new_contents.get_size())
9957         d = p.publish(new_contents)
9958hunk ./src/allmydata/mutable/filenode.py 478
9959-        d.addCallback(self._did_upload, len(new_contents))
9960+        d.addCallback(self._did_upload, new_contents.get_size())
9961         return d
9962     def _did_upload(self, res, size):
9963         self._most_recent_size = size
9964hunk ./src/allmydata/mutable/publish.py 141
9965 
9966         # 0. Setup encoding parameters, encoder, and other such things.
9967         # 1. Encrypt, encode, and publish segments.
9968-        self.data = StringIO(newdata)
9969-        self.datalength = len(newdata)
9970+        assert IMutableUploadable.providedBy(newdata)
9971+
9972+        self.data = newdata
9973+        self.datalength = newdata.get_size()
9974 
9975         self.log("starting publish, datalen is %s" % self.datalength)
9976         self._status.set_size(self.datalength)
9977hunk ./src/allmydata/mutable/publish.py 442
9978 
9979         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
9980         data = self.data.read(segsize)
9981+        # XXX: This is dumb. Why return a list?
9982+        data = "".join(data)
9983 
9984         assert len(data) == segsize
9985 
9986hunk ./src/allmydata/mutable/repairer.py 5
9987 from zope.interface import implements
9988 from twisted.internet import defer
9989 from allmydata.interfaces import IRepairResults, ICheckResults
9990+from allmydata.mutable.publish import MutableDataHandle
9991 
9992 class RepairResults:
9993     implements(IRepairResults)
9994hunk ./src/allmydata/mutable/repairer.py 108
9995             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
9996 
9997         d = self.node.download_version(smap, best_version, fetch_privkey=True)
9998+        d.addCallback(lambda data:
9999+            MutableDataHandle(data))
10000         d.addCallback(self.node.upload, smap)
10001         d.addCallback(self.get_results, smap)
10002         return d
10003hunk ./src/allmydata/nodemaker.py 9
10004 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
10005 from allmydata.immutable.upload import Data
10006 from allmydata.mutable.filenode import MutableFileNode
10007+from allmydata.mutable.publish import MutableDataHandle
10008 from allmydata.dirnode import DirectoryNode, pack_children
10009 from allmydata.unknown import UnknownNode
10010 from allmydata import uri
10011hunk ./src/allmydata/nodemaker.py 111
10012                          "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
10013             node.raise_error()
10014         d = self.create_mutable_file(lambda n:
10015-                                     pack_children(n, initial_children),
10016+                                     MutableDataHandle(
10017+                                        pack_children(n, initial_children)),
10018                                      version)
10019         d.addCallback(self._create_dirnode)
10020         return d
10021hunk ./src/allmydata/web/filenode.py 12
10022 from allmydata.interfaces import ExistingChildError
10023 from allmydata.monitor import Monitor
10024 from allmydata.immutable.upload import FileHandle
10025+from allmydata.mutable.publish import MutableFileHandle
10026 from allmydata.util import log, base32
10027 
10028 from allmydata.web.common import text_plain, WebError, RenderMixin, \
10029hunk ./src/allmydata/web/filenode.py 27
10030         # a new file is being uploaded in our place.
10031         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10032         if mutable:
10033-            req.content.seek(0)
10034-            data = req.content.read()
10035+            data = MutableFileHandle(req.content)
10036             d = client.create_mutable_file(data)
10037             def _uploaded(newnode):
10038                 d2 = self.parentnode.set_node(self.name, newnode,
10039hunk ./src/allmydata/web/filenode.py 61
10040         d.addCallback(lambda res: childnode.get_uri())
10041         return d
10042 
10043-    def _read_data_from_formpost(self, req):
10044-        # SDMF: files are small, and we can only upload data, so we read
10045-        # the whole file into memory before uploading.
10046-        contents = req.fields["file"]
10047-        contents.file.seek(0)
10048-        data = contents.file.read()
10049-        return data
10050 
10051     def replace_me_with_a_formpost(self, req, client, replace):
10052         # create a new file, maybe mutable, maybe immutable
10053hunk ./src/allmydata/web/filenode.py 66
10054         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10055 
10056+        # create an immutable file
10057+        contents = req.fields["file"]
10058         if mutable:
10059hunk ./src/allmydata/web/filenode.py 69
10060-            data = self._read_data_from_formpost(req)
10061-            d = client.create_mutable_file(data)
10062+            uploadable = MutableFileHandle(contents.file)
10063+            d = client.create_mutable_file(uploadable)
10064             def _uploaded(newnode):
10065                 d2 = self.parentnode.set_node(self.name, newnode,
10066                                               overwrite=replace)
10067hunk ./src/allmydata/web/filenode.py 78
10068                 return d2
10069             d.addCallback(_uploaded)
10070             return d
10071-        # create an immutable file
10072-        contents = req.fields["file"]
10073+
10074         uploadable = FileHandle(contents.file, convergence=client.convergence)
10075         d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
10076         d.addCallback(lambda newnode: newnode.get_uri())
10077hunk ./src/allmydata/web/filenode.py 84
10078         return d
10079 
10080+
10081 class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
10082     def __init__(self, client, parentnode, name):
10083         rend.Page.__init__(self)
10084hunk ./src/allmydata/web/filenode.py 278
10085 
10086     def replace_my_contents(self, req):
10087         req.content.seek(0)
10088-        new_contents = req.content.read()
10089+        new_contents = MutableFileHandle(req.content)
10090         d = self.node.overwrite(new_contents)
10091         d.addCallback(lambda res: self.node.get_uri())
10092         return d
10093hunk ./src/allmydata/web/filenode.py 286
10094     def replace_my_contents_with_a_formpost(self, req):
10095         # we have a mutable file. Get the data from the formpost, and replace
10096         # the mutable file's contents with it.
10097-        new_contents = self._read_data_from_formpost(req)
10098+        new_contents = req.fields['file']
10099+        new_contents = MutableFileHandle(new_contents.file)
10100+
10101         d = self.node.overwrite(new_contents)
10102         d.addCallback(lambda res: self.node.get_uri())
10103         return d
10104hunk ./src/allmydata/web/unlinked.py 7
10105 from twisted.internet import defer
10106 from nevow import rend, url, tags as T
10107 from allmydata.immutable.upload import FileHandle
10108+from allmydata.mutable.publish import MutableFileHandle
10109 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
10110      convert_children_json, WebError
10111 from allmydata.web import status
10112hunk ./src/allmydata/web/unlinked.py 23
10113 def PUTUnlinkedSSK(req, client):
10114     # SDMF: files are small, and we can only upload data
10115     req.content.seek(0)
10116-    data = req.content.read()
10117+    data = MutableFileHandle(req.content)
10118     d = client.create_mutable_file(data)
10119     d.addCallback(lambda n: n.get_uri())
10120     return d
10121hunk ./src/allmydata/web/unlinked.py 87
10122     # "POST /uri", to create an unlinked file.
10123     # SDMF: files are small, and we can only upload data
10124     contents = req.fields["file"]
10125-    contents.file.seek(0)
10126-    data = contents.file.read()
10127+    data = MutableFileHandle(contents.file)
10128     d = client.create_mutable_file(data)
10129     d.addCallback(lambda n: n.get_uri())
10130     return d
10131}
10132[test/test_sftp.py: alter a setup routine to work with new mutable file APIs.
10133Kevan Carstensen <kevan@isnotajoke.com>**20100708193522
10134 Ignore-this: 434bbe1347072076c0836d26fca8ac8a
10135] {
10136hunk ./src/allmydata/test/test_sftp.py 32
10137 
10138 from allmydata.util.consumer import download_to_data
10139 from allmydata.immutable import upload
10140+from allmydata.mutable import publish
10141 from allmydata.test.no_network import GridTestMixin
10142 from allmydata.test.common import ShouldFailMixin
10143 from allmydata.test.common_util import ReallyEqualMixin
10144hunk ./src/allmydata/test/test_sftp.py 84
10145         return d
10146 
10147     def _set_up_tree(self):
10148-        d = self.client.create_mutable_file("mutable file contents")
10149+        u = publish.MutableDataHandle("mutable file contents")
10150+        d = self.client.create_mutable_file(u)
10151         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
10152         def _created_mutable(n):
10153             self.mutable = n
10154}
10155[mutable/publish.py: make MutableFileHandle seek to the beginning of its file handle before reading.
10156Kevan Carstensen <kevan@isnotajoke.com>**20100708193600
10157 Ignore-this: 453a737dc62a79c77b3d360fed9000ab
10158] hunk ./src/allmydata/mutable/publish.py 989
10159         assert hasattr(filehandle, "close")
10160 
10161         self._filehandle = filehandle
10162+        # We must start reading at the beginning of the file, or we risk
10163+        # encountering errors when the data read does not match the size
10164+        # reported to the uploader.
10165+        self._filehandle.seek(0)
10166 
10167 
10168     def get_size(self):
10169[Refactor download interfaces to be more uniform, per #993
10170Kevan Carstensen <kevan@isnotajoke.com>**20100709232912
10171 Ignore-this: 277c5699c4a2dd7c03ecfa0a28458f5b
10172] {
10173hunk ./src/allmydata/immutable/filenode.py 10
10174 from foolscap.api import eventually
10175 from allmydata.interfaces import IImmutableFileNode, ICheckable, \
10176      IDownloadTarget, IUploadResults
10177-from allmydata.util import dictutil, log, base32
10178+from allmydata.util import dictutil, log, base32, consumer
10179 from allmydata.uri import CHKFileURI, LiteralFileURI
10180 from allmydata.immutable.checker import Checker
10181 from allmydata.check_results import CheckResults, CheckAndRepairResults
10182hunk ./src/allmydata/immutable/filenode.py 318
10183                       self.download_cache.read(consumer, offset, size))
10184         return d
10185 
10186+    # IReadable, IFileNode
10187+
10188+    def get_best_readable_version(self):
10189+        """
10190+        Return an IReadable of the best version of this file. Since
10191+        immutable files can have only one version, we just return the
10192+        current filenode.
10193+        """
10194+        return self
10195+
10196+
10197+    def download_best_version(self):
10198+        """
10199+        Download the best version of this file, returning its contents
10200+        as a bytestring. Since there is only one version of an immutable
10201+        file, we download and return the contents of this file.
10202+        """
10203+        d = consumer.download_to_data(self)
10204+        return d
10205+
10206+    # for an immutable file, download_to_data (specified in IReadable)
10207+    # is the same as download_best_version (specified in IFileNode). For
10208+    # mutable files, the difference is more meaningful, since they can
10209+    # have multiple versions.
10210+    download_to_data = download_best_version
10211+
10212+
10213+    # get_size() (IReadable), get_current_size() (IFilesystemNode), and
10214+    # get_size_of_best_version(IFileNode) are all the same for immutable
10215+    # files.
10216+    get_size_of_best_version = get_current_size
10217+
10218+
10219 class LiteralProducer:
10220     implements(IPushProducer)
10221     def resumeProducing(self):
10222hunk ./src/allmydata/immutable/filenode.py 409
10223         d = basic.FileSender().beginFileTransfer(StringIO(data), consumer)
10224         d.addCallback(lambda lastSent: consumer)
10225         return d
10226+
10227+    # IReadable, IFileNode, IFilesystemNode
10228+    def get_best_readable_version(self):
10229+        return self
10230+
10231+
10232+    def download_best_version(self):
10233+        return defer.succeed(self.u.data)
10234+
10235+
10236+    download_to_data = download_best_version
10237+    get_size_of_best_version = get_current_size
10238hunk ./src/allmydata/interfaces.py 563
10239 class MustNotBeUnknownRWError(CapConstraintError):
10240     """Cannot add an unknown child cap specified in a rw_uri field."""
10241 
10242+
10243+class IReadable(Interface):
10244+    """I represent a readable object -- either an immutable file, or a
10245+    specific version of a mutable file.
10246+    """
10247+
10248+    def is_readonly():
10249+        """Return True if this reference provides mutable access to the given
10250+        file or directory (i.e. if you can modify it), or False if not. Note
10251+        that even if this reference is read-only, someone else may hold a
10252+        read-write reference to it.
10253+
10254+        For an IReadable returned by get_best_readable_version(), this will
10255+        always return True, but for instances of subinterfaces such as
10256+        IMutableFileVersion, it may return False."""
10257+
10258+    def is_mutable():
10259+        """Return True if this file or directory is mutable (by *somebody*,
10260+        not necessarily you), False if it is is immutable. Note that a file
10261+        might be mutable overall, but your reference to it might be
10262+        read-only. On the other hand, all references to an immutable file
10263+        will be read-only; there are no read-write references to an immutable
10264+        file."""
10265+
10266+    def get_storage_index():
10267+        """Return the storage index of the file."""
10268+
10269+    def get_size():
10270+        """Return the length (in bytes) of this readable object."""
10271+
10272+    def download_to_data():
10273+        """Download all of the file contents. I return a Deferred that fires
10274+        with the contents as a byte string."""
10275+
10276+    def read(consumer, offset=0, size=None):
10277+        """Download a portion (possibly all) of the file's contents, making
10278+        them available to the given IConsumer. Return a Deferred that fires
10279+        (with the consumer) when the consumer is unregistered (either because
10280+        the last byte has been given to it, or because the consumer threw an
10281+        exception during write(), possibly because it no longer wants to
10282+        receive data). The portion downloaded will start at 'offset' and
10283+        contain 'size' bytes (or the remainder of the file if size==None).
10284+
10285+        The consumer will be used in non-streaming mode: an IPullProducer
10286+        will be attached to it.
10287+
10288+        The consumer will not receive data right away: several network trips
10289+        must occur first. The order of events will be::
10290+
10291+         consumer.registerProducer(p, streaming)
10292+          (if streaming == False)::
10293+           consumer does p.resumeProducing()
10294+            consumer.write(data)
10295+           consumer does p.resumeProducing()
10296+            consumer.write(data).. (repeat until all data is written)
10297+         consumer.unregisterProducer()
10298+         deferred.callback(consumer)
10299+
10300+        If a download error occurs, or an exception is raised by
10301+        consumer.registerProducer() or consumer.write(), I will call
10302+        consumer.unregisterProducer() and then deliver the exception via
10303+        deferred.errback(). To cancel the download, the consumer should call
10304+        p.stopProducing(), which will result in an exception being delivered
10305+        via deferred.errback().
10306+
10307+        See src/allmydata/util/consumer.py for an example of a simple
10308+        download-to-memory consumer.
10309+        """
10310+
10311+
10312+class IMutableFileVersion(IReadable):
10313+    """I provide access to a particular version of a mutable file. The
10314+    access is read/write if I was obtained from a filenode derived from
10315+    a write cap, or read-only if the filenode was derived from a read cap.
10316+    """
10317+
10318+    def get_sequence_number():
10319+        """Return the sequence number of this version."""
10320+
10321+    def get_servermap():
10322+        """Return the IMutableFileServerMap instance that was used to create
10323+        this object.
10324+        """
10325+
10326+    def get_writekey():
10327+        """Return this filenode's writekey, or None if the node does not have
10328+        write-capability. This may be used to assist with data structures
10329+        that need to make certain data available only to writers, such as the
10330+        read-write child caps in dirnodes. The recommended process is to have
10331+        reader-visible data be submitted to the filenode in the clear (where
10332+        it will be encrypted by the filenode using the readkey), but encrypt
10333+        writer-visible data using this writekey.
10334+        """
10335+
10336+    # TODO: Can this be overwrite instead of replace?
10337+    def replace(new_contents):
10338+        """Replace the contents of the mutable file, provided that no other
10339+        node has published (or is attempting to publish, concurrently) a
10340+        newer version of the file than this one.
10341+
10342+        I will avoid modifying any share that is different than the version
10343+        given by get_sequence_number(). However, if another node is writing
10344+        to the file at the same time as me, I may manage to update some shares
10345+        while they update others. If I see any evidence of this, I will signal
10346+        UncoordinatedWriteError, and the file will be left in an inconsistent
10347+        state (possibly the version you provided, possibly the old version,
10348+        possibly somebody else's version, and possibly a mix of shares from
10349+        all of these).
10350+
10351+        The recommended response to UncoordinatedWriteError is to either
10352+        return it to the caller (since they failed to coordinate their
10353+        writes), or to attempt some sort of recovery. It may be sufficient to
10354+        wait a random interval (with exponential backoff) and repeat your
10355+        operation. If I do not signal UncoordinatedWriteError, then I was
10356+        able to write the new version without incident.
10357+
10358+        I return a Deferred that fires (with a PublishStatus object) when the
10359+        update has completed.
10360+        """
10361+
10362+    def modify(modifier_cb):
10363+        """Modify the contents of the file, by downloading this version,
10364+        applying the modifier function (or bound method), then uploading
10365+        the new version. This will succeed as long as no other node
10366+        publishes a version between the download and the upload.
10367+        I return a Deferred that fires (with a PublishStatus object) when
10368+        the update is complete.
10369+
10370+        The modifier callable will be given three arguments: a string (with
10371+        the old contents), a 'first_time' boolean, and a servermap. As with
10372+        download_to_data(), the old contents will be from this version,
10373+        but the modifier can use the servermap to make other decisions
10374+        (such as refusing to apply the delta if there are multiple parallel
10375+        versions, or if there is evidence of a newer unrecoverable version).
10376+        'first_time' will be True the first time the modifier is called,
10377+        and False on any subsequent calls.
10378+
10379+        The callable should return a string with the new contents. The
10380+        callable must be prepared to be called multiple times, and must
10381+        examine the input string to see if the change that it wants to make
10382+        is already present in the old version. If it does not need to make
10383+        any changes, it can either return None, or return its input string.
10384+
10385+        If the modifier raises an exception, it will be returned in the
10386+        errback.
10387+        """
10388+
10389+
10390 # The hierarchy looks like this:
10391 #  IFilesystemNode
10392 #   IFileNode
10393hunk ./src/allmydata/interfaces.py 801
10394     def raise_error():
10395         """Raise any error associated with this node."""
10396 
10397+    # XXX: These may not be appropriate outside the context of an IReadable.
10398     def get_size():
10399         """Return the length (in bytes) of the data this node represents. For
10400         directory nodes, I return the size of the backing store. I return
10401hunk ./src/allmydata/interfaces.py 818
10402 class IFileNode(IFilesystemNode):
10403     """I am a node which represents a file: a sequence of bytes. I am not a
10404     container, like IDirectoryNode."""
10405+    def get_best_readable_version():
10406+        """Return a Deferred that fires with an IReadable for the 'best'
10407+        available version of the file. The IReadable provides only read
10408+        access, even if this filenode was derived from a write cap.
10409 
10410hunk ./src/allmydata/interfaces.py 823
10411-class IImmutableFileNode(IFileNode):
10412-    def read(consumer, offset=0, size=None):
10413-        """Download a portion (possibly all) of the file's contents, making
10414-        them available to the given IConsumer. Return a Deferred that fires
10415-        (with the consumer) when the consumer is unregistered (either because
10416-        the last byte has been given to it, or because the consumer threw an
10417-        exception during write(), possibly because it no longer wants to
10418-        receive data). The portion downloaded will start at 'offset' and
10419-        contain 'size' bytes (or the remainder of the file if size==None).
10420-
10421-        The consumer will be used in non-streaming mode: an IPullProducer
10422-        will be attached to it.
10423+        For an immutable file, there is only one version. For a mutable
10424+        file, the 'best' version is the recoverable version with the
10425+        highest sequence number. If no uncoordinated writes have occurred,
10426+        and if enough shares are available, then this will be the most
10427+        recent version that has been uploaded. If no version is recoverable,
10428+        the Deferred will errback with an UnrecoverableFileError.
10429+        """
10430 
10431hunk ./src/allmydata/interfaces.py 831
10432-        The consumer will not receive data right away: several network trips
10433-        must occur first. The order of events will be::
10434+    def download_best_version():
10435+        """Download the contents of the version that would be returned
10436+        by get_best_readable_version(). This is equivalent to calling
10437+        download_to_data() on the IReadable given by that method.
10438 
10439hunk ./src/allmydata/interfaces.py 836
10440-         consumer.registerProducer(p, streaming)
10441-          (if streaming == False)::
10442-           consumer does p.resumeProducing()
10443-            consumer.write(data)
10444-           consumer does p.resumeProducing()
10445-            consumer.write(data).. (repeat until all data is written)
10446-         consumer.unregisterProducer()
10447-         deferred.callback(consumer)
10448+        I return a Deferred that fires with a byte string when the file
10449+        has been fully downloaded. To support streaming download, use
10450+        the 'read' method of IReadable. If no version is recoverable,
10451+        the Deferred will errback with an UnrecoverableFileError.
10452+        """
10453 
10454hunk ./src/allmydata/interfaces.py 842
10455-        If a download error occurs, or an exception is raised by
10456-        consumer.registerProducer() or consumer.write(), I will call
10457-        consumer.unregisterProducer() and then deliver the exception via
10458-        deferred.errback(). To cancel the download, the consumer should call
10459-        p.stopProducing(), which will result in an exception being delivered
10460-        via deferred.errback().
10461+    def get_size_of_best_version():
10462+        """Find the size of the version that would be returned by
10463+        get_best_readable_version().
10464 
10465hunk ./src/allmydata/interfaces.py 846
10466-        See src/allmydata/util/consumer.py for an example of a simple
10467-        download-to-memory consumer.
10468+        I return a Deferred that fires with an integer. If no version
10469+        is recoverable, the Deferred will errback with an
10470+        UnrecoverableFileError.
10471         """
10472 
10473hunk ./src/allmydata/interfaces.py 851
10474+
10475+class IImmutableFileNode(IFileNode, IReadable):
10476+    """I am a node representing an immutable file. Immutable files have
10477+    only one version"""
10478+
10479+
10480 class IMutableFileNode(IFileNode):
10481     """I provide access to a 'mutable file', which retains its identity
10482     regardless of what contents are put in it.
10483hunk ./src/allmydata/interfaces.py 916
10484     only be retrieved and updated all-at-once, as a single big string. Future
10485     versions of our mutable files will remove this restriction.
10486     """
10487-
10488-    def download_best_version():
10489-        """Download the 'best' available version of the file, meaning one of
10490-        the recoverable versions with the highest sequence number. If no
10491+    def get_best_mutable_version():
10492+        """Return a Deferred that fires with an IMutableFileVersion for
10493+        the 'best' available version of the file. The best version is
10494+        the recoverable version with the highest sequence number. If no
10495         uncoordinated writes have occurred, and if enough shares are
10496hunk ./src/allmydata/interfaces.py 921
10497-        available, then this will be the most recent version that has been
10498-        uploaded.
10499-
10500-        I update an internal servermap with MODE_READ, determine which
10501-        version of the file is indicated by
10502-        servermap.best_recoverable_version(), and return a Deferred that
10503-        fires with its contents. If no version is recoverable, the Deferred
10504-        will errback with UnrecoverableFileError.
10505-        """
10506-
10507-    def get_size_of_best_version():
10508-        """Find the size of the version that would be downloaded with
10509-        download_best_version(), without actually downloading the whole file.
10510+        available, then this will be the most recent version that has
10511+        been uploaded.
10512 
10513hunk ./src/allmydata/interfaces.py 924
10514-        I return a Deferred that fires with an integer.
10515+        If no version is recoverable, the Deferred will errback with an
10516+        UnrecoverableFileError.
10517         """
10518 
10519     def overwrite(new_contents):
10520hunk ./src/allmydata/interfaces.py 964
10521         errback.
10522         """
10523 
10524-
10525     def get_servermap(mode):
10526         """Return a Deferred that fires with an IMutableFileServerMap
10527         instance, updated using the given mode.
10528hunk ./src/allmydata/test/test_filenode.py 98
10529         def _check_segment(res):
10530             self.failUnlessEqual(res, DATA[1:1+5])
10531         d.addCallback(_check_segment)
10532+        d.addCallback(lambda ignored:
10533+            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
10534+        d.addCallback(lambda ignored:
10535+            fn1.get_size_of_best_version())
10536+        d.addCallback(lambda size:
10537+            self.failUnlessEqual(size, len(DATA)))
10538+        d.addCallback(lambda ignored:
10539+            fn1.download_to_data())
10540+        d.addCallback(lambda data:
10541+            self.failUnlessEqual(data, DATA))
10542+        d.addCallback(lambda ignored:
10543+            fn1.download_best_version())
10544+        d.addCallback(lambda data:
10545+            self.failUnlessEqual(data, DATA))
10546 
10547         return d
10548 
10549hunk ./src/allmydata/test/test_immutable.py 153
10550         return d
10551 
10552 
10553+    def test_download_to_data(self):
10554+        d = self.n.download_to_data()
10555+        d.addCallback(lambda data:
10556+            self.failUnlessEqual(data, common.TEST_DATA))
10557+        return d
10558+
10559+
10560+    def test_download_best_version(self):
10561+        d = self.n.download_best_version()
10562+        d.addCallback(lambda data:
10563+            self.failUnlessEqual(data, common.TEST_DATA))
10564+        return d
10565+
10566+
10567+    def test_get_best_readable_version(self):
10568+        n = self.n.get_best_readable_version()
10569+        self.failUnlessEqual(n, self.n)
10570+
10571+    def test_get_size_of_best_version(self):
10572+        d = self.n.get_size_of_best_version()
10573+        d.addCallback(lambda size:
10574+            self.failUnlessEqual(size, len(common.TEST_DATA)))
10575+        return d
10576+
10577+
10578 # XXX extend these tests to show bad behavior of various kinds from servers: raising exception from each remove_foo() method, for example
10579 
10580 # XXX test disconnect DeadReferenceError from get_buckets and get_block_whatsit
10581}
10582[frontends/sftpd.py: alter a mutable file overwrite to work with the new API
10583Kevan Carstensen <kevan@isnotajoke.com>**20100709232951
10584 Ignore-this: e0441c3ef2dfe78a1cac3f423d613e40
10585] {
10586hunk ./src/allmydata/frontends/sftpd.py 33
10587 from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, \
10588      NoSuchChildError, ChildOfWrongTypeError
10589 from allmydata.mutable.common import NotWriteableError
10590+from allmydata.mutable.publish import MutableFileHandle
10591 from allmydata.immutable.upload import FileHandle
10592 from allmydata.dirnode import update_metadata
10593 
10594hunk ./src/allmydata/frontends/sftpd.py 867
10595                     assert parent and childname, (parent, childname, self.metadata)
10596                     d2.addCallback(lambda ign: parent.set_metadata_for(childname, self.metadata))
10597 
10598-                d2.addCallback(lambda ign: self.consumer.get_current_size())
10599-                d2.addCallback(lambda size: self.consumer.read(0, size))
10600-                d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents))
10601+                d2.addCallback(lambda ign: self.filenode.overwrite(MutableFileHandle(self.consumer.get_file())))
10602             else:
10603                 def _add_file(ign):
10604                     self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL)
10605}
10606[mutable/filenode.py: implement most of IVersion, per #993
10607Kevan Carstensen <kevan@isnotajoke.com>**20100713231758
10608 Ignore-this: 8a30b8c908d98eeffa0762e084a541f8
10609] {
10610hunk ./src/allmydata/mutable/filenode.py 8
10611 from twisted.internet import defer, reactor
10612 from foolscap.api import eventually
10613 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
10614-                                 NotEnoughSharesError, \
10615-                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
10616+     NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
10617+     IMutableFileVersion
10618 from allmydata.util import hashutil, log
10619 from allmydata.util.assertutil import precondition
10620 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
10621hunk ./src/allmydata/mutable/filenode.py 208
10622 
10623     def get_size(self):
10624         return self._most_recent_size
10625+
10626     def get_current_size(self):
10627         d = self.get_size_of_best_version()
10628         d.addCallback(self._stash_size)
10629hunk ./src/allmydata/mutable/filenode.py 213
10630         return d
10631+
10632     def _stash_size(self, size):
10633         self._most_recent_size = size
10634         return size
10635hunk ./src/allmydata/mutable/filenode.py 272
10636             return cmp(self.__class__, them.__class__)
10637         return cmp(self._uri, them._uri)
10638 
10639-    def _do_serialized(self, cb, *args, **kwargs):
10640-        # note: to avoid deadlock, this callable is *not* allowed to invoke
10641-        # other serialized methods within this (or any other)
10642-        # MutableFileNode. The callable should be a bound method of this same
10643-        # MFN instance.
10644-        d = defer.Deferred()
10645-        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
10646-        # we need to put off d.callback until this Deferred is finished being
10647-        # processed. Otherwise the caller's subsequent activities (like,
10648-        # doing other things with this node) can cause reentrancy problems in
10649-        # the Deferred code itself
10650-        self._serializer.addBoth(lambda res: eventually(d.callback, res))
10651-        # add a log.err just in case something really weird happens, because
10652-        # self._serializer stays around forever, therefore we won't see the
10653-        # usual Unhandled Error in Deferred that would give us a hint.
10654-        self._serializer.addErrback(log.err)
10655-        return d
10656 
10657     #################################
10658     # ICheckable
10659hunk ./src/allmydata/mutable/filenode.py 297
10660 
10661 
10662     #################################
10663-    # IMutableFileNode
10664+    # IFileNode
10665+
10666+    def get_best_readable_version(self):
10667+        """
10668+        I return a Deferred that fires with a MutableFileVersion
10669+        representing the best readable version of the file that I
10670+        represent
10671+        """
10672+        return self.get_readable_version()
10673+
10674+
10675+    def get_readable_version(self, servermap=None, version=None):
10676+        """
10677+        I return a Deferred that fires with an MutableFileVersion for my
10678+        version argument, if there is a recoverable file of that version
10679+        on the grid. If there is no recoverable version, I fire with an
10680+        UnrecoverableFileError.
10681+
10682+        If a servermap is provided, I look in there for the requested
10683+        version. If no servermap is provided, I create and update a new
10684+        one.
10685+
10686+        If no version is provided, then I return a MutableFileVersion
10687+        representing the best recoverable version of the file.
10688+        """
10689+        d = self._get_version_from_servermap(MODE_READ, servermap, version)
10690+        def _build_version((servermap, their_version)):
10691+            assert their_version in servermap.recoverable_versions()
10692+            assert their_version in servermap.make_versionmap()
10693+
10694+            mfv = MutableFileVersion(self,
10695+                                     servermap,
10696+                                     their_version,
10697+                                     self._storage_index,
10698+                                     self._storage_broker,
10699+                                     self._readkey,
10700+                                     history=self._history)
10701+            assert mfv.is_readonly()
10702+            # our caller can use this to download the contents of the
10703+            # mutable file.
10704+            return mfv
10705+        return d.addCallback(_build_version)
10706+
10707+
10708+    def _get_version_from_servermap(self,
10709+                                    mode,
10710+                                    servermap=None,
10711+                                    version=None):
10712+        """
10713+        I return a Deferred that fires with (servermap, version).
10714+
10715+        This function performs validation and a servermap update. If it
10716+        returns (servermap, version), the caller can assume that:
10717+            - servermap was last updated in mode.
10718+            - version is recoverable, and corresponds to the servermap.
10719+
10720+        If version and servermap are provided to me, I will validate
10721+        that version exists in the servermap, and that the servermap was
10722+        updated correctly.
10723+
10724+        If version is not provided, but servermap is, I will validate
10725+        the servermap and return the best recoverable version that I can
10726+        find in the servermap.
10727+
10728+        If the version is provided but the servermap isn't, I will
10729+        obtain a servermap that has been updated in the correct mode and
10730+        validate that version is found and recoverable.
10731+
10732+        If neither servermap nor version are provided, I will obtain a
10733+        servermap updated in the correct mode, and return the best
10734+        recoverable version that I can find in there.
10735+        """
10736+        # XXX: wording ^^^^
10737+        if servermap and servermap.last_update_mode == mode:
10738+            d = defer.succeed(servermap)
10739+        else:
10740+            d = self._get_servermap(mode)
10741+
10742+        def _get_version(servermap, version):
10743+            if version and version not in servermap.recoverable_versions():
10744+                version = None
10745+            else:
10746+                version = servermap.best_recoverable_version()
10747+            if not version:
10748+                raise UnrecoverableFileError("no recoverable versions")
10749+            return (servermap, version)
10750+        return d.addCallback(_get_version, version)
10751+
10752 
10753     def download_best_version(self):
10754hunk ./src/allmydata/mutable/filenode.py 387
10755+        """
10756+        I return a Deferred that fires with the contents of the best
10757+        version of this mutable file.
10758+        """
10759         return self._do_serialized(self._download_best_version)
10760hunk ./src/allmydata/mutable/filenode.py 392
10761+
10762+
10763     def _download_best_version(self):
10764hunk ./src/allmydata/mutable/filenode.py 395
10765-        servermap = ServerMap()
10766-        d = self._try_once_to_download_best_version(servermap, MODE_READ)
10767-        def _maybe_retry(f):
10768-            f.trap(NotEnoughSharesError)
10769-            # the download is worth retrying once. Make sure to use the
10770-            # old servermap, since it is what remembers the bad shares,
10771-            # but use MODE_WRITE to make it look for even more shares.
10772-            # TODO: consider allowing this to retry multiple times.. this
10773-            # approach will let us tolerate about 8 bad shares, I think.
10774-            return self._try_once_to_download_best_version(servermap,
10775-                                                           MODE_WRITE)
10776+        """
10777+        I am the serialized sibling of download_best_version.
10778+        """
10779+        d = self.get_best_readable_version()
10780+        d.addCallback(self._record_size)
10781+        d.addCallback(lambda version: version.download_to_data())
10782+
10783+        # It is possible that the download will fail because there
10784+        # aren't enough shares to be had. If so, we will try again after
10785+        # updating the servermap in MODE_WRITE, which may find more
10786+        # shares than updating in MODE_READ, as we just did. We can do
10787+        # this by getting the best mutable version and downloading from
10788+        # that -- the best mutable version will be a MutableFileVersion
10789+        # with a servermap that was last updated in MODE_WRITE, as we
10790+        # want. If this fails, then we give up.
10791+        def _maybe_retry(failure):
10792+            failure.trap(NotEnoughSharesError)
10793+
10794+            d = self.get_best_mutable_version()
10795+            d.addCallback(self._record_size)
10796+            d.addCallback(lambda version: version.download_to_data())
10797+            return d
10798+
10799         d.addErrback(_maybe_retry)
10800         return d
10801hunk ./src/allmydata/mutable/filenode.py 420
10802-    def _try_once_to_download_best_version(self, servermap, mode):
10803-        d = self._update_servermap(servermap, mode)
10804-        d.addCallback(self._once_updated_download_best_version, servermap)
10805-        return d
10806-    def _once_updated_download_best_version(self, ignored, servermap):
10807-        goal = servermap.best_recoverable_version()
10808-        if not goal:
10809-            raise UnrecoverableFileError("no recoverable versions")
10810-        return self._try_once_to_download_version(servermap, goal)
10811+
10812+
10813+    def _record_size(self, mfv):
10814+        """
10815+        I record the size of a mutable file version.
10816+        """
10817+        self._most_recent_size = mfv.get_size()
10818+        return mfv
10819+
10820 
10821     def get_size_of_best_version(self):
10822hunk ./src/allmydata/mutable/filenode.py 431
10823-        d = self.get_servermap(MODE_READ)
10824-        def _got_servermap(smap):
10825-            ver = smap.best_recoverable_version()
10826-            if not ver:
10827-                raise UnrecoverableFileError("no recoverable version")
10828-            return smap.size_of_version(ver)
10829-        d.addCallback(_got_servermap)
10830-        return d
10831+        """
10832+        I return the size of the best version of this mutable file.
10833+
10834+        This is equivalent to calling get_size() on the result of
10835+        get_best_readable_version().
10836+        """
10837+        d = self.get_best_readable_version()
10838+        return d.addCallback(lambda mfv: mfv.get_size())
10839+
10840+
10841+    #################################
10842+    # IMutableFileNode
10843+
10844+    def get_best_mutable_version(self, servermap=None):
10845+        """
10846+        I return a Deferred that fires with a MutableFileVersion
10847+        representing the best readable version of the file that I
10848+        represent. I am like get_best_readable_version, except that I
10849+        will try to make a writable version if I can.
10850+        """
10851+        return self.get_mutable_version(servermap=servermap)
10852+
10853+
10854+    def get_mutable_version(self, servermap=None, version=None):
10855+        """
10856+        I return a version of this mutable file. I return a Deferred
10857+        that fires with a MutableFileVersion
10858+
10859+        If version is provided, the Deferred will fire with a
10860+        MutableFileVersion initailized with that version. Otherwise, it
10861+        will fire with the best version that I can recover.
10862+
10863+        If servermap is provided, I will use that to find versions
10864+        instead of performing my own servermap update.
10865+        """
10866+        if self.is_readonly():
10867+            return self.get_readable_version(servermap=servermap,
10868+                                             version=version)
10869+
10870+        # get_mutable_version => write intent, so we require that the
10871+        # servermap is updated in MODE_WRITE
10872+        d = self._get_version_from_servermap(MODE_WRITE, servermap, version)
10873+        def _build_version((servermap, smap_version)):
10874+            # these should have been set by the servermap update.
10875+            assert self._secret_holder
10876+            assert self._writekey
10877+
10878+            mfv = MutableFileVersion(self,
10879+                                     servermap,
10880+                                     smap_version,
10881+                                     self._storage_index,
10882+                                     self._storage_broker,
10883+                                     self._readkey,
10884+                                     self._writekey,
10885+                                     self._secret_holder,
10886+                                     history=self._history)
10887+            assert not mfv.is_readonly()
10888+            return mfv
10889+
10890+        return d.addCallback(_build_version)
10891+
10892+
10893+    # XXX: I'm uncomfortable with the difference between upload and
10894+    #      overwrite, which, FWICT, is basically that you don't have to
10895+    #      do a servermap update before you overwrite. We split them up
10896+    #      that way anyway, so I guess there's no real difficulty in
10897+    #      offering both ways to callers, but it also makes the
10898+    #      public-facing API cluttery, and makes it hard to discern the
10899+    #      right way of doing things.
10900 
10901hunk ./src/allmydata/mutable/filenode.py 501
10902+    # In general, we leave it to callers to ensure that they aren't
10903+    # going to cause UncoordinatedWriteErrors when working with
10904+    # MutableFileVersions. We know that the next three operations
10905+    # (upload, overwrite, and modify) will all operate on the same
10906+    # version, so we say that only one of them can be going on at once,
10907+    # and serialize them to ensure that that actually happens, since as
10908+    # the caller in this situation it is our job to do that.
10909     def overwrite(self, new_contents):
10910hunk ./src/allmydata/mutable/filenode.py 509
10911+        """
10912+        I overwrite the contents of the best recoverable version of this
10913+        mutable file with new_contents. This is equivalent to calling
10914+        overwrite on the result of get_best_mutable_version with
10915+        new_contents as an argument. I return a Deferred that eventually
10916+        fires with the results of my replacement process.
10917+        """
10918         return self._do_serialized(self._overwrite, new_contents)
10919hunk ./src/allmydata/mutable/filenode.py 517
10920+
10921+
10922     def _overwrite(self, new_contents):
10923hunk ./src/allmydata/mutable/filenode.py 520
10924-        assert IMutableUploadable.providedBy(new_contents)
10925+        """
10926+        I am the serialized sibling of overwrite.
10927+        """
10928+        d = self.get_best_mutable_version()
10929+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
10930+
10931+
10932+
10933+    def upload(self, new_contents, servermap):
10934+        """
10935+        I overwrite the contents of the best recoverable version of this
10936+        mutable file with new_contents, using servermap instead of
10937+        creating/updating our own servermap. I return a Deferred that
10938+        fires with the results of my upload.
10939+        """
10940+        return self._do_serialized(self._upload, new_contents, servermap)
10941+
10942+
10943+    def _upload(self, new_contents, servermap):
10944+        """
10945+        I am the serialized sibling of upload.
10946+        """
10947+        d = self.get_best_mutable_version(servermap)
10948+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
10949+
10950+
10951+    def modify(self, modifier, backoffer=None):
10952+        """
10953+        I modify the contents of the best recoverable version of this
10954+        mutable file with the modifier. This is equivalent to calling
10955+        modify on the result of get_best_mutable_version. I return a
10956+        Deferred that eventually fires with an UploadResults instance
10957+        describing this process.
10958+        """
10959+        return self._do_serialized(self._modify, modifier, backoffer)
10960+
10961+
10962+    def _modify(self, modifier, backoffer):
10963+        """
10964+        I am the serialized sibling of modify.
10965+        """
10966+        d = self.get_best_mutable_version()
10967+        return d.addCallback(lambda mfv: mfv.modify(modifier, backoffer))
10968+
10969+
10970+    def download_version(self, servermap, version, fetch_privkey=False):
10971+        """
10972+        Download the specified version of this mutable file. I return a
10973+        Deferred that fires with the contents of the specified version
10974+        as a bytestring, or errbacks if the file is not recoverable.
10975+        """
10976+        d = self.get_readable_version(servermap, version)
10977+        return d.addCallback(lambda mfv: mfv.download_to_data(fetch_privkey))
10978+
10979+
10980+    def get_servermap(self, mode):
10981+        """
10982+        I return a servermap that has been updated in mode.
10983+
10984+        mode should be one of MODE_READ, MODE_WRITE, MODE_CHECK or
10985+        MODE_ANYTHING. See servermap.py for more on what these mean.
10986+        """
10987+        return self._do_serialized(self._get_servermap, mode)
10988+
10989 
10990hunk ./src/allmydata/mutable/filenode.py 585
10991+    def _get_servermap(self, mode):
10992+        """
10993+        I am a serialized twin to get_servermap.
10994+        """
10995         servermap = ServerMap()
10996hunk ./src/allmydata/mutable/filenode.py 590
10997-        d = self._update_servermap(servermap, mode=MODE_WRITE)
10998-        d.addCallback(lambda ignored: self._upload(new_contents, servermap))
10999+        return self._update_servermap(servermap, mode)
11000+
11001+
11002+    def _update_servermap(self, servermap, mode):
11003+        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11004+                             mode)
11005+        if self._history:
11006+            self._history.notify_mapupdate(u.get_status())
11007+        return u.update()
11008+
11009+
11010+    def set_version(self, version):
11011+        # I can be set in two ways:
11012+        #  1. When the node is created.
11013+        #  2. (for an existing share) when the Servermap is updated
11014+        #     before I am read.
11015+        assert version in (MDMF_VERSION, SDMF_VERSION)
11016+        self._protocol_version = version
11017+
11018+
11019+    def get_version(self):
11020+        return self._protocol_version
11021+
11022+
11023+    def _do_serialized(self, cb, *args, **kwargs):
11024+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11025+        # other serialized methods within this (or any other)
11026+        # MutableFileNode. The callable should be a bound method of this same
11027+        # MFN instance.
11028+        d = defer.Deferred()
11029+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11030+        # we need to put off d.callback until this Deferred is finished being
11031+        # processed. Otherwise the caller's subsequent activities (like,
11032+        # doing other things with this node) can cause reentrancy problems in
11033+        # the Deferred code itself
11034+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11035+        # add a log.err just in case something really weird happens, because
11036+        # self._serializer stays around forever, therefore we won't see the
11037+        # usual Unhandled Error in Deferred that would give us a hint.
11038+        self._serializer.addErrback(log.err)
11039         return d
11040 
11041 
11042hunk ./src/allmydata/mutable/filenode.py 633
11043+    def _upload(self, new_contents, servermap):
11044+        """
11045+        A MutableFileNode still has to have some way of getting
11046+        published initially, which is what I am here for. After that,
11047+        all publishing, updating, modifying and so on happens through
11048+        MutableFileVersions.
11049+        """
11050+        assert self._pubkey, "update_servermap must be called before publish"
11051+
11052+        p = Publish(self, self._storage_broker, servermap)
11053+        if self._history:
11054+            self._history.notify_publish(p.get_status(),
11055+                                         new_contents.get_size())
11056+        d = p.publish(new_contents)
11057+        d.addCallback(self._did_upload, new_contents.get_size())
11058+        return d
11059+
11060+
11061+    def _did_upload(self, res, size):
11062+        self._most_recent_size = size
11063+        return res
11064+
11065+
11066+class MutableFileVersion:
11067+    """
11068+    I represent a specific version (most likely the best version) of a
11069+    mutable file.
11070+
11071+    Since I implement IReadable, instances which hold a
11072+    reference to an instance of me are guaranteed the ability (absent
11073+    connection difficulties or unrecoverable versions) to read the file
11074+    that I represent. Depending on whether I was initialized with a
11075+    write capability or not, I may also provide callers the ability to
11076+    overwrite or modify the contents of the mutable file that I
11077+    reference.
11078+    """
11079+    implements(IMutableFileVersion)
11080+
11081+    def __init__(self,
11082+                 node,
11083+                 servermap,
11084+                 version,
11085+                 storage_index,
11086+                 storage_broker,
11087+                 readcap,
11088+                 writekey=None,
11089+                 write_secrets=None,
11090+                 history=None):
11091+
11092+        self._node = node
11093+        self._servermap = servermap
11094+        self._version = version
11095+        self._storage_index = storage_index
11096+        self._write_secrets = write_secrets
11097+        self._history = history
11098+        self._storage_broker = storage_broker
11099+
11100+        #assert isinstance(readcap, IURI)
11101+        self._readcap = readcap
11102+
11103+        self._writekey = writekey
11104+        self._serializer = defer.succeed(None)
11105+        self._size = None
11106+
11107+
11108+    def get_sequence_number(self):
11109+        """
11110+        Get the sequence number of the mutable version that I represent.
11111+        """
11112+        return 0
11113+
11114+
11115+    # TODO: Terminology?
11116+    def get_writekey(self):
11117+        """
11118+        I return a writekey or None if I don't have a writekey.
11119+        """
11120+        return self._writekey
11121+
11122+
11123+    def overwrite(self, new_contents):
11124+        """
11125+        I overwrite the contents of this mutable file version with the
11126+        data in new_contents.
11127+        """
11128+        assert not self.is_readonly()
11129+
11130+        return self._do_serialized(self._overwrite, new_contents)
11131+
11132+
11133+    def _overwrite(self, new_contents):
11134+        assert IMutableUploadable.providedBy(new_contents)
11135+        assert self._servermap.last_update_mode == MODE_WRITE
11136+
11137+        return self._upload(new_contents)
11138+
11139+
11140     def modify(self, modifier, backoffer=None):
11141         """I use a modifier callback to apply a change to the mutable file.
11142         I implement the following pseudocode::
11143hunk ./src/allmydata/mutable/filenode.py 770
11144         backoffer should not invoke any methods on this MutableFileNode
11145         instance, and it needs to be highly conscious of deadlock issues.
11146         """
11147+        assert not self.is_readonly()
11148+
11149         return self._do_serialized(self._modify, modifier, backoffer)
11150hunk ./src/allmydata/mutable/filenode.py 773
11151+
11152+
11153     def _modify(self, modifier, backoffer):
11154hunk ./src/allmydata/mutable/filenode.py 776
11155-        servermap = ServerMap()
11156         if backoffer is None:
11157             backoffer = BackoffAgent().delay
11158hunk ./src/allmydata/mutable/filenode.py 778
11159-        return self._modify_and_retry(servermap, modifier, backoffer, True)
11160-    def _modify_and_retry(self, servermap, modifier, backoffer, first_time):
11161-        d = self._modify_once(servermap, modifier, first_time)
11162+        return self._modify_and_retry(modifier, backoffer, True)
11163+
11164+
11165+    def _modify_and_retry(self, modifier, backoffer, first_time):
11166+        """
11167+        I try to apply modifier to the contents of this version of the
11168+        mutable file. If I succeed, I return an UploadResults instance
11169+        describing my success. If I fail, I try again after waiting for
11170+        a little bit.
11171+        """
11172+        log.msg("doing modify")
11173+        d = self._modify_once(modifier, first_time)
11174         def _retry(f):
11175             f.trap(UncoordinatedWriteError)
11176             d2 = defer.maybeDeferred(backoffer, self, f)
11177hunk ./src/allmydata/mutable/filenode.py 794
11178             d2.addCallback(lambda ignored:
11179-                           self._modify_and_retry(servermap, modifier,
11180+                           self._modify_and_retry(modifier,
11181                                                   backoffer, False))
11182             return d2
11183         d.addErrback(_retry)
11184hunk ./src/allmydata/mutable/filenode.py 799
11185         return d
11186-    def _modify_once(self, servermap, modifier, first_time):
11187-        d = self._update_servermap(servermap, MODE_WRITE)
11188-        d.addCallback(self._once_updated_download_best_version, servermap)
11189+
11190+
11191+    def _modify_once(self, modifier, first_time):
11192+        """
11193+        I attempt to apply a modifier to the contents of the mutable
11194+        file.
11195+        """
11196+        assert self._servermap.last_update_mode == MODE_WRITE
11197+
11198+        # download_to_data is serialized, so we have to call this to
11199+        # avoid deadlock.
11200+        d = self._try_to_download_data()
11201         def _apply(old_contents):
11202hunk ./src/allmydata/mutable/filenode.py 812
11203-            new_contents = modifier(old_contents, servermap, first_time)
11204+            new_contents = modifier(old_contents, self._servermap, first_time)
11205             if new_contents is None or new_contents == old_contents:
11206hunk ./src/allmydata/mutable/filenode.py 814
11207+                log.msg("no changes")
11208                 # no changes need to be made
11209                 if first_time:
11210                     return
11211hunk ./src/allmydata/mutable/filenode.py 828
11212                           new_contents is None),
11213                          "Modifier function must return an IMutableUploadable "
11214                          "or None")
11215-            return self._upload(new_contents, servermap)
11216+            return self._upload(new_contents)
11217         d.addCallback(_apply)
11218         return d
11219 
11220hunk ./src/allmydata/mutable/filenode.py 832
11221-    def get_servermap(self, mode):
11222-        return self._do_serialized(self._get_servermap, mode)
11223-    def _get_servermap(self, mode):
11224-        servermap = ServerMap()
11225-        return self._update_servermap(servermap, mode)
11226-    def _update_servermap(self, servermap, mode):
11227-        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11228-                             mode)
11229-        if self._history:
11230-            self._history.notify_mapupdate(u.get_status())
11231-        return u.update()
11232 
11233hunk ./src/allmydata/mutable/filenode.py 833
11234-    def download_version(self, servermap, version, fetch_privkey=False):
11235-        return self._do_serialized(self._try_once_to_download_version,
11236-                                   servermap, version, fetch_privkey)
11237-    def _try_once_to_download_version(self, servermap, version,
11238-                                      fetch_privkey=False):
11239-        r = Retrieve(self, servermap, version, fetch_privkey)
11240+    def is_readonly(self):
11241+        """
11242+        I return True if this MutableFileVersion provides no write
11243+        access to the file that it encapsulates, and False if it
11244+        provides the ability to modify the file.
11245+        """
11246+        return self._writekey is None
11247+
11248+
11249+    def is_mutable(self):
11250+        """
11251+        I return True, since mutable files are always mutable by
11252+        somebody.
11253+        """
11254+        return True
11255+
11256+
11257+    def get_storage_index(self):
11258+        """
11259+        I return the storage index of the reference that I encapsulate.
11260+        """
11261+        return self._storage_index
11262+
11263+
11264+    def get_size(self):
11265+        """
11266+        I return the length, in bytes, of this readable object.
11267+        """
11268+        return self._servermap.size_of_version(self._version)
11269+
11270+
11271+    def download_to_data(self, fetch_privkey=False):
11272+        """
11273+        I return a Deferred that fires with the contents of this
11274+        readable object as a byte string.
11275+
11276+        """
11277+        return self._do_serialized(self._try_to_download_data, fetch_privkey)
11278+
11279+
11280+    def _try_to_download_data(self, fetch_privkey=False):
11281+        """
11282+        I am the serialized sibling of download_to_data. I attempt to
11283+        download all of the mutable file that I represent into a
11284+        bytestring. If I fail, I will try again to see if the situation
11285+        has changed before returning a failure.
11286+        """
11287+        return self._do_download(fetch_privkey)
11288+
11289+
11290+    def _update_servermap(self, mode=MODE_READ):
11291+        """
11292+        I update our Servermap according to my mode argument. I return a
11293+        Deferred that fires with None when this has finished. The
11294+        updated Servermap will be at self._servermap in that case.
11295+        """
11296+        d = self._node.get_servermap(mode)
11297+
11298+        def _got_servermap(servermap):
11299+            assert servermap.last_update_mode == mode
11300+
11301+            self._servermap = servermap
11302+        d.addCallback(_got_servermap)
11303+        return d
11304+
11305+
11306+    def _do_download(self, fetch_privkey):
11307+        """
11308+        I try once to download the data associated with this mutable
11309+        file.
11310+        """
11311+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
11312         if self._history:
11313             self._history.notify_retrieve(r.get_status())
11314         d = r.download()
11315hunk ./src/allmydata/mutable/filenode.py 908
11316-        d.addCallback(self._downloaded_version)
11317+        def _got_data(contents):
11318+            self._size = len(contents)
11319+            return contents
11320+        return d.addCallback(_got_data)
11321+
11322+
11323+    def read(self, consumer, offset=0, size=None):
11324+        """
11325+        I read a portion (possibly all) of the mutable file that I
11326+        reference into consumer.
11327+        """
11328+        pass
11329+
11330+
11331+    def _do_serialized(self, cb, *args, **kwargs):
11332+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11333+        # other serialized methods within this (or any other)
11334+        # MutableFileNode. The callable should be a bound method of this same
11335+        # MFN instance.
11336+        d = defer.Deferred()
11337+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11338+        # we need to put off d.callback until this Deferred is finished being
11339+        # processed. Otherwise the caller's subsequent activities (like,
11340+        # doing other things with this node) can cause reentrancy problems in
11341+        # the Deferred code itself
11342+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11343+        # add a log.err just in case something really weird happens, because
11344+        # self._serializer stays around forever, therefore we won't see the
11345+        # usual Unhandled Error in Deferred that would give us a hint.
11346+        self._serializer.addErrback(log.err)
11347         return d
11348hunk ./src/allmydata/mutable/filenode.py 939
11349-    def _downloaded_version(self, data):
11350-        self._most_recent_size = len(data)
11351-        return data
11352 
11353hunk ./src/allmydata/mutable/filenode.py 940
11354-    def upload(self, new_contents, servermap):
11355-        return self._do_serialized(self._upload, new_contents, servermap)
11356-    def _upload(self, new_contents, servermap):
11357-        assert self._pubkey, "update_servermap must be called before publish"
11358-        assert IMutableUploadable.providedBy(new_contents)
11359 
11360hunk ./src/allmydata/mutable/filenode.py 941
11361-        p = Publish(self, self._storage_broker, servermap)
11362+    def _upload(self, new_contents):
11363+        #assert self._pubkey, "update_servermap must be called before publish"
11364+        p = Publish(self._node, self._storage_broker, self._servermap)
11365         if self._history:
11366hunk ./src/allmydata/mutable/filenode.py 945
11367-            self._history.notify_publish(p.get_status(), new_contents.get_size())
11368+            self._history.notify_publish(p.get_status(),
11369+                                         new_contents.get_size())
11370         d = p.publish(new_contents)
11371         d.addCallback(self._did_upload, new_contents.get_size())
11372         return d
11373hunk ./src/allmydata/mutable/filenode.py 950
11374-    def _did_upload(self, res, size):
11375-        self._most_recent_size = size
11376-        return res
11377-
11378-
11379-    def set_version(self, version):
11380-        # I can be set in two ways:
11381-        #  1. When the node is created.
11382-        #  2. (for an existing share) when the Servermap is updated
11383-        #     before I am read.
11384-        assert version in (MDMF_VERSION, SDMF_VERSION)
11385-        self._protocol_version = version
11386 
11387 
11388hunk ./src/allmydata/mutable/filenode.py 952
11389-    def get_version(self):
11390-        return self._protocol_version
11391+    def _did_upload(self, res, size):
11392+        self._size = size
11393+        return res
11394}
11395
11396Context:
11397
11398[SFTP: don't call .stopProducing on the producer registered with OverwriteableFileConsumer (which breaks with warner's new downloader).
11399david-sarah@jacaranda.org**20100628231926
11400 Ignore-this: 131b7a5787bc85a9a356b5740d9d996f
11401] 
11402[docs/how_to_make_a_tahoe-lafs_release.txt: trivial correction, install.html should now be quickstart.html.
11403david-sarah@jacaranda.org**20100625223929
11404 Ignore-this: 99a5459cac51bd867cc11ad06927ff30
11405] 
11406[setup: in the Makefile, refuse to upload tarballs unless someone has passed the environment variable "BB_BRANCH" with value "trunk"
11407zooko@zooko.com**20100619034928
11408 Ignore-this: 276ddf9b6ad7ec79e27474862e0f7d6
11409] 
11410[trivial: tiny update to in-line comment
11411zooko@zooko.com**20100614045715
11412 Ignore-this: 10851b0ed2abfed542c97749e5d280bc
11413 (I'm actually committing this patch as a test of the new eager-annotation-computation of trac-darcs.)
11414] 
11415[docs: about.html link to home page early on, and be decentralized storage instead of cloud storage this time around
11416zooko@zooko.com**20100619065318
11417 Ignore-this: dc6db03f696e5b6d2848699e754d8053
11418] 
11419[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"
11420zooko@zooko.com**20100619065124
11421 Ignore-this: e292c7f51c337a84ebfeb366fbd24d6c
11422] 
11423[TAG allmydata-tahoe-1.7.0
11424zooko@zooko.com**20100619052631
11425 Ignore-this: d21e27afe6d85e2e3ba6a3292ba2be1
11426] 
11427Patch bundle hash:
11428cc92b921e3bed86288d45b16885c82cb3856dbc1