Ticket #393: 393status20.dpatch

File 393status20.dpatch, 559.3 KB (added by kevan, at 2010-07-17T02:32:06Z)
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 16 18:44:46 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
118  * frontends/sftpd.py: alter a mutable file overwrite to work with the new API
119
120Fri Jul 16 18:45:16 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
121  * mutable/filenode.py: implement most of IVersion, per #993
122
123Fri Jul 16 18:45:49 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
124  * mutable/publish.py: enable segmented uploading for big files, change constants and wording, change MutableDataHandle to MutableData
125
126Fri Jul 16 18:50:49 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
127  * immutable/filenode.py: fix broken implementation of #993 interfaces
128
129Fri Jul 16 18:51:23 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
130  * mutable/retrieve.py: alter Retrieve so that it can download parts of mutable files
131
132Fri Jul 16 18:52:10 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
133  * change MutableDataHandle to MutableData in code.
134
135Fri Jul 16 18:52:30 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
136  * tests: fix tests that were broken by #993
137
138Fri Jul 16 18:54:02 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
139  * test/test_immutable.py: add tests for #993-related modifications
140
141Fri Jul 16 18:54:26 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
142  * web/filenode.py: alter download code to use the new #993 interface.
143
144Fri Jul 16 18:55:01 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
145  * test/common.py: remove FileTooLargeErrors that tested for an SDMF limitation that no longer exists
146
147Fri Jul 16 19:02:40 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
148  * frontends/sftpd.py: fix conflicts with trunk
149
150New patches:
151
152[Misc. changes to support the work I'm doing
153Kevan Carstensen <kevan@isnotajoke.com>**20100624234637
154 Ignore-this: fdd18fa8cc05f4b4b15ff53ee24a1819
155 
156     - Add a notion of file version number to interfaces.py
157     - Alter mutable file node interfaces to have a notion of version,
158       though this may be changed later.
159     - Alter mutable/filenode.py to conform to these changes.
160     - Add a salt hasher to util/hashutil.py
161] {
162hunk ./src/allmydata/interfaces.py 7
163      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
164 
165 HASH_SIZE=32
166+SALT_SIZE=16
167+
168+SDMF_VERSION=0
169+MDMF_VERSION=1
170 
171 Hash = StringConstraint(maxLength=HASH_SIZE,
172                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
173hunk ./src/allmydata/interfaces.py 811
174         writer-visible data using this writekey.
175         """
176 
177+    def set_version(version):
178+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
179+        we upload in SDMF for reasons of compatibility. If you want to
180+        change this, set_version will let you do that.
181+
182+        To say that this file should be uploaded in SDMF, pass in a 0. To
183+        say that the file should be uploaded as MDMF, pass in a 1.
184+        """
185+
186+    def get_version():
187+        """Returns the mutable file protocol version."""
188+
189 class NotEnoughSharesError(Exception):
190     """Download was unable to get enough shares"""
191 
192hunk ./src/allmydata/mutable/filenode.py 8
193 from twisted.internet import defer, reactor
194 from foolscap.api import eventually
195 from allmydata.interfaces import IMutableFileNode, \
196-     ICheckable, ICheckResults, NotEnoughSharesError
197+     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
198 from allmydata.util import hashutil, log
199 from allmydata.util.assertutil import precondition
200 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
201hunk ./src/allmydata/mutable/filenode.py 67
202         self._sharemap = {} # known shares, shnum-to-[nodeids]
203         self._cache = ResponseCache()
204         self._most_recent_size = None
205+        # filled in after __init__ if we're being created for the first time;
206+        # filled in by the servermap updater before publishing, otherwise.
207+        # set to this default value in case neither of those things happen,
208+        # or in case the servermap can't find any shares to tell us what
209+        # to publish as.
210+        # TODO: Set this back to None, and find out why the tests fail
211+        #       with it set to None.
212+        self._protocol_version = SDMF_VERSION
213 
214         # all users of this MutableFileNode go through the serializer. This
215         # takes advantage of the fact that Deferreds discard the callbacks
216hunk ./src/allmydata/mutable/filenode.py 472
217     def _did_upload(self, res, size):
218         self._most_recent_size = size
219         return res
220+
221+
222+    def set_version(self, version):
223+        # I can be set in two ways:
224+        #  1. When the node is created.
225+        #  2. (for an existing share) when the Servermap is updated
226+        #     before I am read.
227+        assert version in (MDMF_VERSION, SDMF_VERSION)
228+        self._protocol_version = version
229+
230+
231+    def get_version(self):
232+        return self._protocol_version
233hunk ./src/allmydata/util/hashutil.py 90
234 MUTABLE_READKEY_TAG = "allmydata_mutable_writekey_to_readkey_v1"
235 MUTABLE_DATAKEY_TAG = "allmydata_mutable_readkey_to_datakey_v1"
236 MUTABLE_STORAGEINDEX_TAG = "allmydata_mutable_readkey_to_storage_index_v1"
237+MUTABLE_SALT_TAG = "allmydata_mutable_segment_salt_v1"
238 
239 # dirnodes
240 DIRNODE_CHILD_WRITECAP_TAG = "allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1"
241hunk ./src/allmydata/util/hashutil.py 134
242 def plaintext_segment_hasher():
243     return tagged_hasher(PLAINTEXT_SEGMENT_TAG)
244 
245+def mutable_salt_hash(data):
246+    return tagged_hash(MUTABLE_SALT_TAG, data)
247+def mutable_salt_hasher():
248+    return tagged_hasher(MUTABLE_SALT_TAG)
249+
250 KEYLEN = 16
251 IVLEN = 16
252 
253}
254[nodemaker.py: create MDMF files when asked to
255Kevan Carstensen <kevan@isnotajoke.com>**20100624234833
256 Ignore-this: 26c16aaca9ddab7a7ce37a4530bc970
257] {
258hunk ./src/allmydata/nodemaker.py 3
259 import weakref
260 from zope.interface import implements
261-from allmydata.interfaces import INodeMaker
262+from allmydata.util.assertutil import precondition
263+from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError, \
264+                                 SDMF_VERSION, MDMF_VERSION
265 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
266 from allmydata.immutable.upload import Data
267 from allmydata.mutable.filenode import MutableFileNode
268hunk ./src/allmydata/nodemaker.py 92
269             return self._create_dirnode(filenode)
270         return None
271 
272-    def create_mutable_file(self, contents=None, keysize=None):
273+    def create_mutable_file(self, contents=None, keysize=None,
274+                            version=SDMF_VERSION):
275         n = MutableFileNode(self.storage_broker, self.secret_holder,
276                             self.default_encoding_parameters, self.history)
277hunk ./src/allmydata/nodemaker.py 96
278+        n.set_version(version)
279         d = self.key_generator.generate(keysize)
280         d.addCallback(n.create_with_keys, contents)
281         d.addCallback(lambda res: n)
282hunk ./src/allmydata/nodemaker.py 102
283         return d
284 
285-    def create_new_mutable_directory(self, initial_children={}):
286+    def create_new_mutable_directory(self, initial_children={},
287+                                     version=SDMF_VERSION):
288+        # initial_children must have metadata (i.e. {} instead of None)
289+        for (name, (node, metadata)) in initial_children.iteritems():
290+            precondition(isinstance(metadata, dict),
291+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
292+            node.raise_error()
293         d = self.create_mutable_file(lambda n:
294hunk ./src/allmydata/nodemaker.py 110
295-                                     pack_children(n, initial_children))
296+                                     pack_children(n, initial_children),
297+                                     version)
298         d.addCallback(self._create_dirnode)
299         return d
300 
301}
302[storage/server.py: minor code cleanup
303Kevan Carstensen <kevan@isnotajoke.com>**20100624234905
304 Ignore-this: 2358c531c39e48d3c8e56b62b5768228
305] {
306hunk ./src/allmydata/storage/server.py 569
307                                          self)
308         return share
309 
310-    def remote_slot_readv(self, storage_index, shares, readv):
311+    def remote_slot_readv(self, storage_index, shares, readvs):
312         start = time.time()
313         self.count("readv")
314         si_s = si_b2a(storage_index)
315hunk ./src/allmydata/storage/server.py 590
316             if sharenum in shares or not shares:
317                 filename = os.path.join(bucketdir, sharenum_s)
318                 msf = MutableShareFile(filename, self)
319-                datavs[sharenum] = msf.readv(readv)
320+                datavs[sharenum] = msf.readv(readvs)
321         log.msg("returning shares %s" % (datavs.keys(),),
322                 facility="tahoe.storage", level=log.NOISY, parent=lp)
323         self.add_latency("readv", time.time() - start)
324}
325[test/test_mutable.py: alter some tests that were failing due to MDMF; minor code cleanup.
326Kevan Carstensen <kevan@isnotajoke.com>**20100624234924
327 Ignore-this: afb86ec1fbdbfe1a5ef6f46f350273c0
328] {
329hunk ./src/allmydata/test/test_mutable.py 151
330             chr(ord(original[byte_offset]) ^ 0x01) +
331             original[byte_offset+1:])
332 
333+def add_two(original, byte_offset):
334+    # It isn't enough to simply flip the bit for the version number,
335+    # because 1 is a valid version number. So we add two instead.
336+    return (original[:byte_offset] +
337+            chr(ord(original[byte_offset]) ^ 0x02) +
338+            original[byte_offset+1:])
339+
340 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
341     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
342     # list of shnums to corrupt.
343hunk ./src/allmydata/test/test_mutable.py 187
344                 real_offset = offset1
345             real_offset = int(real_offset) + offset2 + offset_offset
346             assert isinstance(real_offset, int), offset
347-            shares[shnum] = flip_bit(data, real_offset)
348+            if offset1 == 0: # verbyte
349+                f = add_two
350+            else:
351+                f = flip_bit
352+            shares[shnum] = f(data, real_offset)
353     return res
354 
355 def make_storagebroker(s=None, num_peers=10):
356hunk ./src/allmydata/test/test_mutable.py 423
357         d.addCallback(_created)
358         return d
359 
360+
361     def test_modify_backoffer(self):
362         def _modifier(old_contents, servermap, first_time):
363             return old_contents + "line2"
364hunk ./src/allmydata/test/test_mutable.py 658
365         d.addCallback(_created)
366         return d
367 
368+
369     def _copy_shares(self, ignored, index):
370         shares = self._storage._peers
371         # we need a deep copy
372}
373[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
374Kevan Carstensen <kevan@isnotajoke.com>**20100626003520
375 Ignore-this: 836e59e2fde0535f6b4bea3468dc8244
376] {
377hunk ./src/allmydata/test/test_mutable.py 168
378                 and shnum not in shnums_to_corrupt):
379                 continue
380             data = shares[shnum]
381-            (version,
382-             seqnum,
383-             root_hash,
384-             IV,
385-             k, N, segsize, datalen,
386-             o) = unpack_header(data)
387-            if isinstance(offset, tuple):
388-                offset1, offset2 = offset
389-            else:
390-                offset1 = offset
391-                offset2 = 0
392-            if offset1 == "pubkey":
393-                real_offset = 107
394-            elif offset1 in o:
395-                real_offset = o[offset1]
396-            else:
397-                real_offset = offset1
398-            real_offset = int(real_offset) + offset2 + offset_offset
399-            assert isinstance(real_offset, int), offset
400-            if offset1 == 0: # verbyte
401-                f = add_two
402-            else:
403-                f = flip_bit
404-            shares[shnum] = f(data, real_offset)
405-    return res
406+            # We're feeding the reader all of the share data, so it
407+            # won't need to use the rref that we didn't provide, nor the
408+            # storage index that we didn't provide. We do this because
409+            # the reader will work for both MDMF and SDMF.
410+            reader = MDMFSlotReadProxy(None, None, shnum, data)
411+            # We need to get the offsets for the next part.
412+            d = reader.get_verinfo()
413+            def _do_corruption(verinfo, data, shnum):
414+                (seqnum,
415+                 root_hash,
416+                 IV,
417+                 segsize,
418+                 datalen,
419+                 k, n, prefix, o) = verinfo
420+                if isinstance(offset, tuple):
421+                    offset1, offset2 = offset
422+                else:
423+                    offset1 = offset
424+                    offset2 = 0
425+                if offset1 == "pubkey":
426+                    real_offset = 107
427+                elif offset1 in o:
428+                    real_offset = o[offset1]
429+                else:
430+                    real_offset = offset1
431+                real_offset = int(real_offset) + offset2 + offset_offset
432+                assert isinstance(real_offset, int), offset
433+                if offset1 == 0: # verbyte
434+                    f = add_two
435+                else:
436+                    f = flip_bit
437+                shares[shnum] = f(data, real_offset)
438+            d.addCallback(_do_corruption, data, shnum)
439+            ds.append(d)
440+    dl = defer.DeferredList(ds)
441+    dl.addCallback(lambda ignored: res)
442+    return dl
443 
444 def make_storagebroker(s=None, num_peers=10):
445     if not s:
446hunk ./src/allmydata/test/test_mutable.py 1177
447         return d
448 
449     def test_download_fails(self):
450-        corrupt(None, self._storage, "signature")
451-        d = self.shouldFail(UnrecoverableFileError, "test_download_anyway",
452+        d = corrupt(None, self._storage, "signature")
453+        d.addCallback(lambda ignored:
454+            self.shouldFail(UnrecoverableFileError, "test_download_anyway",
455                             "no recoverable versions",
456                             self._fn.download_best_version)
457         return d
458hunk ./src/allmydata/test/test_mutable.py 1232
459         return d
460 
461     def test_check_all_bad_sig(self):
462-        corrupt(None, self._storage, 1) # bad sig
463-        d = self._fn.check(Monitor())
464+        d = corrupt(None, self._storage, 1) # bad sig
465+        d.addCallback(lambda ignored:
466+            self._fn.check(Monitor()))
467         d.addCallback(self.check_bad, "test_check_all_bad_sig")
468         return d
469 
470hunk ./src/allmydata/test/test_mutable.py 1239
471     def test_check_all_bad_blocks(self):
472-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
473+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
474         # the Checker won't notice this.. it doesn't look at actual data
475hunk ./src/allmydata/test/test_mutable.py 1241
476-        d = self._fn.check(Monitor())
477+        d.addCallback(lambda ignored:
478+            self._fn.check(Monitor()))
479         d.addCallback(self.check_good, "test_check_all_bad_blocks")
480         return d
481 
482hunk ./src/allmydata/test/test_mutable.py 1252
483         return d
484 
485     def test_verify_all_bad_sig(self):
486-        corrupt(None, self._storage, 1) # bad sig
487-        d = self._fn.check(Monitor(), verify=True)
488+        d = corrupt(None, self._storage, 1) # bad sig
489+        d.addCallback(lambda ignored:
490+            self._fn.check(Monitor(), verify=True))
491         d.addCallback(self.check_bad, "test_verify_all_bad_sig")
492         return d
493 
494hunk ./src/allmydata/test/test_mutable.py 1259
495     def test_verify_one_bad_sig(self):
496-        corrupt(None, self._storage, 1, [9]) # bad sig
497-        d = self._fn.check(Monitor(), verify=True)
498+        d = corrupt(None, self._storage, 1, [9]) # bad sig
499+        d.addCallback(lambda ignored:
500+            self._fn.check(Monitor(), verify=True))
501         d.addCallback(self.check_bad, "test_verify_one_bad_sig")
502         return d
503 
504hunk ./src/allmydata/test/test_mutable.py 1266
505     def test_verify_one_bad_block(self):
506-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
507+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
508         # the Verifier *will* notice this, since it examines every byte
509hunk ./src/allmydata/test/test_mutable.py 1268
510-        d = self._fn.check(Monitor(), verify=True)
511+        d.addCallback(lambda ignored:
512+            self._fn.check(Monitor(), verify=True))
513         d.addCallback(self.check_bad, "test_verify_one_bad_block")
514         d.addCallback(self.check_expected_failure,
515                       CorruptShareError, "block hash tree failure",
516hunk ./src/allmydata/test/test_mutable.py 1277
517         return d
518 
519     def test_verify_one_bad_sharehash(self):
520-        corrupt(None, self._storage, "share_hash_chain", [9], 5)
521-        d = self._fn.check(Monitor(), verify=True)
522+        d = corrupt(None, self._storage, "share_hash_chain", [9], 5)
523+        d.addCallback(lambda ignored:
524+            self._fn.check(Monitor(), verify=True))
525         d.addCallback(self.check_bad, "test_verify_one_bad_sharehash")
526         d.addCallback(self.check_expected_failure,
527                       CorruptShareError, "corrupt hashes",
528hunk ./src/allmydata/test/test_mutable.py 1287
529         return d
530 
531     def test_verify_one_bad_encprivkey(self):
532-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
533-        d = self._fn.check(Monitor(), verify=True)
534+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
535+        d.addCallback(lambda ignored:
536+            self._fn.check(Monitor(), verify=True))
537         d.addCallback(self.check_bad, "test_verify_one_bad_encprivkey")
538         d.addCallback(self.check_expected_failure,
539                       CorruptShareError, "invalid privkey",
540hunk ./src/allmydata/test/test_mutable.py 1297
541         return d
542 
543     def test_verify_one_bad_encprivkey_uncheckable(self):
544-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
545+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
546         readonly_fn = self._fn.get_readonly()
547         # a read-only node has no way to validate the privkey
548hunk ./src/allmydata/test/test_mutable.py 1300
549-        d = readonly_fn.check(Monitor(), verify=True)
550+        d.addCallback(lambda ignored:
551+            readonly_fn.check(Monitor(), verify=True))
552         d.addCallback(self.check_good,
553                       "test_verify_one_bad_encprivkey_uncheckable")
554         return d
555}
556[Alter the ServermapUpdater to find MDMF files
557Kevan Carstensen <kevan@isnotajoke.com>**20100626234118
558 Ignore-this: 25f6278209c2983ba8f307cfe0fde0
559 
560 The servermapupdater should find MDMF files on a grid in the same way
561 that it finds SDMF files. This patch makes it do that.
562] {
563hunk ./src/allmydata/mutable/servermap.py 7
564 from itertools import count
565 from twisted.internet import defer
566 from twisted.python import failure
567-from foolscap.api import DeadReferenceError, RemoteException, eventually
568+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
569+                         fireEventually
570 from allmydata.util import base32, hashutil, idlib, log
571 from allmydata.storage.server import si_b2a
572 from allmydata.interfaces import IServermapUpdaterStatus
573hunk ./src/allmydata/mutable/servermap.py 17
574 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
575      DictOfSets, CorruptShareError, NeedMoreDataError
576 from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
577-     SIGNED_PREFIX_LENGTH
578+     SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
579 
580 class UpdateStatus:
581     implements(IServermapUpdaterStatus)
582hunk ./src/allmydata/mutable/servermap.py 254
583         """Return a set of versionids, one for each version that is currently
584         recoverable."""
585         versionmap = self.make_versionmap()
586-
587         recoverable_versions = set()
588         for (verinfo, shares) in versionmap.items():
589             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
590hunk ./src/allmydata/mutable/servermap.py 366
591         self._servers_responded = set()
592 
593         # how much data should we read?
594+        # SDMF:
595         #  * if we only need the checkstring, then [0:75]
596         #  * if we need to validate the checkstring sig, then [543ish:799ish]
597         #  * if we need the verification key, then [107:436ish]
598hunk ./src/allmydata/mutable/servermap.py 374
599         #  * if we need the encrypted private key, we want [-1216ish:]
600         #   * but we can't read from negative offsets
601         #   * the offset table tells us the 'ish', also the positive offset
602-        # A future version of the SMDF slot format should consider using
603-        # fixed-size slots so we can retrieve less data. For now, we'll just
604-        # read 2000 bytes, which also happens to read enough actual data to
605-        # pre-fetch a 9-entry dirnode.
606+        # MDMF:
607+        #  * Checkstring? [0:72]
608+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
609+        #    the offset table will tell us for sure.
610+        #  * If we need the verification key, we have to consult the offset
611+        #    table as well.
612+        # At this point, we don't know which we are. Our filenode can
613+        # tell us, but it might be lying -- in some cases, we're
614+        # responsible for telling it which kind of file it is.
615         self._read_size = 4000
616         if mode == MODE_CHECK:
617             # we use unpack_prefix_and_signature, so we need 1k
618hunk ./src/allmydata/mutable/servermap.py 432
619         self._queries_completed = 0
620 
621         sb = self._storage_broker
622+        # All of the peers, permuted by the storage index, as usual.
623         full_peerlist = sb.get_servers_for_index(self._storage_index)
624         self.full_peerlist = full_peerlist # for use later, immutable
625         self.extra_peers = full_peerlist[:] # peers are removed as we use them
626hunk ./src/allmydata/mutable/servermap.py 439
627         self._good_peers = set() # peers who had some shares
628         self._empty_peers = set() # peers who don't have any shares
629         self._bad_peers = set() # peers to whom our queries failed
630+        self._readers = {} # peerid -> dict(sharewriters), filled in
631+                           # after responses come in.
632 
633         k = self._node.get_required_shares()
634hunk ./src/allmydata/mutable/servermap.py 443
635+        # For what cases can these conditions work?
636         if k is None:
637             # make a guess
638             k = 3
639hunk ./src/allmydata/mutable/servermap.py 456
640         self.num_peers_to_query = k + self.EPSILON
641 
642         if self.mode == MODE_CHECK:
643+            # We want to query all of the peers.
644             initial_peers_to_query = dict(full_peerlist)
645             must_query = set(initial_peers_to_query.keys())
646             self.extra_peers = []
647hunk ./src/allmydata/mutable/servermap.py 464
648             # we're planning to replace all the shares, so we want a good
649             # chance of finding them all. We will keep searching until we've
650             # seen epsilon that don't have a share.
651+            # We don't query all of the peers because that could take a while.
652             self.num_peers_to_query = N + self.EPSILON
653             initial_peers_to_query, must_query = self._build_initial_querylist()
654             self.required_num_empty_peers = self.EPSILON
655hunk ./src/allmydata/mutable/servermap.py 474
656             # might also avoid the round trip required to read the encrypted
657             # private key.
658 
659-        else:
660+        else: # MODE_READ, MODE_ANYTHING
661+            # 2k peers is good enough.
662             initial_peers_to_query, must_query = self._build_initial_querylist()
663 
664         # this is a set of peers that we are required to get responses from:
665hunk ./src/allmydata/mutable/servermap.py 490
666         # before we can consider ourselves finished, and self.extra_peers
667         # contains the overflow (peers that we should tap if we don't get
668         # enough responses)
669+        # I guess that self._must_query is a subset of
670+        # initial_peers_to_query?
671+        assert set(must_query).issubset(set(initial_peers_to_query))
672 
673         self._send_initial_requests(initial_peers_to_query)
674         self._status.timings["initial_queries"] = time.time() - self._started
675hunk ./src/allmydata/mutable/servermap.py 549
676         # errors that aren't handled by _query_failed (and errors caused by
677         # _query_failed) get logged, but we still want to check for doneness.
678         d.addErrback(log.err)
679-        d.addBoth(self._check_for_done)
680         d.addErrback(self._fatal_error)
681hunk ./src/allmydata/mutable/servermap.py 550
682+        d.addCallback(self._check_for_done)
683         return d
684 
685     def _do_read(self, ss, peerid, storage_index, shnums, readv):
686hunk ./src/allmydata/mutable/servermap.py 569
687         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
688         return d
689 
690+
691+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
692+        """
693+        I am called when a remote server returns a corrupt share in
694+        response to one of our queries. By corrupt, I mean a share
695+        without a valid signature. I then record the failure, notify the
696+        server of the corruption, and record the share as bad.
697+        """
698+        f = failure.Failure(e)
699+        self.log(format="bad share: %(f_value)s", f_value=str(f),
700+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
701+        # Notify the server that its share is corrupt.
702+        self.notify_server_corruption(peerid, shnum, str(e))
703+        # By flagging this as a bad peer, we won't count any of
704+        # the other shares on that peer as valid, though if we
705+        # happen to find a valid version string amongst those
706+        # shares, we'll keep track of it so that we don't need
707+        # to validate the signature on those again.
708+        self._bad_peers.add(peerid)
709+        self._last_failure = f
710+        # XXX: Use the reader for this?
711+        checkstring = data[:SIGNED_PREFIX_LENGTH]
712+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
713+        self._servermap.problems.append(f)
714+
715+
716+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
717+        """
718+        If one of my queries returns successfully (which means that we
719+        were able to and successfully did validate the signature), I
720+        cache the data that we initially fetched from the storage
721+        server. This will help reduce the number of roundtrips that need
722+        to occur when the file is downloaded, or when the file is
723+        updated.
724+        """
725+        self._node._add_to_cache(verinfo, shnum, 0, data, now)
726+
727+
728     def _got_results(self, datavs, peerid, readsize, stuff, started):
729         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
730                       peerid=idlib.shortnodeid_b2a(peerid),
731hunk ./src/allmydata/mutable/servermap.py 630
732         else:
733             self._empty_peers.add(peerid)
734 
735-        last_verinfo = None
736-        last_shnum = None
737+        ss, storage_index = stuff
738+        ds = []
739+
740         for shnum,datav in datavs.items():
741             data = datav[0]
742hunk ./src/allmydata/mutable/servermap.py 635
743-            try:
744-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
745-                last_verinfo = verinfo
746-                last_shnum = shnum
747-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
748-            except CorruptShareError, e:
749-                # log it and give the other shares a chance to be processed
750-                f = failure.Failure()
751-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
752-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
753-                self.notify_server_corruption(peerid, shnum, str(e))
754-                self._bad_peers.add(peerid)
755-                self._last_failure = f
756-                checkstring = data[:SIGNED_PREFIX_LENGTH]
757-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
758-                self._servermap.problems.append(f)
759-                pass
760-
761-        self._status.timings["cumulative_verify"] += (time.time() - now)
762+            reader = MDMFSlotReadProxy(ss,
763+                                       storage_index,
764+                                       shnum,
765+                                       data)
766+            self._readers.setdefault(peerid, dict())[shnum] = reader
767+            # our goal, with each response, is to validate the version
768+            # information and share data as best we can at this point --
769+            # we do this by validating the signature. To do this, we
770+            # need to do the following:
771+            #   - If we don't already have the public key, fetch the
772+            #     public key. We use this to validate the signature.
773+            if not self._node.get_pubkey():
774+                # fetch and set the public key.
775+                d = reader.get_verification_key()
776+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
777+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
778+                # XXX: Make self._pubkey_query_failed?
779+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
780+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
781+            else:
782+                # we already have the public key.
783+                d = defer.succeed(None)
784+            # Neither of these two branches return anything of
785+            # consequence, so the first entry in our deferredlist will
786+            # be None.
787 
788hunk ./src/allmydata/mutable/servermap.py 661
789-        if self._need_privkey and last_verinfo:
790-            # send them a request for the privkey. We send one request per
791-            # server.
792-            lp2 = self.log("sending privkey request",
793-                           parent=lp, level=log.NOISY)
794-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
795-             offsets_tuple) = last_verinfo
796-            o = dict(offsets_tuple)
797+            # - Next, we need the version information. We almost
798+            #   certainly got this by reading the first thousand or so
799+            #   bytes of the share on the storage server, so we
800+            #   shouldn't need to fetch anything at this step.
801+            d2 = reader.get_verinfo()
802+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
803+                self._got_corrupt_share(error, shnum, peerid, data, lp))
804+            # - Next, we need the signature. For an SDMF share, it is
805+            #   likely that we fetched this when doing our initial fetch
806+            #   to get the version information. In MDMF, this lives at
807+            #   the end of the share, so unless the file is quite small,
808+            #   we'll need to do a remote fetch to get it.
809+            d3 = reader.get_signature()
810+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
811+                self._got_corrupt_share(error, shnum, peerid, data, lp))
812+            #  Once we have all three of these responses, we can move on
813+            #  to validating the signature
814 
815hunk ./src/allmydata/mutable/servermap.py 679
816-            self._queries_outstanding.add(peerid)
817-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
818-            ss = self._servermap.connections[peerid]
819-            privkey_started = time.time()
820-            d = self._do_read(ss, peerid, self._storage_index,
821-                              [last_shnum], readv)
822-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
823-                          privkey_started, lp2)
824-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
825-            d.addErrback(log.err)
826-            d.addCallback(self._check_for_done)
827-            d.addErrback(self._fatal_error)
828+            # Does the node already have a privkey? If not, we'll try to
829+            # fetch it here.
830+            if self._need_privkey:
831+                d4 = reader.get_encprivkey()
832+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
833+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
834+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
835+                    self._privkey_query_failed(error, shnum, data, lp))
836+            else:
837+                d4 = defer.succeed(None)
838 
839hunk ./src/allmydata/mutable/servermap.py 690
840+            dl = defer.DeferredList([d, d2, d3, d4])
841+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
842+                self._got_signature_one_share(results, shnum, peerid, lp))
843+            dl.addErrback(lambda error, shnum=shnum, data=data:
844+               self._got_corrupt_share(error, shnum, peerid, data, lp))
845+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
846+                self._cache_good_sharedata(verinfo, shnum, now, data))
847+            ds.append(dl)
848+        # dl is a deferred list that will fire when all of the shares
849+        # that we found on this peer are done processing. When dl fires,
850+        # we know that processing is done, so we can decrement the
851+        # semaphore-like thing that we incremented earlier.
852+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
853+        # Are we done? Done means that there are no more queries to
854+        # send, that there are no outstanding queries, and that we
855+        # haven't received any queries that are still processing. If we
856+        # are done, self._check_for_done will cause the done deferred
857+        # that we returned to our caller to fire, which tells them that
858+        # they have a complete servermap, and that we won't be touching
859+        # the servermap anymore.
860+        dl.addCallback(self._check_for_done)
861+        dl.addErrback(self._fatal_error)
862         # all done!
863         self.log("_got_results done", parent=lp, level=log.NOISY)
864hunk ./src/allmydata/mutable/servermap.py 714
865+        return dl
866+
867+
868+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
869+        if self._node.get_pubkey():
870+            return # don't go through this again if we don't have to
871+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
872+        assert len(fingerprint) == 32
873+        if fingerprint != self._node.get_fingerprint():
874+            raise CorruptShareError(peerid, shnum,
875+                                "pubkey doesn't match fingerprint")
876+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
877+        assert self._node.get_pubkey()
878+
879 
880     def notify_server_corruption(self, peerid, shnum, reason):
881         ss = self._servermap.connections[peerid]
882hunk ./src/allmydata/mutable/servermap.py 734
883         ss.callRemoteOnly("advise_corrupt_share",
884                           "mutable", self._storage_index, shnum, reason)
885 
886-    def _got_results_one_share(self, shnum, data, peerid, lp):
887+
888+    def _got_signature_one_share(self, results, shnum, peerid, lp):
889+        # It is our job to give versioninfo to our caller. We need to
890+        # raise CorruptShareError if the share is corrupt for any
891+        # reason, something that our caller will handle.
892         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
893                  shnum=shnum,
894                  peerid=idlib.shortnodeid_b2a(peerid),
895hunk ./src/allmydata/mutable/servermap.py 744
896                  level=log.NOISY,
897                  parent=lp)
898-
899-        # this might raise NeedMoreDataError, if the pubkey and signature
900-        # live at some weird offset. That shouldn't happen, so I'm going to
901-        # treat it as a bad share.
902-        (seqnum, root_hash, IV, k, N, segsize, datalength,
903-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
904-
905-        if not self._node.get_pubkey():
906-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
907-            assert len(fingerprint) == 32
908-            if fingerprint != self._node.get_fingerprint():
909-                raise CorruptShareError(peerid, shnum,
910-                                        "pubkey doesn't match fingerprint")
911-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
912-
913-        if self._need_privkey:
914-            self._try_to_extract_privkey(data, peerid, shnum, lp)
915-
916-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
917-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
918+        _, verinfo, signature, __ = results
919+        (seqnum,
920+         root_hash,
921+         saltish,
922+         segsize,
923+         datalen,
924+         k,
925+         n,
926+         prefix,
927+         offsets) = verinfo[1]
928         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
929 
930hunk ./src/allmydata/mutable/servermap.py 756
931-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
932+        # XXX: This should be done for us in the method, so
933+        # presumably you can go in there and fix it.
934+        verinfo = (seqnum,
935+                   root_hash,
936+                   saltish,
937+                   segsize,
938+                   datalen,
939+                   k,
940+                   n,
941+                   prefix,
942                    offsets_tuple)
943hunk ./src/allmydata/mutable/servermap.py 767
944+        # This tuple uniquely identifies a share on the grid; we use it
945+        # to keep track of the ones that we've already seen.
946 
947         if verinfo not in self._valid_versions:
948hunk ./src/allmydata/mutable/servermap.py 771
949-            # it's a new pair. Verify the signature.
950-            valid = self._node.get_pubkey().verify(prefix, signature)
951+            # This is a new version tuple, and we need to validate it
952+            # against the public key before keeping track of it.
953+            assert self._node.get_pubkey()
954+            valid = self._node.get_pubkey().verify(prefix, signature[1])
955             if not valid:
956hunk ./src/allmydata/mutable/servermap.py 776
957-                raise CorruptShareError(peerid, shnum, "signature is invalid")
958+                raise CorruptShareError(peerid, shnum,
959+                                        "signature is invalid")
960 
961hunk ./src/allmydata/mutable/servermap.py 779
962-            # ok, it's a valid verinfo. Add it to the list of validated
963-            # versions.
964-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
965-                     % (seqnum, base32.b2a(root_hash)[:4],
966-                        idlib.shortnodeid_b2a(peerid), shnum,
967-                        k, N, segsize, datalength),
968-                     parent=lp)
969-            self._valid_versions.add(verinfo)
970-        # We now know that this is a valid candidate verinfo.
971+        # ok, it's a valid verinfo. Add it to the list of validated
972+        # versions.
973+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
974+                 % (seqnum, base32.b2a(root_hash)[:4],
975+                    idlib.shortnodeid_b2a(peerid), shnum,
976+                    k, n, segsize, datalen),
977+                    parent=lp)
978+        self._valid_versions.add(verinfo)
979+        # We now know that this is a valid candidate verinfo. Whether or
980+        # not this instance of it is valid is a matter for the next
981+        # statement; at this point, we just know that if we see this
982+        # version info again, that its signature checks out and that
983+        # we're okay to skip the signature-checking step.
984 
985hunk ./src/allmydata/mutable/servermap.py 793
986+        # (peerid, shnum) are bound in the method invocation.
987         if (peerid, shnum) in self._servermap.bad_shares:
988             # we've been told that the rest of the data in this share is
989             # unusable, so don't add it to the servermap.
990hunk ./src/allmydata/mutable/servermap.py 808
991         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
992         return verinfo
993 
994+
995     def _deserialize_pubkey(self, pubkey_s):
996         verifier = rsa.create_verifying_key_from_string(pubkey_s)
997         return verifier
998hunk ./src/allmydata/mutable/servermap.py 813
999 
1000-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
1001-        try:
1002-            r = unpack_share(data)
1003-        except NeedMoreDataError, e:
1004-            # this share won't help us. oh well.
1005-            offset = e.encprivkey_offset
1006-            length = e.encprivkey_length
1007-            self.log("shnum %d on peerid %s: share was too short (%dB) "
1008-                     "to get the encprivkey; [%d:%d] ought to hold it" %
1009-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
1010-                      offset, offset+length),
1011-                     parent=lp)
1012-            # NOTE: if uncoordinated writes are taking place, someone might
1013-            # change the share (and most probably move the encprivkey) before
1014-            # we get a chance to do one of these reads and fetch it. This
1015-            # will cause us to see a NotEnoughSharesError(unable to fetch
1016-            # privkey) instead of an UncoordinatedWriteError . This is a
1017-            # nuisance, but it will go away when we move to DSA-based mutable
1018-            # files (since the privkey will be small enough to fit in the
1019-            # write cap).
1020-
1021-            return
1022-
1023-        (seqnum, root_hash, IV, k, N, segsize, datalen,
1024-         pubkey, signature, share_hash_chain, block_hash_tree,
1025-         share_data, enc_privkey) = r
1026-
1027-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1028 
1029     def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
1030hunk ./src/allmydata/mutable/servermap.py 815
1031-
1032+        """
1033+        Given a writekey from a remote server, I validate it against the
1034+        writekey stored in my node. If it is valid, then I set the
1035+        privkey and encprivkey properties of the node.
1036+        """
1037         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
1038         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
1039         if alleged_writekey != self._node.get_writekey():
1040hunk ./src/allmydata/mutable/servermap.py 892
1041         self._queries_completed += 1
1042         self._last_failure = f
1043 
1044-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
1045-        now = time.time()
1046-        elapsed = now - started
1047-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
1048-        self._queries_outstanding.discard(peerid)
1049-        if not self._need_privkey:
1050-            return
1051-        if shnum not in datavs:
1052-            self.log("privkey wasn't there when we asked it",
1053-                     level=log.WEIRD, umid="VA9uDQ")
1054-            return
1055-        datav = datavs[shnum]
1056-        enc_privkey = datav[0]
1057-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
1058 
1059     def _privkey_query_failed(self, f, peerid, shnum, lp):
1060         self._queries_outstanding.discard(peerid)
1061hunk ./src/allmydata/mutable/servermap.py 906
1062         self._servermap.problems.append(f)
1063         self._last_failure = f
1064 
1065+
1066     def _check_for_done(self, res):
1067         # exit paths:
1068         #  return self._send_more_queries(outstanding) : send some more queries
1069hunk ./src/allmydata/mutable/servermap.py 912
1070         #  return self._done() : all done
1071         #  return : keep waiting, no new queries
1072-
1073         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
1074                               "%(outstanding)d queries outstanding, "
1075                               "%(extra)d extra peers available, "
1076hunk ./src/allmydata/mutable/servermap.py 1117
1077         self._servermap.last_update_time = self._started
1078         # the servermap will not be touched after this
1079         self.log("servermap: %s" % self._servermap.summarize_versions())
1080+
1081         eventually(self._done_deferred.callback, self._servermap)
1082 
1083     def _fatal_error(self, f):
1084hunk ./src/allmydata/test/test_mutable.py 637
1085         d.addCallback(_created)
1086         return d
1087 
1088-    def publish_multiple(self):
1089+    def publish_mdmf(self):
1090+        # like publish_one, except that the result is guaranteed to be
1091+        # an MDMF file.
1092+        # self.CONTENTS should have more than one segment.
1093+        self.CONTENTS = "This is an MDMF file" * 100000
1094+        self._storage = FakeStorage()
1095+        self._nodemaker = make_nodemaker(self._storage)
1096+        self._storage_broker = self._nodemaker.storage_broker
1097+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
1098+        def _created(node):
1099+            self._fn = node
1100+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1101+        d.addCallback(_created)
1102+        return d
1103+
1104+
1105+    def publish_sdmf(self):
1106+        # like publish_one, except that the result is guaranteed to be
1107+        # an SDMF file
1108+        self.CONTENTS = "This is an SDMF file" * 1000
1109+        self._storage = FakeStorage()
1110+        self._nodemaker = make_nodemaker(self._storage)
1111+        self._storage_broker = self._nodemaker.storage_broker
1112+        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
1113+        def _created(node):
1114+            self._fn = node
1115+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
1116+        d.addCallback(_created)
1117+        return d
1118+
1119+
1120+    def publish_multiple(self, version=0):
1121         self.CONTENTS = ["Contents 0",
1122                          "Contents 1",
1123                          "Contents 2",
1124hunk ./src/allmydata/test/test_mutable.py 677
1125         self._copied_shares = {}
1126         self._storage = FakeStorage()
1127         self._nodemaker = make_nodemaker(self._storage)
1128-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
1129+        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
1130         def _created(node):
1131             self._fn = node
1132             # now create multiple versions of the same file, and accumulate
1133hunk ./src/allmydata/test/test_mutable.py 906
1134         return d
1135 
1136 
1137+    def test_servermapupdater_finds_mdmf_files(self):
1138+        # setUp already published an MDMF file for us. We just need to
1139+        # make sure that when we run the ServermapUpdater, the file is
1140+        # reported to have one recoverable version.
1141+        d = defer.succeed(None)
1142+        d.addCallback(lambda ignored:
1143+            self.publish_mdmf())
1144+        d.addCallback(lambda ignored:
1145+            self.make_servermap(mode=MODE_CHECK))
1146+        # Calling make_servermap also updates the servermap in the mode
1147+        # that we specify, so we just need to see what it says.
1148+        def _check_servermap(sm):
1149+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
1150+        d.addCallback(_check_servermap)
1151+        return d
1152+
1153+
1154+    def test_servermapupdater_finds_sdmf_files(self):
1155+        d = defer.succeed(None)
1156+        d.addCallback(lambda ignored:
1157+            self.publish_sdmf())
1158+        d.addCallback(lambda ignored:
1159+            self.make_servermap(mode=MODE_CHECK))
1160+        d.addCallback(lambda servermap:
1161+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
1162+        return d
1163+
1164 
1165 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
1166     def setUp(self):
1167hunk ./src/allmydata/test/test_mutable.py 1050
1168         return d
1169     test_no_servers_download.timeout = 15
1170 
1171+
1172     def _test_corrupt_all(self, offset, substring,
1173                           should_succeed=False, corrupt_early=True,
1174                           failure_checker=None):
1175}
1176[Make a segmented mutable uploader
1177Kevan Carstensen <kevan@isnotajoke.com>**20100626234204
1178 Ignore-this: d199af8ab0bc64d8ed2bc19c5437bfba
1179 
1180 The mutable file uploader should be able to publish files with one
1181 segment and files with multiple segments. This patch makes it do that.
1182 This is still incomplete, and rather ugly -- I need to flesh out error
1183 handling, I need to write tests, and I need to remove some of the uglier
1184 kludges in the process before I can call this done.
1185] {
1186hunk ./src/allmydata/mutable/publish.py 8
1187 from zope.interface import implements
1188 from twisted.internet import defer
1189 from twisted.python import failure
1190-from allmydata.interfaces import IPublishStatus
1191+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
1192 from allmydata.util import base32, hashutil, mathutil, idlib, log
1193 from allmydata import hashtree, codec
1194 from allmydata.storage.server import si_b2a
1195hunk ./src/allmydata/mutable/publish.py 19
1196      UncoordinatedWriteError, NotEnoughServersError
1197 from allmydata.mutable.servermap import ServerMap
1198 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
1199-     unpack_checkstring, SIGNED_PREFIX
1200+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
1201+
1202+KiB = 1024
1203+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
1204 
1205 class PublishStatus:
1206     implements(IPublishStatus)
1207hunk ./src/allmydata/mutable/publish.py 112
1208         self._status.set_helper(False)
1209         self._status.set_progress(0.0)
1210         self._status.set_active(True)
1211+        # We use this to control how the file is written.
1212+        version = self._node.get_version()
1213+        assert version in (SDMF_VERSION, MDMF_VERSION)
1214+        self._version = version
1215 
1216     def get_status(self):
1217         return self._status
1218hunk ./src/allmydata/mutable/publish.py 134
1219         simultaneous write.
1220         """
1221 
1222-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
1223-        # 2: perform peer selection, get candidate servers
1224-        #  2a: send queries to n+epsilon servers, to determine current shares
1225-        #  2b: based upon responses, create target map
1226-        # 3: send slot_testv_and_readv_and_writev messages
1227-        # 4: as responses return, update share-dispatch table
1228-        # 4a: may need to run recovery algorithm
1229-        # 5: when enough responses are back, we're done
1230+        # 0. Setup encoding parameters, encoder, and other such things.
1231+        # 1. Encrypt, encode, and publish segments.
1232 
1233         self.log("starting publish, datalen is %s" % len(newdata))
1234         self._status.set_size(len(newdata))
1235hunk ./src/allmydata/mutable/publish.py 187
1236         self.bad_peers = set() # peerids who have errbacked/refused requests
1237 
1238         self.newdata = newdata
1239-        self.salt = os.urandom(16)
1240 
1241hunk ./src/allmydata/mutable/publish.py 188
1242+        # This will set self.segment_size, self.num_segments, and
1243+        # self.fec.
1244         self.setup_encoding_parameters()
1245 
1246         # if we experience any surprises (writes which were rejected because
1247hunk ./src/allmydata/mutable/publish.py 238
1248             self.bad_share_checkstrings[key] = old_checkstring
1249             self.connections[peerid] = self._servermap.connections[peerid]
1250 
1251-        # create the shares. We'll discard these as they are delivered. SDMF:
1252-        # we're allowed to hold everything in memory.
1253+        # Now, the process dovetails -- if this is an SDMF file, we need
1254+        # to write an SDMF file. Otherwise, we need to write an MDMF
1255+        # file.
1256+        if self._version == MDMF_VERSION:
1257+            return self._publish_mdmf()
1258+        else:
1259+            return self._publish_sdmf()
1260+        #return self.done_deferred
1261+
1262+    def _publish_mdmf(self):
1263+        # Next, we find homes for all of the shares that we don't have
1264+        # homes for yet.
1265+        # TODO: Make this part do peer selection.
1266+        self.update_goal()
1267+        self.writers = {}
1268+        # For each (peerid, shnum) in self.goal, we make an
1269+        # MDMFSlotWriteProxy for that peer. We'll use this to write
1270+        # shares to the peer.
1271+        for key in self.goal:
1272+            peerid, shnum = key
1273+            write_enabler = self._node.get_write_enabler(peerid)
1274+            renew_secret = self._node.get_renewal_secret(peerid)
1275+            cancel_secret = self._node.get_cancel_secret(peerid)
1276+            secrets = (write_enabler, renew_secret, cancel_secret)
1277+
1278+            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
1279+                                                      self.connections[peerid],
1280+                                                      self._storage_index,
1281+                                                      secrets,
1282+                                                      self._new_seqnum,
1283+                                                      self.required_shares,
1284+                                                      self.total_shares,
1285+                                                      self.segment_size,
1286+                                                      len(self.newdata))
1287+            if (peerid, shnum) in self._servermap.servermap:
1288+                old_versionid, old_timestamp = self._servermap.servermap[key]
1289+                (old_seqnum, old_root_hash, old_salt, old_segsize,
1290+                 old_datalength, old_k, old_N, old_prefix,
1291+                 old_offsets_tuple) = old_versionid
1292+                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
1293+
1294+        # Now, we start pushing shares.
1295+        self._status.timings["setup"] = time.time() - self._started
1296+        def _start_pushing(res):
1297+            self._started_pushing = time.time()
1298+            return res
1299+
1300+        # First, we encrypt, encode, and publish the shares that we need
1301+        # to encrypt, encode, and publish.
1302+
1303+        # This will eventually hold the block hash chain for each share
1304+        # that we publish. We define it this way so that empty publishes
1305+        # will still have something to write to the remote slot.
1306+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
1307+        self.sharehash_leaves = None # eventually [sharehashes]
1308+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
1309+                              # validate the share]
1310 
1311hunk ./src/allmydata/mutable/publish.py 296
1312+        d = defer.succeed(None)
1313+        self.log("Starting push")
1314+        for i in xrange(self.num_segments - 1):
1315+            d.addCallback(lambda ignored, i=i:
1316+                self.push_segment(i))
1317+            d.addCallback(self._turn_barrier)
1318+        # We have at least one segment, so we will have a tail segment
1319+        if self.num_segments > 0:
1320+            d.addCallback(lambda ignored:
1321+                self.push_tail_segment())
1322+
1323+        d.addCallback(lambda ignored:
1324+            self.push_encprivkey())
1325+        d.addCallback(lambda ignored:
1326+            self.push_blockhashes())
1327+        d.addCallback(lambda ignored:
1328+            self.push_sharehashes())
1329+        d.addCallback(lambda ignored:
1330+            self.push_toplevel_hashes_and_signature())
1331+        d.addCallback(lambda ignored:
1332+            self.finish_publishing())
1333+        return d
1334+
1335+
1336+    def _publish_sdmf(self):
1337         self._status.timings["setup"] = time.time() - self._started
1338hunk ./src/allmydata/mutable/publish.py 322
1339+        self.salt = os.urandom(16)
1340+
1341         d = self._encrypt_and_encode()
1342         d.addCallback(self._generate_shares)
1343         def _start_pushing(res):
1344hunk ./src/allmydata/mutable/publish.py 335
1345 
1346         return self.done_deferred
1347 
1348+
1349     def setup_encoding_parameters(self):
1350hunk ./src/allmydata/mutable/publish.py 337
1351-        segment_size = len(self.newdata)
1352+        if self._version == MDMF_VERSION:
1353+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
1354+        else:
1355+            segment_size = len(self.newdata) # SDMF is only one segment
1356         # this must be a multiple of self.required_shares
1357         segment_size = mathutil.next_multiple(segment_size,
1358                                               self.required_shares)
1359hunk ./src/allmydata/mutable/publish.py 350
1360                                                   segment_size)
1361         else:
1362             self.num_segments = 0
1363-        assert self.num_segments in [0, 1,] # SDMF restrictions
1364+        if self._version == SDMF_VERSION:
1365+            assert self.num_segments in (0, 1) # SDMF
1366+            return
1367+        # calculate the tail segment size.
1368+        self.tail_segment_size = len(self.newdata) % segment_size
1369+
1370+        if self.tail_segment_size == 0:
1371+            # The tail segment is the same size as the other segments.
1372+            self.tail_segment_size = segment_size
1373+
1374+        # We'll make an encoder ahead-of-time for the normal-sized
1375+        # segments (defined as any segment of segment_size size.
1376+        # (the part of the code that puts the tail segment will make its
1377+        #  own encoder for that part)
1378+        fec = codec.CRSEncoder()
1379+        fec.set_params(self.segment_size,
1380+                       self.required_shares, self.total_shares)
1381+        self.piece_size = fec.get_block_size()
1382+        self.fec = fec
1383+
1384+
1385+    def push_segment(self, segnum):
1386+        started = time.time()
1387+        segsize = self.segment_size
1388+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
1389+        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
1390+        assert len(data) == segsize
1391+
1392+        salt = os.urandom(16)
1393+
1394+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1395+        enc = AES(key)
1396+        crypttext = enc.process(data)
1397+        assert len(crypttext) == len(data)
1398+
1399+        now = time.time()
1400+        self._status.timings["encrypt"] = now - started
1401+        started = now
1402+
1403+        # now apply FEC
1404+
1405+        self._status.set_status("Encoding")
1406+        crypttext_pieces = [None] * self.required_shares
1407+        piece_size = self.piece_size
1408+        for i in range(len(crypttext_pieces)):
1409+            offset = i * piece_size
1410+            piece = crypttext[offset:offset+piece_size]
1411+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1412+            crypttext_pieces[i] = piece
1413+            assert len(piece) == piece_size
1414+        d = self.fec.encode(crypttext_pieces)
1415+        def _done_encoding(res):
1416+            elapsed = time.time() - started
1417+            self._status.timings["encode"] = elapsed
1418+            return res
1419+        d.addCallback(_done_encoding)
1420+
1421+        def _push_shares_and_salt(results):
1422+            shares, shareids = results
1423+            dl = []
1424+            for i in xrange(len(shares)):
1425+                sharedata = shares[i]
1426+                shareid = shareids[i]
1427+                block_hash = hashutil.block_hash(salt + sharedata)
1428+                self.blockhashes[shareid].append(block_hash)
1429+
1430+                # find the writer for this share
1431+                d = self.writers[shareid].put_block(sharedata, segnum, salt)
1432+                dl.append(d)
1433+            # TODO: Naturally, we need to check on the results of these.
1434+            return defer.DeferredList(dl)
1435+        d.addCallback(_push_shares_and_salt)
1436+        return d
1437+
1438+
1439+    def push_tail_segment(self):
1440+        # This is essentially the same as push_segment, except that we
1441+        # don't use the cached encoder that we use elsewhere.
1442+        self.log("Pushing tail segment")
1443+        started = time.time()
1444+        segsize = self.segment_size
1445+        data = self.newdata[segsize * (self.num_segments-1):]
1446+        assert len(data) == self.tail_segment_size
1447+        salt = os.urandom(16)
1448+
1449+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
1450+        enc = AES(key)
1451+        crypttext = enc.process(data)
1452+        assert len(crypttext) == len(data)
1453+
1454+        now = time.time()
1455+        self._status.timings['encrypt'] = now - started
1456+        started = now
1457+
1458+        self._status.set_status("Encoding")
1459+        tail_fec = codec.CRSEncoder()
1460+        tail_fec.set_params(self.tail_segment_size,
1461+                            self.required_shares,
1462+                            self.total_shares)
1463+
1464+        crypttext_pieces = [None] * self.required_shares
1465+        piece_size = tail_fec.get_block_size()
1466+        for i in range(len(crypttext_pieces)):
1467+            offset = i * piece_size
1468+            piece = crypttext[offset:offset+piece_size]
1469+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
1470+            crypttext_pieces[i] = piece
1471+            assert len(piece) == piece_size
1472+        d = tail_fec.encode(crypttext_pieces)
1473+        def _push_shares_and_salt(results):
1474+            shares, shareids = results
1475+            dl = []
1476+            for i in xrange(len(shares)):
1477+                sharedata = shares[i]
1478+                shareid = shareids[i]
1479+                block_hash = hashutil.block_hash(salt + sharedata)
1480+                self.blockhashes[shareid].append(block_hash)
1481+                # find the writer for this share
1482+                d = self.writers[shareid].put_block(sharedata,
1483+                                                    self.num_segments - 1,
1484+                                                    salt)
1485+                dl.append(d)
1486+            # TODO: Naturally, we need to check on the results of these.
1487+            return defer.DeferredList(dl)
1488+        d.addCallback(_push_shares_and_salt)
1489+        return d
1490+
1491+
1492+    def push_encprivkey(self):
1493+        started = time.time()
1494+        encprivkey = self._encprivkey
1495+        dl = []
1496+        def _spy_on_writer(results):
1497+            print results
1498+            return results
1499+        for shnum, writer in self.writers.iteritems():
1500+            d = writer.put_encprivkey(encprivkey)
1501+            dl.append(d)
1502+        d = defer.DeferredList(dl)
1503+        return d
1504+
1505+
1506+    def push_blockhashes(self):
1507+        started = time.time()
1508+        dl = []
1509+        def _spy_on_results(results):
1510+            print results
1511+            return results
1512+        self.sharehash_leaves = [None] * len(self.blockhashes)
1513+        for shnum, blockhashes in self.blockhashes.iteritems():
1514+            t = hashtree.HashTree(blockhashes)
1515+            self.blockhashes[shnum] = list(t)
1516+            # set the leaf for future use.
1517+            self.sharehash_leaves[shnum] = t[0]
1518+            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
1519+            dl.append(d)
1520+        d = defer.DeferredList(dl)
1521+        return d
1522+
1523+
1524+    def push_sharehashes(self):
1525+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
1526+        share_hash_chain = {}
1527+        ds = []
1528+        def _spy_on_results(results):
1529+            print results
1530+            return results
1531+        for shnum in xrange(len(self.sharehash_leaves)):
1532+            needed_indices = share_hash_tree.needed_hashes(shnum)
1533+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
1534+                                             for i in needed_indices] )
1535+            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
1536+            ds.append(d)
1537+        self.root_hash = share_hash_tree[0]
1538+        d = defer.DeferredList(ds)
1539+        return d
1540+
1541+
1542+    def push_toplevel_hashes_and_signature(self):
1543+        # We need to to three things here:
1544+        #   - Push the root hash and salt hash
1545+        #   - Get the checkstring of the resulting layout; sign that.
1546+        #   - Push the signature
1547+        ds = []
1548+        def _spy_on_results(results):
1549+            print results
1550+            return results
1551+        for shnum in xrange(self.total_shares):
1552+            d = self.writers[shnum].put_root_hash(self.root_hash)
1553+            ds.append(d)
1554+        d = defer.DeferredList(ds)
1555+        def _make_and_place_signature(ignored):
1556+            signable = self.writers[0].get_signable()
1557+            self.signature = self._privkey.sign(signable)
1558+
1559+            ds = []
1560+            for (shnum, writer) in self.writers.iteritems():
1561+                d = writer.put_signature(self.signature)
1562+                ds.append(d)
1563+            return defer.DeferredList(ds)
1564+        d.addCallback(_make_and_place_signature)
1565+        return d
1566+
1567+
1568+    def finish_publishing(self):
1569+        # We're almost done -- we just need to put the verification key
1570+        # and the offsets
1571+        ds = []
1572+        verification_key = self._pubkey.serialize()
1573+
1574+        def _spy_on_results(results):
1575+            print results
1576+            return results
1577+        for (shnum, writer) in self.writers.iteritems():
1578+            d = writer.put_verification_key(verification_key)
1579+            d.addCallback(lambda ignored, writer=writer:
1580+                writer.finish_publishing())
1581+            ds.append(d)
1582+        return defer.DeferredList(ds)
1583+
1584+
1585+    def _turn_barrier(self, res):
1586+        # putting this method in a Deferred chain imposes a guaranteed
1587+        # reactor turn between the pre- and post- portions of that chain.
1588+        # This can be useful to limit memory consumption: since Deferreds do
1589+        # not do tail recursion, code which uses defer.succeed(result) for
1590+        # consistency will cause objects to live for longer than you might
1591+        # normally expect.
1592+        return fireEventually(res)
1593+
1594 
1595     def _fatal_error(self, f):
1596         self.log("error during loop", failure=f, level=log.UNUSUAL)
1597hunk ./src/allmydata/mutable/publish.py 716
1598             self.log_goal(self.goal, "after update: ")
1599 
1600 
1601-
1602     def _encrypt_and_encode(self):
1603         # this returns a Deferred that fires with a list of (sharedata,
1604         # sharenum) tuples. TODO: cache the ciphertext, only produce the
1605hunk ./src/allmydata/mutable/publish.py 757
1606         d.addCallback(_done_encoding)
1607         return d
1608 
1609+
1610     def _generate_shares(self, shares_and_shareids):
1611         # this sets self.shares and self.root_hash
1612         self.log("_generate_shares")
1613hunk ./src/allmydata/mutable/publish.py 1145
1614             self._status.set_progress(1.0)
1615         eventually(self.done_deferred.callback, res)
1616 
1617-
1618hunk ./src/allmydata/test/test_mutable.py 248
1619         d.addCallback(_created)
1620         return d
1621 
1622+
1623+    def test_create_mdmf(self):
1624+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
1625+        def _created(n):
1626+            self.failUnless(isinstance(n, MutableFileNode))
1627+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
1628+            sb = self.nodemaker.storage_broker
1629+            peer0 = sorted(sb.get_all_serverids())[0]
1630+            shnums = self._storage._peers[peer0].keys()
1631+            self.failUnlessEqual(len(shnums), 1)
1632+        d.addCallback(_created)
1633+        return d
1634+
1635+
1636     def test_serialize(self):
1637         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
1638         calls = []
1639hunk ./src/allmydata/test/test_mutable.py 334
1640         d.addCallback(_created)
1641         return d
1642 
1643+
1644+    def test_create_mdmf_with_initial_contents(self):
1645+        initial_contents = "foobarbaz" * 131072 # 900KiB
1646+        d = self.nodemaker.create_mutable_file(initial_contents,
1647+                                               version=MDMF_VERSION)
1648+        def _created(n):
1649+            d = n.download_best_version()
1650+            d.addCallback(lambda data:
1651+                self.failUnlessEqual(data, initial_contents))
1652+            d.addCallback(lambda ignored:
1653+                n.overwrite(initial_contents + "foobarbaz"))
1654+            d.addCallback(lambda ignored:
1655+                n.download_best_version())
1656+            d.addCallback(lambda data:
1657+                self.failUnlessEqual(data, initial_contents +
1658+                                           "foobarbaz"))
1659+            return d
1660+        d.addCallback(_created)
1661+        return d
1662+
1663+
1664     def test_create_with_initial_contents_function(self):
1665         data = "initial contents"
1666         def _make_contents(n):
1667hunk ./src/allmydata/test/test_mutable.py 370
1668         d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
1669         return d
1670 
1671+
1672+    def test_create_mdmf_with_initial_contents_function(self):
1673+        data = "initial contents" * 100000
1674+        def _make_contents(n):
1675+            self.failUnless(isinstance(n, MutableFileNode))
1676+            key = n.get_writekey()
1677+            self.failUnless(isinstance(key, str), key)
1678+            self.failUnlessEqual(len(key), 16)
1679+            return data
1680+        d = self.nodemaker.create_mutable_file(_make_contents,
1681+                                               version=MDMF_VERSION)
1682+        d.addCallback(lambda n:
1683+            n.download_best_version())
1684+        d.addCallback(lambda data2:
1685+            self.failUnlessEqual(data2, data))
1686+        return d
1687+
1688+
1689     def test_create_with_too_large_contents(self):
1690         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
1691         d = self.nodemaker.create_mutable_file(BIG)
1692}
1693[Write a segmented mutable downloader
1694Kevan Carstensen <kevan@isnotajoke.com>**20100626234314
1695 Ignore-this: d2bef531cde1b5c38f2eb28afdd4b17c
1696 
1697 The segmented mutable downloader can deal with MDMF files (files with
1698 one or more segments in MDMF format) and SDMF files (files with one
1699 segment in SDMF format). It is backwards compatible with the old
1700 file format.
1701 
1702 This patch also contains tests for the segmented mutable downloader.
1703] {
1704hunk ./src/allmydata/mutable/retrieve.py 8
1705 from twisted.internet import defer
1706 from twisted.python import failure
1707 from foolscap.api import DeadReferenceError, eventually, fireEventually
1708-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
1709-from allmydata.util import hashutil, idlib, log
1710+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
1711+                                 MDMF_VERSION, SDMF_VERSION
1712+from allmydata.util import hashutil, idlib, log, mathutil
1713 from allmydata import hashtree, codec
1714 from allmydata.storage.server import si_b2a
1715 from pycryptopp.cipher.aes import AES
1716hunk ./src/allmydata/mutable/retrieve.py 17
1717 from pycryptopp.publickey import rsa
1718 
1719 from allmydata.mutable.common import DictOfSets, CorruptShareError, UncoordinatedWriteError
1720-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
1721+from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data, \
1722+                                     MDMFSlotReadProxy
1723 
1724 class RetrieveStatus:
1725     implements(IRetrieveStatus)
1726hunk ./src/allmydata/mutable/retrieve.py 104
1727         self.verinfo = verinfo
1728         # during repair, we may be called upon to grab the private key, since
1729         # it wasn't picked up during a verify=False checker run, and we'll
1730-        # need it for repair to generate the a new version.
1731+        # need it for repair to generate a new version.
1732         self._need_privkey = fetch_privkey
1733         if self._node.get_privkey():
1734             self._need_privkey = False
1735hunk ./src/allmydata/mutable/retrieve.py 109
1736 
1737+        if self._need_privkey:
1738+            # TODO: Evaluate the need for this. We'll use it if we want
1739+            # to limit how many queries are on the wire for the privkey
1740+            # at once.
1741+            self._privkey_query_markers = [] # one Marker for each time we've
1742+                                             # tried to get the privkey.
1743+
1744         self._status = RetrieveStatus()
1745         self._status.set_storage_index(self._storage_index)
1746         self._status.set_helper(False)
1747hunk ./src/allmydata/mutable/retrieve.py 125
1748          offsets_tuple) = self.verinfo
1749         self._status.set_size(datalength)
1750         self._status.set_encoding(k, N)
1751+        self.readers = {}
1752 
1753     def get_status(self):
1754         return self._status
1755hunk ./src/allmydata/mutable/retrieve.py 149
1756         self.remaining_sharemap = DictOfSets()
1757         for (shnum, peerid, timestamp) in shares:
1758             self.remaining_sharemap.add(shnum, peerid)
1759+            # If the servermap update fetched anything, it fetched at least 1
1760+            # KiB, so we ask for that much.
1761+            # TODO: Change the cache methods to allow us to fetch all of the
1762+            # data that they have, then change this method to do that.
1763+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
1764+                                                               shnum,
1765+                                                               0,
1766+                                                               1000)
1767+            ss = self.servermap.connections[peerid]
1768+            reader = MDMFSlotReadProxy(ss,
1769+                                       self._storage_index,
1770+                                       shnum,
1771+                                       any_cache)
1772+            reader.peerid = peerid
1773+            self.readers[shnum] = reader
1774+
1775 
1776         self.shares = {} # maps shnum to validated blocks
1777hunk ./src/allmydata/mutable/retrieve.py 167
1778+        self._active_readers = [] # list of active readers for this dl.
1779+        self._validated_readers = set() # set of readers that we have
1780+                                        # validated the prefix of
1781+        self._block_hash_trees = {} # shnum => hashtree
1782+        # TODO: Make this into a file-backed consumer or something to
1783+        # conserve memory.
1784+        self._plaintext = ""
1785 
1786         # how many shares do we need?
1787hunk ./src/allmydata/mutable/retrieve.py 176
1788-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1789+        (seqnum,
1790+         root_hash,
1791+         IV,
1792+         segsize,
1793+         datalength,
1794+         k,
1795+         N,
1796+         prefix,
1797          offsets_tuple) = self.verinfo
1798hunk ./src/allmydata/mutable/retrieve.py 185
1799-        assert len(self.remaining_sharemap) >= k
1800-        # we start with the lowest shnums we have available, since FEC is
1801-        # faster if we're using "primary shares"
1802-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
1803-        for shnum in self.active_shnums:
1804-            # we use an arbitrary peer who has the share. If shares are
1805-            # doubled up (more than one share per peer), we could make this
1806-            # run faster by spreading the load among multiple peers. But the
1807-            # algorithm to do that is more complicated than I want to write
1808-            # right now, and a well-provisioned grid shouldn't have multiple
1809-            # shares per peer.
1810-            peerid = list(self.remaining_sharemap[shnum])[0]
1811-            self.get_data(shnum, peerid)
1812 
1813hunk ./src/allmydata/mutable/retrieve.py 186
1814-        # control flow beyond this point: state machine. Receiving responses
1815-        # from queries is the input. We might send out more queries, or we
1816-        # might produce a result.
1817 
1818hunk ./src/allmydata/mutable/retrieve.py 187
1819+        # We need one share hash tree for the entire file; its leaves
1820+        # are the roots of the block hash trees for the shares that
1821+        # comprise it, and its root is in the verinfo.
1822+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
1823+        self.share_hash_tree.set_hashes({0: root_hash})
1824+
1825+        # This will set up both the segment decoder and the tail segment
1826+        # decoder, as well as a variety of other instance variables that
1827+        # the download process will use.
1828+        self._setup_encoding_parameters()
1829+        assert len(self.remaining_sharemap) >= k
1830+
1831+        self.log("starting download")
1832+        self._add_active_peers()
1833+        # The download process beyond this is a state machine.
1834+        # _add_active_peers will select the peers that we want to use
1835+        # for the download, and then attempt to start downloading. After
1836+        # each segment, it will check for doneness, reacting to broken
1837+        # peers and corrupt shares as necessary. If it runs out of good
1838+        # peers before downloading all of the segments, _done_deferred
1839+        # will errback.  Otherwise, it will eventually callback with the
1840+        # contents of the mutable file.
1841         return self._done_deferred
1842 
1843hunk ./src/allmydata/mutable/retrieve.py 211
1844-    def get_data(self, shnum, peerid):
1845-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
1846-                 shnum=shnum,
1847-                 peerid=idlib.shortnodeid_b2a(peerid),
1848-                 level=log.NOISY)
1849-        ss = self.servermap.connections[peerid]
1850-        started = time.time()
1851-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1852+
1853+    def _setup_encoding_parameters(self):
1854+        """
1855+        I set up the encoding parameters, including k, n, the number
1856+        of segments associated with this file, and the segment decoder.
1857+        """
1858+        (seqnum,
1859+         root_hash,
1860+         IV,
1861+         segsize,
1862+         datalength,
1863+         k,
1864+         n,
1865+         known_prefix,
1866          offsets_tuple) = self.verinfo
1867hunk ./src/allmydata/mutable/retrieve.py 226
1868-        offsets = dict(offsets_tuple)
1869+        self._required_shares = k
1870+        self._total_shares = n
1871+        self._segment_size = segsize
1872+        self._data_length = datalength
1873+
1874+        if not IV:
1875+            self._version = MDMF_VERSION
1876+        else:
1877+            self._version = SDMF_VERSION
1878+
1879+        if datalength and segsize:
1880+            self._num_segments = mathutil.div_ceil(datalength, segsize)
1881+            self._tail_data_size = datalength % segsize
1882+        else:
1883+            self._num_segments = 0
1884+            self._tail_data_size = 0
1885 
1886hunk ./src/allmydata/mutable/retrieve.py 243
1887-        # we read the checkstring, to make sure that the data we grab is from
1888-        # the right version.
1889-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
1890+        self._segment_decoder = codec.CRSDecoder()
1891+        self._segment_decoder.set_params(segsize, k, n)
1892+        self._current_segment = 0
1893 
1894hunk ./src/allmydata/mutable/retrieve.py 247
1895-        # We also read the data, and the hashes necessary to validate them
1896-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
1897-        # signature or the pubkey, since that was handled during the
1898-        # servermap phase, and we'll be comparing the share hash chain
1899-        # against the roothash that was validated back then.
1900+        if  not self._tail_data_size:
1901+            self._tail_data_size = segsize
1902 
1903hunk ./src/allmydata/mutable/retrieve.py 250
1904-        readv.append( (offsets['share_hash_chain'],
1905-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
1906+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
1907+                                                         self._required_shares)
1908+        if self._tail_segment_size == self._segment_size:
1909+            self._tail_decoder = self._segment_decoder
1910+        else:
1911+            self._tail_decoder = codec.CRSDecoder()
1912+            self._tail_decoder.set_params(self._tail_segment_size,
1913+                                          self._required_shares,
1914+                                          self._total_shares)
1915 
1916hunk ./src/allmydata/mutable/retrieve.py 260
1917-        # if we need the private key (for repair), we also fetch that
1918-        if self._need_privkey:
1919-            readv.append( (offsets['enc_privkey'],
1920-                           offsets['EOF'] - offsets['enc_privkey']) )
1921+        self.log("got encoding parameters: "
1922+                 "k: %d "
1923+                 "n: %d "
1924+                 "%d segments of %d bytes each (%d byte tail segment)" % \
1925+                 (k, n, self._num_segments, self._segment_size,
1926+                  self._tail_segment_size))
1927 
1928hunk ./src/allmydata/mutable/retrieve.py 267
1929-        m = Marker()
1930-        self._outstanding_queries[m] = (peerid, shnum, started)
1931+        for i in xrange(self._total_shares):
1932+            # So we don't have to do this later.
1933+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
1934 
1935hunk ./src/allmydata/mutable/retrieve.py 271
1936-        # ask the cache first
1937-        got_from_cache = False
1938-        datavs = []
1939-        for (offset, length) in readv:
1940-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
1941-                                                            offset, length)
1942-            if data is not None:
1943-                datavs.append(data)
1944-        if len(datavs) == len(readv):
1945-            self.log("got data from cache")
1946-            got_from_cache = True
1947-            d = fireEventually({shnum: datavs})
1948-            # datavs is a dict mapping shnum to a pair of strings
1949-        else:
1950-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
1951-        self.remaining_sharemap.discard(shnum, peerid)
1952+        # If we have more than one segment, we are an SDMF file, which
1953+        # means that we need to validate the salts as we receive them.
1954+        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
1955+        self._salt_hash_tree[0] = IV # from the prefix.
1956 
1957hunk ./src/allmydata/mutable/retrieve.py 276
1958-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
1959-        d.addErrback(self._query_failed, m, peerid)
1960-        # errors that aren't handled by _query_failed (and errors caused by
1961-        # _query_failed) get logged, but we still want to check for doneness.
1962-        def _oops(f):
1963-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
1964-                     shnum=shnum,
1965-                     peerid=idlib.shortnodeid_b2a(peerid),
1966-                     failure=f,
1967-                     level=log.WEIRD, umid="W0xnQA")
1968-        d.addErrback(_oops)
1969-        d.addBoth(self._check_for_done)
1970-        # any error during _check_for_done means the download fails. If the
1971-        # download is successful, _check_for_done will fire _done by itself.
1972-        d.addErrback(self._done)
1973-        d.addErrback(log.err)
1974-        return d # purely for testing convenience
1975 
1976hunk ./src/allmydata/mutable/retrieve.py 277
1977-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
1978-        # isolate the callRemote to a separate method, so tests can subclass
1979-        # Publish and override it
1980-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
1981-        return d
1982+    def _add_active_peers(self):
1983+        """
1984+        I populate self._active_readers with enough active readers to
1985+        retrieve the contents of this mutable file. I am called before
1986+        downloading starts, and (eventually) after each validation
1987+        error, connection error, or other problem in the download.
1988+        """
1989+        # TODO: It would be cool to investigate other heuristics for
1990+        # reader selection. For instance, the cost (in time the user
1991+        # spends waiting for their file) of selecting a really slow peer
1992+        # that happens to have a primary share is probably more than
1993+        # selecting a really fast peer that doesn't have a primary
1994+        # share. Maybe the servermap could be extended to provide this
1995+        # information; it could keep track of latency information while
1996+        # it gathers more important data, and then this routine could
1997+        # use that to select active readers.
1998+        #
1999+        # (these and other questions would be easier to answer with a
2000+        #  robust, configurable tahoe-lafs simulator, which modeled node
2001+        #  failures, differences in node speed, and other characteristics
2002+        #  that we expect storage servers to have.  You could have
2003+        #  presets for really stable grids (like allmydata.com),
2004+        #  friendnets, make it easy to configure your own settings, and
2005+        #  then simulate the effect of big changes on these use cases
2006+        #  instead of just reasoning about what the effect might be. Out
2007+        #  of scope for MDMF, though.)
2008 
2009hunk ./src/allmydata/mutable/retrieve.py 304
2010-    def remove_peer(self, peerid):
2011-        for shnum in list(self.remaining_sharemap.keys()):
2012-            self.remaining_sharemap.discard(shnum, peerid)
2013+        # We need at least self._required_shares readers to download a
2014+        # segment.
2015+        needed = self._required_shares - len(self._active_readers)
2016+        # XXX: Why don't format= log messages work here?
2017+        self.log("adding %d peers to the active peers list" % needed)
2018 
2019hunk ./src/allmydata/mutable/retrieve.py 310
2020-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
2021-        now = time.time()
2022-        elapsed = now - started
2023-        if not got_from_cache:
2024-            self._status.add_fetch_timing(peerid, elapsed)
2025-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
2026-                 shares=len(datavs),
2027-                 peerid=idlib.shortnodeid_b2a(peerid),
2028-                 level=log.NOISY)
2029-        self._outstanding_queries.pop(marker, None)
2030-        if not self._running:
2031-            return
2032+        # We favor lower numbered shares, since FEC is faster with
2033+        # primary shares than with other shares, and lower-numbered
2034+        # shares are more likely to be primary than higher numbered
2035+        # shares.
2036+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
2037+        # We shouldn't consider adding shares that we already have; this
2038+        # will cause problems later.
2039+        active_shnums -= set([reader.shnum for reader in self._active_readers])
2040+        active_shnums = list(active_shnums)[:needed]
2041+        if len(active_shnums) < needed:
2042+            # We don't have enough readers to retrieve the file; fail.
2043+            return self._failed()
2044 
2045hunk ./src/allmydata/mutable/retrieve.py 323
2046-        # note that we only ask for a single share per query, so we only
2047-        # expect a single share back. On the other hand, we use the extra
2048-        # shares if we get them.. seems better than an assert().
2049+        for shnum in active_shnums:
2050+            self._active_readers.append(self.readers[shnum])
2051+            self.log("added reader for share %d" % shnum)
2052+        assert len(self._active_readers) == self._required_shares
2053+        # Conceptually, this is part of the _add_active_peers step. It
2054+        # validates the prefixes of newly added readers to make sure
2055+        # that they match what we are expecting for self.verinfo. If
2056+        # validation is successful, _validate_active_prefixes will call
2057+        # _download_current_segment for us. If validation is
2058+        # unsuccessful, then _validate_prefixes will remove the peer and
2059+        # call _add_active_peers again, where we will attempt to rectify
2060+        # the problem by choosing another peer.
2061+        return self._validate_active_prefixes()
2062 
2063hunk ./src/allmydata/mutable/retrieve.py 337
2064-        for shnum,datav in datavs.items():
2065-            (prefix, hash_and_data) = datav[:2]
2066-            try:
2067-                self._got_results_one_share(shnum, peerid,
2068-                                            prefix, hash_and_data)
2069-            except CorruptShareError, e:
2070-                # log it and give the other shares a chance to be processed
2071-                f = failure.Failure()
2072-                self.log(format="bad share: %(f_value)s",
2073-                         f_value=str(f.value), failure=f,
2074-                         level=log.WEIRD, umid="7fzWZw")
2075-                self.notify_server_corruption(peerid, shnum, str(e))
2076-                self.remove_peer(peerid)
2077-                self.servermap.mark_bad_share(peerid, shnum, prefix)
2078-                self._bad_shares.add( (peerid, shnum) )
2079-                self._status.problems[peerid] = f
2080-                self._last_failure = f
2081-                pass
2082-            if self._need_privkey and len(datav) > 2:
2083-                lp = None
2084-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
2085-        # all done!
2086 
2087hunk ./src/allmydata/mutable/retrieve.py 338
2088-    def notify_server_corruption(self, peerid, shnum, reason):
2089-        ss = self.servermap.connections[peerid]
2090-        ss.callRemoteOnly("advise_corrupt_share",
2091-                          "mutable", self._storage_index, shnum, reason)
2092+    def _validate_active_prefixes(self):
2093+        """
2094+        I check to make sure that the prefixes on the peers that I am
2095+        currently reading from match the prefix that we want to see, as
2096+        said in self.verinfo.
2097 
2098hunk ./src/allmydata/mutable/retrieve.py 344
2099-    def _got_results_one_share(self, shnum, peerid,
2100-                               got_prefix, got_hash_and_data):
2101-        self.log("_got_results: got shnum #%d from peerid %s"
2102-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
2103-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2104+        If I find that all of the active peers have acceptable prefixes,
2105+        I pass control to _download_current_segment, which will use
2106+        those peers to do cool things. If I find that some of the active
2107+        peers have unacceptable prefixes, I will remove them from active
2108+        peers (and from further consideration) and call
2109+        _add_active_peers to attempt to rectify the situation. I keep
2110+        track of which peers I have already validated so that I don't
2111+        need to do so again.
2112+        """
2113+        assert self._active_readers, "No more active readers"
2114+
2115+        ds = []
2116+        new_readers = set(self._active_readers) - self._validated_readers
2117+        self.log('validating %d newly-added active readers' % len(new_readers))
2118+
2119+        for reader in new_readers:
2120+            # We force a remote read here -- otherwise, we are relying
2121+            # on cached data that we already verified as valid, and we
2122+            # won't detect an uncoordinated write that has occurred
2123+            # since the last servermap update.
2124+            d = reader.get_prefix(force_remote=True)
2125+            d.addCallback(self._try_to_validate_prefix, reader)
2126+            ds.append(d)
2127+        dl = defer.DeferredList(ds, consumeErrors=True)
2128+        def _check_results(results):
2129+            # Each result in results will be of the form (success, msg).
2130+            # We don't care about msg, but success will tell us whether
2131+            # or not the checkstring validated. If it didn't, we need to
2132+            # remove the offending (peer,share) from our active readers,
2133+            # and ensure that active readers is again populated.
2134+            bad_readers = []
2135+            for i, result in enumerate(results):
2136+                if not result[0]:
2137+                    reader = self._active_readers[i]
2138+                    f = result[1]
2139+                    assert isinstance(f, failure.Failure)
2140+
2141+                    self.log("The reader %s failed to "
2142+                             "properly validate: %s" % \
2143+                             (reader, str(f.value)))
2144+                    bad_readers.append((reader, f))
2145+                else:
2146+                    reader = self._active_readers[i]
2147+                    self.log("the reader %s checks out, so we'll use it" % \
2148+                             reader)
2149+                    self._validated_readers.add(reader)
2150+                    # Each time we validate a reader, we check to see if
2151+                    # we need the private key. If we do, we politely ask
2152+                    # for it and then continue computing. If we find
2153+                    # that we haven't gotten it at the end of
2154+                    # segment decoding, then we'll take more drastic
2155+                    # measures.
2156+                    if self._need_privkey:
2157+                        d = reader.get_encprivkey()
2158+                        d.addCallback(self._try_to_validate_privkey, reader)
2159+            if bad_readers:
2160+                # We do them all at once, or else we screw up list indexing.
2161+                for (reader, f) in bad_readers:
2162+                    self._mark_bad_share(reader, f)
2163+                return self._add_active_peers()
2164+            else:
2165+                return self._download_current_segment()
2166+            # The next step will assert that it has enough active
2167+            # readers to fetch shares; we just need to remove it.
2168+        dl.addCallback(_check_results)
2169+        return dl
2170+
2171+
2172+    def _try_to_validate_prefix(self, prefix, reader):
2173+        """
2174+        I check that the prefix returned by a candidate server for
2175+        retrieval matches the prefix that the servermap knows about
2176+        (and, hence, the prefix that was validated earlier). If it does,
2177+        I return True, which means that I approve of the use of the
2178+        candidate server for segment retrieval. If it doesn't, I return
2179+        False, which means that another server must be chosen.
2180+        """
2181+        (seqnum,
2182+         root_hash,
2183+         IV,
2184+         segsize,
2185+         datalength,
2186+         k,
2187+         N,
2188+         known_prefix,
2189          offsets_tuple) = self.verinfo
2190hunk ./src/allmydata/mutable/retrieve.py 430
2191-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
2192-        if got_prefix != prefix:
2193-            msg = "someone wrote to the data since we read the servermap: prefix changed"
2194-            raise UncoordinatedWriteError(msg)
2195-        (share_hash_chain, block_hash_tree,
2196-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
2197+        if known_prefix != prefix:
2198+            self.log("prefix from share %d doesn't match" % reader.shnum)
2199+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
2200+                                          "indicate an uncoordinated write")
2201+        # Otherwise, we're okay -- no issues.
2202 
2203hunk ./src/allmydata/mutable/retrieve.py 436
2204-        assert isinstance(share_data, str)
2205-        # build the block hash tree. SDMF has only one leaf.
2206-        leaves = [hashutil.block_hash(share_data)]
2207-        t = hashtree.HashTree(leaves)
2208-        if list(t) != block_hash_tree:
2209-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
2210-        share_hash_leaf = t[0]
2211-        t2 = hashtree.IncompleteHashTree(N)
2212-        # root_hash was checked by the signature
2213-        t2.set_hashes({0: root_hash})
2214-        try:
2215-            t2.set_hashes(hashes=share_hash_chain,
2216-                          leaves={shnum: share_hash_leaf})
2217-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
2218-                IndexError), e:
2219-            msg = "corrupt hashes: %s" % (e,)
2220-            raise CorruptShareError(peerid, shnum, msg)
2221-        self.log(" data valid! len=%d" % len(share_data))
2222-        # each query comes down to this: placing validated share data into
2223-        # self.shares
2224-        self.shares[shnum] = share_data
2225 
2226hunk ./src/allmydata/mutable/retrieve.py 437
2227-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
2228+    def _remove_reader(self, reader):
2229+        """
2230+        At various points, we will wish to remove a peer from
2231+        consideration and/or use. These include, but are not necessarily
2232+        limited to:
2233 
2234hunk ./src/allmydata/mutable/retrieve.py 443
2235-        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2236-        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2237-        if alleged_writekey != self._node.get_writekey():
2238-            self.log("invalid privkey from %s shnum %d" %
2239-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
2240-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
2241-            return
2242+            - A connection error.
2243+            - A mismatched prefix (that is, a prefix that does not match
2244+              our conception of the version information string).
2245+            - A failing block hash, salt hash, or share hash, which can
2246+              indicate disk failure/bit flips, or network trouble.
2247 
2248hunk ./src/allmydata/mutable/retrieve.py 449
2249-        # it's good
2250-        self.log("got valid privkey from shnum %d on peerid %s" %
2251-                 (shnum, idlib.shortnodeid_b2a(peerid)),
2252-                 parent=lp)
2253-        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2254-        self._node._populate_encprivkey(enc_privkey)
2255-        self._node._populate_privkey(privkey)
2256-        self._need_privkey = False
2257+        This method will do that. I will make sure that the
2258+        (shnum,reader) combination represented by my reader argument is
2259+        not used for anything else during this download. I will not
2260+        advise the reader of any corruption, something that my callers
2261+        may wish to do on their own.
2262+        """
2263+        # TODO: When you're done writing this, see if this is ever
2264+        # actually used for something that _mark_bad_share isn't. I have
2265+        # a feeling that they will be used for very similar things, and
2266+        # that having them both here is just going to be an epic amount
2267+        # of code duplication.
2268+        #
2269+        # (well, okay, not epic, but meaningful)
2270+        self.log("removing reader %s" % reader)
2271+        # Remove the reader from _active_readers
2272+        self._active_readers.remove(reader)
2273+        # TODO: self.readers.remove(reader)?
2274+        for shnum in list(self.remaining_sharemap.keys()):
2275+            self.remaining_sharemap.discard(shnum, reader.peerid)
2276 
2277hunk ./src/allmydata/mutable/retrieve.py 469
2278-    def _query_failed(self, f, marker, peerid):
2279-        self.log(format="query to [%(peerid)s] failed",
2280-                 peerid=idlib.shortnodeid_b2a(peerid),
2281-                 level=log.NOISY)
2282-        self._status.problems[peerid] = f
2283-        self._outstanding_queries.pop(marker, None)
2284-        if not self._running:
2285-            return
2286-        self._last_failure = f
2287-        self.remove_peer(peerid)
2288-        level = log.WEIRD
2289-        if f.check(DeadReferenceError):
2290-            level = log.UNUSUAL
2291-        self.log(format="error during query: %(f_value)s",
2292-                 f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
2293 
2294hunk ./src/allmydata/mutable/retrieve.py 470
2295-    def _check_for_done(self, res):
2296-        # exit paths:
2297-        #  return : keep waiting, no new queries
2298-        #  return self._send_more_queries(outstanding) : send some more queries
2299-        #  fire self._done(plaintext) : download successful
2300-        #  raise exception : download fails
2301+    def _mark_bad_share(self, reader, f):
2302+        """
2303+        I mark the (peerid, shnum) encapsulated by my reader argument as
2304+        a bad share, which means that it will not be used anywhere else.
2305 
2306hunk ./src/allmydata/mutable/retrieve.py 475
2307-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
2308-                 running=self._running, decoding=self._decoding,
2309-                 level=log.NOISY)
2310-        if not self._running:
2311-            return
2312-        if self._decoding:
2313-            return
2314-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2315-         offsets_tuple) = self.verinfo
2316+        There are several reasons to want to mark something as a bad
2317+        share. These include:
2318 
2319hunk ./src/allmydata/mutable/retrieve.py 478
2320-        if len(self.shares) < k:
2321-            # we don't have enough shares yet
2322-            return self._maybe_send_more_queries(k)
2323-        if self._need_privkey:
2324-            # we got k shares, but none of them had a valid privkey. TODO:
2325-            # look further. Adding code to do this is a bit complicated, and
2326-            # I want to avoid that complication, and this should be pretty
2327-            # rare (k shares with bitflips in the enc_privkey but not in the
2328-            # data blocks). If we actually do get here, the subsequent repair
2329-            # will fail for lack of a privkey.
2330-            self.log("got k shares but still need_privkey, bummer",
2331-                     level=log.WEIRD, umid="MdRHPA")
2332+            - A connection error to the peer.
2333+            - A mismatched prefix (that is, a prefix that does not match
2334+              our local conception of the version information string).
2335+            - A failing block hash, salt hash, share hash, or other
2336+              integrity check.
2337 
2338hunk ./src/allmydata/mutable/retrieve.py 484
2339-        # we have enough to finish. All the shares have had their hashes
2340-        # checked, so if something fails at this point, we don't know how
2341-        # to fix it, so the download will fail.
2342+        This method will ensure that readers that we wish to mark bad
2343+        (for these reasons or other reasons) are not used for the rest
2344+        of the download. Additionally, it will attempt to tell the
2345+        remote peer (with no guarantee of success) that its share is
2346+        corrupt.
2347+        """
2348+        self.log("marking share %d on server %s as bad" % \
2349+                 (reader.shnum, reader))
2350+        self._remove_reader(reader)
2351+        self._bad_shares.add((reader.peerid, reader.shnum))
2352+        self._status.problems[reader.peerid] = f
2353+        self._last_failure = f
2354+        self.notify_server_corruption(reader.peerid, reader.shnum,
2355+                                      str(f.value))
2356 
2357hunk ./src/allmydata/mutable/retrieve.py 499
2358-        self._decoding = True # avoid reentrancy
2359-        self._status.set_status("decoding")
2360-        now = time.time()
2361-        elapsed = now - self._started
2362-        self._status.timings["fetch"] = elapsed
2363 
2364hunk ./src/allmydata/mutable/retrieve.py 500
2365-        d = defer.maybeDeferred(self._decode)
2366-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
2367-        d.addBoth(self._done)
2368-        return d # purely for test convenience
2369+    def _download_current_segment(self):
2370+        """
2371+        I download, validate, decode, decrypt, and assemble the segment
2372+        that this Retrieve is currently responsible for downloading.
2373+        """
2374+        assert len(self._active_readers) >= self._required_shares
2375+        if self._current_segment < self._num_segments:
2376+            d = self._process_segment(self._current_segment)
2377+        else:
2378+            d = defer.succeed(None)
2379+        d.addCallback(self._check_for_done)
2380+        return d
2381 
2382hunk ./src/allmydata/mutable/retrieve.py 513
2383-    def _maybe_send_more_queries(self, k):
2384-        # we don't have enough shares yet. Should we send out more queries?
2385-        # There are some number of queries outstanding, each for a single
2386-        # share. If we can generate 'needed_shares' additional queries, we do
2387-        # so. If we can't, then we know this file is a goner, and we raise
2388-        # NotEnoughSharesError.
2389-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
2390-                         "outstanding=%(outstanding)d"),
2391-                 have=len(self.shares), k=k,
2392-                 outstanding=len(self._outstanding_queries),
2393-                 level=log.NOISY)
2394 
2395hunk ./src/allmydata/mutable/retrieve.py 514
2396-        remaining_shares = k - len(self.shares)
2397-        needed = remaining_shares - len(self._outstanding_queries)
2398-        if not needed:
2399-            # we have enough queries in flight already
2400+    def _process_segment(self, segnum):
2401+        """
2402+        I download, validate, decode, and decrypt one segment of the
2403+        file that this Retrieve is retrieving. This means coordinating
2404+        the process of getting k blocks of that file, validating them,
2405+        assembling them into one segment with the decoder, and then
2406+        decrypting them.
2407+        """
2408+        self.log("processing segment %d" % segnum)
2409 
2410hunk ./src/allmydata/mutable/retrieve.py 524
2411-            # TODO: but if they've been in flight for a long time, and we
2412-            # have reason to believe that new queries might respond faster
2413-            # (i.e. we've seen other queries come back faster, then consider
2414-            # sending out new queries. This could help with peers which have
2415-            # silently gone away since the servermap was updated, for which
2416-            # we're still waiting for the 15-minute TCP disconnect to happen.
2417-            self.log("enough queries are in flight, no more are needed",
2418-                     level=log.NOISY)
2419-            return
2420+        # TODO: The old code uses a marker. Should this code do that
2421+        # too? What did the Marker do?
2422+        assert len(self._active_readers) >= self._required_shares
2423+
2424+        # We need to ask each of our active readers for its block and
2425+        # salt. We will then validate those. If validation is
2426+        # successful, we will assemble the results into plaintext.
2427+        ds = []
2428+        for reader in self._active_readers:
2429+            d = reader.get_block_and_salt(segnum, queue=True)
2430+            d2 = self._get_needed_hashes(reader, segnum)
2431+            dl = defer.DeferredList([d, d2], consumeErrors=True)
2432+            dl.addCallback(self._validate_block, segnum, reader)
2433+            dl.addErrback(self._validation_or_decoding_failed, [reader])
2434+            ds.append(dl)
2435+            reader.flush()
2436+        dl = defer.DeferredList(ds)
2437+        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
2438+        return dl
2439 
2440hunk ./src/allmydata/mutable/retrieve.py 544
2441-        outstanding_shnums = set([shnum
2442-                                  for (peerid, shnum, started)
2443-                                  in self._outstanding_queries.values()])
2444-        # prefer low-numbered shares, they are more likely to be primary
2445-        available_shnums = sorted(self.remaining_sharemap.keys())
2446-        for shnum in available_shnums:
2447-            if shnum in outstanding_shnums:
2448-                # skip ones that are already in transit
2449-                continue
2450-            if shnum not in self.remaining_sharemap:
2451-                # no servers for that shnum. note that DictOfSets removes
2452-                # empty sets from the dict for us.
2453-                continue
2454-            peerid = list(self.remaining_sharemap[shnum])[0]
2455-            # get_data will remove that peerid from the sharemap, and add the
2456-            # query to self._outstanding_queries
2457-            self._status.set_status("Retrieving More Shares")
2458-            self.get_data(shnum, peerid)
2459-            needed -= 1
2460-            if not needed:
2461+
2462+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
2463+        """
2464+        I take the results of fetching and validating the blocks from a
2465+        callback chain in another method. If the results are such that
2466+        they tell me that validation and fetching succeeded without
2467+        incident, I will proceed with decoding and decryption.
2468+        Otherwise, I will do nothing.
2469+        """
2470+        self.log("trying to decode and decrypt segment %d" % segnum)
2471+        failures = False
2472+        for block_and_salt in blocks_and_salts:
2473+            if not block_and_salt[0] or block_and_salt[1] == None:
2474+                self.log("some validation operations failed; not proceeding")
2475+                failures = True
2476                 break
2477hunk ./src/allmydata/mutable/retrieve.py 560
2478+        if not failures:
2479+            self.log("everything looks ok, building segment %d" % segnum)
2480+            d = self._decode_blocks(blocks_and_salts, segnum)
2481+            d.addCallback(self._decrypt_segment)
2482+            d.addErrback(self._validation_or_decoding_failed,
2483+                         self._active_readers)
2484+            d.addCallback(self._set_segment)
2485+            return d
2486+        else:
2487+            return defer.succeed(None)
2488+
2489+
2490+    def _set_segment(self, segment):
2491+        """
2492+        Given a plaintext segment, I register that segment with the
2493+        target that is handling the file download.
2494+        """
2495+        self.log("got plaintext for segment %d" % self._current_segment)
2496+        self._plaintext += segment
2497+        self._current_segment += 1
2498 
2499hunk ./src/allmydata/mutable/retrieve.py 581
2500-        # at this point, we have as many outstanding queries as we can. If
2501-        # needed!=0 then we might not have enough to recover the file.
2502-        if needed:
2503-            format = ("ran out of peers: "
2504-                      "have %(have)d shares (k=%(k)d), "
2505-                      "%(outstanding)d queries in flight, "
2506-                      "need %(need)d more, "
2507-                      "found %(bad)d bad shares")
2508-            args = {"have": len(self.shares),
2509-                    "k": k,
2510-                    "outstanding": len(self._outstanding_queries),
2511-                    "need": needed,
2512-                    "bad": len(self._bad_shares),
2513-                    }
2514-            self.log(format=format,
2515-                     level=log.WEIRD, umid="ezTfjw", **args)
2516-            err = NotEnoughSharesError("%s, last failure: %s" %
2517-                                      (format % args, self._last_failure))
2518-            if self._bad_shares:
2519-                self.log("We found some bad shares this pass. You should "
2520-                         "update the servermap and try again to check "
2521-                         "more peers",
2522-                         level=log.WEIRD, umid="EFkOlA")
2523-                err.servermap = self.servermap
2524-            raise err
2525 
2526hunk ./src/allmydata/mutable/retrieve.py 582
2527+    def _validation_or_decoding_failed(self, f, readers):
2528+        """
2529+        I am called when a block or a salt fails to correctly validate, or when
2530+        the decryption or decoding operation fails for some reason.  I react to
2531+        this failure by notifying the remote server of corruption, and then
2532+        removing the remote peer from further activity.
2533+        """
2534+        assert isinstance(readers, list)
2535+        bad_shnums = [reader.shnum for reader in readers]
2536+
2537+        self.log("validation or decoding failed on share(s) %s, peer(s) %s "
2538+                 ", segment %d: %s" % \
2539+                 (bad_shnums, readers, self._current_segment, str(f)))
2540+        for reader in readers:
2541+            self._mark_bad_share(reader, f)
2542         return
2543 
2544hunk ./src/allmydata/mutable/retrieve.py 599
2545-    def _decode(self):
2546-        started = time.time()
2547-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2548-         offsets_tuple) = self.verinfo
2549 
2550hunk ./src/allmydata/mutable/retrieve.py 600
2551-        # shares_dict is a dict mapping shnum to share data, but the codec
2552-        # wants two lists.
2553-        shareids = []; shares = []
2554-        for shareid, share in self.shares.items():
2555+    def _validate_block(self, results, segnum, reader):
2556+        """
2557+        I validate a block from one share on a remote server.
2558+        """
2559+        # Grab the part of the block hash tree that is necessary to
2560+        # validate this block, then generate the block hash root.
2561+        self.log("validating share %d for segment %d" % (reader.shnum,
2562+                                                             segnum))
2563+        # Did we fail to fetch either of the things that we were
2564+        # supposed to? Fail if so.
2565+        if not results[0][0] and results[1][0]:
2566+            # handled by the errback handler.
2567+
2568+            # These all get batched into one query, so the resulting
2569+            # failure should be the same for all of them, so we can just
2570+            # use the first one.
2571+            assert isinstance(results[0][1], failure.Failure)
2572+
2573+            f = results[0][1]
2574+            raise CorruptShareError(reader.peerid,
2575+                                    reader.shnum,
2576+                                    "Connection error: %s" % str(f))
2577+
2578+        block_and_salt, block_and_sharehashes = results
2579+        block, salt = block_and_salt[1]
2580+        blockhashes, sharehashes = block_and_sharehashes[1]
2581+
2582+        blockhashes = dict(enumerate(blockhashes[1]))
2583+        self.log("the reader gave me the following blockhashes: %s" % \
2584+                 blockhashes.keys())
2585+        self.log("the reader gave me the following sharehashes: %s" % \
2586+                 sharehashes[1].keys())
2587+        bht = self._block_hash_trees[reader.shnum]
2588+
2589+        if bht.needed_hashes(segnum, include_leaf=True):
2590+            try:
2591+                bht.set_hashes(blockhashes)
2592+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2593+                    IndexError), e:
2594+                raise CorruptShareError(reader.peerid,
2595+                                        reader.shnum,
2596+                                        "block hash tree failure: %s" % e)
2597+
2598+        if self._version == MDMF_VERSION:
2599+            blockhash = hashutil.block_hash(salt + block)
2600+        else:
2601+            blockhash = hashutil.block_hash(block)
2602+        # If this works without an error, then validation is
2603+        # successful.
2604+        try:
2605+           bht.set_hashes(leaves={segnum: blockhash})
2606+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2607+                IndexError), e:
2608+            raise CorruptShareError(reader.peerid,
2609+                                    reader.shnum,
2610+                                    "block hash tree failure: %s" % e)
2611+
2612+        # Reaching this point means that we know that this segment
2613+        # is correct. Now we need to check to see whether the share
2614+        # hash chain is also correct.
2615+        # SDMF wrote share hash chains that didn't contain the
2616+        # leaves, which would be produced from the block hash tree.
2617+        # So we need to validate the block hash tree first. If
2618+        # successful, then bht[0] will contain the root for the
2619+        # shnum, which will be a leaf in the share hash tree, which
2620+        # will allow us to validate the rest of the tree.
2621+        if self.share_hash_tree.needed_hashes(reader.shnum,
2622+                                               include_leaf=True):
2623+            try:
2624+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
2625+                                            leaves={reader.shnum: bht[0]})
2626+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
2627+                    IndexError), e:
2628+                raise CorruptShareError(reader.peerid,
2629+                                        reader.shnum,
2630+                                        "corrupt hashes: %s" % e)
2631+
2632+        # TODO: Validate the salt, too.
2633+        self.log('share %d is valid for segment %d' % (reader.shnum,
2634+                                                       segnum))
2635+        return {reader.shnum: (block, salt)}
2636+
2637+
2638+    def _get_needed_hashes(self, reader, segnum):
2639+        """
2640+        I get the hashes needed to validate segnum from the reader, then return
2641+        to my caller when this is done.
2642+        """
2643+        bht = self._block_hash_trees[reader.shnum]
2644+        needed = bht.needed_hashes(segnum, include_leaf=True)
2645+        # The root of the block hash tree is also a leaf in the share
2646+        # hash tree. So we don't need to fetch it from the remote
2647+        # server. In the case of files with one segment, this means that
2648+        # we won't fetch any block hash tree from the remote server,
2649+        # since the hash of each share of the file is the entire block
2650+        # hash tree, and is a leaf in the share hash tree. This is fine,
2651+        # since any share corruption will be detected in the share hash
2652+        # tree.
2653+        #needed.discard(0)
2654+        self.log("getting blockhashes for segment %d, share %d: %s" % \
2655+                 (segnum, reader.shnum, str(needed)))
2656+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
2657+        if self.share_hash_tree.needed_hashes(reader.shnum):
2658+            need = self.share_hash_tree.needed_hashes(reader.shnum)
2659+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
2660+                                                                 str(need)))
2661+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
2662+        else:
2663+            d2 = defer.succeed({}) # the logic in the next method
2664+                                   # expects a dict
2665+        dl = defer.DeferredList([d1, d2], consumeErrors=True)
2666+        return dl
2667+
2668+
2669+    def _decode_blocks(self, blocks_and_salts, segnum):
2670+        """
2671+        I take a list of k blocks and salts, and decode that into a
2672+        single encrypted segment.
2673+        """
2674+        d = {}
2675+        # We want to merge our dictionaries to the form
2676+        # {shnum: blocks_and_salts}
2677+        #
2678+        # The dictionaries come from validate block that way, so we just
2679+        # need to merge them.
2680+        for block_and_salt in blocks_and_salts:
2681+            d.update(block_and_salt[1])
2682+
2683+        # All of these blocks should have the same salt; in SDMF, it is
2684+        # the file-wide IV, while in MDMF it is the per-segment salt. In
2685+        # either case, we just need to get one of them and use it.
2686+        #
2687+        # d.items()[0] is like (shnum, (block, salt))
2688+        # d.items()[0][1] is like (block, salt)
2689+        # d.items()[0][1][1] is the salt.
2690+        salt = d.items()[0][1][1]
2691+        # Next, extract just the blocks from the dict. We'll use the
2692+        # salt in the next step.
2693+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
2694+        d2 = dict(share_and_shareids)
2695+        shareids = []
2696+        shares = []
2697+        for shareid, share in d2.items():
2698             shareids.append(shareid)
2699             shares.append(share)
2700 
2701hunk ./src/allmydata/mutable/retrieve.py 746
2702-        assert len(shareids) >= k, len(shareids)
2703+        assert len(shareids) >= self._required_shares, len(shareids)
2704         # zfec really doesn't want extra shares
2705hunk ./src/allmydata/mutable/retrieve.py 748
2706-        shareids = shareids[:k]
2707-        shares = shares[:k]
2708-
2709-        fec = codec.CRSDecoder()
2710-        fec.set_params(segsize, k, N)
2711-
2712-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
2713-        self.log("about to decode, shareids=%s" % (shareids,))
2714-        d = defer.maybeDeferred(fec.decode, shares, shareids)
2715-        def _done(buffers):
2716-            self._status.timings["decode"] = time.time() - started
2717-            self.log(" decode done, %d buffers" % len(buffers))
2718+        shareids = shareids[:self._required_shares]
2719+        shares = shares[:self._required_shares]
2720+        self.log("decoding segment %d" % segnum)
2721+        if segnum == self._num_segments - 1:
2722+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
2723+        else:
2724+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
2725+        def _process(buffers):
2726             segment = "".join(buffers)
2727hunk ./src/allmydata/mutable/retrieve.py 757
2728+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
2729+                     segnum=segnum,
2730+                     numsegs=self._num_segments,
2731+                     level=log.NOISY)
2732             self.log(" joined length %d, datalength %d" %
2733hunk ./src/allmydata/mutable/retrieve.py 762
2734-                     (len(segment), datalength))
2735-            segment = segment[:datalength]
2736+                     (len(segment), self._data_length))
2737+            if segnum == self._num_segments - 1:
2738+                size_to_use = self._tail_data_size
2739+            else:
2740+                size_to_use = self._segment_size
2741+            segment = segment[:size_to_use]
2742             self.log(" segment len=%d" % len(segment))
2743hunk ./src/allmydata/mutable/retrieve.py 769
2744-            return segment
2745-        def _err(f):
2746-            self.log(" decode failed: %s" % f)
2747-            return f
2748-        d.addCallback(_done)
2749-        d.addErrback(_err)
2750+            return segment, salt
2751+        d.addCallback(_process)
2752         return d
2753 
2754hunk ./src/allmydata/mutable/retrieve.py 773
2755-    def _decrypt(self, crypttext, IV, readkey):
2756+
2757+    def _decrypt_segment(self, segment_and_salt):
2758+        """
2759+        I take a single segment and its salt, and decrypt it. I return
2760+        the plaintext of the segment that is in my argument.
2761+        """
2762+        segment, salt = segment_and_salt
2763         self._status.set_status("decrypting")
2764hunk ./src/allmydata/mutable/retrieve.py 781
2765+        self.log("decrypting segment %d" % self._current_segment)
2766         started = time.time()
2767hunk ./src/allmydata/mutable/retrieve.py 783
2768-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
2769+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
2770         decryptor = AES(key)
2771hunk ./src/allmydata/mutable/retrieve.py 785
2772-        plaintext = decryptor.process(crypttext)
2773+        plaintext = decryptor.process(segment)
2774         self._status.timings["decrypt"] = time.time() - started
2775         return plaintext
2776 
2777hunk ./src/allmydata/mutable/retrieve.py 789
2778-    def _done(self, res):
2779-        if not self._running:
2780+
2781+    def notify_server_corruption(self, peerid, shnum, reason):
2782+        ss = self.servermap.connections[peerid]
2783+        ss.callRemoteOnly("advise_corrupt_share",
2784+                          "mutable", self._storage_index, shnum, reason)
2785+
2786+
2787+    def _try_to_validate_privkey(self, enc_privkey, reader):
2788+
2789+        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
2790+        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
2791+        if alleged_writekey != self._node.get_writekey():
2792+            self.log("invalid privkey from %s shnum %d" %
2793+                     (reader, reader.shnum),
2794+                     level=log.WEIRD, umid="YIw4tA")
2795             return
2796hunk ./src/allmydata/mutable/retrieve.py 805
2797-        self._running = False
2798-        self._status.set_active(False)
2799-        self._status.timings["total"] = time.time() - self._started
2800-        # res is either the new contents, or a Failure
2801-        if isinstance(res, failure.Failure):
2802-            self.log("Retrieve done, with failure", failure=res,
2803-                     level=log.UNUSUAL)
2804-            self._status.set_status("Failed")
2805-        else:
2806-            self.log("Retrieve done, success!")
2807-            self._status.set_status("Finished")
2808-            self._status.set_progress(1.0)
2809-            # remember the encoding parameters, use them again next time
2810-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
2811-             offsets_tuple) = self.verinfo
2812-            self._node._populate_required_shares(k)
2813-            self._node._populate_total_shares(N)
2814-        eventually(self._done_deferred.callback, res)
2815 
2816hunk ./src/allmydata/mutable/retrieve.py 806
2817+        # it's good
2818+        self.log("got valid privkey from shnum %d on reader %s" %
2819+                 (reader.shnum, reader))
2820+        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
2821+        self._node._populate_encprivkey(enc_privkey)
2822+        self._node._populate_privkey(privkey)
2823+        self._need_privkey = False
2824+
2825+
2826+    def _check_for_done(self, res):
2827+        """
2828+        I check to see if this Retrieve object has successfully finished
2829+        its work.
2830+
2831+        I can exit in the following ways:
2832+            - If there are no more segments to download, then I exit by
2833+              causing self._done_deferred to fire with the plaintext
2834+              content requested by the caller.
2835+            - If there are still segments to be downloaded, and there
2836+              are enough active readers (readers which have not broken
2837+              and have not given us corrupt data) to continue
2838+              downloading, I send control back to
2839+              _download_current_segment.
2840+            - If there are still segments to be downloaded but there are
2841+              not enough active peers to download them, I ask
2842+              _add_active_peers to add more peers. If it is successful,
2843+              it will call _download_current_segment. If there are not
2844+              enough peers to retrieve the file, then that will cause
2845+              _done_deferred to errback.
2846+        """
2847+        self.log("checking for doneness")
2848+        if self._current_segment == self._num_segments:
2849+            # No more segments to download, we're done.
2850+            self.log("got plaintext, done")
2851+            return self._done()
2852+
2853+        if len(self._active_readers) >= self._required_shares:
2854+            # More segments to download, but we have enough good peers
2855+            # in self._active_readers that we can do that without issue,
2856+            # so go nab the next segment.
2857+            self.log("not done yet: on segment %d of %d" % \
2858+                     (self._current_segment + 1, self._num_segments))
2859+            return self._download_current_segment()
2860+
2861+        self.log("not done yet: on segment %d of %d, need to add peers" % \
2862+                 (self._current_segment + 1, self._num_segments))
2863+        return self._add_active_peers()
2864+
2865+
2866+    def _done(self):
2867+        """
2868+        I am called by _check_for_done when the download process has
2869+        finished successfully. After making some useful logging
2870+        statements, I return the decrypted contents to the owner of this
2871+        Retrieve object through self._done_deferred.
2872+        """
2873+        eventually(self._done_deferred.callback, self._plaintext)
2874+
2875+
2876+    def _failed(self):
2877+        """
2878+        I am called by _add_active_peers when there are not enough
2879+        active peers left to complete the download. After making some
2880+        useful logging statements, I return an exception to that effect
2881+        to the caller of this Retrieve object through
2882+        self._done_deferred.
2883+        """
2884+        format = ("ran out of peers: "
2885+                  "have %(have)d of %(total)d segments "
2886+                  "found %(bad)d bad shares "
2887+                  "encoding %(k)d-of-%(n)d")
2888+        args = {"have": self._current_segment,
2889+                "total": self._num_segments,
2890+                "k": self._required_shares,
2891+                "n": self._total_shares,
2892+                "bad": len(self._bad_shares)}
2893+        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
2894+                                                        str(self._last_failure)))
2895+        f = failure.Failure(e)
2896+        eventually(self._done_deferred.callback, f)
2897hunk ./src/allmydata/test/test_mutable.py 12
2898 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
2899      ssk_pubkey_fingerprint_hash
2900 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
2901-     NotEnoughSharesError
2902+     NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
2903 from allmydata.monitor import Monitor
2904 from allmydata.test.common import ShouldFailMixin
2905 from allmydata.test.no_network import GridTestMixin
2906hunk ./src/allmydata/test/test_mutable.py 28
2907 from allmydata.mutable.retrieve import Retrieve
2908 from allmydata.mutable.publish import Publish
2909 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
2910-from allmydata.mutable.layout import unpack_header, unpack_share
2911+from allmydata.mutable.layout import unpack_header, unpack_share, \
2912+                                     MDMFSlotReadProxy
2913 from allmydata.mutable.repairer import MustForceRepairError
2914 
2915 import allmydata.test.common_util as testutil
2916hunk ./src/allmydata/test/test_mutable.py 104
2917         d = fireEventually()
2918         d.addCallback(lambda res: _call())
2919         return d
2920+
2921     def callRemoteOnly(self, methname, *args, **kwargs):
2922         d = self.callRemote(methname, *args, **kwargs)
2923         d.addBoth(lambda ignore: None)
2924hunk ./src/allmydata/test/test_mutable.py 163
2925 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
2926     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
2927     # list of shnums to corrupt.
2928+    ds = []
2929     for peerid in s._peers:
2930         shares = s._peers[peerid]
2931         for shnum in shares:
2932hunk ./src/allmydata/test/test_mutable.py 190
2933                 else:
2934                     offset1 = offset
2935                     offset2 = 0
2936-                if offset1 == "pubkey":
2937+                if offset1 == "pubkey" and IV:
2938                     real_offset = 107
2939hunk ./src/allmydata/test/test_mutable.py 192
2940+                elif offset1 == "share_data" and not IV:
2941+                    real_offset = 104
2942                 elif offset1 in o:
2943                     real_offset = o[offset1]
2944                 else:
2945hunk ./src/allmydata/test/test_mutable.py 327
2946         d.addCallback(_created)
2947         return d
2948 
2949+
2950+    def test_upload_and_download_mdmf(self):
2951+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
2952+        def _created(n):
2953+            d = defer.succeed(None)
2954+            d.addCallback(lambda ignored:
2955+                n.get_servermap(MODE_READ))
2956+            def _then(servermap):
2957+                dumped = servermap.dump(StringIO())
2958+                self.failUnlessIn("3-of-10", dumped.getvalue())
2959+            d.addCallback(_then)
2960+            # Now overwrite the contents with some new contents. We want
2961+            # to make them big enough to force the file to be uploaded
2962+            # in more than one segment.
2963+            big_contents = "contents1" * 100000 # about 900 KiB
2964+            d.addCallback(lambda ignored:
2965+                n.overwrite(big_contents))
2966+            d.addCallback(lambda ignored:
2967+                n.download_best_version())
2968+            d.addCallback(lambda data:
2969+                self.failUnlessEqual(data, big_contents))
2970+            # Overwrite the contents again with some new contents. As
2971+            # before, they need to be big enough to force multiple
2972+            # segments, so that we make the downloader deal with
2973+            # multiple segments.
2974+            bigger_contents = "contents2" * 1000000 # about 9MiB
2975+            d.addCallback(lambda ignored:
2976+                n.overwrite(bigger_contents))
2977+            d.addCallback(lambda ignored:
2978+                n.download_best_version())
2979+            d.addCallback(lambda data:
2980+                self.failUnlessEqual(data, bigger_contents))
2981+            return d
2982+        d.addCallback(_created)
2983+        return d
2984+
2985+
2986     def test_create_with_initial_contents(self):
2987         d = self.nodemaker.create_mutable_file("contents 1")
2988         def _created(n):
2989hunk ./src/allmydata/test/test_mutable.py 1147
2990 
2991 
2992     def _test_corrupt_all(self, offset, substring,
2993-                          should_succeed=False, corrupt_early=True,
2994-                          failure_checker=None):
2995+                          should_succeed=False,
2996+                          corrupt_early=True,
2997+                          failure_checker=None,
2998+                          fetch_privkey=False):
2999         d = defer.succeed(None)
3000         if corrupt_early:
3001             d.addCallback(corrupt, self._storage, offset)
3002hunk ./src/allmydata/test/test_mutable.py 1167
3003                     self.failUnlessIn(substring, "".join(allproblems))
3004                 return servermap
3005             if should_succeed:
3006-                d1 = self._fn.download_version(servermap, ver)
3007+                d1 = self._fn.download_version(servermap, ver,
3008+                                               fetch_privkey)
3009                 d1.addCallback(lambda new_contents:
3010                                self.failUnlessEqual(new_contents, self.CONTENTS))
3011             else:
3012hunk ./src/allmydata/test/test_mutable.py 1175
3013                 d1 = self.shouldFail(NotEnoughSharesError,
3014                                      "_corrupt_all(offset=%s)" % (offset,),
3015                                      substring,
3016-                                     self._fn.download_version, servermap, ver)
3017+                                     self._fn.download_version, servermap,
3018+                                                                ver,
3019+                                                                fetch_privkey)
3020             if failure_checker:
3021                 d1.addCallback(failure_checker)
3022             d1.addCallback(lambda res: servermap)
3023hunk ./src/allmydata/test/test_mutable.py 1186
3024         return d
3025 
3026     def test_corrupt_all_verbyte(self):
3027-        # when the version byte is not 0, we hit an UnknownVersionError error
3028-        # in unpack_share().
3029+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
3030+        # error in unpack_share().
3031         d = self._test_corrupt_all(0, "UnknownVersionError")
3032         def _check_servermap(servermap):
3033             # and the dump should mention the problems
3034hunk ./src/allmydata/test/test_mutable.py 1193
3035             s = StringIO()
3036             dump = servermap.dump(s).getvalue()
3037-            self.failUnless("10 PROBLEMS" in dump, dump)
3038+            self.failUnless("30 PROBLEMS" in dump, dump)
3039         d.addCallback(_check_servermap)
3040         return d
3041 
3042hunk ./src/allmydata/test/test_mutable.py 1263
3043         return self._test_corrupt_all("enc_privkey", None, should_succeed=True)
3044 
3045 
3046+    def test_corrupt_all_encprivkey_late(self):
3047+        # this should work for the same reason as above, but we corrupt
3048+        # after the servermap update to exercise the error handling
3049+        # code.
3050+        # We need to remove the privkey from the node, or the retrieve
3051+        # process won't know to update it.
3052+        self._fn._privkey = None
3053+        return self._test_corrupt_all("enc_privkey",
3054+                                      None, # this shouldn't fail
3055+                                      should_succeed=True,
3056+                                      corrupt_early=False,
3057+                                      fetch_privkey=True)
3058+
3059+
3060     def test_corrupt_all_seqnum_late(self):
3061         # corrupting the seqnum between mapupdate and retrieve should result
3062         # in NotEnoughSharesError, since each share will look invalid
3063hunk ./src/allmydata/test/test_mutable.py 1283
3064         def _check(res):
3065             f = res[0]
3066             self.failUnless(f.check(NotEnoughSharesError))
3067-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
3068+            self.failUnless("uncoordinated write" in str(f))
3069         return self._test_corrupt_all(1, "ran out of peers",
3070                                       corrupt_early=False,
3071                                       failure_checker=_check)
3072hunk ./src/allmydata/test/test_mutable.py 1333
3073                       self.failUnlessEqual(new_contents, self.CONTENTS))
3074         return d
3075 
3076-    def test_corrupt_some(self):
3077-        # corrupt the data of first five shares (so the servermap thinks
3078-        # they're good but retrieve marks them as bad), so that the
3079-        # MODE_READ set of 6 will be insufficient, forcing node.download to
3080-        # retry with more servers.
3081-        corrupt(None, self._storage, "share_data", range(5))
3082-        d = self.make_servermap()
3083+
3084+    def _test_corrupt_some(self, offset, mdmf=False):
3085+        if mdmf:
3086+            d = self.publish_mdmf()
3087+        else:
3088+            d = defer.succeed(None)
3089+        d.addCallback(lambda ignored:
3090+            corrupt(None, self._storage, offset, range(5)))
3091+        d.addCallback(lambda ignored:
3092+            self.make_servermap())
3093         def _do_retrieve(servermap):
3094             ver = servermap.best_recoverable_version()
3095             self.failUnless(ver)
3096hunk ./src/allmydata/test/test_mutable.py 1349
3097             return self._fn.download_best_version()
3098         d.addCallback(_do_retrieve)
3099         d.addCallback(lambda new_contents:
3100-                      self.failUnlessEqual(new_contents, self.CONTENTS))
3101+            self.failUnlessEqual(new_contents, self.CONTENTS))
3102         return d
3103 
3104hunk ./src/allmydata/test/test_mutable.py 1352
3105+
3106+    def test_corrupt_some(self):
3107+        # corrupt the data of first five shares (so the servermap thinks
3108+        # they're good but retrieve marks them as bad), so that the
3109+        # MODE_READ set of 6 will be insufficient, forcing node.download to
3110+        # retry with more servers.
3111+        return self._test_corrupt_some("share_data")
3112+
3113+
3114     def test_download_fails(self):
3115         d = corrupt(None, self._storage, "signature")
3116         d.addCallback(lambda ignored:
3117hunk ./src/allmydata/test/test_mutable.py 1366
3118             self.shouldFail(UnrecoverableFileError, "test_download_anyway",
3119                             "no recoverable versions",
3120-                            self._fn.download_best_version)
3121+                            self._fn.download_best_version))
3122         return d
3123 
3124 
3125hunk ./src/allmydata/test/test_mutable.py 1370
3126+
3127+    def test_corrupt_mdmf_block_hash_tree(self):
3128+        d = self.publish_mdmf()
3129+        d.addCallback(lambda ignored:
3130+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3131+                                   "block hash tree failure",
3132+                                   corrupt_early=False,
3133+                                   should_succeed=False))
3134+        return d
3135+
3136+
3137+    def test_corrupt_mdmf_block_hash_tree_late(self):
3138+        d = self.publish_mdmf()
3139+        d.addCallback(lambda ignored:
3140+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
3141+                                   "block hash tree failure",
3142+                                   corrupt_early=True,
3143+                                   should_succeed=False))
3144+        return d
3145+
3146+
3147+    def test_corrupt_mdmf_share_data(self):
3148+        d = self.publish_mdmf()
3149+        d.addCallback(lambda ignored:
3150+            # TODO: Find out what the block size is and corrupt a
3151+            # specific block, rather than just guessing.
3152+            self._test_corrupt_all(("share_data", 12 * 40),
3153+                                    "block hash tree failure",
3154+                                    corrupt_early=True,
3155+                                    should_succeed=False))
3156+        return d
3157+
3158+
3159+    def test_corrupt_some_mdmf(self):
3160+        return self._test_corrupt_some(("share_data", 12 * 40),
3161+                                       mdmf=True)
3162+
3163+
3164 class CheckerMixin:
3165     def check_good(self, r, where):
3166         self.failUnless(r.is_healthy(), where)
3167hunk ./src/allmydata/test/test_mutable.py 2116
3168             d.addCallback(lambda res:
3169                           self.shouldFail(NotEnoughSharesError,
3170                                           "test_retrieve_surprise",
3171-                                          "ran out of peers: have 0 shares (k=3)",
3172+                                          "ran out of peers: have 0 of 1",
3173                                           n.download_version,
3174                                           self.old_map,
3175                                           self.old_map.best_recoverable_version(),
3176hunk ./src/allmydata/test/test_mutable.py 2125
3177         d.addCallback(_created)
3178         return d
3179 
3180+
3181     def test_unexpected_shares(self):
3182         # upload the file, take a servermap, shut down one of the servers,
3183         # upload it again (causing shares to appear on a new server), then
3184hunk ./src/allmydata/test/test_mutable.py 2329
3185         self.basedir = "mutable/Problems/test_privkey_query_missing"
3186         self.set_up_grid(num_servers=20)
3187         nm = self.g.clients[0].nodemaker
3188-        LARGE = "These are Larger contents" * 2000 # about 50KB
3189+        LARGE = "These are Larger contents" * 2000 # about 50KiB
3190         nm._node_cache = DevNullDictionary() # disable the nodecache
3191 
3192         d = nm.create_mutable_file(LARGE)
3193hunk ./src/allmydata/test/test_mutable.py 2342
3194         d.addCallback(_created)
3195         d.addCallback(lambda res: self.n2.get_servermap(MODE_WRITE))
3196         return d
3197+
3198+
3199+    def test_block_and_hash_query_error(self):
3200+        # This tests for what happens when a query to a remote server
3201+        # fails in either the hash validation step or the block getting
3202+        # step (because of batching, this is the same actual query).
3203+        # We need to have the storage server persist up until the point
3204+        # that its prefix is validated, then suddenly die. This
3205+        # exercises some exception handling code in Retrieve.
3206+        self.basedir = "mutable/Problems/test_block_and_hash_query_error"
3207+        self.set_up_grid(num_servers=20)
3208+        nm = self.g.clients[0].nodemaker
3209+        CONTENTS = "contents" * 2000
3210+        d = nm.create_mutable_file(CONTENTS)
3211+        def _created(node):
3212+            self._node = node
3213+        d.addCallback(_created)
3214+        d.addCallback(lambda ignored:
3215+            self._node.get_servermap(MODE_READ))
3216+        def _then(servermap):
3217+            # we have our servermap. Now we set up the servers like the
3218+            # tests above -- the first one that gets a read call should
3219+            # start throwing errors, but only after returning its prefix
3220+            # for validation. Since we'll download without fetching the
3221+            # private key, the next query to the remote server will be
3222+            # for either a block and salt or for hashes, either of which
3223+            # will exercise the error handling code.
3224+            killer = FirstServerGetsKilled()
3225+            for (serverid, ss) in nm.storage_broker.get_all_servers():
3226+                ss.post_call_notifier = killer.notify
3227+            ver = servermap.best_recoverable_version()
3228+            assert ver
3229+            return self._node.download_version(servermap, ver)
3230+        d.addCallback(_then)
3231+        d.addCallback(lambda data:
3232+            self.failUnlessEqual(data, CONTENTS))
3233+        return d
3234}
3235[mutable/checker.py: check MDMF files
3236Kevan Carstensen <kevan@isnotajoke.com>**20100628225048
3237 Ignore-this: fb697b36285d60552df6ca5ac6a37629
3238 
3239 This patch adapts the mutable file checker and verifier to check and
3240 verify MDMF files. It does this by using the new segmented downloader,
3241 which is trained to perform verification operations on request. This
3242 removes some code duplication.
3243] {
3244hunk ./src/allmydata/mutable/checker.py 12
3245 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
3246 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
3247 from allmydata.mutable.layout import unpack_share, SIGNED_PREFIX_LENGTH
3248+from allmydata.mutable.retrieve import Retrieve # for verifying
3249 
3250 class MutableChecker:
3251 
3252hunk ./src/allmydata/mutable/checker.py 29
3253 
3254     def check(self, verify=False, add_lease=False):
3255         servermap = ServerMap()
3256+        # Updating the servermap in MODE_CHECK will stand a good chance
3257+        # of finding all of the shares, and getting a good idea of
3258+        # recoverability, etc, without verifying.
3259         u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
3260                              servermap, MODE_CHECK, add_lease=add_lease)
3261         if self._history:
3262hunk ./src/allmydata/mutable/checker.py 55
3263         if num_recoverable:
3264             self.best_version = servermap.best_recoverable_version()
3265 
3266+        # The file is unhealthy and needs to be repaired if:
3267+        # - There are unrecoverable versions.
3268         if servermap.unrecoverable_versions():
3269             self.need_repair = True
3270hunk ./src/allmydata/mutable/checker.py 59
3271+        # - There isn't a recoverable version.
3272         if num_recoverable != 1:
3273             self.need_repair = True
3274hunk ./src/allmydata/mutable/checker.py 62
3275+        # - The best recoverable version is missing some shares.
3276         if self.best_version:
3277             available_shares = servermap.shares_available()
3278             (num_distinct_shares, k, N) = available_shares[self.best_version]
3279hunk ./src/allmydata/mutable/checker.py 73
3280 
3281     def _verify_all_shares(self, servermap):
3282         # read every byte of each share
3283+        #
3284+        # This logic is going to be very nearly the same as the
3285+        # downloader. I bet we could pass the downloader a flag that
3286+        # makes it do this, and piggyback onto that instead of
3287+        # duplicating a bunch of code.
3288+        #
3289+        # Like:
3290+        #  r = Retrieve(blah, blah, blah, verify=True)
3291+        #  d = r.download()
3292+        #  (wait, wait, wait, d.callback)
3293+        # 
3294+        #  Then, when it has finished, we can check the servermap (which
3295+        #  we provided to Retrieve) to figure out which shares are bad,
3296+        #  since the Retrieve process will have updated the servermap as
3297+        #  it went along.
3298+        #
3299+        #  By passing the verify=True flag to the constructor, we are
3300+        #  telling the downloader a few things.
3301+        #
3302+        #  1. It needs to download all N shares, not just K shares.
3303+        #  2. It doesn't need to decrypt or decode the shares, only
3304+        #     verify them.
3305         if not self.best_version:
3306             return
3307hunk ./src/allmydata/mutable/checker.py 97
3308-        versionmap = servermap.make_versionmap()
3309-        shares = versionmap[self.best_version]
3310-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3311-         offsets_tuple) = self.best_version
3312-        offsets = dict(offsets_tuple)
3313-        readv = [ (0, offsets["EOF"]) ]
3314-        dl = []
3315-        for (shnum, peerid, timestamp) in shares:
3316-            ss = servermap.connections[peerid]
3317-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
3318-            d.addCallback(self._got_answer, peerid, servermap)
3319-            dl.append(d)
3320-        return defer.DeferredList(dl, fireOnOneErrback=True, consumeErrors=True)
3321 
3322hunk ./src/allmydata/mutable/checker.py 98
3323-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
3324-        # isolate the callRemote to a separate method, so tests can subclass
3325-        # Publish and override it
3326-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
3327+        r = Retrieve(self._node, servermap, self.best_version, verify=True)
3328+        d = r.download()
3329+        d.addCallback(self._process_bad_shares)
3330         return d
3331 
3332hunk ./src/allmydata/mutable/checker.py 103
3333-    def _got_answer(self, datavs, peerid, servermap):
3334-        for shnum,datav in datavs.items():
3335-            data = datav[0]
3336-            try:
3337-                self._got_results_one_share(shnum, peerid, data)
3338-            except CorruptShareError:
3339-                f = failure.Failure()
3340-                self.need_repair = True
3341-                self.bad_shares.append( (peerid, shnum, f) )
3342-                prefix = data[:SIGNED_PREFIX_LENGTH]
3343-                servermap.mark_bad_share(peerid, shnum, prefix)
3344-                ss = servermap.connections[peerid]
3345-                self.notify_server_corruption(ss, shnum, str(f.value))
3346-
3347-    def check_prefix(self, peerid, shnum, data):
3348-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
3349-         offsets_tuple) = self.best_version
3350-        got_prefix = data[:SIGNED_PREFIX_LENGTH]
3351-        if got_prefix != prefix:
3352-            raise CorruptShareError(peerid, shnum,
3353-                                    "prefix mismatch: share changed while we were reading it")
3354-
3355-    def _got_results_one_share(self, shnum, peerid, data):
3356-        self.check_prefix(peerid, shnum, data)
3357-
3358-        # the [seqnum:signature] pieces are validated by _compare_prefix,
3359-        # which checks their signature against the pubkey known to be
3360-        # associated with this file.
3361 
3362hunk ./src/allmydata/mutable/checker.py 104
3363-        (seqnum, root_hash, IV, k, N, segsize, datalen, pubkey, signature,
3364-         share_hash_chain, block_hash_tree, share_data,
3365-         enc_privkey) = unpack_share(data)
3366-
3367-        # validate [share_hash_chain,block_hash_tree,share_data]
3368-
3369-        leaves = [hashutil.block_hash(share_data)]
3370-        t = hashtree.HashTree(leaves)
3371-        if list(t) != block_hash_tree:
3372-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
3373-        share_hash_leaf = t[0]
3374-        t2 = hashtree.IncompleteHashTree(N)
3375-        # root_hash was checked by the signature
3376-        t2.set_hashes({0: root_hash})
3377-        try:
3378-            t2.set_hashes(hashes=share_hash_chain,
3379-                          leaves={shnum: share_hash_leaf})
3380-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
3381-                IndexError), e:
3382-            msg = "corrupt hashes: %s" % (e,)
3383-            raise CorruptShareError(peerid, shnum, msg)
3384-
3385-        # validate enc_privkey: only possible if we have a write-cap
3386-        if not self._node.is_readonly():
3387-            alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3388-            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3389-            if alleged_writekey != self._node.get_writekey():
3390-                raise CorruptShareError(peerid, shnum, "invalid privkey")
3391+    def _process_bad_shares(self, bad_shares):
3392+        if bad_shares:
3393+            self.need_repair = True
3394+        self.bad_shares = bad_shares
3395 
3396hunk ./src/allmydata/mutable/checker.py 109
3397-    def notify_server_corruption(self, ss, shnum, reason):
3398-        ss.callRemoteOnly("advise_corrupt_share",
3399-                          "mutable", self._storage_index, shnum, reason)
3400 
3401     def _count_shares(self, smap, version):
3402         available_shares = smap.shares_available()
3403hunk ./src/allmydata/test/test_mutable.py 193
3404                 if offset1 == "pubkey" and IV:
3405                     real_offset = 107
3406                 elif offset1 == "share_data" and not IV:
3407-                    real_offset = 104
3408+                    real_offset = 107
3409                 elif offset1 in o:
3410                     real_offset = o[offset1]
3411                 else:
3412hunk ./src/allmydata/test/test_mutable.py 395
3413             return d
3414         d.addCallback(_created)
3415         return d
3416+    test_create_mdmf_with_initial_contents.timeout = 20
3417 
3418 
3419     def test_create_with_initial_contents_function(self):
3420hunk ./src/allmydata/test/test_mutable.py 700
3421                                            k, N, segsize, datalen)
3422                 self.failUnless(p._pubkey.verify(sig_material, signature))
3423                 #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
3424-                self.failUnless(isinstance(share_hash_chain, dict))
3425-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3426+                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
3427                 for shnum,share_hash in share_hash_chain.items():
3428                     self.failUnless(isinstance(shnum, int))
3429                     self.failUnless(isinstance(share_hash, str))
3430hunk ./src/allmydata/test/test_mutable.py 820
3431                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
3432 
3433 
3434+
3435+
3436 class Servermap(unittest.TestCase, PublishMixin):
3437     def setUp(self):
3438         return self.publish_one()
3439hunk ./src/allmydata/test/test_mutable.py 951
3440         self._storage._peers = {} # delete all shares
3441         ms = self.make_servermap
3442         d = defer.succeed(None)
3443-
3444+#
3445         d.addCallback(lambda res: ms(mode=MODE_CHECK))
3446         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
3447 
3448hunk ./src/allmydata/test/test_mutable.py 1440
3449         d.addCallback(self.check_good, "test_check_good")
3450         return d
3451 
3452+    def test_check_mdmf_good(self):
3453+        d = self.publish_mdmf()
3454+        d.addCallback(lambda ignored:
3455+            self._fn.check(Monitor()))
3456+        d.addCallback(self.check_good, "test_check_mdmf_good")
3457+        return d
3458+
3459     def test_check_no_shares(self):
3460         for shares in self._storage._peers.values():
3461             shares.clear()
3462hunk ./src/allmydata/test/test_mutable.py 1454
3463         d.addCallback(self.check_bad, "test_check_no_shares")
3464         return d
3465 
3466+    def test_check_mdmf_no_shares(self):
3467+        d = self.publish_mdmf()
3468+        def _then(ignored):
3469+            for share in self._storage._peers.values():
3470+                share.clear()
3471+        d.addCallback(_then)
3472+        d.addCallback(lambda ignored:
3473+            self._fn.check(Monitor()))
3474+        d.addCallback(self.check_bad, "test_check_mdmf_no_shares")
3475+        return d
3476+
3477     def test_check_not_enough_shares(self):
3478         for shares in self._storage._peers.values():
3479             for shnum in shares.keys():
3480hunk ./src/allmydata/test/test_mutable.py 1474
3481         d.addCallback(self.check_bad, "test_check_not_enough_shares")
3482         return d
3483 
3484+    def test_check_mdmf_not_enough_shares(self):
3485+        d = self.publish_mdmf()
3486+        def _then(ignored):
3487+            for shares in self._storage._peers.values():
3488+                for shnum in shares.keys():
3489+                    if shnum > 0:
3490+                        del shares[shnum]
3491+        d.addCallback(_then)
3492+        d.addCallback(lambda ignored:
3493+            self._fn.check(Monitor()))
3494+        d.addCallback(self.check_bad, "test_check_mdmf_not_enougH_shares")
3495+        return d
3496+
3497+
3498     def test_check_all_bad_sig(self):
3499         d = corrupt(None, self._storage, 1) # bad sig
3500         d.addCallback(lambda ignored:
3501hunk ./src/allmydata/test/test_mutable.py 1495
3502         d.addCallback(self.check_bad, "test_check_all_bad_sig")
3503         return d
3504 
3505+    def test_check_mdmf_all_bad_sig(self):
3506+        d = self.publish_mdmf()
3507+        d.addCallback(lambda ignored:
3508+            corrupt(None, self._storage, 1))
3509+        d.addCallback(lambda ignored:
3510+            self._fn.check(Monitor()))
3511+        d.addCallback(self.check_bad, "test_check_mdmf_all_bad_sig")
3512+        return d
3513+
3514     def test_check_all_bad_blocks(self):
3515         d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
3516         # the Checker won't notice this.. it doesn't look at actual data
3517hunk ./src/allmydata/test/test_mutable.py 1512
3518         d.addCallback(self.check_good, "test_check_all_bad_blocks")
3519         return d
3520 
3521+
3522+    def test_check_mdmf_all_bad_blocks(self):
3523+        d = self.publish_mdmf()
3524+        d.addCallback(lambda ignored:
3525+            corrupt(None, self._storage, "share_data"))
3526+        d.addCallback(lambda ignored:
3527+            self._fn.check(Monitor()))
3528+        d.addCallback(self.check_good, "test_check_mdmf_all_bad_blocks")
3529+        return d
3530+
3531     def test_verify_good(self):
3532         d = self._fn.check(Monitor(), verify=True)
3533         d.addCallback(self.check_good, "test_verify_good")
3534hunk ./src/allmydata/test/test_mutable.py 1582
3535                       "test_verify_one_bad_encprivkey_uncheckable")
3536         return d
3537 
3538+
3539+    def test_verify_mdmf_good(self):
3540+        d = self.publish_mdmf()
3541+        d.addCallback(lambda ignored:
3542+            self._fn.check(Monitor(), verify=True))
3543+        d.addCallback(self.check_good, "test_verify_mdmf_good")
3544+        return d
3545+
3546+
3547+    def test_verify_mdmf_one_bad_block(self):
3548+        d = self.publish_mdmf()
3549+        d.addCallback(lambda ignored:
3550+            corrupt(None, self._storage, "share_data", [1]))
3551+        d.addCallback(lambda ignored:
3552+            self._fn.check(Monitor(), verify=True))
3553+        # We should find one bad block here
3554+        d.addCallback(self.check_bad, "test_verify_mdmf_one_bad_block")
3555+        d.addCallback(self.check_expected_failure,
3556+                      CorruptShareError, "block hash tree failure",
3557+                      "test_verify_mdmf_one_bad_block")
3558+        return d
3559+
3560+
3561+    def test_verify_mdmf_bad_encprivkey(self):
3562+        d = self.publish_mdmf()
3563+        d.addCallback(lambda ignored:
3564+            corrupt(None, self._storage, "enc_privkey", [1]))
3565+        d.addCallback(lambda ignored:
3566+            self._fn.check(Monitor(), verify=True))
3567+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
3568+        d.addCallback(self.check_expected_failure,
3569+                      CorruptShareError, "privkey",
3570+                      "test_verify_mdmf_bad_encprivkey")
3571+        return d
3572+
3573+
3574+    def test_verify_mdmf_bad_sig(self):
3575+        d = self.publish_mdmf()
3576+        d.addCallback(lambda ignored:
3577+            corrupt(None, self._storage, 1, [1]))
3578+        d.addCallback(lambda ignored:
3579+            self._fn.check(Monitor(), verify=True))
3580+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_sig")
3581+        return d
3582+
3583+
3584+    def test_verify_mdmf_bad_encprivkey_uncheckable(self):
3585+        d = self.publish_mdmf()
3586+        d.addCallback(lambda ignored:
3587+            corrupt(None, self._storage, "enc_privkey", [1]))
3588+        d.addCallback(lambda ignored:
3589+            self._fn.get_readonly())
3590+        d.addCallback(lambda fn:
3591+            fn.check(Monitor(), verify=True))
3592+        d.addCallback(self.check_good,
3593+                      "test_verify_mdmf_bad_encprivkey_uncheckable")
3594+        return d
3595+
3596+
3597 class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
3598 
3599     def get_shares(self, s):
3600hunk ./src/allmydata/test/test_mutable.py 1706
3601         current_shares = self.old_shares[-1]
3602         self.failUnlessEqual(old_shares, current_shares)
3603 
3604+
3605     def test_unrepairable_0shares(self):
3606         d = self.publish_one()
3607         def _delete_all_shares(ign):
3608hunk ./src/allmydata/test/test_mutable.py 1721
3609         d.addCallback(_check)
3610         return d
3611 
3612+    def test_mdmf_unrepairable_0shares(self):
3613+        d = self.publish_mdmf()
3614+        def _delete_all_shares(ign):
3615+            shares = self._storage._peers
3616+            for peerid in shares:
3617+                shares[peerid] = {}
3618+        d.addCallback(_delete_all_shares)
3619+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3620+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3621+        d.addCallback(lambda crr: self.failIf(crr.get_successful()))
3622+        return d
3623+
3624+
3625     def test_unrepairable_1share(self):
3626         d = self.publish_one()
3627         def _delete_all_shares(ign):
3628hunk ./src/allmydata/test/test_mutable.py 1750
3629         d.addCallback(_check)
3630         return d
3631 
3632+    def test_mdmf_unrepairable_1share(self):
3633+        d = self.publish_mdmf()
3634+        def _delete_all_shares(ign):
3635+            shares = self._storage._peers
3636+            for peerid in shares:
3637+                for shnum in list(shares[peerid]):
3638+                    if shnum > 0:
3639+                        del shares[peerid][shnum]
3640+        d.addCallback(_delete_all_shares)
3641+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3642+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3643+        def _check(crr):
3644+            self.failUnlessEqual(crr.get_successful(), False)
3645+        d.addCallback(_check)
3646+        return d
3647+
3648+    def test_repairable_5shares(self):
3649+        d = self.publish_mdmf()
3650+        def _delete_all_shares(ign):
3651+            shares = self._storage._peers
3652+            for peerid in shares:
3653+                for shnum in list(shares[peerid]):
3654+                    if shnum > 4:
3655+                        del shares[peerid][shnum]
3656+        d.addCallback(_delete_all_shares)
3657+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3658+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3659+        def _check(crr):
3660+            self.failUnlessEqual(crr.get_successful(), True)
3661+        d.addCallback(_check)
3662+        return d
3663+
3664+    def test_mdmf_repairable_5shares(self):
3665+        d = self.publish_mdmf()
3666+        def _delete_all_shares(ign):
3667+            shares = self._storage._peers
3668+            for peerid in shares:
3669+                for shnum in list(shares[peerid]):
3670+                    if shnum > 5:
3671+                        del shares[peerid][shnum]
3672+        d.addCallback(_delete_all_shares)
3673+        d.addCallback(lambda ign: self._fn.check(Monitor()))
3674+        d.addCallback(lambda check_results: self._fn.repair(check_results))
3675+        def _check(crr):
3676+            self.failUnlessEqual(crr.get_successful(), True)
3677+        d.addCallback(_check)
3678+        return d
3679+
3680+
3681     def test_merge(self):
3682         self.old_shares = []
3683         d = self.publish_multiple()
3684}
3685[mutable/retrieve.py: learn how to verify mutable files
3686Kevan Carstensen <kevan@isnotajoke.com>**20100628225201
3687 Ignore-this: 989af7800c47589620918461ec989483
3688] {
3689hunk ./src/allmydata/mutable/retrieve.py 86
3690     # Retrieve object will remain tied to a specific version of the file, and
3691     # will use a single ServerMap instance.
3692 
3693-    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False):
3694+    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
3695+                 verify=False):
3696         self._node = filenode
3697         assert self._node.get_pubkey()
3698         self._storage_index = filenode.get_storage_index()
3699hunk ./src/allmydata/mutable/retrieve.py 106
3700         # during repair, we may be called upon to grab the private key, since
3701         # it wasn't picked up during a verify=False checker run, and we'll
3702         # need it for repair to generate a new version.
3703-        self._need_privkey = fetch_privkey
3704-        if self._node.get_privkey():
3705+        self._need_privkey = fetch_privkey or verify
3706+        if self._node.get_privkey() and not verify:
3707             self._need_privkey = False
3708 
3709         if self._need_privkey:
3710hunk ./src/allmydata/mutable/retrieve.py 117
3711             self._privkey_query_markers = [] # one Marker for each time we've
3712                                              # tried to get the privkey.
3713 
3714+        # verify means that we are using the downloader logic to verify all
3715+        # of our shares. This tells the downloader a few things.
3716+        #
3717+        # 1. We need to download all of the shares.
3718+        # 2. We don't need to decode or decrypt the shares, since our
3719+        #    caller doesn't care about the plaintext, only the
3720+        #    information about which shares are or are not valid.
3721+        # 3. When we are validating readers, we need to validate the
3722+        #    signature on the prefix. Do we? We already do this in the
3723+        #    servermap update?
3724+        #
3725+        # (just work on 1 and 2 for now, I guess)
3726+        self._verify = False
3727+        if verify:
3728+            self._verify = True
3729+
3730         self._status = RetrieveStatus()
3731         self._status.set_storage_index(self._storage_index)
3732         self._status.set_helper(False)
3733hunk ./src/allmydata/mutable/retrieve.py 323
3734 
3735         # We need at least self._required_shares readers to download a
3736         # segment.
3737-        needed = self._required_shares - len(self._active_readers)
3738+        if self._verify:
3739+            needed = self._total_shares
3740+        else:
3741+            needed = self._required_shares - len(self._active_readers)
3742         # XXX: Why don't format= log messages work here?
3743         self.log("adding %d peers to the active peers list" % needed)
3744 
3745hunk ./src/allmydata/mutable/retrieve.py 339
3746         # will cause problems later.
3747         active_shnums -= set([reader.shnum for reader in self._active_readers])
3748         active_shnums = list(active_shnums)[:needed]
3749-        if len(active_shnums) < needed:
3750+        if len(active_shnums) < needed and not self._verify:
3751             # We don't have enough readers to retrieve the file; fail.
3752             return self._failed()
3753 
3754hunk ./src/allmydata/mutable/retrieve.py 346
3755         for shnum in active_shnums:
3756             self._active_readers.append(self.readers[shnum])
3757             self.log("added reader for share %d" % shnum)
3758-        assert len(self._active_readers) == self._required_shares
3759+        assert len(self._active_readers) >= self._required_shares
3760         # Conceptually, this is part of the _add_active_peers step. It
3761         # validates the prefixes of newly added readers to make sure
3762         # that they match what we are expecting for self.verinfo. If
3763hunk ./src/allmydata/mutable/retrieve.py 416
3764                     # that we haven't gotten it at the end of
3765                     # segment decoding, then we'll take more drastic
3766                     # measures.
3767-                    if self._need_privkey:
3768+                    if self._need_privkey and not self._node.is_readonly():
3769                         d = reader.get_encprivkey()
3770                         d.addCallback(self._try_to_validate_privkey, reader)
3771             if bad_readers:
3772hunk ./src/allmydata/mutable/retrieve.py 423
3773                 # We do them all at once, or else we screw up list indexing.
3774                 for (reader, f) in bad_readers:
3775                     self._mark_bad_share(reader, f)
3776-                return self._add_active_peers()
3777+                if self._verify:
3778+                    if len(self._active_readers) >= self._required_shares:
3779+                        return self._download_current_segment()
3780+                    else:
3781+                        return self._failed()
3782+                else:
3783+                    return self._add_active_peers()
3784             else:
3785                 return self._download_current_segment()
3786             # The next step will assert that it has enough active
3787hunk ./src/allmydata/mutable/retrieve.py 518
3788         """
3789         self.log("marking share %d on server %s as bad" % \
3790                  (reader.shnum, reader))
3791+        prefix = self.verinfo[-2]
3792+        self.servermap.mark_bad_share(reader.peerid,
3793+                                      reader.shnum,
3794+                                      prefix)
3795         self._remove_reader(reader)
3796hunk ./src/allmydata/mutable/retrieve.py 523
3797-        self._bad_shares.add((reader.peerid, reader.shnum))
3798+        self._bad_shares.add((reader.peerid, reader.shnum, f))
3799         self._status.problems[reader.peerid] = f
3800         self._last_failure = f
3801         self.notify_server_corruption(reader.peerid, reader.shnum,
3802hunk ./src/allmydata/mutable/retrieve.py 571
3803             ds.append(dl)
3804             reader.flush()
3805         dl = defer.DeferredList(ds)
3806-        dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3807+        if self._verify:
3808+            dl.addCallback(lambda ignored: "")
3809+            dl.addCallback(self._set_segment)
3810+        else:
3811+            dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
3812         return dl
3813 
3814 
3815hunk ./src/allmydata/mutable/retrieve.py 701
3816         # shnum, which will be a leaf in the share hash tree, which
3817         # will allow us to validate the rest of the tree.
3818         if self.share_hash_tree.needed_hashes(reader.shnum,
3819-                                               include_leaf=True):
3820+                                              include_leaf=True) or \
3821+                                              self._verify:
3822             try:
3823                 self.share_hash_tree.set_hashes(hashes=sharehashes[1],
3824                                             leaves={reader.shnum: bht[0]})
3825hunk ./src/allmydata/mutable/retrieve.py 832
3826 
3827 
3828     def _try_to_validate_privkey(self, enc_privkey, reader):
3829-
3830         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
3831         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
3832         if alleged_writekey != self._node.get_writekey():
3833hunk ./src/allmydata/mutable/retrieve.py 838
3834             self.log("invalid privkey from %s shnum %d" %
3835                      (reader, reader.shnum),
3836                      level=log.WEIRD, umid="YIw4tA")
3837+            if self._verify:
3838+                self.servermap.mark_bad_share(reader.peerid, reader.shnum,
3839+                                              self.verinfo[-2])
3840+                e = CorruptShareError(reader.peerid,
3841+                                      reader.shnum,
3842+                                      "invalid privkey")
3843+                f = failure.Failure(e)
3844+                self._bad_shares.add((reader.peerid, reader.shnum, f))
3845             return
3846 
3847         # it's good
3848hunk ./src/allmydata/mutable/retrieve.py 904
3849         statements, I return the decrypted contents to the owner of this
3850         Retrieve object through self._done_deferred.
3851         """
3852-        eventually(self._done_deferred.callback, self._plaintext)
3853+        if self._verify:
3854+            ret = list(self._bad_shares)
3855+            self.log("done verifying, found %d bad shares" % len(ret))
3856+        else:
3857+            ret = self._plaintext
3858+        eventually(self._done_deferred.callback, ret)
3859 
3860 
3861     def _failed(self):
3862hunk ./src/allmydata/mutable/retrieve.py 920
3863         to the caller of this Retrieve object through
3864         self._done_deferred.
3865         """
3866-        format = ("ran out of peers: "
3867-                  "have %(have)d of %(total)d segments "
3868-                  "found %(bad)d bad shares "
3869-                  "encoding %(k)d-of-%(n)d")
3870-        args = {"have": self._current_segment,
3871-                "total": self._num_segments,
3872-                "k": self._required_shares,
3873-                "n": self._total_shares,
3874-                "bad": len(self._bad_shares)}
3875-        e = NotEnoughSharesError("%s, last failure: %s" % (format % args,
3876-                                                        str(self._last_failure)))
3877-        f = failure.Failure(e)
3878-        eventually(self._done_deferred.callback, f)
3879+        if self._verify:
3880+            ret = list(self._bad_shares)
3881+        else:
3882+            format = ("ran out of peers: "
3883+                      "have %(have)d of %(total)d segments "
3884+                      "found %(bad)d bad shares "
3885+                      "encoding %(k)d-of-%(n)d")
3886+            args = {"have": self._current_segment,
3887+                    "total": self._num_segments,
3888+                    "k": self._required_shares,
3889+                    "n": self._total_shares,
3890+                    "bad": len(self._bad_shares)}
3891+            e = NotEnoughSharesError("%s, last failure: %s" % \
3892+                                     (format % args, str(self._last_failure)))
3893+            f = failure.Failure(e)
3894+            ret = f
3895+        eventually(self._done_deferred.callback, ret)
3896}
3897[interfaces.py: add IMutableSlotWriter
3898Kevan Carstensen <kevan@isnotajoke.com>**20100630183305
3899 Ignore-this: ff9dca96ef1a009ae85485682f81ea5
3900] hunk ./src/allmydata/interfaces.py 418
3901         """
3902 
3903 
3904+class IMutableSlotWriter(Interface):
3905+    """
3906+    The interface for a writer around a mutable slot on a remote server.
3907+    """
3908+    def set_checkstring(checkstring, *args):
3909+        """
3910+        Set the checkstring that I will pass to the remote server when
3911+        writing.
3912+
3913+            @param checkstring A packed checkstring to use.
3914+
3915+        Note that implementations can differ in which semantics they
3916+        wish to support for set_checkstring -- they can, for example,
3917+        build the checkstring themselves from its constituents, or
3918+        some other thing.
3919+        """
3920+
3921+    def get_checkstring():
3922+        """
3923+        Get the checkstring that I think currently exists on the remote
3924+        server.
3925+        """
3926+
3927+    def put_block(data, segnum, salt):
3928+        """
3929+        Add a block and salt to the share.
3930+        """
3931+
3932+    def put_encprivey(encprivkey):
3933+        """
3934+        Add the encrypted private key to the share.
3935+        """
3936+
3937+    def put_blockhashes(blockhashes=list):
3938+        """
3939+        Add the block hash tree to the share.
3940+        """
3941+
3942+    def put_sharehashes(sharehashes=dict):
3943+        """
3944+        Add the share hash chain to the share.
3945+        """
3946+
3947+    def get_signable():
3948+        """
3949+        Return the part of the share that needs to be signed.
3950+        """
3951+
3952+    def put_signature(signature):
3953+        """
3954+        Add the signature to the share.
3955+        """
3956+
3957+    def put_verification_key(verification_key):
3958+        """
3959+        Add the verification key to the share.
3960+        """
3961+
3962+    def finish_publishing():
3963+        """
3964+        Do anything necessary to finish writing the share to a remote
3965+        server. I require that no further publishing needs to take place
3966+        after this method has been called.
3967+        """
3968+
3969+
3970 class IURI(Interface):
3971     def init_from_string(uri):
3972         """Accept a string (as created by my to_string() method) and populate
3973[test/test_mutable.py: temporarily disable two tests that are now irrelevant
3974Kevan Carstensen <kevan@isnotajoke.com>**20100701232806
3975 Ignore-this: 701e143567f3954812ca6960af1d6ac7
3976] {
3977hunk ./src/allmydata/test/test_mutable.py 651
3978             self.failUnlessEqual(len(share_ids), 10)
3979         d.addCallback(_done)
3980         return d
3981+    test_encrypt.todo = "Write an equivalent of this for the new uploader"
3982 
3983     def test_generate(self):
3984         nm = make_nodemaker()
3985hunk ./src/allmydata/test/test_mutable.py 713
3986                 self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
3987         d.addCallback(_generated)
3988         return d
3989+    test_generate.todo = "Write an equivalent of this for the new uploader"
3990 
3991     # TODO: when we publish to 20 peers, we should get one share per peer on 10
3992     # when we publish to 3 peers, we should get either 3 or 4 shares per peer
3993}
3994[Add MDMF reader and writer, and SDMF writer
3995Kevan Carstensen <kevan@isnotajoke.com>**20100702225531
3996 Ignore-this: bf6276a91d27dcb4e779b0eb82ea1843
3997 
3998 The MDMF/SDMF reader MDMF writer, and SDMF writer are similar to the
3999 object proxies that exist for immutable files. They abstract away
4000 details of connection, state, and caching from their callers (in this
4001 case, the download, servermap updater, and uploader), and expose methods
4002 to get and set information on the remote server.
4003 
4004 MDMFSlotReadProxy reads a mutable file from the server, doing the right
4005 thing (in most cases) regardless of whether the file is MDMF or SDMF. It
4006 allows callers to tell it how to batch and flush reads.
4007 
4008 MDMFSlotWriteProxy writes an MDMF mutable file to a server.
4009 
4010 SDMFSlotWriteProxy writes an SDMF mutable file to a server.
4011 
4012 This patch also includes tests for MDMFSlotReadProxy,
4013 SDMFSlotWriteProxy, and MDMFSlotWriteProxy.
4014] {
4015hunk ./src/allmydata/mutable/layout.py 4
4016 
4017 import struct
4018 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
4019+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
4020+                                 MDMF_VERSION, IMutableSlotWriter
4021+from allmydata.util import mathutil, observer
4022+from twisted.python import failure
4023+from twisted.internet import defer
4024+from zope.interface import implements
4025+
4026+
4027+# These strings describe the format of the packed structs they help process
4028+# Here's what they mean:
4029+#
4030+#  PREFIX:
4031+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4032+#    B: The version information; an 8 bit version identifier. Stored as
4033+#       an unsigned char. This is currently 00 00 00 00; our modifications
4034+#       will turn it into 00 00 00 01.
4035+#    Q: The sequence number; this is sort of like a revision history for
4036+#       mutable files; they start at 1 and increase as they are changed after
4037+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4038+#       length.
4039+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4040+#       characters = 32 bytes to store the value.
4041+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4042+#       16 characters.
4043+#
4044+#  SIGNED_PREFIX additions, things that are covered by the signature:
4045+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4046+#       which is convenient because our erasure coding scheme cannot
4047+#       encode if you ask for more than 255 pieces.
4048+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4049+#       same reasons as above.
4050+#    Q: The segment size of the uploaded file. This will essentially be the
4051+#       length of the file in SDMF. An unsigned long long, so we can store
4052+#       files of quite large size.
4053+#    Q: The data length of the uploaded file. Modulo padding, this will be
4054+#       the same of the data length field. Like the data length field, it is
4055+#       an unsigned long long and can be quite large.
4056+#
4057+#   HEADER additions:
4058+#     L: The offset of the signature of this. An unsigned long.
4059+#     L: The offset of the share hash chain. An unsigned long.
4060+#     L: The offset of the block hash tree. An unsigned long.
4061+#     L: The offset of the share data. An unsigned long.
4062+#     Q: The offset of the encrypted private key. An unsigned long long, to
4063+#        account for the possibility of a lot of share data.
4064+#     Q: The offset of the EOF. An unsigned long long, to account for the
4065+#        possibility of a lot of share data.
4066+#
4067+#  After all of these, we have the following:
4068+#    - The verification key: Occupies the space between the end of the header
4069+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4070+#    - The signature, which goes from the signature offset to the share hash
4071+#      chain offset.
4072+#    - The share hash chain, which goes from the share hash chain offset to
4073+#      the block hash tree offset.
4074+#    - The share data, which goes from the share data offset to the encrypted
4075+#      private key offset.
4076+#    - The encrypted private key offset, which goes until the end of the file.
4077+#
4078+#  The block hash tree in this encoding has only one share, so the offset of
4079+#  the share data will be 32 bits more than the offset of the block hash tree.
4080+#  Given this, we may need to check to see how many bytes a reasonably sized
4081+#  block hash tree will take up.
4082 
4083 PREFIX = ">BQ32s16s" # each version has a different prefix
4084 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4085hunk ./src/allmydata/mutable/layout.py 73
4086 SIGNED_PREFIX_LENGTH = struct.calcsize(SIGNED_PREFIX)
4087 HEADER = ">BQ32s16s BBQQ LLLLQQ" # includes offsets
4088 HEADER_LENGTH = struct.calcsize(HEADER)
4089+OFFSETS = ">LLLLQQ"
4090+OFFSETS_LENGTH = struct.calcsize(OFFSETS)
4091 
4092 def unpack_header(data):
4093     o = {}
4094hunk ./src/allmydata/mutable/layout.py 194
4095     return (share_hash_chain, block_hash_tree, share_data)
4096 
4097 
4098-def pack_checkstring(seqnum, root_hash, IV):
4099+def pack_checkstring(seqnum, root_hash, IV, version=0):
4100     return struct.pack(PREFIX,
4101hunk ./src/allmydata/mutable/layout.py 196
4102-                       0, # version,
4103+                       version,
4104                        seqnum,
4105                        root_hash,
4106                        IV)
4107hunk ./src/allmydata/mutable/layout.py 269
4108                            encprivkey])
4109     return final_share
4110 
4111+def pack_prefix(seqnum, root_hash, IV,
4112+                required_shares, total_shares,
4113+                segment_size, data_length):
4114+    prefix = struct.pack(SIGNED_PREFIX,
4115+                         0, # version,
4116+                         seqnum,
4117+                         root_hash,
4118+                         IV,
4119+                         required_shares,
4120+                         total_shares,
4121+                         segment_size,
4122+                         data_length,
4123+                         )
4124+    return prefix
4125+
4126+
4127+class SDMFSlotWriteProxy:
4128+    implements(IMutableSlotWriter)
4129+    """
4130+    I represent a remote write slot for an SDMF mutable file. I build a
4131+    share in memory, and then write it in one piece to the remote
4132+    server. This mimics how SDMF shares were built before MDMF (and the
4133+    new MDMF uploader), but provides that functionality in a way that
4134+    allows the MDMF uploader to be built without much special-casing for
4135+    file format, which makes the uploader code more readable.
4136+    """
4137+    def __init__(self,
4138+                 shnum,
4139+                 rref, # a remote reference to a storage server
4140+                 storage_index,
4141+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4142+                 seqnum, # the sequence number of the mutable file
4143+                 required_shares,
4144+                 total_shares,
4145+                 segment_size,
4146+                 data_length): # the length of the original file
4147+        self.shnum = shnum
4148+        self._rref = rref
4149+        self._storage_index = storage_index
4150+        self._secrets = secrets
4151+        self._seqnum = seqnum
4152+        self._required_shares = required_shares
4153+        self._total_shares = total_shares
4154+        self._segment_size = segment_size
4155+        self._data_length = data_length
4156+
4157+        # This is an SDMF file, so it should have only one segment, so,
4158+        # modulo padding of the data length, the segment size and the
4159+        # data length should be the same.
4160+        expected_segment_size = mathutil.next_multiple(data_length,
4161+                                                       self._required_shares)
4162+        assert expected_segment_size == segment_size
4163+
4164+        self._block_size = self._segment_size / self._required_shares
4165+
4166+        # This is meant to mimic how SDMF files were built before MDMF
4167+        # entered the picture: we generate each share in its entirety,
4168+        # then push it off to the storage server in one write. When
4169+        # callers call set_*, they are just populating this dict.
4170+        # finish_publishing will stitch these pieces together into a
4171+        # coherent share, and then write the coherent share to the
4172+        # storage server.
4173+        self._share_pieces = {}
4174+
4175+        # This tells the write logic what checkstring to use when
4176+        # writing remote shares.
4177+        self._testvs = []
4178+
4179+        self._readvs = [(0, struct.calcsize(PREFIX))]
4180+
4181+
4182+    def set_checkstring(self, checkstring_or_seqnum,
4183+                              root_hash=None,
4184+                              salt=None):
4185+        """
4186+        Set the checkstring that I will pass to the remote server when
4187+        writing.
4188+
4189+            @param checkstring_or_seqnum: A packed checkstring to use,
4190+                   or a sequence number. I will treat this as a checkstr
4191+
4192+        Note that implementations can differ in which semantics they
4193+        wish to support for set_checkstring -- they can, for example,
4194+        build the checkstring themselves from its constituents, or
4195+        some other thing.
4196+        """
4197+        if root_hash and salt:
4198+            checkstring = struct.pack(PREFIX,
4199+                                      0,
4200+                                      checkstring_or_seqnum,
4201+                                      root_hash,
4202+                                      salt)
4203+        else:
4204+            checkstring = checkstring_or_seqnum
4205+        self._testvs = [(0, len(checkstring), "eq", checkstring)]
4206+
4207+
4208+    def get_checkstring(self):
4209+        """
4210+        Get the checkstring that I think currently exists on the remote
4211+        server.
4212+        """
4213+        if self._testvs:
4214+            return self._testvs[0][3]
4215+        return ""
4216+
4217+
4218+    def put_block(self, data, segnum, salt):
4219+        """
4220+        Add a block and salt to the share.
4221+        """
4222+        # SDMF files have only one segment
4223+        assert segnum == 0
4224+        assert len(data) == self._block_size
4225+        assert len(salt) == SALT_SIZE
4226+
4227+        self._share_pieces['sharedata'] = data
4228+        self._share_pieces['salt'] = salt
4229+
4230+        # TODO: Figure out something intelligent to return.
4231+        return defer.succeed(None)
4232+
4233+
4234+    def put_encprivkey(self, encprivkey):
4235+        """
4236+        Add the encrypted private key to the share.
4237+        """
4238+        self._share_pieces['encprivkey'] = encprivkey
4239+
4240+        return defer.succeed(None)
4241+
4242+
4243+    def put_blockhashes(self, blockhashes):
4244+        """
4245+        Add the block hash tree to the share.
4246+        """
4247+        assert isinstance(blockhashes, list)
4248+        for h in blockhashes:
4249+            assert len(h) == HASH_SIZE
4250+
4251+        # serialize the blockhashes, then set them.
4252+        blockhashes_s = "".join(blockhashes)
4253+        self._share_pieces['block_hash_tree'] = blockhashes_s
4254+
4255+        return defer.succeed(None)
4256+
4257+
4258+    def put_sharehashes(self, sharehashes):
4259+        """
4260+        Add the share hash chain to the share.
4261+        """
4262+        assert isinstance(sharehashes, dict)
4263+        for h in sharehashes.itervalues():
4264+            assert len(h) == HASH_SIZE
4265+
4266+        # serialize the sharehashes, then set them.
4267+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4268+                                 for i in sorted(sharehashes.keys())])
4269+        self._share_pieces['share_hash_chain'] = sharehashes_s
4270+
4271+        return defer.succeed(None)
4272+
4273+
4274+    def put_root_hash(self, root_hash):
4275+        """
4276+        Add the root hash to the share.
4277+        """
4278+        assert len(root_hash) == HASH_SIZE
4279+
4280+        self._share_pieces['root_hash'] = root_hash
4281+
4282+        return defer.succeed(None)
4283+
4284+
4285+    def put_salt(self, salt):
4286+        """
4287+        Add a salt to an empty SDMF file.
4288+        """
4289+        assert len(salt) == SALT_SIZE
4290+
4291+        self._share_pieces['salt'] = salt
4292+        self._share_pieces['sharedata'] = ""
4293+
4294+
4295+    def get_signable(self):
4296+        """
4297+        Return the part of the share that needs to be signed.
4298+
4299+        SDMF writers need to sign the packed representation of the
4300+        first eight fields of the remote share, that is:
4301+            - version number (0)
4302+            - sequence number
4303+            - root of the share hash tree
4304+            - salt
4305+            - k
4306+            - n
4307+            - segsize
4308+            - datalen
4309+
4310+        This method is responsible for returning that to callers.
4311+        """
4312+        return struct.pack(SIGNED_PREFIX,
4313+                           0,
4314+                           self._seqnum,
4315+                           self._share_pieces['root_hash'],
4316+                           self._share_pieces['salt'],
4317+                           self._required_shares,
4318+                           self._total_shares,
4319+                           self._segment_size,
4320+                           self._data_length)
4321+
4322+
4323+    def put_signature(self, signature):
4324+        """
4325+        Add the signature to the share.
4326+        """
4327+        self._share_pieces['signature'] = signature
4328+
4329+        return defer.succeed(None)
4330+
4331+
4332+    def put_verification_key(self, verification_key):
4333+        """
4334+        Add the verification key to the share.
4335+        """
4336+        self._share_pieces['verification_key'] = verification_key
4337+
4338+        return defer.succeed(None)
4339+
4340+
4341+    def get_verinfo(self):
4342+        """
4343+        I return my verinfo tuple. This is used by the ServermapUpdater
4344+        to keep track of versions of mutable files.
4345+
4346+        The verinfo tuple for MDMF files contains:
4347+            - seqnum
4348+            - root hash
4349+            - a blank (nothing)
4350+            - segsize
4351+            - datalen
4352+            - k
4353+            - n
4354+            - prefix (the thing that you sign)
4355+            - a tuple of offsets
4356+
4357+        We include the nonce in MDMF to simplify processing of version
4358+        information tuples.
4359+
4360+        The verinfo tuple for SDMF files is the same, but contains a
4361+        16-byte IV instead of a hash of salts.
4362+        """
4363+        return (self._seqnum,
4364+                self._share_pieces['root_hash'],
4365+                self._share_pieces['salt'],
4366+                self._segment_size,
4367+                self._data_length,
4368+                self._required_shares,
4369+                self._total_shares,
4370+                self.get_signable(),
4371+                self._get_offsets_tuple())
4372+
4373+    def _get_offsets_dict(self):
4374+        post_offset = HEADER_LENGTH
4375+        offsets = {}
4376+
4377+        verification_key_length = len(self._share_pieces['verification_key'])
4378+        o1 = offsets['signature'] = post_offset + verification_key_length
4379+
4380+        signature_length = len(self._share_pieces['signature'])
4381+        o2 = offsets['share_hash_chain'] = o1 + signature_length
4382+
4383+        share_hash_chain_length = len(self._share_pieces['share_hash_chain'])
4384+        o3 = offsets['block_hash_tree'] = o2 + share_hash_chain_length
4385+
4386+        block_hash_tree_length = len(self._share_pieces['block_hash_tree'])
4387+        o4 = offsets['share_data'] = o3 + block_hash_tree_length
4388+
4389+        share_data_length = len(self._share_pieces['sharedata'])
4390+        o5 = offsets['enc_privkey'] = o4 + share_data_length
4391+
4392+        encprivkey_length = len(self._share_pieces['encprivkey'])
4393+        offsets['EOF'] = o5 + encprivkey_length
4394+        return offsets
4395+
4396+
4397+    def _get_offsets_tuple(self):
4398+        offsets = self._get_offsets_dict()
4399+        return tuple([(key, value) for key, value in offsets.items()])
4400+
4401+
4402+    def _pack_offsets(self):
4403+        offsets = self._get_offsets_dict()
4404+        return struct.pack(">LLLLQQ",
4405+                           offsets['signature'],
4406+                           offsets['share_hash_chain'],
4407+                           offsets['block_hash_tree'],
4408+                           offsets['share_data'],
4409+                           offsets['enc_privkey'],
4410+                           offsets['EOF'])
4411+
4412+
4413+    def finish_publishing(self):
4414+        """
4415+        Do anything necessary to finish writing the share to a remote
4416+        server. I require that no further publishing needs to take place
4417+        after this method has been called.
4418+        """
4419+        for k in ["sharedata", "encprivkey", "signature", "verification_key",
4420+                  "share_hash_chain", "block_hash_tree"]:
4421+            assert k in self._share_pieces
4422+        # This is the only method that actually writes something to the
4423+        # remote server.
4424+        # First, we need to pack the share into data that we can write
4425+        # to the remote server in one write.
4426+        offsets = self._pack_offsets()
4427+        prefix = self.get_signable()
4428+        final_share = "".join([prefix,
4429+                               offsets,
4430+                               self._share_pieces['verification_key'],
4431+                               self._share_pieces['signature'],
4432+                               self._share_pieces['share_hash_chain'],
4433+                               self._share_pieces['block_hash_tree'],
4434+                               self._share_pieces['sharedata'],
4435+                               self._share_pieces['encprivkey']])
4436+
4437+        # Our only data vector is going to be writing the final share,
4438+        # in its entirely.
4439+        datavs = [(0, final_share)]
4440+
4441+        if not self._testvs:
4442+            # Our caller has not provided us with another checkstring
4443+            # yet, so we assume that we are writing a new share, and set
4444+            # a test vector that will allow a new share to be written.
4445+            self._testvs = []
4446+            self._testvs.append(tuple([0, 1, "eq", ""]))
4447+            new_share = True
4448+
4449+        tw_vectors = {}
4450+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
4451+        return self._rref.callRemote("slot_testv_and_readv_and_writev",
4452+                                     self._storage_index,
4453+                                     self._secrets,
4454+                                     tw_vectors,
4455+                                     # TODO is it useful to read something?
4456+                                     self._readvs)
4457+
4458+
4459+MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
4460+MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
4461+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4462+MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4463+MDMFCHECKSTRING = ">BQ32s"
4464+MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
4465+MDMFOFFSETS = ">QQQQQQ"
4466+MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
4467+
4468+class MDMFSlotWriteProxy:
4469+    implements(IMutableSlotWriter)
4470+
4471+    """
4472+    I represent a remote write slot for an MDMF mutable file.
4473+
4474+    I abstract away from my caller the details of block and salt
4475+    management, and the implementation of the on-disk format for MDMF
4476+    shares.
4477+    """
4478+    # Expected layout, MDMF:
4479+    # offset:     size:       name:
4480+    #-- signed part --
4481+    # 0           1           version number (01)
4482+    # 1           8           sequence number
4483+    # 9           32          share tree root hash
4484+    # 41          1           The "k" encoding parameter
4485+    # 42          1           The "N" encoding parameter
4486+    # 43          8           The segment size of the uploaded file
4487+    # 51          8           The data length of the original plaintext
4488+    #-- end signed part --
4489+    # 59          8           The offset of the encrypted private key
4490+    # 67          8           The offset of the block hash tree
4491+    # 75          8           The offset of the share hash chain
4492+    # 83          8           The offset of the signature
4493+    # 91          8           The offset of the verification key
4494+    # 99          8           The offset of the EOF
4495+    #
4496+    # followed by salts and share data, the encrypted private key, the
4497+    # block hash tree, the salt hash tree, the share hash chain, a
4498+    # signature over the first eight fields, and a verification key.
4499+    #
4500+    # The checkstring is the first three fields -- the version number,
4501+    # sequence number, root hash and root salt hash. This is consistent
4502+    # in meaning to what we have with SDMF files, except now instead of
4503+    # using the literal salt, we use a value derived from all of the
4504+    # salts -- the share hash root.
4505+    #
4506+    # The salt is stored before the block for each segment. The block
4507+    # hash tree is computed over the combination of block and salt for
4508+    # each segment. In this way, we get integrity checking for both
4509+    # block and salt with the current block hash tree arrangement.
4510+    #
4511+    # The ordering of the offsets is different to reflect the dependencies
4512+    # that we'll run into with an MDMF file. The expected write flow is
4513+    # something like this:
4514+    #
4515+    #   0: Initialize with the sequence number, encoding parameters and
4516+    #      data length. From this, we can deduce the number of segments,
4517+    #      and where they should go.. We can also figure out where the
4518+    #      encrypted private key should go, because we can figure out how
4519+    #      big the share data will be.
4520+    #
4521+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4522+    #      like
4523+    #
4524+    #       put_block(data, segnum, salt)
4525+    #
4526+    #      to write a block and a salt to the disk. We can do both of
4527+    #      these operations now because we have enough of the offsets to
4528+    #      know where to put them.
4529+    #
4530+    #   2: Put the encrypted private key. Use:
4531+    #
4532+    #        put_encprivkey(encprivkey)
4533+    #
4534+    #      Now that we know the length of the private key, we can fill
4535+    #      in the offset for the block hash tree.
4536+    #
4537+    #   3: We're now in a position to upload the block hash tree for
4538+    #      a share. Put that using something like:
4539+    #       
4540+    #        put_blockhashes(block_hash_tree)
4541+    #
4542+    #      Note that block_hash_tree is a list of hashes -- we'll take
4543+    #      care of the details of serializing that appropriately. When
4544+    #      we get the block hash tree, we are also in a position to
4545+    #      calculate the offset for the share hash chain, and fill that
4546+    #      into the offsets table.
4547+    #
4548+    #   4: At the same time, we're in a position to upload the salt hash
4549+    #      tree. This is a Merkle tree over all of the salts. We use a
4550+    #      Merkle tree so that we can validate each block,salt pair as
4551+    #      we download them later. We do this using
4552+    #
4553+    #        put_salthashes(salt_hash_tree)
4554+    #
4555+    #      When you do this, I automatically put the root of the tree
4556+    #      (the hash at index 0 of the list) in its appropriate slot in
4557+    #      the signed prefix of the share.
4558+    #
4559+    #   5: We're now in a position to upload the share hash chain for
4560+    #      a share. Do that with something like:
4561+    #     
4562+    #        put_sharehashes(share_hash_chain)
4563+    #
4564+    #      share_hash_chain should be a dictionary mapping shnums to
4565+    #      32-byte hashes -- the wrapper handles serialization.
4566+    #      We'll know where to put the signature at this point, also.
4567+    #      The root of this tree will be put explicitly in the next
4568+    #      step.
4569+    #
4570+    #      TODO: Why? Why not just include it in the tree here?
4571+    #
4572+    #   6: Before putting the signature, we must first put the
4573+    #      root_hash. Do this with:
4574+    #
4575+    #        put_root_hash(root_hash).
4576+    #     
4577+    #      In terms of knowing where to put this value, it was always
4578+    #      possible to place it, but it makes sense semantically to
4579+    #      place it after the share hash tree, so that's why you do it
4580+    #      in this order.
4581+    #
4582+    #   6: With the root hash put, we can now sign the header. Use:
4583+    #
4584+    #        get_signable()
4585+    #
4586+    #      to get the part of the header that you want to sign, and use:
4587+    #       
4588+    #        put_signature(signature)
4589+    #
4590+    #      to write your signature to the remote server.
4591+    #
4592+    #   6: Add the verification key, and finish. Do:
4593+    #
4594+    #        put_verification_key(key)
4595+    #
4596+    #      and
4597+    #
4598+    #        finish_publish()
4599+    #
4600+    # Checkstring management:
4601+    #
4602+    # To write to a mutable slot, we have to provide test vectors to ensure
4603+    # that we are writing to the same data that we think we are. These
4604+    # vectors allow us to detect uncoordinated writes; that is, writes
4605+    # where both we and some other shareholder are writing to the
4606+    # mutable slot, and to report those back to the parts of the program
4607+    # doing the writing.
4608+    #
4609+    # With SDMF, this was easy -- all of the share data was written in
4610+    # one go, so it was easy to detect uncoordinated writes, and we only
4611+    # had to do it once. With MDMF, not all of the file is written at
4612+    # once.
4613+    #
4614+    # If a share is new, we write out as much of the header as we can
4615+    # before writing out anything else. This gives other writers a
4616+    # canary that they can use to detect uncoordinated writes, and, if
4617+    # they do the same thing, gives us the same canary. We them update
4618+    # the share. We won't be able to write out two fields of the header
4619+    # -- the share tree hash and the salt hash -- until we finish
4620+    # writing out the share. We only require the writer to provide the
4621+    # initial checkstring, and keep track of what it should be after
4622+    # updates ourselves.
4623+    #
4624+    # If we haven't written anything yet, then on the first write (which
4625+    # will probably be a block + salt of a share), we'll also write out
4626+    # the header. On subsequent passes, we'll expect to see the header.
4627+    # This changes in two places:
4628+    #
4629+    #   - When we write out the salt hash
4630+    #   - When we write out the root of the share hash tree
4631+    #
4632+    # since these values will change the header. It is possible that we
4633+    # can just make those be written in one operation to minimize
4634+    # disruption.
4635+    def __init__(self,
4636+                 shnum,
4637+                 rref, # a remote reference to a storage server
4638+                 storage_index,
4639+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4640+                 seqnum, # the sequence number of the mutable file
4641+                 required_shares,
4642+                 total_shares,
4643+                 segment_size,
4644+                 data_length): # the length of the original file
4645+        self.shnum = shnum
4646+        self._rref = rref
4647+        self._storage_index = storage_index
4648+        self._seqnum = seqnum
4649+        self._required_shares = required_shares
4650+        assert self.shnum >= 0 and self.shnum < total_shares
4651+        self._total_shares = total_shares
4652+        # We build up the offset table as we write things. It is the
4653+        # last thing we write to the remote server.
4654+        self._offsets = {}
4655+        self._testvs = []
4656+        self._secrets = secrets
4657+        # The segment size needs to be a multiple of the k parameter --
4658+        # any padding should have been carried out by the publisher
4659+        # already.
4660+        assert segment_size % required_shares == 0
4661+        self._segment_size = segment_size
4662+        self._data_length = data_length
4663+
4664+        # These are set later -- we define them here so that we can
4665+        # check for their existence easily
4666+
4667+        # This is the root of the share hash tree -- the Merkle tree
4668+        # over the roots of the block hash trees computed for shares in
4669+        # this upload.
4670+        self._root_hash = None
4671+
4672+        # We haven't yet written anything to the remote bucket. By
4673+        # setting this, we tell the _write method as much. The write
4674+        # method will then know that it also needs to add a write vector
4675+        # for the checkstring (or what we have of it) to the first write
4676+        # request. We'll then record that value for future use.  If
4677+        # we're expecting something to be there already, we need to call
4678+        # set_checkstring before we write anything to tell the first
4679+        # write about that.
4680+        self._written = False
4681+
4682+        # When writing data to the storage servers, we get a read vector
4683+        # for free. We'll read the checkstring, which will help us
4684+        # figure out what's gone wrong if a write fails.
4685+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
4686+
4687+        # We calculate the number of segments because it tells us
4688+        # where the salt part of the file ends/share segment begins,
4689+        # and also because it provides a useful amount of bounds checking.
4690+        self._num_segments = mathutil.div_ceil(self._data_length,
4691+                                               self._segment_size)
4692+        self._block_size = self._segment_size / self._required_shares
4693+        # We also calculate the share size, to help us with block
4694+        # constraints later.
4695+        tail_size = self._data_length % self._segment_size
4696+        if not tail_size:
4697+            self._tail_block_size = self._block_size
4698+        else:
4699+            self._tail_block_size = mathutil.next_multiple(tail_size,
4700+                                                           self._required_shares)
4701+            self._tail_block_size /= self._required_shares
4702+
4703+        # We already know where the sharedata starts; right after the end
4704+        # of the header (which is defined as the signable part + the offsets)
4705+        # We can also calculate where the encrypted private key begins
4706+        # from what we know know.
4707+        self._actual_block_size = self._block_size + SALT_SIZE
4708+        data_size = self._actual_block_size * (self._num_segments - 1)
4709+        data_size += self._tail_block_size
4710+        data_size += SALT_SIZE
4711+        self._offsets['enc_privkey'] = MDMFHEADERSIZE
4712+        self._offsets['enc_privkey'] += data_size
4713+        # We'll wait for the rest. Callers can now call my "put_block" and
4714+        # "set_checkstring" methods.
4715+
4716+
4717+    def set_checkstring(self,
4718+                        seqnum_or_checkstring,
4719+                        root_hash=None,
4720+                        salt=None):
4721+        """
4722+        Set checkstring checkstring for the given shnum.
4723+
4724+        This can be invoked in one of two ways.
4725+
4726+        With one argument, I assume that you are giving me a literal
4727+        checkstring -- e.g., the output of get_checkstring. I will then
4728+        set that checkstring as it is. This form is used by unit tests.
4729+
4730+        With two arguments, I assume that you are giving me a sequence
4731+        number and root hash to make a checkstring from. In that case, I
4732+        will build a checkstring and set it for you. This form is used
4733+        by the publisher.
4734+
4735+        By default, I assume that I am writing new shares to the grid.
4736+        If you don't explcitly set your own checkstring, I will use
4737+        one that requires that the remote share not exist. You will want
4738+        to use this method if you are updating a share in-place;
4739+        otherwise, writes will fail.
4740+        """
4741+        # You're allowed to overwrite checkstrings with this method;
4742+        # I assume that users know what they are doing when they call
4743+        # it.
4744+        if root_hash:
4745+            checkstring = struct.pack(MDMFCHECKSTRING,
4746+                                      1,
4747+                                      seqnum_or_checkstring,
4748+                                      root_hash)
4749+        else:
4750+            checkstring = seqnum_or_checkstring
4751+
4752+        if checkstring == "":
4753+            # We special-case this, since len("") = 0, but we need
4754+            # length of 1 for the case of an empty share to work on the
4755+            # storage server, which is what a checkstring that is the
4756+            # empty string means.
4757+            self._testvs = []
4758+        else:
4759+            self._testvs = []
4760+            self._testvs.append((0, len(checkstring), "eq", checkstring))
4761+
4762+
4763+    def __repr__(self):
4764+        return "MDMFSlotWriteProxy for share %d" % self.shnum
4765+
4766+
4767+    def get_checkstring(self):
4768+        """
4769+        Given a share number, I return a representation of what the
4770+        checkstring for that share on the server will look like.
4771+
4772+        I am mostly used for tests.
4773+        """
4774+        if self._root_hash:
4775+            roothash = self._root_hash
4776+        else:
4777+            roothash = "\x00" * 32
4778+        return struct.pack(MDMFCHECKSTRING,
4779+                           1,
4780+                           self._seqnum,
4781+                           roothash)
4782+
4783+
4784+    def put_block(self, data, segnum, salt):
4785+        """
4786+        Put the encrypted-and-encoded data segment in the slot, along
4787+        with the salt.
4788+        """
4789+        if segnum >= self._num_segments:
4790+            raise LayoutInvalid("I won't overwrite the private key")
4791+        if len(salt) != SALT_SIZE:
4792+            raise LayoutInvalid("I was given a salt of size %d, but "
4793+                                "I wanted a salt of size %d")
4794+        if segnum + 1 == self._num_segments:
4795+            if len(data) != self._tail_block_size:
4796+                raise LayoutInvalid("I was given the wrong size block to write")
4797+        elif len(data) != self._block_size:
4798+            raise LayoutInvalid("I was given the wrong size block to write")
4799+
4800+        # We want to write at len(MDMFHEADER) + segnum * block_size.
4801+
4802+        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
4803+        data = salt + data
4804+
4805+        datavs = [tuple([offset, data])]
4806+        return self._write(datavs)
4807+
4808+
4809+    def put_encprivkey(self, encprivkey):
4810+        """
4811+        Put the encrypted private key in the remote slot.
4812+        """
4813+        assert self._offsets
4814+        assert self._offsets['enc_privkey']
4815+        # You shouldn't re-write the encprivkey after the block hash
4816+        # tree is written, since that could cause the private key to run
4817+        # into the block hash tree. Before it writes the block hash
4818+        # tree, the block hash tree writing method writes the offset of
4819+        # the salt hash tree. So that's a good indicator of whether or
4820+        # not the block hash tree has been written.
4821+        if "share_hash_chain" in self._offsets:
4822+            raise LayoutInvalid("You must write this before the block hash tree")
4823+
4824+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey)
4825+        datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))]
4826+        def _on_failure():
4827+            del(self._offsets['block_hash_tree'])
4828+        return self._write(datavs, on_failure=_on_failure)
4829+
4830+
4831+    def put_blockhashes(self, blockhashes):
4832+        """
4833+        Put the block hash tree in the remote slot.
4834+
4835+        The encrypted private key must be put before the block hash
4836+        tree, since we need to know how large it is to know where the
4837+        block hash tree should go. The block hash tree must be put
4838+        before the salt hash tree, since its size determines the
4839+        offset of the share hash chain.
4840+        """
4841+        assert self._offsets
4842+        assert isinstance(blockhashes, list)
4843+        if "block_hash_tree" not in self._offsets:
4844+            raise LayoutInvalid("You must put the encrypted private key "
4845+                                "before you put the block hash tree")
4846+        # If written, the share hash chain causes the signature offset
4847+        # to be defined.
4848+        if "signature" in self._offsets:
4849+            raise LayoutInvalid("You must put the block hash tree before "
4850+                                "you put the share hash chain")
4851+        blockhashes_s = "".join(blockhashes)
4852+        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
4853+        datavs = []
4854+        datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s]))
4855+        def _on_failure():
4856+            del(self._offsets['share_hash_chain'])
4857+        return self._write(datavs, on_failure=_on_failure)
4858+
4859+
4860+    def put_sharehashes(self, sharehashes):
4861+        """
4862+        Put the share hash chain in the remote slot.
4863+
4864+        The salt hash tree must be put before the share hash chain,
4865+        since we need to know where the salt hash tree ends before we
4866+        can know where the share hash chain starts. The share hash chain
4867+        must be put before the signature, since the length of the packed
4868+        share hash chain determines the offset of the signature. Also,
4869+        semantically, you must know what the root of the salt hash tree
4870+        is before you can generate a valid signature.
4871+        """
4872+        assert isinstance(sharehashes, dict)
4873+        if "share_hash_chain" not in self._offsets:
4874+            raise LayoutInvalid("You need to put the salt hash tree before "
4875+                                "you can put the share hash chain")
4876+        # The signature comes after the share hash chain. If the
4877+        # signature has already been written, we must not write another
4878+        # share hash chain. The signature writes the verification key
4879+        # offset when it gets sent to the remote server, so we look for
4880+        # that.
4881+        if "verification_key" in self._offsets:
4882+            raise LayoutInvalid("You must write the share hash chain "
4883+                                "before you write the signature")
4884+        datavs = []
4885+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4886+                                  for i in sorted(sharehashes.keys())])
4887+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
4888+        datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s]))
4889+        def _on_failure():
4890+            del(self._offsets['signature'])
4891+        return self._write(datavs, on_failure=_on_failure)
4892+
4893+
4894+    def put_root_hash(self, roothash):
4895+        """
4896+        Put the root hash (the root of the share hash tree) in the
4897+        remote slot.
4898+        """
4899+        # It does not make sense to be able to put the root
4900+        # hash without first putting the share hashes, since you need
4901+        # the share hashes to generate the root hash.
4902+        #
4903+        # Signature is defined by the routine that places the share hash
4904+        # chain, so it's a good thing to look for in finding out whether
4905+        # or not the share hash chain exists on the remote server.
4906+        if "signature" not in self._offsets:
4907+            raise LayoutInvalid("You need to put the share hash chain "
4908+                                "before you can put the root share hash")
4909+        if len(roothash) != HASH_SIZE:
4910+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
4911+                                 % HASH_SIZE)
4912+        datavs = []
4913+        self._root_hash = roothash
4914+        # To write both of these values, we update the checkstring on
4915+        # the remote server, which includes them
4916+        checkstring = self.get_checkstring()
4917+        datavs.append(tuple([0, checkstring]))
4918+        # This write, if successful, changes the checkstring, so we need
4919+        # to update our internal checkstring to be consistent with the
4920+        # one on the server.
4921+        def _on_success():
4922+            self._testvs = [(0, len(checkstring), "eq", checkstring)]
4923+        def _on_failure():
4924+            self._root_hash = None
4925+        return self._write(datavs,
4926+                           on_success=_on_success,
4927+                           on_failure=_on_failure)
4928+
4929+
4930+    def get_signable(self):
4931+        """
4932+        Get the first seven fields of the mutable file; the parts that
4933+        are signed.
4934+        """
4935+        if not self._root_hash:
4936+            raise LayoutInvalid("You need to set the root hash "
4937+                                "before getting something to "
4938+                                "sign")
4939+        return struct.pack(MDMFSIGNABLEHEADER,
4940+                           1,
4941+                           self._seqnum,
4942+                           self._root_hash,
4943+                           self._required_shares,
4944+                           self._total_shares,
4945+                           self._segment_size,
4946+                           self._data_length)
4947+
4948+
4949+    def put_signature(self, signature):
4950+        """
4951+        Put the signature field to the remote slot.
4952+
4953+        I require that the root hash and share hash chain have been put
4954+        to the grid before I will write the signature to the grid.
4955+        """
4956+        if "signature" not in self._offsets:
4957+            raise LayoutInvalid("You must put the share hash chain "
4958+        # It does not make sense to put a signature without first
4959+        # putting the root hash and the salt hash (since otherwise
4960+        # the signature would be incomplete), so we don't allow that.
4961+                       "before putting the signature")
4962+        if not self._root_hash:
4963+            raise LayoutInvalid("You must complete the signed prefix "
4964+                                "before computing a signature")
4965+        # If we put the signature after we put the verification key, we
4966+        # could end up running into the verification key, and will
4967+        # probably screw up the offsets as well. So we don't allow that.
4968+        # The method that writes the verification key defines the EOF
4969+        # offset before writing the verification key, so look for that.
4970+        if "EOF" in self._offsets:
4971+            raise LayoutInvalid("You must write the signature before the verification key")
4972+
4973+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
4974+        datavs = []
4975+        datavs.append(tuple([self._offsets['signature'], signature]))
4976+        def _on_failure():
4977+            del(self._offsets['verification_key'])
4978+        return self._write(datavs, on_failure=_on_failure)
4979+
4980+
4981+    def put_verification_key(self, verification_key):
4982+        """
4983+        Put the verification key into the remote slot.
4984+
4985+        I require that the signature have been written to the storage
4986+        server before I allow the verification key to be written to the
4987+        remote server.
4988+        """
4989+        if "verification_key" not in self._offsets:
4990+            raise LayoutInvalid("You must put the signature before you "
4991+                                "can put the verification key")
4992+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
4993+        datavs = []
4994+        datavs.append(tuple([self._offsets['verification_key'], verification_key]))
4995+        def _on_failure():
4996+            del(self._offsets['EOF'])
4997+        return self._write(datavs, on_failure=_on_failure)
4998+
4999+    def _get_offsets_tuple(self):
5000+        return tuple([(key, value) for key, value in self._offsets.items()])
5001+
5002+    def get_verinfo(self):
5003+        return (self._seqnum,
5004+                self._root_hash,
5005+                self._required_shares,
5006+                self._total_shares,
5007+                self._segment_size,
5008+                self._data_length,
5009+                self.get_signable(),
5010+                self._get_offsets_tuple())
5011+
5012+
5013+    def finish_publishing(self):
5014+        """
5015+        Write the offset table and encoding parameters to the remote
5016+        slot, since that's the only thing we have yet to publish at this
5017+        point.
5018+        """
5019+        if "EOF" not in self._offsets:
5020+            raise LayoutInvalid("You must put the verification key before "
5021+                                "you can publish the offsets")
5022+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
5023+        offsets = struct.pack(MDMFOFFSETS,
5024+                              self._offsets['enc_privkey'],
5025+                              self._offsets['block_hash_tree'],
5026+                              self._offsets['share_hash_chain'],
5027+                              self._offsets['signature'],
5028+                              self._offsets['verification_key'],
5029+                              self._offsets['EOF'])
5030+        datavs = []
5031+        datavs.append(tuple([offsets_offset, offsets]))
5032+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5033+        params = struct.pack(">BBQQ",
5034+                             self._required_shares,
5035+                             self._total_shares,
5036+                             self._segment_size,
5037+                             self._data_length)
5038+        datavs.append(tuple([encoding_parameters_offset, params]))
5039+        return self._write(datavs)
5040+
5041+
5042+    def _write(self, datavs, on_failure=None, on_success=None):
5043+        """I write the data vectors in datavs to the remote slot."""
5044+        tw_vectors = {}
5045+        new_share = False
5046+        if not self._testvs:
5047+            self._testvs = []
5048+            self._testvs.append(tuple([0, 1, "eq", ""]))
5049+            new_share = True
5050+        if not self._written:
5051+            # Write a new checkstring to the share when we write it, so
5052+            # that we have something to check later.
5053+            new_checkstring = self.get_checkstring()
5054+            datavs.append((0, new_checkstring))
5055+            def _first_write():
5056+                self._written = True
5057+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5058+            on_success = _first_write
5059+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
5060+        datalength = sum([len(x[1]) for x in datavs])
5061+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5062+                                  self._storage_index,
5063+                                  self._secrets,
5064+                                  tw_vectors,
5065+                                  self._readv)
5066+        def _result(results):
5067+            if isinstance(results, failure.Failure) or not results[0]:
5068+                # Do nothing; the write was unsuccessful.
5069+                if on_failure: on_failure()
5070+            else:
5071+                if on_success: on_success()
5072+            return results
5073+        d.addCallback(_result)
5074+        return d
5075+
5076+
5077+class MDMFSlotReadProxy:
5078+    """
5079+    I read from a mutable slot filled with data written in the MDMF data
5080+    format (which is described above).
5081+
5082+    I can be initialized with some amount of data, which I will use (if
5083+    it is valid) to eliminate some of the need to fetch it from servers.
5084+    """
5085+    def __init__(self,
5086+                 rref,
5087+                 storage_index,
5088+                 shnum,
5089+                 data=""):
5090+        # Start the initialization process.
5091+        self._rref = rref
5092+        self._storage_index = storage_index
5093+        self.shnum = shnum
5094+
5095+        # Before doing anything, the reader is probably going to want to
5096+        # verify that the signature is correct. To do that, they'll need
5097+        # the verification key, and the signature. To get those, we'll
5098+        # need the offset table. So fetch the offset table on the
5099+        # assumption that that will be the first thing that a reader is
5100+        # going to do.
5101+
5102+        # The fact that these encoding parameters are None tells us
5103+        # that we haven't yet fetched them from the remote share, so we
5104+        # should. We could just not set them, but the checks will be
5105+        # easier to read if we don't have to use hasattr.
5106+        self._version_number = None
5107+        self._sequence_number = None
5108+        self._root_hash = None
5109+        # Filled in if we're dealing with an SDMF file. Unused
5110+        # otherwise.
5111+        self._salt = None
5112+        self._required_shares = None
5113+        self._total_shares = None
5114+        self._segment_size = None
5115+        self._data_length = None
5116+        self._offsets = None
5117+
5118+        # If the user has chosen to initialize us with some data, we'll
5119+        # try to satisfy subsequent data requests with that data before
5120+        # asking the storage server for it. If
5121+        self._data = data
5122+        # The way callers interact with cache in the filenode returns
5123+        # None if there isn't any cached data, but the way we index the
5124+        # cached data requires a string, so convert None to "".
5125+        if self._data == None:
5126+            self._data = ""
5127+
5128+        self._queue_observers = observer.ObserverList()
5129+        self._queue_errbacks = observer.ObserverList()
5130+        self._readvs = []
5131+
5132+
5133+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5134+        """
5135+        I fetch the offset table and the header from the remote slot if
5136+        I don't already have them. If I do have them, I do nothing and
5137+        return an empty Deferred.
5138+        """
5139+        if self._offsets:
5140+            return defer.succeed(None)
5141+        # At this point, we may be either SDMF or MDMF. Fetching 107
5142+        # bytes will be enough to get header and offsets for both SDMF and
5143+        # MDMF, though we'll be left with 4 more bytes than we
5144+        # need if this ends up being MDMF. This is probably less
5145+        # expensive than the cost of a second roundtrip.
5146+        readvs = [(0, 107)]
5147+        d = self._read(readvs, force_remote)
5148+        d.addCallback(self._process_encoding_parameters)
5149+        d.addCallback(self._process_offsets)
5150+        return d
5151+
5152+
5153+    def _process_encoding_parameters(self, encoding_parameters):
5154+        assert self.shnum in encoding_parameters
5155+        encoding_parameters = encoding_parameters[self.shnum][0]
5156+        # The first byte is the version number. It will tell us what
5157+        # to do next.
5158+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5159+        if verno == MDMF_VERSION:
5160+            read_size = MDMFHEADERWITHOUTOFFSETSSIZE
5161+            (verno,
5162+             seqnum,
5163+             root_hash,
5164+             k,
5165+             n,
5166+             segsize,
5167+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5168+                                      encoding_parameters[:read_size])
5169+            if segsize == 0 and datalen == 0:
5170+                # Empty file, no segments.
5171+                self._num_segments = 0
5172+            else:
5173+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5174+
5175+        elif verno == SDMF_VERSION:
5176+            read_size = SIGNED_PREFIX_LENGTH
5177+            (verno,
5178+             seqnum,
5179+             root_hash,
5180+             salt,
5181+             k,
5182+             n,
5183+             segsize,
5184+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5185+                                encoding_parameters[:SIGNED_PREFIX_LENGTH])
5186+            self._salt = salt
5187+            if segsize == 0 and datalen == 0:
5188+                # empty file
5189+                self._num_segments = 0
5190+            else:
5191+                # non-empty SDMF files have one segment.
5192+                self._num_segments = 1
5193+        else:
5194+            raise UnknownVersionError("You asked me to read mutable file "
5195+                                      "version %d, but I only understand "
5196+                                      "%d and %d" % (verno, SDMF_VERSION,
5197+                                                     MDMF_VERSION))
5198+
5199+        self._version_number = verno
5200+        self._sequence_number = seqnum
5201+        self._root_hash = root_hash
5202+        self._required_shares = k
5203+        self._total_shares = n
5204+        self._segment_size = segsize
5205+        self._data_length = datalen
5206+
5207+        self._block_size = self._segment_size / self._required_shares
5208+        # We can upload empty files, and need to account for this fact
5209+        # so as to avoid zero-division and zero-modulo errors.
5210+        if datalen > 0:
5211+            tail_size = self._data_length % self._segment_size
5212+        else:
5213+            tail_size = 0
5214+        if not tail_size:
5215+            self._tail_block_size = self._block_size
5216+        else:
5217+            self._tail_block_size = mathutil.next_multiple(tail_size,
5218+                                                    self._required_shares)
5219+            self._tail_block_size /= self._required_shares
5220+
5221+        return encoding_parameters
5222+
5223+
5224+    def _process_offsets(self, offsets):
5225+        if self._version_number == 0:
5226+            read_size = OFFSETS_LENGTH
5227+            read_offset = SIGNED_PREFIX_LENGTH
5228+            end = read_size + read_offset
5229+            (signature,
5230+             share_hash_chain,
5231+             block_hash_tree,
5232+             share_data,
5233+             enc_privkey,
5234+             EOF) = struct.unpack(">LLLLQQ",
5235+                                  offsets[read_offset:end])
5236+            self._offsets = {}
5237+            self._offsets['signature'] = signature
5238+            self._offsets['share_data'] = share_data
5239+            self._offsets['block_hash_tree'] = block_hash_tree
5240+            self._offsets['share_hash_chain'] = share_hash_chain
5241+            self._offsets['enc_privkey'] = enc_privkey
5242+            self._offsets['EOF'] = EOF
5243+
5244+        elif self._version_number == 1:
5245+            read_offset = MDMFHEADERWITHOUTOFFSETSSIZE
5246+            read_length = MDMFOFFSETS_LENGTH
5247+            end = read_offset + read_length
5248+            (encprivkey,
5249+             blockhashes,
5250+             sharehashes,
5251+             signature,
5252+             verification_key,
5253+             eof) = struct.unpack(MDMFOFFSETS,
5254+                                  offsets[read_offset:end])
5255+            self._offsets = {}
5256+            self._offsets['enc_privkey'] = encprivkey
5257+            self._offsets['block_hash_tree'] = blockhashes
5258+            self._offsets['share_hash_chain'] = sharehashes
5259+            self._offsets['signature'] = signature
5260+            self._offsets['verification_key'] = verification_key
5261+            self._offsets['EOF'] = eof
5262+
5263+
5264+    def get_block_and_salt(self, segnum, queue=False):
5265+        """
5266+        I return (block, salt), where block is the block data and
5267+        salt is the salt used to encrypt that segment.
5268+        """
5269+        d = self._maybe_fetch_offsets_and_header()
5270+        def _then(ignored):
5271+            if self._version_number == 1:
5272+                base_share_offset = MDMFHEADERSIZE
5273+            else:
5274+                base_share_offset = self._offsets['share_data']
5275+
5276+            if segnum + 1 > self._num_segments:
5277+                raise LayoutInvalid("Not a valid segment number")
5278+
5279+            if self._version_number == 0:
5280+                share_offset = base_share_offset + self._block_size * segnum
5281+            else:
5282+                share_offset = base_share_offset + (self._block_size + \
5283+                                                    SALT_SIZE) * segnum
5284+            if segnum + 1 == self._num_segments:
5285+                data = self._tail_block_size
5286+            else:
5287+                data = self._block_size
5288+
5289+            if self._version_number == 1:
5290+                data += SALT_SIZE
5291+
5292+            readvs = [(share_offset, data)]
5293+            return readvs
5294+        d.addCallback(_then)
5295+        d.addCallback(lambda readvs:
5296+            self._read(readvs, queue=queue))
5297+        def _process_results(results):
5298+            assert self.shnum in results
5299+            if self._version_number == 0:
5300+                # We only read the share data, but we know the salt from
5301+                # when we fetched the header
5302+                data = results[self.shnum]
5303+                if not data:
5304+                    data = ""
5305+                else:
5306+                    assert len(data) == 1
5307+                    data = data[0]
5308+                salt = self._salt
5309+            else:
5310+                data = results[self.shnum]
5311+                if not data:
5312+                    salt = data = ""
5313+                else:
5314+                    salt_and_data = results[self.shnum][0]
5315+                    salt = salt_and_data[:SALT_SIZE]
5316+                    data = salt_and_data[SALT_SIZE:]
5317+            return data, salt
5318+        d.addCallback(_process_results)
5319+        return d
5320+
5321+
5322+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5323+        """
5324+        I return the block hash tree
5325+
5326+        I take an optional argument, needed, which is a set of indices
5327+        correspond to hashes that I should fetch. If this argument is
5328+        missing, I will fetch the entire block hash tree; otherwise, I
5329+        may attempt to fetch fewer hashes, based on what needed says
5330+        that I should do. Note that I may fetch as many hashes as I
5331+        want, so long as the set of hashes that I do fetch is a superset
5332+        of the ones that I am asked for, so callers should be prepared
5333+        to tolerate additional hashes.
5334+        """
5335+        # TODO: Return only the parts of the block hash tree necessary
5336+        # to validate the blocknum provided?
5337+        # This is a good idea, but it is hard to implement correctly. It
5338+        # is bad to fetch any one block hash more than once, so we
5339+        # probably just want to fetch the whole thing at once and then
5340+        # serve it.
5341+        if needed == set([]):
5342+            return defer.succeed([])
5343+        d = self._maybe_fetch_offsets_and_header()
5344+        def _then(ignored):
5345+            blockhashes_offset = self._offsets['block_hash_tree']
5346+            if self._version_number == 1:
5347+                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
5348+            else:
5349+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5350+            readvs = [(blockhashes_offset, blockhashes_length)]
5351+            return readvs
5352+        d.addCallback(_then)
5353+        d.addCallback(lambda readvs:
5354+            self._read(readvs, queue=queue, force_remote=force_remote))
5355+        def _build_block_hash_tree(results):
5356+            assert self.shnum in results
5357+
5358+            rawhashes = results[self.shnum][0]
5359+            results = [rawhashes[i:i+HASH_SIZE]
5360+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5361+            return results
5362+        d.addCallback(_build_block_hash_tree)
5363+        return d
5364+
5365+
5366+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5367+        """
5368+        I return the part of the share hash chain placed to validate
5369+        this share.
5370+
5371+        I take an optional argument, needed. Needed is a set of indices
5372+        that correspond to the hashes that I should fetch. If needed is
5373+        not present, I will fetch and return the entire share hash
5374+        chain. Otherwise, I may fetch and return any part of the share
5375+        hash chain that is a superset of the part that I am asked to
5376+        fetch. Callers should be prepared to deal with more hashes than
5377+        they've asked for.
5378+        """
5379+        if needed == set([]):
5380+            return defer.succeed([])
5381+        d = self._maybe_fetch_offsets_and_header()
5382+
5383+        def _make_readvs(ignored):
5384+            sharehashes_offset = self._offsets['share_hash_chain']
5385+            if self._version_number == 0:
5386+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5387+            else:
5388+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5389+            readvs = [(sharehashes_offset, sharehashes_length)]
5390+            return readvs
5391+        d.addCallback(_make_readvs)
5392+        d.addCallback(lambda readvs:
5393+            self._read(readvs, queue=queue, force_remote=force_remote))
5394+        def _build_share_hash_chain(results):
5395+            assert self.shnum in results
5396+
5397+            sharehashes = results[self.shnum][0]
5398+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5399+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5400+            results = dict([struct.unpack(">H32s", data)
5401+                            for data in results])
5402+            return results
5403+        d.addCallback(_build_share_hash_chain)
5404+        return d
5405+
5406+
5407+    def get_encprivkey(self, queue=False):
5408+        """
5409+        I return the encrypted private key.
5410+        """
5411+        d = self._maybe_fetch_offsets_and_header()
5412+
5413+        def _make_readvs(ignored):
5414+            privkey_offset = self._offsets['enc_privkey']
5415+            if self._version_number == 0:
5416+                privkey_length = self._offsets['EOF'] - privkey_offset
5417+            else:
5418+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5419+            readvs = [(privkey_offset, privkey_length)]
5420+            return readvs
5421+        d.addCallback(_make_readvs)
5422+        d.addCallback(lambda readvs:
5423+            self._read(readvs, queue=queue))
5424+        def _process_results(results):
5425+            assert self.shnum in results
5426+            privkey = results[self.shnum][0]
5427+            return privkey
5428+        d.addCallback(_process_results)
5429+        return d
5430+
5431+
5432+    def get_signature(self, queue=False):
5433+        """
5434+        I return the signature of my share.
5435+        """
5436+        d = self._maybe_fetch_offsets_and_header()
5437+
5438+        def _make_readvs(ignored):
5439+            signature_offset = self._offsets['signature']
5440+            if self._version_number == 1:
5441+                signature_length = self._offsets['verification_key'] - signature_offset
5442+            else:
5443+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5444+            readvs = [(signature_offset, signature_length)]
5445+            return readvs
5446+        d.addCallback(_make_readvs)
5447+        d.addCallback(lambda readvs:
5448+            self._read(readvs, queue=queue))
5449+        def _process_results(results):
5450+            assert self.shnum in results
5451+            signature = results[self.shnum][0]
5452+            return signature
5453+        d.addCallback(_process_results)
5454+        return d
5455+
5456+
5457+    def get_verification_key(self, queue=False):
5458+        """
5459+        I return the verification key.
5460+        """
5461+        d = self._maybe_fetch_offsets_and_header()
5462+
5463+        def _make_readvs(ignored):
5464+            if self._version_number == 1:
5465+                vk_offset = self._offsets['verification_key']
5466+                vk_length = self._offsets['EOF'] - vk_offset
5467+            else:
5468+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5469+                vk_length = self._offsets['signature'] - vk_offset
5470+            readvs = [(vk_offset, vk_length)]
5471+            return readvs
5472+        d.addCallback(_make_readvs)
5473+        d.addCallback(lambda readvs:
5474+            self._read(readvs, queue=queue))
5475+        def _process_results(results):
5476+            assert self.shnum in results
5477+            verification_key = results[self.shnum][0]
5478+            return verification_key
5479+        d.addCallback(_process_results)
5480+        return d
5481+
5482+
5483+    def get_encoding_parameters(self):
5484+        """
5485+        I return (k, n, segsize, datalen)
5486+        """
5487+        d = self._maybe_fetch_offsets_and_header()
5488+        d.addCallback(lambda ignored:
5489+            (self._required_shares,
5490+             self._total_shares,
5491+             self._segment_size,
5492+             self._data_length))
5493+        return d
5494+
5495+
5496+    def get_seqnum(self):
5497+        """
5498+        I return the sequence number for this share.
5499+        """
5500+        d = self._maybe_fetch_offsets_and_header()
5501+        d.addCallback(lambda ignored:
5502+            self._sequence_number)
5503+        return d
5504+
5505+
5506+    def get_root_hash(self):
5507+        """
5508+        I return the root of the block hash tree
5509+        """
5510+        d = self._maybe_fetch_offsets_and_header()
5511+        d.addCallback(lambda ignored: self._root_hash)
5512+        return d
5513+
5514+
5515+    def get_checkstring(self):
5516+        """
5517+        I return the packed representation of the following:
5518+
5519+            - version number
5520+            - sequence number
5521+            - root hash
5522+            - salt hash
5523+
5524+        which my users use as a checkstring to detect other writers.
5525+        """
5526+        d = self._maybe_fetch_offsets_and_header()
5527+        def _build_checkstring(ignored):
5528+            if self._salt:
5529+                checkstring = strut.pack(PREFIX,
5530+                                         self._version_number,
5531+                                         self._sequence_number,
5532+                                         self._root_hash,
5533+                                         self._salt)
5534+            else:
5535+                checkstring = struct.pack(MDMFCHECKSTRING,
5536+                                          self._version_number,
5537+                                          self._sequence_number,
5538+                                          self._root_hash)
5539+
5540+            return checkstring
5541+        d.addCallback(_build_checkstring)
5542+        return d
5543+
5544+
5545+    def get_prefix(self, force_remote):
5546+        d = self._maybe_fetch_offsets_and_header(force_remote)
5547+        d.addCallback(lambda ignored:
5548+            self._build_prefix())
5549+        return d
5550+
5551+
5552+    def _build_prefix(self):
5553+        # The prefix is another name for the part of the remote share
5554+        # that gets signed. It consists of everything up to and
5555+        # including the datalength, packed by struct.
5556+        if self._version_number == SDMF_VERSION:
5557+            return struct.pack(SIGNED_PREFIX,
5558+                           self._version_number,
5559+                           self._sequence_number,
5560+                           self._root_hash,
5561+                           self._salt,
5562+                           self._required_shares,
5563+                           self._total_shares,
5564+                           self._segment_size,
5565+                           self._data_length)
5566+
5567+        else:
5568+            return struct.pack(MDMFSIGNABLEHEADER,
5569+                           self._version_number,
5570+                           self._sequence_number,
5571+                           self._root_hash,
5572+                           self._required_shares,
5573+                           self._total_shares,
5574+                           self._segment_size,
5575+                           self._data_length)
5576+
5577+
5578+    def _get_offsets_tuple(self):
5579+        # The offsets tuple is another component of the version
5580+        # information tuple. It is basically our offsets dictionary,
5581+        # itemized and in a tuple.
5582+        return self._offsets.copy()
5583+
5584+
5585+    def get_verinfo(self):
5586+        """
5587+        I return my verinfo tuple. This is used by the ServermapUpdater
5588+        to keep track of versions of mutable files.
5589+
5590+        The verinfo tuple for MDMF files contains:
5591+            - seqnum
5592+            - root hash
5593+            - a blank (nothing)
5594+            - segsize
5595+            - datalen
5596+            - k
5597+            - n
5598+            - prefix (the thing that you sign)
5599+            - a tuple of offsets
5600+
5601+        We include the nonce in MDMF to simplify processing of version
5602+        information tuples.
5603+
5604+        The verinfo tuple for SDMF files is the same, but contains a
5605+        16-byte IV instead of a hash of salts.
5606+        """
5607+        d = self._maybe_fetch_offsets_and_header()
5608+        def _build_verinfo(ignored):
5609+            if self._version_number == SDMF_VERSION:
5610+                salt_to_use = self._salt
5611+            else:
5612+                salt_to_use = None
5613+            return (self._sequence_number,
5614+                    self._root_hash,
5615+                    salt_to_use,
5616+                    self._segment_size,
5617+                    self._data_length,
5618+                    self._required_shares,
5619+                    self._total_shares,
5620+                    self._build_prefix(),
5621+                    self._get_offsets_tuple())
5622+        d.addCallback(_build_verinfo)
5623+        return d
5624+
5625+
5626+    def flush(self):
5627+        """
5628+        I flush my queue of read vectors.
5629+        """
5630+        d = self._read(self._readvs)
5631+        def _then(results):
5632+            self._readvs = []
5633+            if isinstance(results, failure.Failure):
5634+                self._queue_errbacks.notify(results)
5635+            else:
5636+                self._queue_observers.notify(results)
5637+            self._queue_observers = observer.ObserverList()
5638+            self._queue_errbacks = observer.ObserverList()
5639+        d.addBoth(_then)
5640+
5641+
5642+    def _read(self, readvs, force_remote=False, queue=False):
5643+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
5644+        # TODO: It's entirely possible to tweak this so that it just
5645+        # fulfills the requests that it can, and not demand that all
5646+        # requests are satisfiable before running it.
5647+        if not unsatisfiable and not force_remote:
5648+            results = [self._data[offset:offset+length]
5649+                       for (offset, length) in readvs]
5650+            results = {self.shnum: results}
5651+            return defer.succeed(results)
5652+        else:
5653+            if queue:
5654+                start = len(self._readvs)
5655+                self._readvs += readvs
5656+                end = len(self._readvs)
5657+                def _get_results(results, start, end):
5658+                    if not self.shnum in results:
5659+                        return {self._shnum: [""]}
5660+                    return {self.shnum: results[self.shnum][start:end]}
5661+                d = defer.Deferred()
5662+                d.addCallback(_get_results, start, end)
5663+                self._queue_observers.subscribe(d.callback)
5664+                self._queue_errbacks.subscribe(d.errback)
5665+                return d
5666+            return self._rref.callRemote("slot_readv",
5667+                                         self._storage_index,
5668+                                         [self.shnum],
5669+                                         readvs)
5670+
5671+
5672+    def is_sdmf(self):
5673+        """I tell my caller whether or not my remote file is SDMF or MDMF
5674+        """
5675+        d = self._maybe_fetch_offsets_and_header()
5676+        d.addCallback(lambda ignored:
5677+            self._version_number == 0)
5678+        return d
5679+
5680+
5681+class LayoutInvalid(Exception):
5682+    """
5683+    This isn't a valid MDMF mutable file
5684+    """
5685hunk ./src/allmydata/test/test_storage.py 2
5686 
5687-import time, os.path, stat, re, simplejson, struct
5688+import time, os.path, stat, re, simplejson, struct, shutil
5689 
5690 from twisted.trial import unittest
5691 
5692hunk ./src/allmydata/test/test_storage.py 22
5693 from allmydata.storage.expirer import LeaseCheckingCrawler
5694 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
5695      ReadBucketProxy
5696-from allmydata.interfaces import BadWriteEnablerError
5697-from allmydata.test.common import LoggingServiceParent
5698+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
5699+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
5700+                                     SIGNED_PREFIX, MDMFHEADER, \
5701+                                     MDMFOFFSETS, SDMFSlotWriteProxy
5702+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
5703+                                 SDMF_VERSION
5704+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
5705 from allmydata.test.common_web import WebRenderingMixin
5706 from allmydata.web.storage import StorageStatus, remove_prefix
5707 
5708hunk ./src/allmydata/test/test_storage.py 106
5709 
5710 class RemoteBucket:
5711 
5712+    def __init__(self):
5713+        self.read_count = 0
5714+        self.write_count = 0
5715+
5716     def callRemote(self, methname, *args, **kwargs):
5717         def _call():
5718             meth = getattr(self.target, "remote_" + methname)
5719hunk ./src/allmydata/test/test_storage.py 114
5720             return meth(*args, **kwargs)
5721+
5722+        if methname == "slot_readv":
5723+            self.read_count += 1
5724+        if "writev" in methname:
5725+            self.write_count += 1
5726+
5727         return defer.maybeDeferred(_call)
5728 
5729hunk ./src/allmydata/test/test_storage.py 122
5730+
5731 class BucketProxy(unittest.TestCase):
5732     def make_bucket(self, name, size):
5733         basedir = os.path.join("storage", "BucketProxy", name)
5734hunk ./src/allmydata/test/test_storage.py 1299
5735         self.failUnless(os.path.exists(prefixdir), prefixdir)
5736         self.failIf(os.path.exists(bucketdir), bucketdir)
5737 
5738+
5739+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
5740+    def setUp(self):
5741+        self.sparent = LoggingServiceParent()
5742+        self._lease_secret = itertools.count()
5743+        self.ss = self.create("MDMFProxies storage test server")
5744+        self.rref = RemoteBucket()
5745+        self.rref.target = self.ss
5746+        self.secrets = (self.write_enabler("we_secret"),
5747+                        self.renew_secret("renew_secret"),
5748+                        self.cancel_secret("cancel_secret"))
5749+        self.segment = "aaaaaa"
5750+        self.block = "aa"
5751+        self.salt = "a" * 16
5752+        self.block_hash = "a" * 32
5753+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
5754+        self.share_hash = self.block_hash
5755+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
5756+        self.signature = "foobarbaz"
5757+        self.verification_key = "vvvvvv"
5758+        self.encprivkey = "private"
5759+        self.root_hash = self.block_hash
5760+        self.salt_hash = self.root_hash
5761+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
5762+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
5763+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
5764+        # blockhashes and salt hashes are serialized in the same way,
5765+        # only we lop off the first element and store that in the
5766+        # header.
5767+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
5768+
5769+
5770+    def tearDown(self):
5771+        self.sparent.stopService()
5772+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
5773+
5774+
5775+    def write_enabler(self, we_tag):
5776+        return hashutil.tagged_hash("we_blah", we_tag)
5777+
5778+
5779+    def renew_secret(self, tag):
5780+        return hashutil.tagged_hash("renew_blah", str(tag))
5781+
5782+
5783+    def cancel_secret(self, tag):
5784+        return hashutil.tagged_hash("cancel_blah", str(tag))
5785+
5786+
5787+    def workdir(self, name):
5788+        basedir = os.path.join("storage", "MutableServer", name)
5789+        return basedir
5790+
5791+
5792+    def create(self, name):
5793+        workdir = self.workdir(name)
5794+        ss = StorageServer(workdir, "\x00" * 20)
5795+        ss.setServiceParent(self.sparent)
5796+        return ss
5797+
5798+
5799+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
5800+        # Start with the checkstring
5801+        data = struct.pack(">BQ32s",
5802+                           1,
5803+                           0,
5804+                           self.root_hash)
5805+        self.checkstring = data
5806+        # Next, the encoding parameters
5807+        if tail_segment:
5808+            data += struct.pack(">BBQQ",
5809+                                3,
5810+                                10,
5811+                                6,
5812+                                33)
5813+        elif empty:
5814+            data += struct.pack(">BBQQ",
5815+                                3,
5816+                                10,
5817+                                0,
5818+                                0)
5819+        else:
5820+            data += struct.pack(">BBQQ",
5821+                                3,
5822+                                10,
5823+                                6,
5824+                                36)
5825+        # Now we'll build the offsets.
5826+        sharedata = ""
5827+        if not tail_segment and not empty:
5828+            for i in xrange(6):
5829+                sharedata += self.salt + self.block
5830+        elif tail_segment:
5831+            for i in xrange(5):
5832+                sharedata += self.salt + self.block
5833+            sharedata += self.salt + "a"
5834+
5835+        # The encrypted private key comes after the shares + salts
5836+        offset_size = struct.calcsize(MDMFOFFSETS)
5837+        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
5838+        # The blockhashes come after the private key
5839+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
5840+        # The sharehashes come after the salt hashes
5841+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
5842+        # The signature comes after the share hash chain
5843+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
5844+        # The verification key comes after the signature
5845+        verification_offset = signature_offset + len(self.signature)
5846+        # The EOF comes after the verification key
5847+        eof_offset = verification_offset + len(self.verification_key)
5848+        data += struct.pack(MDMFOFFSETS,
5849+                            encrypted_private_key_offset,
5850+                            blockhashes_offset,
5851+                            sharehashes_offset,
5852+                            signature_offset,
5853+                            verification_offset,
5854+                            eof_offset)
5855+        self.offsets = {}
5856+        self.offsets['enc_privkey'] = encrypted_private_key_offset
5857+        self.offsets['block_hash_tree'] = blockhashes_offset
5858+        self.offsets['share_hash_chain'] = sharehashes_offset
5859+        self.offsets['signature'] = signature_offset
5860+        self.offsets['verification_key'] = verification_offset
5861+        self.offsets['EOF'] = eof_offset
5862+        # Next, we'll add in the salts and share data,
5863+        data += sharedata
5864+        # the private key,
5865+        data += self.encprivkey
5866+        # the block hash tree,
5867+        data += self.block_hash_tree_s
5868+        # the share hash chain,
5869+        data += self.share_hash_chain_s
5870+        # the signature,
5871+        data += self.signature
5872+        # and the verification key
5873+        data += self.verification_key
5874+        return data
5875+
5876+
5877+    def write_test_share_to_server(self,
5878+                                   storage_index,
5879+                                   tail_segment=False,
5880+                                   empty=False):
5881+        """
5882+        I write some data for the read tests to read to self.ss
5883+
5884+        If tail_segment=True, then I will write a share that has a
5885+        smaller tail segment than other segments.
5886+        """
5887+        write = self.ss.remote_slot_testv_and_readv_and_writev
5888+        data = self.build_test_mdmf_share(tail_segment, empty)
5889+        # Finally, we write the whole thing to the storage server in one
5890+        # pass.
5891+        testvs = [(0, 1, "eq", "")]
5892+        tws = {}
5893+        tws[0] = (testvs, [(0, data)], None)
5894+        readv = [(0, 1)]
5895+        results = write(storage_index, self.secrets, tws, readv)
5896+        self.failUnless(results[0])
5897+
5898+
5899+    def build_test_sdmf_share(self, empty=False):
5900+        if empty:
5901+            sharedata = ""
5902+        else:
5903+            sharedata = self.segment * 6
5904+        self.sharedata = sharedata
5905+        blocksize = len(sharedata) / 3
5906+        block = sharedata[:blocksize]
5907+        self.blockdata = block
5908+        prefix = struct.pack(">BQ32s16s BBQQ",
5909+                             0, # version,
5910+                             0,
5911+                             self.root_hash,
5912+                             self.salt,
5913+                             3,
5914+                             10,
5915+                             len(sharedata),
5916+                             len(sharedata),
5917+                            )
5918+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5919+        signature_offset = post_offset + len(self.verification_key)
5920+        sharehashes_offset = signature_offset + len(self.signature)
5921+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
5922+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
5923+        encprivkey_offset = sharedata_offset + len(block)
5924+        eof_offset = encprivkey_offset + len(self.encprivkey)
5925+        offsets = struct.pack(">LLLLQQ",
5926+                              signature_offset,
5927+                              sharehashes_offset,
5928+                              blockhashes_offset,
5929+                              sharedata_offset,
5930+                              encprivkey_offset,
5931+                              eof_offset)
5932+        final_share = "".join([prefix,
5933+                           offsets,
5934+                           self.verification_key,
5935+                           self.signature,
5936+                           self.share_hash_chain_s,
5937+                           self.block_hash_tree_s,
5938+                           block,
5939+                           self.encprivkey])
5940+        self.offsets = {}
5941+        self.offsets['signature'] = signature_offset
5942+        self.offsets['share_hash_chain'] = sharehashes_offset
5943+        self.offsets['block_hash_tree'] = blockhashes_offset
5944+        self.offsets['share_data'] = sharedata_offset
5945+        self.offsets['enc_privkey'] = encprivkey_offset
5946+        self.offsets['EOF'] = eof_offset
5947+        return final_share
5948+
5949+
5950+    def write_sdmf_share_to_server(self,
5951+                                   storage_index,
5952+                                   empty=False):
5953+        # Some tests need SDMF shares to verify that we can still
5954+        # read them. This method writes one, which resembles but is not
5955+        assert self.rref
5956+        write = self.ss.remote_slot_testv_and_readv_and_writev
5957+        share = self.build_test_sdmf_share(empty)
5958+        testvs = [(0, 1, "eq", "")]
5959+        tws = {}
5960+        tws[0] = (testvs, [(0, share)], None)
5961+        readv = []
5962+        results = write(storage_index, self.secrets, tws, readv)
5963+        self.failUnless(results[0])
5964+
5965+
5966+    def test_read(self):
5967+        self.write_test_share_to_server("si1")
5968+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
5969+        # Check that every method equals what we expect it to.
5970+        d = defer.succeed(None)
5971+        def _check_block_and_salt((block, salt)):
5972+            self.failUnlessEqual(block, self.block)
5973+            self.failUnlessEqual(salt, self.salt)
5974+
5975+        for i in xrange(6):
5976+            d.addCallback(lambda ignored, i=i:
5977+                mr.get_block_and_salt(i))
5978+            d.addCallback(_check_block_and_salt)
5979+
5980+        d.addCallback(lambda ignored:
5981+            mr.get_encprivkey())
5982+        d.addCallback(lambda encprivkey:
5983+            self.failUnlessEqual(self.encprivkey, encprivkey))
5984+
5985+        d.addCallback(lambda ignored:
5986+            mr.get_blockhashes())
5987+        d.addCallback(lambda blockhashes:
5988+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
5989+
5990+        d.addCallback(lambda ignored:
5991+            mr.get_sharehashes())
5992+        d.addCallback(lambda sharehashes:
5993+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
5994+
5995+        d.addCallback(lambda ignored:
5996+            mr.get_signature())
5997+        d.addCallback(lambda signature:
5998+            self.failUnlessEqual(signature, self.signature))
5999+
6000+        d.addCallback(lambda ignored:
6001+            mr.get_verification_key())
6002+        d.addCallback(lambda verification_key:
6003+            self.failUnlessEqual(verification_key, self.verification_key))
6004+
6005+        d.addCallback(lambda ignored:
6006+            mr.get_seqnum())
6007+        d.addCallback(lambda seqnum:
6008+            self.failUnlessEqual(seqnum, 0))
6009+
6010+        d.addCallback(lambda ignored:
6011+            mr.get_root_hash())
6012+        d.addCallback(lambda root_hash:
6013+            self.failUnlessEqual(self.root_hash, root_hash))
6014+
6015+        d.addCallback(lambda ignored:
6016+            mr.get_seqnum())
6017+        d.addCallback(lambda seqnum:
6018+            self.failUnlessEqual(0, seqnum))
6019+
6020+        d.addCallback(lambda ignored:
6021+            mr.get_encoding_parameters())
6022+        def _check_encoding_parameters((k, n, segsize, datalen)):
6023+            self.failUnlessEqual(k, 3)
6024+            self.failUnlessEqual(n, 10)
6025+            self.failUnlessEqual(segsize, 6)
6026+            self.failUnlessEqual(datalen, 36)
6027+        d.addCallback(_check_encoding_parameters)
6028+
6029+        d.addCallback(lambda ignored:
6030+            mr.get_checkstring())
6031+        d.addCallback(lambda checkstring:
6032+            self.failUnlessEqual(checkstring, checkstring))
6033+        return d
6034+
6035+
6036+    def test_read_with_different_tail_segment_size(self):
6037+        self.write_test_share_to_server("si1", tail_segment=True)
6038+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6039+        d = mr.get_block_and_salt(5)
6040+        def _check_tail_segment(results):
6041+            block, salt = results
6042+            self.failUnlessEqual(len(block), 1)
6043+            self.failUnlessEqual(block, "a")
6044+        d.addCallback(_check_tail_segment)
6045+        return d
6046+
6047+
6048+    def test_get_block_with_invalid_segnum(self):
6049+        self.write_test_share_to_server("si1")
6050+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6051+        d = defer.succeed(None)
6052+        d.addCallback(lambda ignored:
6053+            self.shouldFail(LayoutInvalid, "test invalid segnum",
6054+                            None,
6055+                            mr.get_block_and_salt, 7))
6056+        return d
6057+
6058+
6059+    def test_get_encoding_parameters_first(self):
6060+        self.write_test_share_to_server("si1")
6061+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6062+        d = mr.get_encoding_parameters()
6063+        def _check_encoding_parameters((k, n, segment_size, datalen)):
6064+            self.failUnlessEqual(k, 3)
6065+            self.failUnlessEqual(n, 10)
6066+            self.failUnlessEqual(segment_size, 6)
6067+            self.failUnlessEqual(datalen, 36)
6068+        d.addCallback(_check_encoding_parameters)
6069+        return d
6070+
6071+
6072+    def test_get_seqnum_first(self):
6073+        self.write_test_share_to_server("si1")
6074+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6075+        d = mr.get_seqnum()
6076+        d.addCallback(lambda seqnum:
6077+            self.failUnlessEqual(seqnum, 0))
6078+        return d
6079+
6080+
6081+    def test_get_root_hash_first(self):
6082+        self.write_test_share_to_server("si1")
6083+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6084+        d = mr.get_root_hash()
6085+        d.addCallback(lambda root_hash:
6086+            self.failUnlessEqual(root_hash, self.root_hash))
6087+        return d
6088+
6089+
6090+    def test_get_checkstring_first(self):
6091+        self.write_test_share_to_server("si1")
6092+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6093+        d = mr.get_checkstring()
6094+        d.addCallback(lambda checkstring:
6095+            self.failUnlessEqual(checkstring, self.checkstring))
6096+        return d
6097+
6098+
6099+    def test_write_read_vectors(self):
6100+        # When writing for us, the storage server will return to us a
6101+        # read vector, along with its result. If a write fails because
6102+        # the test vectors failed, this read vector can help us to
6103+        # diagnose the problem. This test ensures that the read vector
6104+        # is working appropriately.
6105+        mw = self._make_new_mw("si1", 0)
6106+        d = defer.succeed(None)
6107+
6108+        # Write one share. This should return a checkstring of nothing,
6109+        # since there is no data there.
6110+        d.addCallback(lambda ignored:
6111+            mw.put_block(self.block, 0, self.salt))
6112+        def _check_first_write(results):
6113+            result, readvs = results
6114+            self.failUnless(result)
6115+            self.failIf(readvs)
6116+        d.addCallback(_check_first_write)
6117+        # Now, there should be a different checkstring returned when
6118+        # we write other shares
6119+        d.addCallback(lambda ignored:
6120+            mw.put_block(self.block, 1, self.salt))
6121+        def _check_next_write(results):
6122+            result, readvs = results
6123+            self.failUnless(result)
6124+            self.expected_checkstring = mw.get_checkstring()
6125+            self.failUnlessIn(0, readvs)
6126+            self.failUnlessEqual(readvs[0][0], self.expected_checkstring)
6127+        d.addCallback(_check_next_write)
6128+        # Add the other four shares
6129+        for i in xrange(2, 6):
6130+            d.addCallback(lambda ignored, i=i:
6131+                mw.put_block(self.block, i, self.salt))
6132+            d.addCallback(_check_next_write)
6133+        # Add the encrypted private key
6134+        d.addCallback(lambda ignored:
6135+            mw.put_encprivkey(self.encprivkey))
6136+        d.addCallback(_check_next_write)
6137+        # Add the block hash tree and share hash tree
6138+        d.addCallback(lambda ignored:
6139+            mw.put_blockhashes(self.block_hash_tree))
6140+        d.addCallback(_check_next_write)
6141+        d.addCallback(lambda ignored:
6142+            mw.put_sharehashes(self.share_hash_chain))
6143+        d.addCallback(_check_next_write)
6144+        # Add the root hash and the salt hash. This should change the
6145+        # checkstring, but not in a way that we'll be able to see right
6146+        # now, since the read vectors are applied before the write
6147+        # vectors.
6148+        d.addCallback(lambda ignored:
6149+            mw.put_root_hash(self.root_hash))
6150+        def _check_old_testv_after_new_one_is_written(results):
6151+            result, readvs = results
6152+            self.failUnless(result)
6153+            self.failUnlessIn(0, readvs)
6154+            self.failUnlessEqual(self.expected_checkstring,
6155+                                 readvs[0][0])
6156+            new_checkstring = mw.get_checkstring()
6157+            self.failIfEqual(new_checkstring,
6158+                             readvs[0][0])
6159+        d.addCallback(_check_old_testv_after_new_one_is_written)
6160+        # Now add the signature. This should succeed, meaning that the
6161+        # data gets written and the read vector matches what the writer
6162+        # thinks should be there.
6163+        d.addCallback(lambda ignored:
6164+            mw.put_signature(self.signature))
6165+        d.addCallback(_check_next_write)
6166+        # The checkstring remains the same for the rest of the process.
6167+        return d
6168+
6169+
6170+    def test_blockhashes_after_share_hash_chain(self):
6171+        mw = self._make_new_mw("si1", 0)
6172+        d = defer.succeed(None)
6173+        # Put everything up to and including the share hash chain
6174+        for i in xrange(6):
6175+            d.addCallback(lambda ignored, i=i:
6176+                mw.put_block(self.block, i, self.salt))
6177+        d.addCallback(lambda ignored:
6178+            mw.put_encprivkey(self.encprivkey))
6179+        d.addCallback(lambda ignored:
6180+            mw.put_blockhashes(self.block_hash_tree))
6181+        d.addCallback(lambda ignored:
6182+            mw.put_sharehashes(self.share_hash_chain))
6183+
6184+        # Now try to put the block hash tree again.
6185+        d.addCallback(lambda ignored:
6186+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
6187+                            None,
6188+                            mw.put_blockhashes, self.block_hash_tree))
6189+        return d
6190+
6191+
6192+    def test_encprivkey_after_blockhashes(self):
6193+        mw = self._make_new_mw("si1", 0)
6194+        d = defer.succeed(None)
6195+        # Put everything up to and including the block hash tree
6196+        for i in xrange(6):
6197+            d.addCallback(lambda ignored, i=i:
6198+                mw.put_block(self.block, i, self.salt))
6199+        d.addCallback(lambda ignored:
6200+            mw.put_encprivkey(self.encprivkey))
6201+        d.addCallback(lambda ignored:
6202+            mw.put_blockhashes(self.block_hash_tree))
6203+        d.addCallback(lambda ignored:
6204+            self.shouldFail(LayoutInvalid, "out of order private key",
6205+                            None,
6206+                            mw.put_encprivkey, self.encprivkey))
6207+        return d
6208+
6209+
6210+    def test_share_hash_chain_after_signature(self):
6211+        mw = self._make_new_mw("si1", 0)
6212+        d = defer.succeed(None)
6213+        # Put everything up to and including the signature
6214+        for i in xrange(6):
6215+            d.addCallback(lambda ignored, i=i:
6216+                mw.put_block(self.block, i, self.salt))
6217+        d.addCallback(lambda ignored:
6218+            mw.put_encprivkey(self.encprivkey))
6219+        d.addCallback(lambda ignored:
6220+            mw.put_blockhashes(self.block_hash_tree))
6221+        d.addCallback(lambda ignored:
6222+            mw.put_sharehashes(self.share_hash_chain))
6223+        d.addCallback(lambda ignored:
6224+            mw.put_root_hash(self.root_hash))
6225+        d.addCallback(lambda ignored:
6226+            mw.put_signature(self.signature))
6227+        # Now try to put the share hash chain again. This should fail
6228+        d.addCallback(lambda ignored:
6229+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
6230+                            None,
6231+                            mw.put_sharehashes, self.share_hash_chain))
6232+        return d
6233+
6234+
6235+    def test_signature_after_verification_key(self):
6236+        mw = self._make_new_mw("si1", 0)
6237+        d = defer.succeed(None)
6238+        # Put everything up to and including the verification key.
6239+        for i in xrange(6):
6240+            d.addCallback(lambda ignored, i=i:
6241+                mw.put_block(self.block, i, self.salt))
6242+        d.addCallback(lambda ignored:
6243+            mw.put_encprivkey(self.encprivkey))
6244+        d.addCallback(lambda ignored:
6245+            mw.put_blockhashes(self.block_hash_tree))
6246+        d.addCallback(lambda ignored:
6247+            mw.put_sharehashes(self.share_hash_chain))
6248+        d.addCallback(lambda ignored:
6249+            mw.put_root_hash(self.root_hash))
6250+        d.addCallback(lambda ignored:
6251+            mw.put_signature(self.signature))
6252+        d.addCallback(lambda ignored:
6253+            mw.put_verification_key(self.verification_key))
6254+        # Now try to put the signature again. This should fail
6255+        d.addCallback(lambda ignored:
6256+            self.shouldFail(LayoutInvalid, "signature after verification",
6257+                            None,
6258+                            mw.put_signature, self.signature))
6259+        return d
6260+
6261+
6262+    def test_uncoordinated_write(self):
6263+        # Make two mutable writers, both pointing to the same storage
6264+        # server, both at the same storage index, and try writing to the
6265+        # same share.
6266+        mw1 = self._make_new_mw("si1", 0)
6267+        mw2 = self._make_new_mw("si1", 0)
6268+        d = defer.succeed(None)
6269+        def _check_success(results):
6270+            result, readvs = results
6271+            self.failUnless(result)
6272+
6273+        def _check_failure(results):
6274+            result, readvs = results
6275+            self.failIf(result)
6276+
6277+        d.addCallback(lambda ignored:
6278+            mw1.put_block(self.block, 0, self.salt))
6279+        d.addCallback(_check_success)
6280+        d.addCallback(lambda ignored:
6281+            mw2.put_block(self.block, 0, self.salt))
6282+        d.addCallback(_check_failure)
6283+        return d
6284+
6285+
6286+    def test_invalid_salt_size(self):
6287+        # Salts need to be 16 bytes in size. Writes that attempt to
6288+        # write more or less than this should be rejected.
6289+        mw = self._make_new_mw("si1", 0)
6290+        invalid_salt = "a" * 17 # 17 bytes
6291+        another_invalid_salt = "b" * 15 # 15 bytes
6292+        d = defer.succeed(None)
6293+        d.addCallback(lambda ignored:
6294+            self.shouldFail(LayoutInvalid, "salt too big",
6295+                            None,
6296+                            mw.put_block, self.block, 0, invalid_salt))
6297+        d.addCallback(lambda ignored:
6298+            self.shouldFail(LayoutInvalid, "salt too small",
6299+                            None,
6300+                            mw.put_block, self.block, 0,
6301+                            another_invalid_salt))
6302+        return d
6303+
6304+
6305+    def test_write_test_vectors(self):
6306+        # If we give the write proxy a bogus test vector at
6307+        # any point during the process, it should fail to write.
6308+        mw = self._make_new_mw("si1", 0)
6309+        mw.set_checkstring("this is a lie")
6310+        # The initial write should be expecting to find the improbable
6311+        # checkstring above in place; finding nothing, it should fail.
6312+        d = defer.succeed(None)
6313+        d.addCallback(lambda ignored:
6314+            mw.put_block(self.block, 0, self.salt))
6315+        def _check_failure(results):
6316+            result, readv = results
6317+            self.failIf(result)
6318+        d.addCallback(_check_failure)
6319+        # Now set the checkstring to the empty string, which
6320+        # indicates that no share is there.
6321+        d.addCallback(lambda ignored:
6322+            mw.set_checkstring(""))
6323+        d.addCallback(lambda ignored:
6324+            mw.put_block(self.block, 0, self.salt))
6325+        def _check_success(results):
6326+            result, readv = results
6327+            self.failUnless(result)
6328+        d.addCallback(_check_success)
6329+        # Now set the checkstring to something wrong
6330+        d.addCallback(lambda ignored:
6331+            mw.set_checkstring("something wrong"))
6332+        # This should fail to do anything
6333+        d.addCallback(lambda ignored:
6334+            mw.put_block(self.block, 1, self.salt))
6335+        d.addCallback(_check_failure)
6336+        # Now set it back to what it should be.
6337+        d.addCallback(lambda ignored:
6338+            mw.set_checkstring(mw.get_checkstring()))
6339+        for i in xrange(1, 6):
6340+            d.addCallback(lambda ignored, i=i:
6341+                mw.put_block(self.block, i, self.salt))
6342+            d.addCallback(_check_success)
6343+        d.addCallback(lambda ignored:
6344+            mw.put_encprivkey(self.encprivkey))
6345+        d.addCallback(_check_success)
6346+        d.addCallback(lambda ignored:
6347+            mw.put_blockhashes(self.block_hash_tree))
6348+        d.addCallback(_check_success)
6349+        d.addCallback(lambda ignored:
6350+            mw.put_sharehashes(self.share_hash_chain))
6351+        d.addCallback(_check_success)
6352+        def _keep_old_checkstring(ignored):
6353+            self.old_checkstring = mw.get_checkstring()
6354+            mw.set_checkstring("foobarbaz")
6355+        d.addCallback(_keep_old_checkstring)
6356+        d.addCallback(lambda ignored:
6357+            mw.put_root_hash(self.root_hash))
6358+        d.addCallback(_check_failure)
6359+        d.addCallback(lambda ignored:
6360+            self.failUnlessEqual(self.old_checkstring, mw.get_checkstring()))
6361+        def _restore_old_checkstring(ignored):
6362+            mw.set_checkstring(self.old_checkstring)
6363+        d.addCallback(_restore_old_checkstring)
6364+        d.addCallback(lambda ignored:
6365+            mw.put_root_hash(self.root_hash))
6366+        d.addCallback(_check_success)
6367+        # The checkstring should have been set appropriately for us on
6368+        # the last write; if we try to change it to something else,
6369+        # that change should cause the verification key step to fail.
6370+        d.addCallback(lambda ignored:
6371+            mw.set_checkstring("something else"))
6372+        d.addCallback(lambda ignored:
6373+            mw.put_signature(self.signature))
6374+        d.addCallback(_check_failure)
6375+        d.addCallback(lambda ignored:
6376+            mw.set_checkstring(mw.get_checkstring()))
6377+        d.addCallback(lambda ignored:
6378+            mw.put_signature(self.signature))
6379+        d.addCallback(_check_success)
6380+        d.addCallback(lambda ignored:
6381+            mw.put_verification_key(self.verification_key))
6382+        d.addCallback(_check_success)
6383+        return d
6384+
6385+
6386+    def test_offset_only_set_on_success(self):
6387+        # The write proxy should be smart enough to detect when a write
6388+        # has failed, and to temper its definition of progress based on
6389+        # that.
6390+        mw = self._make_new_mw("si1", 0)
6391+        d = defer.succeed(None)
6392+        for i in xrange(1, 6):
6393+            d.addCallback(lambda ignored, i=i:
6394+                mw.put_block(self.block, i, self.salt))
6395+        def _break_checkstring(ignored):
6396+            self._old_checkstring = mw.get_checkstring()
6397+            mw.set_checkstring("foobarbaz")
6398+
6399+        def _fix_checkstring(ignored):
6400+            mw.set_checkstring(self._old_checkstring)
6401+
6402+        d.addCallback(_break_checkstring)
6403+
6404+        # Setting the encrypted private key shouldn't work now, which is
6405+        # to be expected and is tested elsewhere. We also want to make
6406+        # sure that we can't add the block hash tree after a failed
6407+        # write of this sort.
6408+        d.addCallback(lambda ignored:
6409+            mw.put_encprivkey(self.encprivkey))
6410+        d.addCallback(lambda ignored:
6411+            self.shouldFail(LayoutInvalid, "test out-of-order blockhashes",
6412+                            None,
6413+                            mw.put_blockhashes, self.block_hash_tree))
6414+        d.addCallback(_fix_checkstring)
6415+        d.addCallback(lambda ignored:
6416+            mw.put_encprivkey(self.encprivkey))
6417+        d.addCallback(_break_checkstring)
6418+        d.addCallback(lambda ignored:
6419+            mw.put_blockhashes(self.block_hash_tree))
6420+        d.addCallback(lambda ignored:
6421+            self.shouldFail(LayoutInvalid, "test out-of-order sharehashes",
6422+                            None,
6423+                            mw.put_sharehashes, self.share_hash_chain))
6424+        d.addCallback(_fix_checkstring)
6425+        d.addCallback(lambda ignored:
6426+            mw.put_blockhashes(self.block_hash_tree))
6427+        d.addCallback(_break_checkstring)
6428+        d.addCallback(lambda ignored:
6429+            mw.put_sharehashes(self.share_hash_chain))
6430+        d.addCallback(lambda ignored:
6431+            self.shouldFail(LayoutInvalid, "out-of-order root hash",
6432+                            None,
6433+                            mw.put_root_hash, self.root_hash))
6434+        d.addCallback(_fix_checkstring)
6435+        d.addCallback(lambda ignored:
6436+            mw.put_sharehashes(self.share_hash_chain))
6437+        d.addCallback(_break_checkstring)
6438+        d.addCallback(lambda ignored:
6439+            mw.put_root_hash(self.root_hash))
6440+        d.addCallback(lambda ignored:
6441+            self.shouldFail(LayoutInvalid, "out-of-order signature",
6442+                            None,
6443+                            mw.put_signature, self.signature))
6444+        d.addCallback(_fix_checkstring)
6445+        d.addCallback(lambda ignored:
6446+            mw.put_root_hash(self.root_hash))
6447+        d.addCallback(_break_checkstring)
6448+        d.addCallback(lambda ignored:
6449+            mw.put_signature(self.signature))
6450+        d.addCallback(lambda ignored:
6451+            self.shouldFail(LayoutInvalid, "out-of-order verification key",
6452+                            None,
6453+                            mw.put_verification_key,
6454+                            self.verification_key))
6455+        d.addCallback(_fix_checkstring)
6456+        d.addCallback(lambda ignored:
6457+            mw.put_signature(self.signature))
6458+        d.addCallback(_break_checkstring)
6459+        d.addCallback(lambda ignored:
6460+            mw.put_verification_key(self.verification_key))
6461+        d.addCallback(lambda ignored:
6462+            self.shouldFail(LayoutInvalid, "out-of-order finish",
6463+                            None,
6464+                            mw.finish_publishing))
6465+        return d
6466+
6467+
6468+    def serialize_blockhashes(self, blockhashes):
6469+        return "".join(blockhashes)
6470+
6471+
6472+    def serialize_sharehashes(self, sharehashes):
6473+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
6474+                        for i in sorted(sharehashes.keys())])
6475+        return ret
6476+
6477+
6478+    def test_write(self):
6479+        # This translates to a file with 6 6-byte segments, and with 2-byte
6480+        # blocks.
6481+        mw = self._make_new_mw("si1", 0)
6482+        mw2 = self._make_new_mw("si1", 1)
6483+        # Test writing some blocks.
6484+        read = self.ss.remote_slot_readv
6485+        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
6486+        written_block_size = 2 + len(self.salt)
6487+        written_block = self.block + self.salt
6488+        def _check_block_write(i, share):
6489+            self.failUnlessEqual(read("si1", [share], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
6490+                                {share: [written_block]})
6491+        d = defer.succeed(None)
6492+        for i in xrange(6):
6493+            d.addCallback(lambda ignored, i=i:
6494+                mw.put_block(self.block, i, self.salt))
6495+            d.addCallback(lambda ignored, i=i:
6496+                _check_block_write(i, 0))
6497+        # Now try the same thing, but with share 1 instead of share 0.
6498+        for i in xrange(6):
6499+            d.addCallback(lambda ignored, i=i:
6500+                mw2.put_block(self.block, i, self.salt))
6501+            d.addCallback(lambda ignored, i=i:
6502+                _check_block_write(i, 1))
6503+
6504+        # Next, we make a fake encrypted private key, and put it onto the
6505+        # storage server.
6506+        d.addCallback(lambda ignored:
6507+            mw.put_encprivkey(self.encprivkey))
6508+        expected_private_key_offset = expected_sharedata_offset + \
6509+                                      len(written_block) * 6
6510+        self.failUnlessEqual(len(self.encprivkey), 7)
6511+        d.addCallback(lambda ignored:
6512+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6513+                                 {0: [self.encprivkey]}))
6514+
6515+        # Next, we put a fake block hash tree.
6516+        d.addCallback(lambda ignored:
6517+            mw.put_blockhashes(self.block_hash_tree))
6518+        expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6519+        self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6520+        d.addCallback(lambda ignored:
6521+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6522+                                 {0: [self.block_hash_tree_s]}))
6523+
6524+        # Next, put a fake share hash chain
6525+        d.addCallback(lambda ignored:
6526+            mw.put_sharehashes(self.share_hash_chain))
6527+        expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6528+        d.addCallback(lambda ignored:
6529+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6530+                                 {0: [self.share_hash_chain_s]}))
6531+
6532+        # Next, we put what is supposed to be the root hash of
6533+        # our share hash tree but isn't       
6534+        d.addCallback(lambda ignored:
6535+            mw.put_root_hash(self.root_hash))
6536+        # The root hash gets inserted at byte 9 (its position is in the header,
6537+        # and is fixed).
6538+        def _check(ignored):
6539+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
6540+                                 {0: [self.root_hash]})
6541+        d.addCallback(_check)
6542+
6543+        # Next, we put a signature of the header block.
6544+        d.addCallback(lambda ignored:
6545+            mw.put_signature(self.signature))
6546+        expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6547+        self.failUnlessEqual(len(self.signature), 9)
6548+        d.addCallback(lambda ignored:
6549+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6550+                                 {0: [self.signature]}))
6551+
6552+        # Next, we put the verification key
6553+        d.addCallback(lambda ignored:
6554+            mw.put_verification_key(self.verification_key))
6555+        expected_verification_key_offset = expected_signature_offset + len(self.signature)
6556+        self.failUnlessEqual(len(self.verification_key), 6)
6557+        d.addCallback(lambda ignored:
6558+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6559+                                 {0: [self.verification_key]}))
6560+
6561+        def _check_signable(ignored):
6562+            # Make sure that the signable is what we think it should be.
6563+            signable = mw.get_signable()
6564+            verno, seq, roothash, k, n, segsize, datalen = \
6565+                                            struct.unpack(">BQ32sBBQQ",
6566+                                                          signable)
6567+            self.failUnlessEqual(verno, 1)
6568+            self.failUnlessEqual(seq, 0)
6569+            self.failUnlessEqual(roothash, self.root_hash)
6570+            self.failUnlessEqual(k, 3)
6571+            self.failUnlessEqual(n, 10)
6572+            self.failUnlessEqual(segsize, 6)
6573+            self.failUnlessEqual(datalen, 36)
6574+        d.addCallback(_check_signable)
6575+        # Next, we cause the offset table to be published.
6576+        d.addCallback(lambda ignored:
6577+            mw.finish_publishing())
6578+        expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6579+
6580+        def _check_offsets(ignored):
6581+            # Check the version number to make sure that it is correct.
6582+            expected_version_number = struct.pack(">B", 1)
6583+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
6584+                                 {0: [expected_version_number]})
6585+            # Check the sequence number to make sure that it is correct
6586+            expected_sequence_number = struct.pack(">Q", 0)
6587+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
6588+                                 {0: [expected_sequence_number]})
6589+            # Check that the encoding parameters (k, N, segement size, data
6590+            # length) are what they should be. These are  3, 10, 6, 36
6591+            expected_k = struct.pack(">B", 3)
6592+            self.failUnlessEqual(read("si1", [0], [(41, 1)]),
6593+                                 {0: [expected_k]})
6594+            expected_n = struct.pack(">B", 10)
6595+            self.failUnlessEqual(read("si1", [0], [(42, 1)]),
6596+                                 {0: [expected_n]})
6597+            expected_segment_size = struct.pack(">Q", 6)
6598+            self.failUnlessEqual(read("si1", [0], [(43, 8)]),
6599+                                 {0: [expected_segment_size]})
6600+            expected_data_length = struct.pack(">Q", 36)
6601+            self.failUnlessEqual(read("si1", [0], [(51, 8)]),
6602+                                 {0: [expected_data_length]})
6603+            expected_offset = struct.pack(">Q", expected_private_key_offset)
6604+            self.failUnlessEqual(read("si1", [0], [(59, 8)]),
6605+                                 {0: [expected_offset]})
6606+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
6607+            self.failUnlessEqual(read("si1", [0], [(67, 8)]),
6608+                                 {0: [expected_offset]})
6609+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6610+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
6611+                                 {0: [expected_offset]})
6612+            expected_offset = struct.pack(">Q", expected_signature_offset)
6613+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6614+                                 {0: [expected_offset]})
6615+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6616+            self.failUnlessEqual(read("si1", [0], [(91, 8)]),
6617+                                 {0: [expected_offset]})
6618+            expected_offset = struct.pack(">Q", expected_eof_offset)
6619+            self.failUnlessEqual(read("si1", [0], [(99, 8)]),
6620+                                 {0: [expected_offset]})
6621+        d.addCallback(_check_offsets)
6622+        return d
6623+
6624+    def _make_new_mw(self, si, share, datalength=36):
6625+        # This is a file of size 36 bytes. Since it has a segment
6626+        # size of 6, we know that it has 6 byte segments, which will
6627+        # be split into blocks of 2 bytes because our FEC k
6628+        # parameter is 3.
6629+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
6630+                                6, datalength)
6631+        return mw
6632+
6633+
6634+    def test_write_rejected_with_too_many_blocks(self):
6635+        mw = self._make_new_mw("si0", 0)
6636+
6637+        # Try writing too many blocks. We should not be able to write
6638+        # more than 6
6639+        # blocks into each share.
6640+        d = defer.succeed(None)
6641+        for i in xrange(6):
6642+            d.addCallback(lambda ignored, i=i:
6643+                mw.put_block(self.block, i, self.salt))
6644+        d.addCallback(lambda ignored:
6645+            self.shouldFail(LayoutInvalid, "too many blocks",
6646+                            None,
6647+                            mw.put_block, self.block, 7, self.salt))
6648+        return d
6649+
6650+
6651+    def test_write_rejected_with_invalid_salt(self):
6652+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
6653+        # less should cause an error.
6654+        mw = self._make_new_mw("si1", 0)
6655+        bad_salt = "a" * 17 # 17 bytes
6656+        d = defer.succeed(None)
6657+        d.addCallback(lambda ignored:
6658+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
6659+                            None, mw.put_block, self.block, 7, bad_salt))
6660+        return d
6661+
6662+
6663+    def test_write_rejected_with_invalid_root_hash(self):
6664+        # Try writing an invalid root hash. This should be SHA256d, and
6665+        # 32 bytes long as a result.
6666+        mw = self._make_new_mw("si2", 0)
6667+        # 17 bytes != 32 bytes
6668+        invalid_root_hash = "a" * 17
6669+        d = defer.succeed(None)
6670+        # Before this test can work, we need to put some blocks + salts,
6671+        # a block hash tree, and a share hash tree. Otherwise, we'll see
6672+        # failures that match what we are looking for, but are caused by
6673+        # the constraints imposed on operation ordering.
6674+        for i in xrange(6):
6675+            d.addCallback(lambda ignored, i=i:
6676+                mw.put_block(self.block, i, self.salt))
6677+        d.addCallback(lambda ignored:
6678+            mw.put_encprivkey(self.encprivkey))
6679+        d.addCallback(lambda ignored:
6680+            mw.put_blockhashes(self.block_hash_tree))
6681+        d.addCallback(lambda ignored:
6682+            mw.put_sharehashes(self.share_hash_chain))
6683+        d.addCallback(lambda ignored:
6684+            self.shouldFail(LayoutInvalid, "invalid root hash",
6685+                            None, mw.put_root_hash, invalid_root_hash))
6686+        return d
6687+
6688+
6689+    def test_write_rejected_with_invalid_blocksize(self):
6690+        # The blocksize implied by the writer that we get from
6691+        # _make_new_mw is 2bytes -- any more or any less than this
6692+        # should be cause for failure, unless it is the tail segment, in
6693+        # which case it may not be failure.
6694+        invalid_block = "a"
6695+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
6696+                                             # one byte blocks
6697+        # 1 bytes != 2 bytes
6698+        d = defer.succeed(None)
6699+        d.addCallback(lambda ignored, invalid_block=invalid_block:
6700+            self.shouldFail(LayoutInvalid, "test blocksize too small",
6701+                            None, mw.put_block, invalid_block, 0,
6702+                            self.salt))
6703+        invalid_block = invalid_block * 3
6704+        # 3 bytes != 2 bytes
6705+        d.addCallback(lambda ignored:
6706+            self.shouldFail(LayoutInvalid, "test blocksize too large",
6707+                            None,
6708+                            mw.put_block, invalid_block, 0, self.salt))
6709+        for i in xrange(5):
6710+            d.addCallback(lambda ignored, i=i:
6711+                mw.put_block(self.block, i, self.salt))
6712+        # Try to put an invalid tail segment
6713+        d.addCallback(lambda ignored:
6714+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
6715+                            None,
6716+                            mw.put_block, self.block, 5, self.salt))
6717+        valid_block = "a"
6718+        d.addCallback(lambda ignored:
6719+            mw.put_block(valid_block, 5, self.salt))
6720+        return d
6721+
6722+
6723+    def test_write_enforces_order_constraints(self):
6724+        # We require that the MDMFSlotWriteProxy be interacted with in a
6725+        # specific way.
6726+        # That way is:
6727+        # 0: __init__
6728+        # 1: write blocks and salts
6729+        # 2: Write the encrypted private key
6730+        # 3: Write the block hashes
6731+        # 4: Write the share hashes
6732+        # 5: Write the root hash and salt hash
6733+        # 6: Write the signature and verification key
6734+        # 7: Write the file.
6735+        #
6736+        # Some of these can be performed out-of-order, and some can't.
6737+        # The dependencies that I want to test here are:
6738+        #  - Private key before block hashes
6739+        #  - share hashes and block hashes before root hash
6740+        #  - root hash before signature
6741+        #  - signature before verification key
6742+        mw0 = self._make_new_mw("si0", 0)
6743+        # Write some shares
6744+        d = defer.succeed(None)
6745+        for i in xrange(6):
6746+            d.addCallback(lambda ignored, i=i:
6747+                mw0.put_block(self.block, i, self.salt))
6748+        # Try to write the block hashes before writing the encrypted
6749+        # private key
6750+        d.addCallback(lambda ignored:
6751+            self.shouldFail(LayoutInvalid, "block hashes before key",
6752+                            None, mw0.put_blockhashes,
6753+                            self.block_hash_tree))
6754+
6755+        # Write the private key.
6756+        d.addCallback(lambda ignored:
6757+            mw0.put_encprivkey(self.encprivkey))
6758+
6759+
6760+        # Try to write the share hash chain without writing the block
6761+        # hash tree
6762+        d.addCallback(lambda ignored:
6763+            self.shouldFail(LayoutInvalid, "share hash chain before "
6764+                                           "salt hash tree",
6765+                            None,
6766+                            mw0.put_sharehashes, self.share_hash_chain))
6767+
6768+        # Try to write the root hash and without writing either the
6769+        # block hashes or the or the share hashes
6770+        d.addCallback(lambda ignored:
6771+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6772+                            None,
6773+                            mw0.put_root_hash, self.root_hash))
6774+
6775+        # Now write the block hashes and try again
6776+        d.addCallback(lambda ignored:
6777+            mw0.put_blockhashes(self.block_hash_tree))
6778+
6779+        d.addCallback(lambda ignored:
6780+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
6781+                            None, mw0.put_root_hash, self.root_hash))
6782+
6783+        # We haven't yet put the root hash on the share, so we shouldn't
6784+        # be able to sign it.
6785+        d.addCallback(lambda ignored:
6786+            self.shouldFail(LayoutInvalid, "signature before root hash",
6787+                            None, mw0.put_signature, self.signature))
6788+
6789+        d.addCallback(lambda ignored:
6790+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
6791+
6792+        # ..and, since that fails, we also shouldn't be able to put the
6793+        # verification key.
6794+        d.addCallback(lambda ignored:
6795+            self.shouldFail(LayoutInvalid, "key before signature",
6796+                            None, mw0.put_verification_key,
6797+                            self.verification_key))
6798+
6799+        # Now write the share hashes.
6800+        d.addCallback(lambda ignored:
6801+            mw0.put_sharehashes(self.share_hash_chain))
6802+        # We should be able to write the root hash now too
6803+        d.addCallback(lambda ignored:
6804+            mw0.put_root_hash(self.root_hash))
6805+
6806+        # We should still be unable to put the verification key
6807+        d.addCallback(lambda ignored:
6808+            self.shouldFail(LayoutInvalid, "key before signature",
6809+                            None, mw0.put_verification_key,
6810+                            self.verification_key))
6811+
6812+        d.addCallback(lambda ignored:
6813+            mw0.put_signature(self.signature))
6814+
6815+        # We shouldn't be able to write the offsets to the remote server
6816+        # until the offset table is finished; IOW, until we have written
6817+        # the verification key.
6818+        d.addCallback(lambda ignored:
6819+            self.shouldFail(LayoutInvalid, "offsets before verification key",
6820+                            None,
6821+                            mw0.finish_publishing))
6822+
6823+        d.addCallback(lambda ignored:
6824+            mw0.put_verification_key(self.verification_key))
6825+        return d
6826+
6827+
6828+    def test_end_to_end(self):
6829+        mw = self._make_new_mw("si1", 0)
6830+        # Write a share using the mutable writer, and make sure that the
6831+        # reader knows how to read everything back to us.
6832+        d = defer.succeed(None)
6833+        for i in xrange(6):
6834+            d.addCallback(lambda ignored, i=i:
6835+                mw.put_block(self.block, i, self.salt))
6836+        d.addCallback(lambda ignored:
6837+            mw.put_encprivkey(self.encprivkey))
6838+        d.addCallback(lambda ignored:
6839+            mw.put_blockhashes(self.block_hash_tree))
6840+        d.addCallback(lambda ignored:
6841+            mw.put_sharehashes(self.share_hash_chain))
6842+        d.addCallback(lambda ignored:
6843+            mw.put_root_hash(self.root_hash))
6844+        d.addCallback(lambda ignored:
6845+            mw.put_signature(self.signature))
6846+        d.addCallback(lambda ignored:
6847+            mw.put_verification_key(self.verification_key))
6848+        d.addCallback(lambda ignored:
6849+            mw.finish_publishing())
6850+
6851+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6852+        def _check_block_and_salt((block, salt)):
6853+            self.failUnlessEqual(block, self.block)
6854+            self.failUnlessEqual(salt, self.salt)
6855+
6856+        for i in xrange(6):
6857+            d.addCallback(lambda ignored, i=i:
6858+                mr.get_block_and_salt(i))
6859+            d.addCallback(_check_block_and_salt)
6860+
6861+        d.addCallback(lambda ignored:
6862+            mr.get_encprivkey())
6863+        d.addCallback(lambda encprivkey:
6864+            self.failUnlessEqual(self.encprivkey, encprivkey))
6865+
6866+        d.addCallback(lambda ignored:
6867+            mr.get_blockhashes())
6868+        d.addCallback(lambda blockhashes:
6869+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6870+
6871+        d.addCallback(lambda ignored:
6872+            mr.get_sharehashes())
6873+        d.addCallback(lambda sharehashes:
6874+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6875+
6876+        d.addCallback(lambda ignored:
6877+            mr.get_signature())
6878+        d.addCallback(lambda signature:
6879+            self.failUnlessEqual(signature, self.signature))
6880+
6881+        d.addCallback(lambda ignored:
6882+            mr.get_verification_key())
6883+        d.addCallback(lambda verification_key:
6884+            self.failUnlessEqual(verification_key, self.verification_key))
6885+
6886+        d.addCallback(lambda ignored:
6887+            mr.get_seqnum())
6888+        d.addCallback(lambda seqnum:
6889+            self.failUnlessEqual(seqnum, 0))
6890+
6891+        d.addCallback(lambda ignored:
6892+            mr.get_root_hash())
6893+        d.addCallback(lambda root_hash:
6894+            self.failUnlessEqual(self.root_hash, root_hash))
6895+
6896+        d.addCallback(lambda ignored:
6897+            mr.get_encoding_parameters())
6898+        def _check_encoding_parameters((k, n, segsize, datalen)):
6899+            self.failUnlessEqual(k, 3)
6900+            self.failUnlessEqual(n, 10)
6901+            self.failUnlessEqual(segsize, 6)
6902+            self.failUnlessEqual(datalen, 36)
6903+        d.addCallback(_check_encoding_parameters)
6904+
6905+        d.addCallback(lambda ignored:
6906+            mr.get_checkstring())
6907+        d.addCallback(lambda checkstring:
6908+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
6909+        return d
6910+
6911+
6912+    def test_is_sdmf(self):
6913+        # The MDMFSlotReadProxy should also know how to read SDMF files,
6914+        # since it will encounter them on the grid. Callers use the
6915+        # is_sdmf method to test this.
6916+        self.write_sdmf_share_to_server("si1")
6917+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6918+        d = mr.is_sdmf()
6919+        d.addCallback(lambda issdmf:
6920+            self.failUnless(issdmf))
6921+        return d
6922+
6923+
6924+    def test_reads_sdmf(self):
6925+        # The slot read proxy should, naturally, know how to tell us
6926+        # about data in the SDMF format
6927+        self.write_sdmf_share_to_server("si1")
6928+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6929+        d = defer.succeed(None)
6930+        d.addCallback(lambda ignored:
6931+            mr.is_sdmf())
6932+        d.addCallback(lambda issdmf:
6933+            self.failUnless(issdmf))
6934+
6935+        # What do we need to read?
6936+        #  - The sharedata
6937+        #  - The salt
6938+        d.addCallback(lambda ignored:
6939+            mr.get_block_and_salt(0))
6940+        def _check_block_and_salt(results):
6941+            block, salt = results
6942+            # Our original file is 36 bytes long. Then each share is 12
6943+            # bytes in size. The share is composed entirely of the
6944+            # letter a. self.block contains 2 as, so 6 * self.block is
6945+            # what we are looking for.
6946+            self.failUnlessEqual(block, self.block * 6)
6947+            self.failUnlessEqual(salt, self.salt)
6948+        d.addCallback(_check_block_and_salt)
6949+
6950+        #  - The blockhashes
6951+        d.addCallback(lambda ignored:
6952+            mr.get_blockhashes())
6953+        d.addCallback(lambda blockhashes:
6954+            self.failUnlessEqual(self.block_hash_tree,
6955+                                 blockhashes,
6956+                                 blockhashes))
6957+        #  - The sharehashes
6958+        d.addCallback(lambda ignored:
6959+            mr.get_sharehashes())
6960+        d.addCallback(lambda sharehashes:
6961+            self.failUnlessEqual(self.share_hash_chain,
6962+                                 sharehashes))
6963+        #  - The keys
6964+        d.addCallback(lambda ignored:
6965+            mr.get_encprivkey())
6966+        d.addCallback(lambda encprivkey:
6967+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
6968+        d.addCallback(lambda ignored:
6969+            mr.get_verification_key())
6970+        d.addCallback(lambda verification_key:
6971+            self.failUnlessEqual(verification_key,
6972+                                 self.verification_key,
6973+                                 verification_key))
6974+        #  - The signature
6975+        d.addCallback(lambda ignored:
6976+            mr.get_signature())
6977+        d.addCallback(lambda signature:
6978+            self.failUnlessEqual(signature, self.signature, signature))
6979+
6980+        #  - The sequence number
6981+        d.addCallback(lambda ignored:
6982+            mr.get_seqnum())
6983+        d.addCallback(lambda seqnum:
6984+            self.failUnlessEqual(seqnum, 0, seqnum))
6985+
6986+        #  - The root hash
6987+        d.addCallback(lambda ignored:
6988+            mr.get_root_hash())
6989+        d.addCallback(lambda root_hash:
6990+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
6991+        return d
6992+
6993+
6994+    def test_only_reads_one_segment_sdmf(self):
6995+        # SDMF shares have only one segment, so it doesn't make sense to
6996+        # read more segments than that. The reader should know this and
6997+        # complain if we try to do that.
6998+        self.write_sdmf_share_to_server("si1")
6999+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7000+        d = defer.succeed(None)
7001+        d.addCallback(lambda ignored:
7002+            mr.is_sdmf())
7003+        d.addCallback(lambda issdmf:
7004+            self.failUnless(issdmf))
7005+        d.addCallback(lambda ignored:
7006+            self.shouldFail(LayoutInvalid, "test bad segment",
7007+                            None,
7008+                            mr.get_block_and_salt, 1))
7009+        return d
7010+
7011+
7012+    def test_read_with_prefetched_mdmf_data(self):
7013+        # The MDMFSlotReadProxy will prefill certain fields if you pass
7014+        # it data that you have already fetched. This is useful for
7015+        # cases like the Servermap, which prefetches ~2kb of data while
7016+        # finding out which shares are on the remote peer so that it
7017+        # doesn't waste round trips.
7018+        mdmf_data = self.build_test_mdmf_share()
7019+        self.write_test_share_to_server("si1")
7020+        def _make_mr(ignored, length):
7021+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
7022+            return mr
7023+
7024+        d = defer.succeed(None)
7025+        # This should be enough to fill in both the encoding parameters
7026+        # and the table of offsets, which will complete the version
7027+        # information tuple.
7028+        d.addCallback(_make_mr, 107)
7029+        d.addCallback(lambda mr:
7030+            mr.get_verinfo())
7031+        def _check_verinfo(verinfo):
7032+            self.failUnless(verinfo)
7033+            self.failUnlessEqual(len(verinfo), 9)
7034+            (seqnum,
7035+             root_hash,
7036+             salt_hash,
7037+             segsize,
7038+             datalen,
7039+             k,
7040+             n,
7041+             prefix,
7042+             offsets) = verinfo
7043+            self.failUnlessEqual(seqnum, 0)
7044+            self.failUnlessEqual(root_hash, self.root_hash)
7045+            self.failUnlessEqual(segsize, 6)
7046+            self.failUnlessEqual(datalen, 36)
7047+            self.failUnlessEqual(k, 3)
7048+            self.failUnlessEqual(n, 10)
7049+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
7050+                                          1,
7051+                                          seqnum,
7052+                                          root_hash,
7053+                                          k,
7054+                                          n,
7055+                                          segsize,
7056+                                          datalen)
7057+            self.failUnlessEqual(expected_prefix, prefix)
7058+            self.failUnlessEqual(self.rref.read_count, 0)
7059+        d.addCallback(_check_verinfo)
7060+        # This is not enough data to read a block and a share, so the
7061+        # wrapper should attempt to read this from the remote server.
7062+        d.addCallback(_make_mr, 107)
7063+        d.addCallback(lambda mr:
7064+            mr.get_block_and_salt(0))
7065+        def _check_block_and_salt((block, salt)):
7066+            self.failUnlessEqual(block, self.block)
7067+            self.failUnlessEqual(salt, self.salt)
7068+            self.failUnlessEqual(self.rref.read_count, 1)
7069+        # This should be enough data to read one block.
7070+        d.addCallback(_make_mr, 249)
7071+        d.addCallback(lambda mr:
7072+            mr.get_block_and_salt(0))
7073+        d.addCallback(_check_block_and_salt)
7074+        return d
7075+
7076+
7077+    def test_read_with_prefetched_sdmf_data(self):
7078+        sdmf_data = self.build_test_sdmf_share()
7079+        self.write_sdmf_share_to_server("si1")
7080+        def _make_mr(ignored, length):
7081+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
7082+            return mr
7083+
7084+        d = defer.succeed(None)
7085+        # This should be enough to get us the encoding parameters,
7086+        # offset table, and everything else we need to build a verinfo
7087+        # string.
7088+        d.addCallback(_make_mr, 107)
7089+        d.addCallback(lambda mr:
7090+            mr.get_verinfo())
7091+        def _check_verinfo(verinfo):
7092+            self.failUnless(verinfo)
7093+            self.failUnlessEqual(len(verinfo), 9)
7094+            (seqnum,
7095+             root_hash,
7096+             salt,
7097+             segsize,
7098+             datalen,
7099+             k,
7100+             n,
7101+             prefix,
7102+             offsets) = verinfo
7103+            self.failUnlessEqual(seqnum, 0)
7104+            self.failUnlessEqual(root_hash, self.root_hash)
7105+            self.failUnlessEqual(salt, self.salt)
7106+            self.failUnlessEqual(segsize, 36)
7107+            self.failUnlessEqual(datalen, 36)
7108+            self.failUnlessEqual(k, 3)
7109+            self.failUnlessEqual(n, 10)
7110+            expected_prefix = struct.pack(SIGNED_PREFIX,
7111+                                          0,
7112+                                          seqnum,
7113+                                          root_hash,
7114+                                          salt,
7115+                                          k,
7116+                                          n,
7117+                                          segsize,
7118+                                          datalen)
7119+            self.failUnlessEqual(expected_prefix, prefix)
7120+            self.failUnlessEqual(self.rref.read_count, 0)
7121+        d.addCallback(_check_verinfo)
7122+        # This shouldn't be enough to read any share data.
7123+        d.addCallback(_make_mr, 107)
7124+        d.addCallback(lambda mr:
7125+            mr.get_block_and_salt(0))
7126+        def _check_block_and_salt((block, salt)):
7127+            self.failUnlessEqual(block, self.block * 6)
7128+            self.failUnlessEqual(salt, self.salt)
7129+            # TODO: Fix the read routine so that it reads only the data
7130+            #       that it has cached if it can't read all of it.
7131+            self.failUnlessEqual(self.rref.read_count, 2)
7132+
7133+        # This should be enough to read share data.
7134+        d.addCallback(_make_mr, self.offsets['share_data'])
7135+        d.addCallback(lambda mr:
7136+            mr.get_block_and_salt(0))
7137+        d.addCallback(_check_block_and_salt)
7138+        return d
7139+
7140+
7141+    def test_read_with_empty_mdmf_file(self):
7142+        # Some tests upload a file with no contents to test things
7143+        # unrelated to the actual handling of the content of the file.
7144+        # The reader should behave intelligently in these cases.
7145+        self.write_test_share_to_server("si1", empty=True)
7146+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7147+        # We should be able to get the encoding parameters, and they
7148+        # should be correct.
7149+        d = defer.succeed(None)
7150+        d.addCallback(lambda ignored:
7151+            mr.get_encoding_parameters())
7152+        def _check_encoding_parameters(params):
7153+            self.failUnlessEqual(len(params), 4)
7154+            k, n, segsize, datalen = params
7155+            self.failUnlessEqual(k, 3)
7156+            self.failUnlessEqual(n, 10)
7157+            self.failUnlessEqual(segsize, 0)
7158+            self.failUnlessEqual(datalen, 0)
7159+        d.addCallback(_check_encoding_parameters)
7160+
7161+        # We should not be able to fetch a block, since there are no
7162+        # blocks to fetch
7163+        d.addCallback(lambda ignored:
7164+            self.shouldFail(LayoutInvalid, "get block on empty file",
7165+                            None,
7166+                            mr.get_block_and_salt, 0))
7167+        return d
7168+
7169+
7170+    def test_read_with_empty_sdmf_file(self):
7171+        self.write_sdmf_share_to_server("si1", empty=True)
7172+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7173+        # We should be able to get the encoding parameters, and they
7174+        # should be correct
7175+        d = defer.succeed(None)
7176+        d.addCallback(lambda ignored:
7177+            mr.get_encoding_parameters())
7178+        def _check_encoding_parameters(params):
7179+            self.failUnlessEqual(len(params), 4)
7180+            k, n, segsize, datalen = params
7181+            self.failUnlessEqual(k, 3)
7182+            self.failUnlessEqual(n, 10)
7183+            self.failUnlessEqual(segsize, 0)
7184+            self.failUnlessEqual(datalen, 0)
7185+        d.addCallback(_check_encoding_parameters)
7186+
7187+        # It does not make sense to get a block in this format, so we
7188+        # should not be able to.
7189+        d.addCallback(lambda ignored:
7190+            self.shouldFail(LayoutInvalid, "get block on an empty file",
7191+                            None,
7192+                            mr.get_block_and_salt, 0))
7193+        return d
7194+
7195+
7196+    def test_verinfo_with_sdmf_file(self):
7197+        self.write_sdmf_share_to_server("si1")
7198+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7199+        # We should be able to get the version information.
7200+        d = defer.succeed(None)
7201+        d.addCallback(lambda ignored:
7202+            mr.get_verinfo())
7203+        def _check_verinfo(verinfo):
7204+            self.failUnless(verinfo)
7205+            self.failUnlessEqual(len(verinfo), 9)
7206+            (seqnum,
7207+             root_hash,
7208+             salt,
7209+             segsize,
7210+             datalen,
7211+             k,
7212+             n,
7213+             prefix,
7214+             offsets) = verinfo
7215+            self.failUnlessEqual(seqnum, 0)
7216+            self.failUnlessEqual(root_hash, self.root_hash)
7217+            self.failUnlessEqual(salt, self.salt)
7218+            self.failUnlessEqual(segsize, 36)
7219+            self.failUnlessEqual(datalen, 36)
7220+            self.failUnlessEqual(k, 3)
7221+            self.failUnlessEqual(n, 10)
7222+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
7223+                                          0,
7224+                                          seqnum,
7225+                                          root_hash,
7226+                                          salt,
7227+                                          k,
7228+                                          n,
7229+                                          segsize,
7230+                                          datalen)
7231+            self.failUnlessEqual(prefix, expected_prefix)
7232+            self.failUnlessEqual(offsets, self.offsets)
7233+        d.addCallback(_check_verinfo)
7234+        return d
7235+
7236+
7237+    def test_verinfo_with_mdmf_file(self):
7238+        self.write_test_share_to_server("si1")
7239+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7240+        d = defer.succeed(None)
7241+        d.addCallback(lambda ignored:
7242+            mr.get_verinfo())
7243+        def _check_verinfo(verinfo):
7244+            self.failUnless(verinfo)
7245+            self.failUnlessEqual(len(verinfo), 9)
7246+            (seqnum,
7247+             root_hash,
7248+             IV,
7249+             segsize,
7250+             datalen,
7251+             k,
7252+             n,
7253+             prefix,
7254+             offsets) = verinfo
7255+            self.failUnlessEqual(seqnum, 0)
7256+            self.failUnlessEqual(root_hash, self.root_hash)
7257+            self.failIf(IV)
7258+            self.failUnlessEqual(segsize, 6)
7259+            self.failUnlessEqual(datalen, 36)
7260+            self.failUnlessEqual(k, 3)
7261+            self.failUnlessEqual(n, 10)
7262+            expected_prefix = struct.pack(">BQ32s BBQQ",
7263+                                          1,
7264+                                          seqnum,
7265+                                          root_hash,
7266+                                          k,
7267+                                          n,
7268+                                          segsize,
7269+                                          datalen)
7270+            self.failUnlessEqual(prefix, expected_prefix)
7271+            self.failUnlessEqual(offsets, self.offsets)
7272+        d.addCallback(_check_verinfo)
7273+        return d
7274+
7275+
7276+    def test_reader_queue(self):
7277+        self.write_test_share_to_server('si1')
7278+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7279+        d1 = mr.get_block_and_salt(0, queue=True)
7280+        d2 = mr.get_blockhashes(queue=True)
7281+        d3 = mr.get_sharehashes(queue=True)
7282+        d4 = mr.get_signature(queue=True)
7283+        d5 = mr.get_verification_key(queue=True)
7284+        dl = defer.DeferredList([d1, d2, d3, d4, d5])
7285+        mr.flush()
7286+        def _print(results):
7287+            self.failUnlessEqual(len(results), 5)
7288+            # We have one read for version information and offsets, and
7289+            # one for everything else.
7290+            self.failUnlessEqual(self.rref.read_count, 2)
7291+            block, salt = results[0][1] # results[0] is a boolean that says
7292+                                           # whether or not the operation
7293+                                           # worked.
7294+            self.failUnlessEqual(self.block, block)
7295+            self.failUnlessEqual(self.salt, salt)
7296+
7297+            blockhashes = results[1][1]
7298+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
7299+
7300+            sharehashes = results[2][1]
7301+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
7302+
7303+            signature = results[3][1]
7304+            self.failUnlessEqual(self.signature, signature)
7305+
7306+            verification_key = results[4][1]
7307+            self.failUnlessEqual(self.verification_key, verification_key)
7308+        dl.addCallback(_print)
7309+        return dl
7310+
7311+
7312+    def test_sdmf_writer(self):
7313+        # Go through the motions of writing an SDMF share to the storage
7314+        # server. Then read the storage server to see that the share got
7315+        # written in the way that we think it should have.
7316+
7317+        # We do this first so that the necessary instance variables get
7318+        # set the way we want them for the tests below.
7319+        data = self.build_test_sdmf_share()
7320+        sdmfr = SDMFSlotWriteProxy(0,
7321+                                   self.rref,
7322+                                   "si1",
7323+                                   self.secrets,
7324+                                   0, 3, 10, 36, 36)
7325+        # Put the block and salt.
7326+        sdmfr.put_block(self.blockdata, 0, self.salt)
7327+
7328+        # Put the encprivkey
7329+        sdmfr.put_encprivkey(self.encprivkey)
7330+
7331+        # Put the block and share hash chains
7332+        sdmfr.put_blockhashes(self.block_hash_tree)
7333+        sdmfr.put_sharehashes(self.share_hash_chain)
7334+        sdmfr.put_root_hash(self.root_hash)
7335+
7336+        # Put the signature
7337+        sdmfr.put_signature(self.signature)
7338+
7339+        # Put the verification key
7340+        sdmfr.put_verification_key(self.verification_key)
7341+
7342+        # Now check to make sure that nothing has been written yet.
7343+        self.failUnlessEqual(self.rref.write_count, 0)
7344+
7345+        # Now finish publishing
7346+        d = sdmfr.finish_publishing()
7347+        def _then(ignored):
7348+            self.failUnlessEqual(self.rref.write_count, 1)
7349+            read = self.ss.remote_slot_readv
7350+            self.failUnlessEqual(read("si1", [0], [(0, len(data))]),
7351+                                 {0: [data]})
7352+        d.addCallback(_then)
7353+        return d
7354+
7355+
7356+    def test_sdmf_writer_preexisting_share(self):
7357+        data = self.build_test_sdmf_share()
7358+        self.write_sdmf_share_to_server("si1")
7359+
7360+        # Now there is a share on the storage server. To successfully
7361+        # write, we need to set the checkstring correctly. When we
7362+        # don't, no write should occur.
7363+        sdmfw = SDMFSlotWriteProxy(0,
7364+                                   self.rref,
7365+                                   "si1",
7366+                                   self.secrets,
7367+                                   1, 3, 10, 36, 36)
7368+        sdmfw.put_block(self.blockdata, 0, self.salt)
7369+
7370+        # Put the encprivkey
7371+        sdmfw.put_encprivkey(self.encprivkey)
7372+
7373+        # Put the block and share hash chains
7374+        sdmfw.put_blockhashes(self.block_hash_tree)
7375+        sdmfw.put_sharehashes(self.share_hash_chain)
7376+
7377+        # Put the root hash
7378+        sdmfw.put_root_hash(self.root_hash)
7379+
7380+        # Put the signature
7381+        sdmfw.put_signature(self.signature)
7382+
7383+        # Put the verification key
7384+        sdmfw.put_verification_key(self.verification_key)
7385+
7386+        # We shouldn't have a checkstring yet
7387+        self.failUnlessEqual(sdmfw.get_checkstring(), "")
7388+
7389+        d = sdmfw.finish_publishing()
7390+        def _then(results):
7391+            self.failIf(results[0])
7392+            # this is the correct checkstring
7393+            self._expected_checkstring = results[1][0][0]
7394+            return self._expected_checkstring
7395+
7396+        d.addCallback(_then)
7397+        d.addCallback(sdmfw.set_checkstring)
7398+        d.addCallback(lambda ignored:
7399+            sdmfw.get_checkstring())
7400+        d.addCallback(lambda checkstring:
7401+            self.failUnlessEqual(checkstring, self._expected_checkstring))
7402+        d.addCallback(lambda ignored:
7403+            sdmfw.finish_publishing())
7404+        def _then_again(results):
7405+            self.failUnless(results[0])
7406+            read = self.ss.remote_slot_readv
7407+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
7408+                                 {0: [struct.pack(">Q", 1)]})
7409+            self.failUnlessEqual(read("si1", [0], [(9, len(data) - 9)]),
7410+                                 {0: [data[9:]]})
7411+        d.addCallback(_then_again)
7412+        return d
7413+
7414+
7415 class Stats(unittest.TestCase):
7416 
7417     def setUp(self):
7418}
7419[mutable/publish.py: cleanup + simplification
7420Kevan Carstensen <kevan@isnotajoke.com>**20100702225554
7421 Ignore-this: 36a58424ceceffb1ddc55cc5934399e2
7422] {
7423hunk ./src/allmydata/mutable/publish.py 19
7424      UncoordinatedWriteError, NotEnoughServersError
7425 from allmydata.mutable.servermap import ServerMap
7426 from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
7427-     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy
7428+     unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy, \
7429+     SDMFSlotWriteProxy
7430 
7431 KiB = 1024
7432 DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
7433hunk ./src/allmydata/mutable/publish.py 24
7434+PUSHING_BLOCKS_STATE = 0
7435+PUSHING_EVERYTHING_ELSE_STATE = 1
7436+DONE_STATE = 2
7437 
7438 class PublishStatus:
7439     implements(IPublishStatus)
7440hunk ./src/allmydata/mutable/publish.py 229
7441 
7442         self.bad_share_checkstrings = {}
7443 
7444+        # This is set at the last step of the publishing process.
7445+        self.versioninfo = ""
7446+
7447         # we use the servermap to populate the initial goal: this way we will
7448         # try to update each existing share in place.
7449         for (peerid, shnum) in self._servermap.servermap:
7450hunk ./src/allmydata/mutable/publish.py 245
7451             self.bad_share_checkstrings[key] = old_checkstring
7452             self.connections[peerid] = self._servermap.connections[peerid]
7453 
7454-        # Now, the process dovetails -- if this is an SDMF file, we need
7455-        # to write an SDMF file. Otherwise, we need to write an MDMF
7456-        # file.
7457-        if self._version == MDMF_VERSION:
7458-            return self._publish_mdmf()
7459-        else:
7460-            return self._publish_sdmf()
7461-        #return self.done_deferred
7462-
7463-    def _publish_mdmf(self):
7464-        # Next, we find homes for all of the shares that we don't have
7465-        # homes for yet.
7466         # TODO: Make this part do peer selection.
7467         self.update_goal()
7468         self.writers = {}
7469hunk ./src/allmydata/mutable/publish.py 248
7470-        # For each (peerid, shnum) in self.goal, we make an
7471-        # MDMFSlotWriteProxy for that peer. We'll use this to write
7472+        if self._version == MDMF_VERSION:
7473+            writer_class = MDMFSlotWriteProxy
7474+        else:
7475+            writer_class = SDMFSlotWriteProxy
7476+
7477+        # For each (peerid, shnum) in self.goal, we make a
7478+        # write proxy for that peer. We'll use this to write
7479         # shares to the peer.
7480         for key in self.goal:
7481             peerid, shnum = key
7482hunk ./src/allmydata/mutable/publish.py 263
7483             cancel_secret = self._node.get_cancel_secret(peerid)
7484             secrets = (write_enabler, renew_secret, cancel_secret)
7485 
7486-            self.writers[shnum] =  MDMFSlotWriteProxy(shnum,
7487-                                                      self.connections[peerid],
7488-                                                      self._storage_index,
7489-                                                      secrets,
7490-                                                      self._new_seqnum,
7491-                                                      self.required_shares,
7492-                                                      self.total_shares,
7493-                                                      self.segment_size,
7494-                                                      len(self.newdata))
7495+            self.writers[shnum] =  writer_class(shnum,
7496+                                                self.connections[peerid],
7497+                                                self._storage_index,
7498+                                                secrets,
7499+                                                self._new_seqnum,
7500+                                                self.required_shares,
7501+                                                self.total_shares,
7502+                                                self.segment_size,
7503+                                                len(self.newdata))
7504+            self.writers[shnum].peerid = peerid
7505             if (peerid, shnum) in self._servermap.servermap:
7506                 old_versionid, old_timestamp = self._servermap.servermap[key]
7507                 (old_seqnum, old_root_hash, old_salt, old_segsize,
7508hunk ./src/allmydata/mutable/publish.py 278
7509                  old_datalength, old_k, old_N, old_prefix,
7510                  old_offsets_tuple) = old_versionid
7511-                self.writers[shnum].set_checkstring(old_seqnum, old_root_hash)
7512+                self.writers[shnum].set_checkstring(old_seqnum,
7513+                                                    old_root_hash,
7514+                                                    old_salt)
7515+            elif (peerid, shnum) in self.bad_share_checkstrings:
7516+                old_checkstring = self.bad_share_checkstrings[(peerid, shnum)]
7517+                self.writers[shnum].set_checkstring(old_checkstring)
7518+
7519+        # Our remote shares will not have a complete checkstring until
7520+        # after we are done writing share data and have started to write
7521+        # blocks. In the meantime, we need to know what to look for when
7522+        # writing, so that we can detect UncoordinatedWriteErrors.
7523+        self._checkstring = self.writers.values()[0].get_checkstring()
7524 
7525         # Now, we start pushing shares.
7526         self._status.timings["setup"] = time.time() - self._started
7527hunk ./src/allmydata/mutable/publish.py 293
7528-        def _start_pushing(res):
7529-            self._started_pushing = time.time()
7530-            return res
7531-
7532         # First, we encrypt, encode, and publish the shares that we need
7533         # to encrypt, encode, and publish.
7534 
7535hunk ./src/allmydata/mutable/publish.py 306
7536 
7537         d = defer.succeed(None)
7538         self.log("Starting push")
7539-        for i in xrange(self.num_segments - 1):
7540-            d.addCallback(lambda ignored, i=i:
7541-                self.push_segment(i))
7542-            d.addCallback(self._turn_barrier)
7543-        # We have at least one segment, so we will have a tail segment
7544-        if self.num_segments > 0:
7545-            d.addCallback(lambda ignored:
7546-                self.push_tail_segment())
7547-
7548-        d.addCallback(lambda ignored:
7549-            self.push_encprivkey())
7550-        d.addCallback(lambda ignored:
7551-            self.push_blockhashes())
7552-        d.addCallback(lambda ignored:
7553-            self.push_sharehashes())
7554-        d.addCallback(lambda ignored:
7555-            self.push_toplevel_hashes_and_signature())
7556-        d.addCallback(lambda ignored:
7557-            self.finish_publishing())
7558-        return d
7559-
7560-
7561-    def _publish_sdmf(self):
7562-        self._status.timings["setup"] = time.time() - self._started
7563-        self.salt = os.urandom(16)
7564 
7565hunk ./src/allmydata/mutable/publish.py 307
7566-        d = self._encrypt_and_encode()
7567-        d.addCallback(self._generate_shares)
7568-        def _start_pushing(res):
7569-            self._started_pushing = time.time()
7570-            return res
7571-        d.addCallback(_start_pushing)
7572-        d.addCallback(self.loop) # trigger delivery
7573-        d.addErrback(self._fatal_error)
7574+        self._state = PUSHING_BLOCKS_STATE
7575+        self._push()
7576 
7577         return self.done_deferred
7578 
7579hunk ./src/allmydata/mutable/publish.py 327
7580                                                   segment_size)
7581         else:
7582             self.num_segments = 0
7583+
7584+        self.log("building encoding parameters for file")
7585+        self.log("got segsize %d" % self.segment_size)
7586+        self.log("got %d segments" % self.num_segments)
7587+
7588         if self._version == SDMF_VERSION:
7589             assert self.num_segments in (0, 1) # SDMF
7590hunk ./src/allmydata/mutable/publish.py 334
7591-            return
7592         # calculate the tail segment size.
7593hunk ./src/allmydata/mutable/publish.py 335
7594-        self.tail_segment_size = len(self.newdata) % segment_size
7595 
7596hunk ./src/allmydata/mutable/publish.py 336
7597-        if self.tail_segment_size == 0:
7598+        if segment_size and self.newdata:
7599+            self.tail_segment_size = len(self.newdata) % segment_size
7600+        else:
7601+            self.tail_segment_size = 0
7602+
7603+        if self.tail_segment_size == 0 and segment_size:
7604             # The tail segment is the same size as the other segments.
7605             self.tail_segment_size = segment_size
7606 
7607hunk ./src/allmydata/mutable/publish.py 345
7608-        # We'll make an encoder ahead-of-time for the normal-sized
7609-        # segments (defined as any segment of segment_size size.
7610-        # (the part of the code that puts the tail segment will make its
7611-        #  own encoder for that part)
7612+        # Make FEC encoders
7613         fec = codec.CRSEncoder()
7614         fec.set_params(self.segment_size,
7615                        self.required_shares, self.total_shares)
7616hunk ./src/allmydata/mutable/publish.py 352
7617         self.piece_size = fec.get_block_size()
7618         self.fec = fec
7619 
7620+        if self.tail_segment_size == self.segment_size:
7621+            self.tail_fec = self.fec
7622+        else:
7623+            tail_fec = codec.CRSEncoder()
7624+            tail_fec.set_params(self.tail_segment_size,
7625+                                self.required_shares,
7626+                                self.total_shares)
7627+            self.tail_fec = tail_fec
7628+
7629+        self._current_segment = 0
7630+
7631+
7632+    def _push(self, ignored=None):
7633+        """
7634+        I manage state transitions. In particular, I see that we still
7635+        have a good enough number of writers to complete the upload
7636+        successfully.
7637+        """
7638+        # Can we still successfully publish this file?
7639+        # TODO: Keep track of outstanding queries before aborting the
7640+        #       process.
7641+        if len(self.writers) <= self.required_shares or self.surprised:
7642+            return self._failure()
7643+
7644+        # Figure out what we need to do next. Each of these needs to
7645+        # return a deferred so that we don't block execution when this
7646+        # is first called in the upload method.
7647+        if self._state == PUSHING_BLOCKS_STATE:
7648+            return self.push_segment(self._current_segment)
7649+
7650+        # XXX: Do we want more granularity in states? Is that useful at
7651+        #      all?
7652+        #      Yes -- quicker reaction to UCW.
7653+        elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
7654+            return self.push_everything_else()
7655+
7656+        # If we make it to this point, we were successful in placing the
7657+        # file.
7658+        return self._done(None)
7659+
7660 
7661     def push_segment(self, segnum):
7662hunk ./src/allmydata/mutable/publish.py 394
7663+        if self.num_segments == 0 and self._version == SDMF_VERSION:
7664+            self._add_dummy_salts()
7665+
7666+        if segnum == self.num_segments:
7667+            # We don't have any more segments to push.
7668+            self._state = PUSHING_EVERYTHING_ELSE_STATE
7669+            return self._push()
7670+
7671+        d = self._encode_segment(segnum)
7672+        d.addCallback(self._push_segment, segnum)
7673+        def _increment_segnum(ign):
7674+            self._current_segment += 1
7675+        # XXX: I don't think we need to do addBoth here -- any errBacks
7676+        # should be handled within push_segment.
7677+        d.addBoth(_increment_segnum)
7678+        d.addBoth(self._push)
7679+
7680+
7681+    def _add_dummy_salts(self):
7682+        """
7683+        SDMF files need a salt even if they're empty, or the signature
7684+        won't make sense. This method adds a dummy salt to each of our
7685+        SDMF writers so that they can write the signature later.
7686+        """
7687+        salt = os.urandom(16)
7688+        assert self._version == SDMF_VERSION
7689+
7690+        for writer in self.writers.itervalues():
7691+            writer.put_salt(salt)
7692+
7693+
7694+    def _encode_segment(self, segnum):
7695+        """
7696+        I encrypt and encode the segment segnum.
7697+        """
7698         started = time.time()
7699hunk ./src/allmydata/mutable/publish.py 430
7700-        segsize = self.segment_size
7701+
7702+        if segnum + 1 == self.num_segments:
7703+            segsize = self.tail_segment_size
7704+        else:
7705+            segsize = self.segment_size
7706+
7707+
7708+        offset = self.segment_size * segnum
7709+        length = segsize + offset
7710         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
7711hunk ./src/allmydata/mutable/publish.py 440
7712-        data = self.newdata[segsize * segnum:segsize*(segnum + 1)]
7713+        data = self.newdata[offset:length]
7714         assert len(data) == segsize
7715 
7716         salt = os.urandom(16)
7717hunk ./src/allmydata/mutable/publish.py 455
7718         started = now
7719 
7720         # now apply FEC
7721+        if segnum + 1 == self.num_segments:
7722+            fec = self.tail_fec
7723+        else:
7724+            fec = self.fec
7725 
7726         self._status.set_status("Encoding")
7727         crypttext_pieces = [None] * self.required_shares
7728hunk ./src/allmydata/mutable/publish.py 462
7729-        piece_size = self.piece_size
7730+        piece_size = fec.get_block_size()
7731         for i in range(len(crypttext_pieces)):
7732             offset = i * piece_size
7733             piece = crypttext[offset:offset+piece_size]
7734hunk ./src/allmydata/mutable/publish.py 469
7735             piece = piece + "\x00"*(piece_size - len(piece)) # padding
7736             crypttext_pieces[i] = piece
7737             assert len(piece) == piece_size
7738-        d = self.fec.encode(crypttext_pieces)
7739+        d = fec.encode(crypttext_pieces)
7740         def _done_encoding(res):
7741             elapsed = time.time() - started
7742             self._status.timings["encode"] = elapsed
7743hunk ./src/allmydata/mutable/publish.py 473
7744-            return res
7745+            return (res, salt)
7746         d.addCallback(_done_encoding)
7747hunk ./src/allmydata/mutable/publish.py 475
7748-
7749-        def _push_shares_and_salt(results):
7750-            shares, shareids = results
7751-            dl = []
7752-            for i in xrange(len(shares)):
7753-                sharedata = shares[i]
7754-                shareid = shareids[i]
7755-                block_hash = hashutil.block_hash(salt + sharedata)
7756-                self.blockhashes[shareid].append(block_hash)
7757-
7758-                # find the writer for this share
7759-                d = self.writers[shareid].put_block(sharedata, segnum, salt)
7760-                dl.append(d)
7761-            # TODO: Naturally, we need to check on the results of these.
7762-            return defer.DeferredList(dl)
7763-        d.addCallback(_push_shares_and_salt)
7764         return d
7765 
7766 
7767hunk ./src/allmydata/mutable/publish.py 478
7768-    def push_tail_segment(self):
7769-        # This is essentially the same as push_segment, except that we
7770-        # don't use the cached encoder that we use elsewhere.
7771-        self.log("Pushing tail segment")
7772+    def _push_segment(self, encoded_and_salt, segnum):
7773+        """
7774+        I push (data, salt) as segment number segnum.
7775+        """
7776+        results, salt = encoded_and_salt
7777+        shares, shareids = results
7778         started = time.time()
7779hunk ./src/allmydata/mutable/publish.py 485
7780-        segsize = self.segment_size
7781-        data = self.newdata[segsize * (self.num_segments-1):]
7782-        assert len(data) == self.tail_segment_size
7783-        salt = os.urandom(16)
7784-
7785-        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
7786-        enc = AES(key)
7787-        crypttext = enc.process(data)
7788-        assert len(crypttext) == len(data)
7789+        dl = []
7790+        for i in xrange(len(shares)):
7791+            sharedata = shares[i]
7792+            shareid = shareids[i]
7793+            if self._version == MDMF_VERSION:
7794+                hashed = salt + sharedata
7795+            else:
7796+                hashed = sharedata
7797+            block_hash = hashutil.block_hash(hashed)
7798+            self.blockhashes[shareid].append(block_hash)
7799 
7800hunk ./src/allmydata/mutable/publish.py 496
7801-        now = time.time()
7802-        self._status.timings['encrypt'] = now - started
7803-        started = now
7804+            # find the writer for this share
7805+            writer = self.writers[shareid]
7806+            d = writer.put_block(sharedata, segnum, salt)
7807+            d.addCallback(self._got_write_answer, writer, started)
7808+            d.addErrback(self._connection_problem, writer)
7809+            dl.append(d)
7810+            # TODO: Naturally, we need to check on the results of these.
7811+        return defer.DeferredList(dl)
7812 
7813hunk ./src/allmydata/mutable/publish.py 505
7814-        self._status.set_status("Encoding")
7815-        tail_fec = codec.CRSEncoder()
7816-        tail_fec.set_params(self.tail_segment_size,
7817-                            self.required_shares,
7818-                            self.total_shares)
7819 
7820hunk ./src/allmydata/mutable/publish.py 506
7821-        crypttext_pieces = [None] * self.required_shares
7822-        piece_size = tail_fec.get_block_size()
7823-        for i in range(len(crypttext_pieces)):
7824-            offset = i * piece_size
7825-            piece = crypttext[offset:offset+piece_size]
7826-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
7827-            crypttext_pieces[i] = piece
7828-            assert len(piece) == piece_size
7829-        d = tail_fec.encode(crypttext_pieces)
7830-        def _push_shares_and_salt(results):
7831-            shares, shareids = results
7832-            dl = []
7833-            for i in xrange(len(shares)):
7834-                sharedata = shares[i]
7835-                shareid = shareids[i]
7836-                block_hash = hashutil.block_hash(salt + sharedata)
7837-                self.blockhashes[shareid].append(block_hash)
7838-                # find the writer for this share
7839-                d = self.writers[shareid].put_block(sharedata,
7840-                                                    self.num_segments - 1,
7841-                                                    salt)
7842-                dl.append(d)
7843-            # TODO: Naturally, we need to check on the results of these.
7844-            return defer.DeferredList(dl)
7845-        d.addCallback(_push_shares_and_salt)
7846+    def push_everything_else(self):
7847+        """
7848+        I put everything else associated with a share.
7849+        """
7850+        encprivkey = self._encprivkey
7851+        d = self.push_encprivkey()
7852+        d.addCallback(self.push_blockhashes)
7853+        d.addCallback(self.push_sharehashes)
7854+        d.addCallback(self.push_toplevel_hashes_and_signature)
7855+        d.addCallback(self.finish_publishing)
7856+        def _change_state(ignored):
7857+            self._state = DONE_STATE
7858+        d.addCallback(_change_state)
7859+        d.addCallback(self._push)
7860         return d
7861 
7862 
7863hunk ./src/allmydata/mutable/publish.py 527
7864         started = time.time()
7865         encprivkey = self._encprivkey
7866         dl = []
7867-        def _spy_on_writer(results):
7868-            print results
7869-            return results
7870-        for shnum, writer in self.writers.iteritems():
7871+        for writer in self.writers.itervalues():
7872             d = writer.put_encprivkey(encprivkey)
7873hunk ./src/allmydata/mutable/publish.py 529
7874+            d.addCallback(self._got_write_answer, writer, started)
7875+            d.addErrback(self._connection_problem, writer)
7876             dl.append(d)
7877         d = defer.DeferredList(dl)
7878         return d
7879hunk ./src/allmydata/mutable/publish.py 536
7880 
7881 
7882-    def push_blockhashes(self):
7883+    def push_blockhashes(self, ignored):
7884         started = time.time()
7885         dl = []
7886hunk ./src/allmydata/mutable/publish.py 539
7887-        def _spy_on_results(results):
7888-            print results
7889-            return results
7890         self.sharehash_leaves = [None] * len(self.blockhashes)
7891         for shnum, blockhashes in self.blockhashes.iteritems():
7892             t = hashtree.HashTree(blockhashes)
7893hunk ./src/allmydata/mutable/publish.py 545
7894             self.blockhashes[shnum] = list(t)
7895             # set the leaf for future use.
7896             self.sharehash_leaves[shnum] = t[0]
7897-            d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum])
7898+            writer = self.writers[shnum]
7899+            d = writer.put_blockhashes(self.blockhashes[shnum])
7900+            d.addCallback(self._got_write_answer, writer, started)
7901+            d.addErrback(self._connection_problem, self.writers[shnum])
7902             dl.append(d)
7903         d = defer.DeferredList(dl)
7904         return d
7905hunk ./src/allmydata/mutable/publish.py 554
7906 
7907 
7908-    def push_sharehashes(self):
7909+    def push_sharehashes(self, ignored):
7910+        started = time.time()
7911         share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
7912         share_hash_chain = {}
7913         ds = []
7914hunk ./src/allmydata/mutable/publish.py 559
7915-        def _spy_on_results(results):
7916-            print results
7917-            return results
7918         for shnum in xrange(len(self.sharehash_leaves)):
7919             needed_indices = share_hash_tree.needed_hashes(shnum)
7920             self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
7921hunk ./src/allmydata/mutable/publish.py 563
7922                                              for i in needed_indices] )
7923-            d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum])
7924+            writer = self.writers[shnum]
7925+            d = writer.put_sharehashes(self.sharehashes[shnum])
7926+            d.addCallback(self._got_write_answer, writer, started)
7927+            d.addErrback(self._connection_problem, writer)
7928             ds.append(d)
7929         self.root_hash = share_hash_tree[0]
7930         d = defer.DeferredList(ds)
7931hunk ./src/allmydata/mutable/publish.py 573
7932         return d
7933 
7934 
7935-    def push_toplevel_hashes_and_signature(self):
7936+    def push_toplevel_hashes_and_signature(self, ignored):
7937         # We need to to three things here:
7938         #   - Push the root hash and salt hash
7939         #   - Get the checkstring of the resulting layout; sign that.
7940hunk ./src/allmydata/mutable/publish.py 578
7941         #   - Push the signature
7942+        started = time.time()
7943         ds = []
7944hunk ./src/allmydata/mutable/publish.py 580
7945-        def _spy_on_results(results):
7946-            print results
7947-            return results
7948         for shnum in xrange(self.total_shares):
7949hunk ./src/allmydata/mutable/publish.py 581
7950-            d = self.writers[shnum].put_root_hash(self.root_hash)
7951+            writer = self.writers[shnum]
7952+            d = writer.put_root_hash(self.root_hash)
7953+            d.addCallback(self._got_write_answer, writer, started)
7954             ds.append(d)
7955         d = defer.DeferredList(ds)
7956hunk ./src/allmydata/mutable/publish.py 586
7957-        def _make_and_place_signature(ignored):
7958-            signable = self.writers[0].get_signable()
7959-            self.signature = self._privkey.sign(signable)
7960-
7961-            ds = []
7962-            for (shnum, writer) in self.writers.iteritems():
7963-                d = writer.put_signature(self.signature)
7964-                ds.append(d)
7965-            return defer.DeferredList(ds)
7966-        d.addCallback(_make_and_place_signature)
7967+        d.addCallback(self._update_checkstring)
7968+        d.addCallback(self._make_and_place_signature)
7969         return d
7970 
7971 
7972hunk ./src/allmydata/mutable/publish.py 591
7973-    def finish_publishing(self):
7974+    def _update_checkstring(self, ignored):
7975+        """
7976+        After putting the root hash, MDMF files will have the
7977+        checkstring written to the storage server. This means that we
7978+        can update our copy of the checkstring so we can detect
7979+        uncoordinated writes. SDMF files will have the same checkstring,
7980+        so we need not do anything.
7981+        """
7982+        self._checkstring = self.writers.values()[0].get_checkstring()
7983+
7984+
7985+    def _make_and_place_signature(self, ignored):
7986+        """
7987+        I create and place the signature.
7988+        """
7989+        started = time.time()
7990+        signable = self.writers[0].get_signable()
7991+        self.signature = self._privkey.sign(signable)
7992+
7993+        ds = []
7994+        for (shnum, writer) in self.writers.iteritems():
7995+            d = writer.put_signature(self.signature)
7996+            d.addCallback(self._got_write_answer, writer, started)
7997+            d.addErrback(self._connection_problem, writer)
7998+            ds.append(d)
7999+        return defer.DeferredList(ds)
8000+
8001+
8002+    def finish_publishing(self, ignored):
8003         # We're almost done -- we just need to put the verification key
8004         # and the offsets
8005hunk ./src/allmydata/mutable/publish.py 622
8006+        started = time.time()
8007         ds = []
8008         verification_key = self._pubkey.serialize()
8009 
8010hunk ./src/allmydata/mutable/publish.py 626
8011-        def _spy_on_results(results):
8012-            print results
8013-            return results
8014+
8015+        # TODO: Bad, since we remove from this same dict. We need to
8016+        # make a copy, or just use a non-iterated value.
8017         for (shnum, writer) in self.writers.iteritems():
8018             d = writer.put_verification_key(verification_key)
8019hunk ./src/allmydata/mutable/publish.py 631
8020+            d.addCallback(self._got_write_answer, writer, started)
8021+            d.addCallback(self._record_verinfo)
8022             d.addCallback(lambda ignored, writer=writer:
8023                 writer.finish_publishing())
8024hunk ./src/allmydata/mutable/publish.py 635
8025+            d.addCallback(self._got_write_answer, writer, started)
8026+            d.addErrback(self._connection_problem, writer)
8027             ds.append(d)
8028         return defer.DeferredList(ds)
8029 
8030hunk ./src/allmydata/mutable/publish.py 641
8031 
8032-    def _turn_barrier(self, res):
8033-        # putting this method in a Deferred chain imposes a guaranteed
8034-        # reactor turn between the pre- and post- portions of that chain.
8035-        # This can be useful to limit memory consumption: since Deferreds do
8036-        # not do tail recursion, code which uses defer.succeed(result) for
8037-        # consistency will cause objects to live for longer than you might
8038-        # normally expect.
8039-        return fireEventually(res)
8040+    def _record_verinfo(self, ignored):
8041+        self.versioninfo = self.writers.values()[0].get_verinfo()
8042 
8043 
8044hunk ./src/allmydata/mutable/publish.py 645
8045-    def _fatal_error(self, f):
8046-        self.log("error during loop", failure=f, level=log.UNUSUAL)
8047-        self._done(f)
8048+    def _connection_problem(self, f, writer):
8049+        """
8050+        We ran into a connection problem while working with writer, and
8051+        need to deal with that.
8052+        """
8053+        self.log("found problem: %s" % str(f))
8054+        self._last_failure = f
8055+        del(self.writers[writer.shnum])
8056 
8057hunk ./src/allmydata/mutable/publish.py 654
8058-    def _update_status(self):
8059-        self._status.set_status("Sending Shares: %d placed out of %d, "
8060-                                "%d messages outstanding" %
8061-                                (len(self.placed),
8062-                                 len(self.goal),
8063-                                 len(self.outstanding)))
8064-        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
8065 
8066     def loop(self, ignored=None):
8067         self.log("entering loop", level=log.NOISY)
8068hunk ./src/allmydata/mutable/publish.py 778
8069             self.log_goal(self.goal, "after update: ")
8070 
8071 
8072-    def _encrypt_and_encode(self):
8073-        # this returns a Deferred that fires with a list of (sharedata,
8074-        # sharenum) tuples. TODO: cache the ciphertext, only produce the
8075-        # shares that we care about.
8076-        self.log("_encrypt_and_encode")
8077-
8078-        self._status.set_status("Encrypting")
8079-        started = time.time()
8080+    def _got_write_answer(self, answer, writer, started):
8081+        if not answer:
8082+            # SDMF writers only pretend to write when readers set their
8083+            # blocks, salts, and so on -- they actually just write once,
8084+            # at the end of the upload process. In fake writes, they
8085+            # return defer.succeed(None). If we see that, we shouldn't
8086+            # bother checking it.
8087+            return
8088 
8089hunk ./src/allmydata/mutable/publish.py 787
8090-        key = hashutil.ssk_readkey_data_hash(self.salt, self.readkey)
8091-        enc = AES(key)
8092-        crypttext = enc.process(self.newdata)
8093-        assert len(crypttext) == len(self.newdata)
8094+        peerid = writer.peerid
8095+        lp = self.log("_got_write_answer from %s, share %d" %
8096+                      (idlib.shortnodeid_b2a(peerid), writer.shnum))
8097 
8098         now = time.time()
8099hunk ./src/allmydata/mutable/publish.py 792
8100-        self._status.timings["encrypt"] = now - started
8101-        started = now
8102-
8103-        # now apply FEC
8104-
8105-        self._status.set_status("Encoding")
8106-        fec = codec.CRSEncoder()
8107-        fec.set_params(self.segment_size,
8108-                       self.required_shares, self.total_shares)
8109-        piece_size = fec.get_block_size()
8110-        crypttext_pieces = [None] * self.required_shares
8111-        for i in range(len(crypttext_pieces)):
8112-            offset = i * piece_size
8113-            piece = crypttext[offset:offset+piece_size]
8114-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
8115-            crypttext_pieces[i] = piece
8116-            assert len(piece) == piece_size
8117-
8118-        d = fec.encode(crypttext_pieces)
8119-        def _done_encoding(res):
8120-            elapsed = time.time() - started
8121-            self._status.timings["encode"] = elapsed
8122-            return res
8123-        d.addCallback(_done_encoding)
8124-        return d
8125-
8126-
8127-    def _generate_shares(self, shares_and_shareids):
8128-        # this sets self.shares and self.root_hash
8129-        self.log("_generate_shares")
8130-        self._status.set_status("Generating Shares")
8131-        started = time.time()
8132-
8133-        # we should know these by now
8134-        privkey = self._privkey
8135-        encprivkey = self._encprivkey
8136-        pubkey = self._pubkey
8137-
8138-        (shares, share_ids) = shares_and_shareids
8139-
8140-        assert len(shares) == len(share_ids)
8141-        assert len(shares) == self.total_shares
8142-        all_shares = {}
8143-        block_hash_trees = {}
8144-        share_hash_leaves = [None] * len(shares)
8145-        for i in range(len(shares)):
8146-            share_data = shares[i]
8147-            shnum = share_ids[i]
8148-            all_shares[shnum] = share_data
8149-
8150-            # build the block hash tree. SDMF has only one leaf.
8151-            leaves = [hashutil.block_hash(share_data)]
8152-            t = hashtree.HashTree(leaves)
8153-            block_hash_trees[shnum] = list(t)
8154-            share_hash_leaves[shnum] = t[0]
8155-        for leaf in share_hash_leaves:
8156-            assert leaf is not None
8157-        share_hash_tree = hashtree.HashTree(share_hash_leaves)
8158-        share_hash_chain = {}
8159-        for shnum in range(self.total_shares):
8160-            needed_hashes = share_hash_tree.needed_hashes(shnum)
8161-            share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i])
8162-                                              for i in needed_hashes ] )
8163-        root_hash = share_hash_tree[0]
8164-        assert len(root_hash) == 32
8165-        self.log("my new root_hash is %s" % base32.b2a(root_hash))
8166-        self._new_version_info = (self._new_seqnum, root_hash, self.salt)
8167-
8168-        prefix = pack_prefix(self._new_seqnum, root_hash, self.salt,
8169-                             self.required_shares, self.total_shares,
8170-                             self.segment_size, len(self.newdata))
8171-
8172-        # now pack the beginning of the share. All shares are the same up
8173-        # to the signature, then they have divergent share hash chains,
8174-        # then completely different block hash trees + salt + share data,
8175-        # then they all share the same encprivkey at the end. The sizes
8176-        # of everything are the same for all shares.
8177-
8178-        sign_started = time.time()
8179-        signature = privkey.sign(prefix)
8180-        self._status.timings["sign"] = time.time() - sign_started
8181-
8182-        verification_key = pubkey.serialize()
8183-
8184-        final_shares = {}
8185-        for shnum in range(self.total_shares):
8186-            final_share = pack_share(prefix,
8187-                                     verification_key,
8188-                                     signature,
8189-                                     share_hash_chain[shnum],
8190-                                     block_hash_trees[shnum],
8191-                                     all_shares[shnum],
8192-                                     encprivkey)
8193-            final_shares[shnum] = final_share
8194-        elapsed = time.time() - started
8195-        self._status.timings["pack"] = elapsed
8196-        self.shares = final_shares
8197-        self.root_hash = root_hash
8198-
8199-        # we also need to build up the version identifier for what we're
8200-        # pushing. Extract the offsets from one of our shares.
8201-        assert final_shares
8202-        offsets = unpack_header(final_shares.values()[0])[-1]
8203-        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
8204-        verinfo = (self._new_seqnum, root_hash, self.salt,
8205-                   self.segment_size, len(self.newdata),
8206-                   self.required_shares, self.total_shares,
8207-                   prefix, offsets_tuple)
8208-        self.versioninfo = verinfo
8209-
8210-
8211-
8212-    def _send_shares(self, needed):
8213-        self.log("_send_shares")
8214-
8215-        # we're finally ready to send out our shares. If we encounter any
8216-        # surprises here, it's because somebody else is writing at the same
8217-        # time. (Note: in the future, when we remove the _query_peers() step
8218-        # and instead speculate about [or remember] which shares are where,
8219-        # surprises here are *not* indications of UncoordinatedWriteError,
8220-        # and we'll need to respond to them more gracefully.)
8221-
8222-        # needed is a set of (peerid, shnum) tuples. The first thing we do is
8223-        # organize it by peerid.
8224-
8225-        peermap = DictOfSets()
8226-        for (peerid, shnum) in needed:
8227-            peermap.add(peerid, shnum)
8228-
8229-        # the next thing is to build up a bunch of test vectors. The
8230-        # semantics of Publish are that we perform the operation if the world
8231-        # hasn't changed since the ServerMap was constructed (more or less).
8232-        # For every share we're trying to place, we create a test vector that
8233-        # tests to see if the server*share still corresponds to the
8234-        # map.
8235-
8236-        all_tw_vectors = {} # maps peerid to tw_vectors
8237-        sm = self._servermap.servermap
8238-
8239-        for key in needed:
8240-            (peerid, shnum) = key
8241-
8242-            if key in sm:
8243-                # an old version of that share already exists on the
8244-                # server, according to our servermap. We will create a
8245-                # request that attempts to replace it.
8246-                old_versionid, old_timestamp = sm[key]
8247-                (old_seqnum, old_root_hash, old_salt, old_segsize,
8248-                 old_datalength, old_k, old_N, old_prefix,
8249-                 old_offsets_tuple) = old_versionid
8250-                old_checkstring = pack_checkstring(old_seqnum,
8251-                                                   old_root_hash,
8252-                                                   old_salt)
8253-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8254-
8255-            elif key in self.bad_share_checkstrings:
8256-                old_checkstring = self.bad_share_checkstrings[key]
8257-                testv = (0, len(old_checkstring), "eq", old_checkstring)
8258-
8259-            else:
8260-                # add a testv that requires the share not exist
8261-
8262-                # Unfortunately, foolscap-0.2.5 has a bug in the way inbound
8263-                # constraints are handled. If the same object is referenced
8264-                # multiple times inside the arguments, foolscap emits a
8265-                # 'reference' token instead of a distinct copy of the
8266-                # argument. The bug is that these 'reference' tokens are not
8267-                # accepted by the inbound constraint code. To work around
8268-                # this, we need to prevent python from interning the
8269-                # (constant) tuple, by creating a new copy of this vector
8270-                # each time.
8271-
8272-                # This bug is fixed in foolscap-0.2.6, and even though this
8273-                # version of Tahoe requires foolscap-0.3.1 or newer, we are
8274-                # supposed to be able to interoperate with older versions of
8275-                # Tahoe which are allowed to use older versions of foolscap,
8276-                # including foolscap-0.2.5 . In addition, I've seen other
8277-                # foolscap problems triggered by 'reference' tokens (see #541
8278-                # for details). So we must keep this workaround in place.
8279-
8280-                #testv = (0, 1, 'eq', "")
8281-                testv = tuple([0, 1, 'eq', ""])
8282-
8283-            testvs = [testv]
8284-            # the write vector is simply the share
8285-            writev = [(0, self.shares[shnum])]
8286-
8287-            if peerid not in all_tw_vectors:
8288-                all_tw_vectors[peerid] = {}
8289-                # maps shnum to (testvs, writevs, new_length)
8290-            assert shnum not in all_tw_vectors[peerid]
8291-
8292-            all_tw_vectors[peerid][shnum] = (testvs, writev, None)
8293-
8294-        # we read the checkstring back from each share, however we only use
8295-        # it to detect whether there was a new share that we didn't know
8296-        # about. The success or failure of the write will tell us whether
8297-        # there was a collision or not. If there is a collision, the first
8298-        # thing we'll do is update the servermap, which will find out what
8299-        # happened. We could conceivably reduce a roundtrip by using the
8300-        # readv checkstring to populate the servermap, but really we'd have
8301-        # to read enough data to validate the signatures too, so it wouldn't
8302-        # be an overall win.
8303-        read_vector = [(0, struct.calcsize(SIGNED_PREFIX))]
8304-
8305-        # ok, send the messages!
8306-        self.log("sending %d shares" % len(all_tw_vectors), level=log.NOISY)
8307-        started = time.time()
8308-        for (peerid, tw_vectors) in all_tw_vectors.items():
8309-
8310-            write_enabler = self._node.get_write_enabler(peerid)
8311-            renew_secret = self._node.get_renewal_secret(peerid)
8312-            cancel_secret = self._node.get_cancel_secret(peerid)
8313-            secrets = (write_enabler, renew_secret, cancel_secret)
8314-            shnums = tw_vectors.keys()
8315-
8316-            for shnum in shnums:
8317-                self.outstanding.add( (peerid, shnum) )
8318-
8319-            d = self._do_testreadwrite(peerid, secrets,
8320-                                       tw_vectors, read_vector)
8321-            d.addCallbacks(self._got_write_answer, self._got_write_error,
8322-                           callbackArgs=(peerid, shnums, started),
8323-                           errbackArgs=(peerid, shnums, started))
8324-            # tolerate immediate errback, like with DeadReferenceError
8325-            d.addBoth(fireEventually)
8326-            d.addCallback(self.loop)
8327-            d.addErrback(self._fatal_error)
8328-
8329-        self._update_status()
8330-        self.log("%d shares sent" % len(all_tw_vectors), level=log.NOISY)
8331+        elapsed = now - started
8332 
8333hunk ./src/allmydata/mutable/publish.py 794
8334-    def _do_testreadwrite(self, peerid, secrets,
8335-                          tw_vectors, read_vector):
8336-        storage_index = self._storage_index
8337-        ss = self.connections[peerid]
8338+        self._status.add_per_server_time(peerid, elapsed)
8339 
8340hunk ./src/allmydata/mutable/publish.py 796
8341-        #print "SS[%s] is %s" % (idlib.shortnodeid_b2a(peerid), ss), ss.tracker.interfaceName
8342-        d = ss.callRemote("slot_testv_and_readv_and_writev",
8343-                          storage_index,
8344-                          secrets,
8345-                          tw_vectors,
8346-                          read_vector)
8347-        return d
8348+        wrote, read_data = answer
8349 
8350hunk ./src/allmydata/mutable/publish.py 798
8351-    def _got_write_answer(self, answer, peerid, shnums, started):
8352-        lp = self.log("_got_write_answer from %s" %
8353-                      idlib.shortnodeid_b2a(peerid))
8354-        for shnum in shnums:
8355-            self.outstanding.discard( (peerid, shnum) )
8356+        surprise_shares = set(read_data.keys()) - set([writer.shnum])
8357 
8358hunk ./src/allmydata/mutable/publish.py 800
8359-        now = time.time()
8360-        elapsed = now - started
8361-        self._status.add_per_server_time(peerid, elapsed)
8362+        # We need to remove from surprise_shares any shares that we are
8363+        # knowingly also writing to that peer from other writers.
8364 
8365hunk ./src/allmydata/mutable/publish.py 803
8366-        wrote, read_data = answer
8367+        # TODO: Precompute this.
8368+        known_shnums = [x.shnum for x in self.writers.values()
8369+                        if x.peerid == peerid]
8370+        surprise_shares -= set(known_shnums)
8371+        self.log("found the following surprise shares: %s" %
8372+                 str(surprise_shares))
8373 
8374hunk ./src/allmydata/mutable/publish.py 810
8375-        surprise_shares = set(read_data.keys()) - set(shnums)
8376+        # Now surprise shares contains all of the shares that we did not
8377+        # expect to be there.
8378 
8379         surprised = False
8380         for shnum in surprise_shares:
8381hunk ./src/allmydata/mutable/publish.py 817
8382             # read_data is a dict mapping shnum to checkstring (SIGNED_PREFIX)
8383             checkstring = read_data[shnum][0]
8384-            their_version_info = unpack_checkstring(checkstring)
8385-            if their_version_info == self._new_version_info:
8386+            # What we want to do here is to see if their (seqnum,
8387+            # roothash, salt) is the same as our (seqnum, roothash,
8388+            # salt), or the equivalent for MDMF. The best way to do this
8389+            # is to store a packed representation of our checkstring
8390+            # somewhere, then not bother unpacking the other
8391+            # checkstring.
8392+            if checkstring == self._checkstring:
8393                 # they have the right share, somehow
8394 
8395                 if (peerid,shnum) in self.goal:
8396hunk ./src/allmydata/mutable/publish.py 902
8397             self.log("our testv failed, so the write did not happen",
8398                      parent=lp, level=log.WEIRD, umid="8sc26g")
8399             self.surprised = True
8400-            self.bad_peers.add(peerid) # don't ask them again
8401+            # TODO: This needs to
8402+            self.bad_peers.add(writer) # don't ask them again
8403             # use the checkstring to add information to the log message
8404             for (shnum,readv) in read_data.items():
8405                 checkstring = readv[0]
8406hunk ./src/allmydata/mutable/publish.py 928
8407             # self.loop() will take care of finding new homes
8408             return
8409 
8410-        for shnum in shnums:
8411-            self.placed.add( (peerid, shnum) )
8412-            # and update the servermap
8413-            self._servermap.add_new_share(peerid, shnum,
8414+        # and update the servermap
8415+        # self.versioninfo is set during the last phase of publishing.
8416+        # If we get there, we know that responses correspond to placed
8417+        # shares, and can safely execute these statements.
8418+        if self.versioninfo:
8419+            self.log("wrote successfully: adding new share to servermap")
8420+            self._servermap.add_new_share(peerid, writer.shnum,
8421                                           self.versioninfo, started)
8422hunk ./src/allmydata/mutable/publish.py 936
8423-
8424-        # self.loop() will take care of checking to see if we're done
8425-        return
8426+            self.placed.add( (peerid, writer.shnum) )
8427 
8428hunk ./src/allmydata/mutable/publish.py 938
8429-    def _got_write_error(self, f, peerid, shnums, started):
8430-        for shnum in shnums:
8431-            self.outstanding.discard( (peerid, shnum) )
8432-        self.bad_peers.add(peerid)
8433-        if self._first_write_error is None:
8434-            self._first_write_error = f
8435-        self.log(format="error while writing shares %(shnums)s to peerid %(peerid)s",
8436-                 shnums=list(shnums), peerid=idlib.shortnodeid_b2a(peerid),
8437-                 failure=f,
8438-                 level=log.UNUSUAL)
8439         # self.loop() will take care of checking to see if we're done
8440         return
8441 
8442hunk ./src/allmydata/mutable/publish.py 949
8443         now = time.time()
8444         self._status.timings["total"] = now - self._started
8445         self._status.set_active(False)
8446-        if isinstance(res, failure.Failure):
8447-            self.log("Publish done, with failure", failure=res,
8448-                     level=log.WEIRD, umid="nRsR9Q")
8449-            self._status.set_status("Failed")
8450-        elif self.surprised:
8451-            self.log("Publish done, UncoordinatedWriteError", level=log.UNUSUAL)
8452-            self._status.set_status("UncoordinatedWriteError")
8453-            # deliver a failure
8454-            res = failure.Failure(UncoordinatedWriteError())
8455-            # TODO: recovery
8456-        else:
8457-            self.log("Publish done, success")
8458-            self._status.set_status("Finished")
8459-            self._status.set_progress(1.0)
8460+        self.log("Publish done, success")
8461+        self._status.set_status("Finished")
8462+        self._status.set_progress(1.0)
8463         eventually(self.done_deferred.callback, res)
8464 
8465hunk ./src/allmydata/mutable/publish.py 954
8466+    def _failure(self):
8467+
8468+        if not self.surprised:
8469+            # We ran out of servers
8470+            self.log("Publish ran out of good servers, "
8471+                     "last failure was: %s" % str(self._last_failure))
8472+            e = NotEnoughServersError("Ran out of non-bad servers, "
8473+                                      "last failure was %s" %
8474+                                      str(self._last_failure))
8475+        else:
8476+            # We ran into shares that we didn't recognize, which means
8477+            # that we need to return an UncoordinatedWriteError.
8478+            self.log("Publish failed with UncoordinatedWriteError")
8479+            e = UncoordinatedWriteError()
8480+        f = failure.Failure(e)
8481+        eventually(self.done_deferred.callback, f)
8482}
8483[test/test_mutable.py: remove tests that are no longer relevant
8484Kevan Carstensen <kevan@isnotajoke.com>**20100702225710
8485 Ignore-this: 90a26b4cc4b2e190a635474ba7097e21
8486] hunk ./src/allmydata/test/test_mutable.py 627
8487         return d
8488 
8489 
8490-class MakeShares(unittest.TestCase):
8491-    def test_encrypt(self):
8492-        nm = make_nodemaker()
8493-        CONTENTS = "some initial contents"
8494-        d = nm.create_mutable_file(CONTENTS)
8495-        def _created(fn):
8496-            p = Publish(fn, nm.storage_broker, None)
8497-            p.salt = "SALT" * 4
8498-            p.readkey = "\x00" * 16
8499-            p.newdata = CONTENTS
8500-            p.required_shares = 3
8501-            p.total_shares = 10
8502-            p.setup_encoding_parameters()
8503-            return p._encrypt_and_encode()
8504-        d.addCallback(_created)
8505-        def _done(shares_and_shareids):
8506-            (shares, share_ids) = shares_and_shareids
8507-            self.failUnlessEqual(len(shares), 10)
8508-            for sh in shares:
8509-                self.failUnless(isinstance(sh, str))
8510-                self.failUnlessEqual(len(sh), 7)
8511-            self.failUnlessEqual(len(share_ids), 10)
8512-        d.addCallback(_done)
8513-        return d
8514-    test_encrypt.todo = "Write an equivalent of this for the new uploader"
8515-
8516-    def test_generate(self):
8517-        nm = make_nodemaker()
8518-        CONTENTS = "some initial contents"
8519-        d = nm.create_mutable_file(CONTENTS)
8520-        def _created(fn):
8521-            self._fn = fn
8522-            p = Publish(fn, nm.storage_broker, None)
8523-            self._p = p
8524-            p.newdata = CONTENTS
8525-            p.required_shares = 3
8526-            p.total_shares = 10
8527-            p.setup_encoding_parameters()
8528-            p._new_seqnum = 3
8529-            p.salt = "SALT" * 4
8530-            # make some fake shares
8531-            shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
8532-            p._privkey = fn.get_privkey()
8533-            p._encprivkey = fn.get_encprivkey()
8534-            p._pubkey = fn.get_pubkey()
8535-            return p._generate_shares(shares_and_ids)
8536-        d.addCallback(_created)
8537-        def _generated(res):
8538-            p = self._p
8539-            final_shares = p.shares
8540-            root_hash = p.root_hash
8541-            self.failUnlessEqual(len(root_hash), 32)
8542-            self.failUnless(isinstance(final_shares, dict))
8543-            self.failUnlessEqual(len(final_shares), 10)
8544-            self.failUnlessEqual(sorted(final_shares.keys()), range(10))
8545-            for i,sh in final_shares.items():
8546-                self.failUnless(isinstance(sh, str))
8547-                # feed the share through the unpacker as a sanity-check
8548-                pieces = unpack_share(sh)
8549-                (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
8550-                 pubkey, signature, share_hash_chain, block_hash_tree,
8551-                 share_data, enc_privkey) = pieces
8552-                self.failUnlessEqual(u_seqnum, 3)
8553-                self.failUnlessEqual(u_root_hash, root_hash)
8554-                self.failUnlessEqual(k, 3)
8555-                self.failUnlessEqual(N, 10)
8556-                self.failUnlessEqual(segsize, 21)
8557-                self.failUnlessEqual(datalen, len(CONTENTS))
8558-                self.failUnlessEqual(pubkey, p._pubkey.serialize())
8559-                sig_material = struct.pack(">BQ32s16s BBQQ",
8560-                                           0, p._new_seqnum, root_hash, IV,
8561-                                           k, N, segsize, datalen)
8562-                self.failUnless(p._pubkey.verify(sig_material, signature))
8563-                #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
8564-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
8565-                for shnum,share_hash in share_hash_chain.items():
8566-                    self.failUnless(isinstance(shnum, int))
8567-                    self.failUnless(isinstance(share_hash, str))
8568-                    self.failUnlessEqual(len(share_hash), 32)
8569-                self.failUnless(isinstance(block_hash_tree, list))
8570-                self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
8571-                self.failUnlessEqual(IV, "SALT"*4)
8572-                self.failUnlessEqual(len(share_data), len("%07d" % 1))
8573-                self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
8574-        d.addCallback(_generated)
8575-        return d
8576-    test_generate.todo = "Write an equivalent of this for the new uploader"
8577-
8578-    # TODO: when we publish to 20 peers, we should get one share per peer on 10
8579-    # when we publish to 3 peers, we should get either 3 or 4 shares per peer
8580-    # when we publish to zero peers, we should get a NotEnoughSharesError
8581-
8582 class PublishMixin:
8583     def publish_one(self):
8584         # publish a file and create shares, which can then be manipulated
8585[interfaces.py: create IMutableUploadable
8586Kevan Carstensen <kevan@isnotajoke.com>**20100706215217
8587 Ignore-this: bee202ec2bfbd8e41f2d4019cce176c7
8588] hunk ./src/allmydata/interfaces.py 1693
8589         """The upload is finished, and whatever filehandle was in use may be
8590         closed."""
8591 
8592+
8593+class IMutableUploadable(Interface):
8594+    """
8595+    I represent content that is due to be uploaded to a mutable filecap.
8596+    """
8597+    # This is somewhat simpler than the IUploadable interface above
8598+    # because mutable files do not need to be concerned with possibly
8599+    # generating a CHK, nor with per-file keys. It is a subset of the
8600+    # methods in IUploadable, though, so we could just as well implement
8601+    # the mutable uploadables as IUploadables that don't happen to use
8602+    # those methods (with the understanding that the unused methods will
8603+    # never be called on such objects)
8604+    def get_size():
8605+        """
8606+        Returns a Deferred that fires with the size of the content held
8607+        by the uploadable.
8608+        """
8609+
8610+    def read(length):
8611+        """
8612+        Returns a list of strings which, when concatenated, are the next
8613+        length bytes of the file, or fewer if there are fewer bytes
8614+        between the current location and the end of the file.
8615+        """
8616+
8617+    def close():
8618+        """
8619+        The process that used the Uploadable is finished using it, so
8620+        the uploadable may be closed.
8621+        """
8622+
8623 class IUploadResults(Interface):
8624     """I am returned by upload() methods. I contain a number of public
8625     attributes which can be read to determine the results of the upload. Some
8626[mutable/publish.py: add MutableDataHandle and MutableFileHandle
8627Kevan Carstensen <kevan@isnotajoke.com>**20100706215257
8628 Ignore-this: 295ea3bc2a962fd14fb7877fc76c011c
8629] {
8630hunk ./src/allmydata/mutable/publish.py 8
8631 from zope.interface import implements
8632 from twisted.internet import defer
8633 from twisted.python import failure
8634-from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION
8635+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
8636+                                 IMutableUploadable
8637 from allmydata.util import base32, hashutil, mathutil, idlib, log
8638 from allmydata import hashtree, codec
8639 from allmydata.storage.server import si_b2a
8640hunk ./src/allmydata/mutable/publish.py 971
8641             e = UncoordinatedWriteError()
8642         f = failure.Failure(e)
8643         eventually(self.done_deferred.callback, f)
8644+
8645+
8646+class MutableFileHandle:
8647+    """
8648+    I am a mutable uploadable built around a filehandle-like object,
8649+    usually either a StringIO instance or a handle to an actual file.
8650+    """
8651+    implements(IMutableUploadable)
8652+
8653+    def __init__(self, filehandle):
8654+        # The filehandle is defined as a generally file-like object that
8655+        # has these two methods. We don't care beyond that.
8656+        assert hasattr(filehandle, "read")
8657+        assert hasattr(filehandle, "close")
8658+
8659+        self._filehandle = filehandle
8660+
8661+
8662+    def get_size(self):
8663+        """
8664+        I return the amount of data in my filehandle.
8665+        """
8666+        if not hasattr(self, "_size"):
8667+            old_position = self._filehandle.tell()
8668+            # Seek to the end of the file by seeking 0 bytes from the
8669+            # file's end
8670+            self._filehandle.seek(0, os.SEEK_END)
8671+            self._size = self._filehandle.tell()
8672+            # Restore the previous position, in case this was called
8673+            # after a read.
8674+            self._filehandle.seek(old_position)
8675+            assert self._filehandle.tell() == old_position
8676+
8677+        assert hasattr(self, "_size")
8678+        return self._size
8679+
8680+
8681+    def read(self, length):
8682+        """
8683+        I return some data (up to length bytes) from my filehandle.
8684+
8685+        In most cases, I return length bytes. If I don't, it is because
8686+        length is longer than the distance between my current position
8687+        in the file that I represent and its end. In that case, I return
8688+        as many bytes as I can before going over the EOF.
8689+        """
8690+        return [self._filehandle.read(length)]
8691+
8692+
8693+    def close(self):
8694+        """
8695+        I close the underlying filehandle. Any further operations on the
8696+        filehandle fail at this point.
8697+        """
8698+        self._filehandle.close()
8699+
8700+
8701+class MutableDataHandle(MutableFileHandle):
8702+    """
8703+    I am a mutable uploadable built around a string, which I then cast
8704+    into a StringIO and treat as a filehandle.
8705+    """
8706+
8707+    def __init__(self, s):
8708+        # Take a string and return a file-like uploadable.
8709+        assert isinstance(s, str)
8710+
8711+        MutableFileHandle.__init__(self, StringIO(s))
8712}
8713[mutable/publish.py: reorganize in preparation of file-like uploadables
8714Kevan Carstensen <kevan@isnotajoke.com>**20100706215541
8715 Ignore-this: 5346c9f919ee5b73807c8f287c64e8ce
8716] {
8717hunk ./src/allmydata/mutable/publish.py 4
8718 
8719 
8720 import os, struct, time
8721+from StringIO import StringIO
8722 from itertools import count
8723 from zope.interface import implements
8724 from twisted.internet import defer
8725hunk ./src/allmydata/mutable/publish.py 118
8726         self._status.set_helper(False)
8727         self._status.set_progress(0.0)
8728         self._status.set_active(True)
8729-        # We use this to control how the file is written.
8730-        version = self._node.get_version()
8731-        assert version in (SDMF_VERSION, MDMF_VERSION)
8732-        self._version = version
8733+        self._version = self._node.get_version()
8734+        assert self._version in (SDMF_VERSION, MDMF_VERSION)
8735+
8736 
8737     def get_status(self):
8738         return self._status
8739hunk ./src/allmydata/mutable/publish.py 141
8740 
8741         # 0. Setup encoding parameters, encoder, and other such things.
8742         # 1. Encrypt, encode, and publish segments.
8743+        self.data = StringIO(newdata)
8744+        self.datalength = len(newdata)
8745 
8746hunk ./src/allmydata/mutable/publish.py 144
8747-        self.log("starting publish, datalen is %s" % len(newdata))
8748-        self._status.set_size(len(newdata))
8749+        self.log("starting publish, datalen is %s" % self.datalength)
8750+        self._status.set_size(self.datalength)
8751         self._status.set_status("Started")
8752         self._started = time.time()
8753 
8754hunk ./src/allmydata/mutable/publish.py 193
8755         self.full_peerlist = full_peerlist # for use later, immutable
8756         self.bad_peers = set() # peerids who have errbacked/refused requests
8757 
8758-        self.newdata = newdata
8759-
8760         # This will set self.segment_size, self.num_segments, and
8761         # self.fec.
8762         self.setup_encoding_parameters()
8763hunk ./src/allmydata/mutable/publish.py 272
8764                                                 self.required_shares,
8765                                                 self.total_shares,
8766                                                 self.segment_size,
8767-                                                len(self.newdata))
8768+                                                self.datalength)
8769             self.writers[shnum].peerid = peerid
8770             if (peerid, shnum) in self._servermap.servermap:
8771                 old_versionid, old_timestamp = self._servermap.servermap[key]
8772hunk ./src/allmydata/mutable/publish.py 318
8773         if self._version == MDMF_VERSION:
8774             segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
8775         else:
8776-            segment_size = len(self.newdata) # SDMF is only one segment
8777+            segment_size = self.datalength # SDMF is only one segment
8778         # this must be a multiple of self.required_shares
8779         segment_size = mathutil.next_multiple(segment_size,
8780                                               self.required_shares)
8781hunk ./src/allmydata/mutable/publish.py 324
8782         self.segment_size = segment_size
8783         if segment_size:
8784-            self.num_segments = mathutil.div_ceil(len(self.newdata),
8785+            self.num_segments = mathutil.div_ceil(self.datalength,
8786                                                   segment_size)
8787         else:
8788             self.num_segments = 0
8789hunk ./src/allmydata/mutable/publish.py 337
8790             assert self.num_segments in (0, 1) # SDMF
8791         # calculate the tail segment size.
8792 
8793-        if segment_size and self.newdata:
8794-            self.tail_segment_size = len(self.newdata) % segment_size
8795+        if segment_size and self.datalength:
8796+            self.tail_segment_size = self.datalength % segment_size
8797         else:
8798             self.tail_segment_size = 0
8799 
8800hunk ./src/allmydata/mutable/publish.py 438
8801             segsize = self.segment_size
8802 
8803 
8804-        offset = self.segment_size * segnum
8805-        length = segsize + offset
8806         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
8807hunk ./src/allmydata/mutable/publish.py 439
8808-        data = self.newdata[offset:length]
8809+        data = self.data.read(segsize)
8810+
8811         assert len(data) == segsize
8812 
8813         salt = os.urandom(16)
8814hunk ./src/allmydata/mutable/publish.py 502
8815             d.addCallback(self._got_write_answer, writer, started)
8816             d.addErrback(self._connection_problem, writer)
8817             dl.append(d)
8818-            # TODO: Naturally, we need to check on the results of these.
8819         return defer.DeferredList(dl)
8820 
8821 
8822}
8823[test/test_mutable.py: write tests for MutableFileHandle and MutableDataHandle
8824Kevan Carstensen <kevan@isnotajoke.com>**20100706215649
8825 Ignore-this: df719a0c52b4bbe9be4fae206c7ab3e7
8826] {
8827hunk ./src/allmydata/test/test_mutable.py 2
8828 
8829-import struct
8830+import struct, os
8831 from cStringIO import StringIO
8832 from twisted.trial import unittest
8833 from twisted.internet import defer, reactor
8834hunk ./src/allmydata/test/test_mutable.py 26
8835      NeedMoreDataError, UnrecoverableFileError, UncoordinatedWriteError, \
8836      NotEnoughServersError, CorruptShareError
8837 from allmydata.mutable.retrieve import Retrieve
8838-from allmydata.mutable.publish import Publish
8839+from allmydata.mutable.publish import Publish, MutableFileHandle, \
8840+                                      MutableDataHandle
8841 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
8842 from allmydata.mutable.layout import unpack_header, unpack_share, \
8843                                      MDMFSlotReadProxy
8844hunk ./src/allmydata/test/test_mutable.py 2465
8845         d.addCallback(lambda data:
8846             self.failUnlessEqual(data, CONTENTS))
8847         return d
8848+
8849+
8850+class FileHandle(unittest.TestCase):
8851+    def setUp(self):
8852+        self.test_data = "Test Data" * 50000
8853+        self.sio = StringIO(self.test_data)
8854+        self.uploadable = MutableFileHandle(self.sio)
8855+
8856+
8857+    def test_filehandle_read(self):
8858+        self.basedir = "mutable/FileHandle/test_filehandle_read"
8859+        chunk_size = 10
8860+        for i in xrange(0, len(self.test_data), chunk_size):
8861+            data = self.uploadable.read(chunk_size)
8862+            data = "".join(data)
8863+            start = i
8864+            end = i + chunk_size
8865+            self.failUnlessEqual(data, self.test_data[start:end])
8866+
8867+
8868+    def test_filehandle_get_size(self):
8869+        self.basedir = "mutable/FileHandle/test_filehandle_get_size"
8870+        actual_size = len(self.test_data)
8871+        size = self.uploadable.get_size()
8872+        self.failUnlessEqual(size, actual_size)
8873+
8874+
8875+    def test_filehandle_get_size_out_of_order(self):
8876+        # We should be able to call get_size whenever we want without
8877+        # disturbing the location of the seek pointer.
8878+        chunk_size = 100
8879+        data = self.uploadable.read(chunk_size)
8880+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8881+
8882+        # Now get the size.
8883+        size = self.uploadable.get_size()
8884+        self.failUnlessEqual(size, len(self.test_data))
8885+
8886+        # Now get more data. We should be right where we left off.
8887+        more_data = self.uploadable.read(chunk_size)
8888+        start = chunk_size
8889+        end = chunk_size * 2
8890+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8891+
8892+
8893+    def test_filehandle_file(self):
8894+        # Make sure that the MutableFileHandle works on a file as well
8895+        # as a StringIO object, since in some cases it will be asked to
8896+        # deal with files.
8897+        self.basedir = self.mktemp()
8898+        # necessary? What am I doing wrong here?
8899+        os.mkdir(self.basedir)
8900+        f_path = os.path.join(self.basedir, "test_file")
8901+        f = open(f_path, "w")
8902+        f.write(self.test_data)
8903+        f.close()
8904+        f = open(f_path, "r")
8905+
8906+        uploadable = MutableFileHandle(f)
8907+
8908+        data = uploadable.read(len(self.test_data))
8909+        self.failUnlessEqual("".join(data), self.test_data)
8910+        size = uploadable.get_size()
8911+        self.failUnlessEqual(size, len(self.test_data))
8912+
8913+
8914+    def test_close(self):
8915+        # Make sure that the MutableFileHandle closes its handle when
8916+        # told to do so.
8917+        self.uploadable.close()
8918+        self.failUnless(self.sio.closed)
8919+
8920+
8921+class DataHandle(unittest.TestCase):
8922+    def setUp(self):
8923+        self.test_data = "Test Data" * 50000
8924+        self.uploadable = MutableDataHandle(self.test_data)
8925+
8926+
8927+    def test_datahandle_read(self):
8928+        chunk_size = 10
8929+        for i in xrange(0, len(self.test_data), chunk_size):
8930+            data = self.uploadable.read(chunk_size)
8931+            data = "".join(data)
8932+            start = i
8933+            end = i + chunk_size
8934+            self.failUnlessEqual(data, self.test_data[start:end])
8935+
8936+
8937+    def test_datahandle_get_size(self):
8938+        actual_size = len(self.test_data)
8939+        size = self.uploadable.get_size()
8940+        self.failUnlessEqual(size, actual_size)
8941+
8942+
8943+    def test_datahandle_get_size_out_of_order(self):
8944+        # We should be able to call get_size whenever we want without
8945+        # disturbing the location of the seek pointer.
8946+        chunk_size = 100
8947+        data = self.uploadable.read(chunk_size)
8948+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
8949+
8950+        # Now get the size.
8951+        size = self.uploadable.get_size()
8952+        self.failUnlessEqual(size, len(self.test_data))
8953+
8954+        # Now get more data. We should be right where we left off.
8955+        more_data = self.uploadable.read(chunk_size)
8956+        start = chunk_size
8957+        end = chunk_size * 2
8958+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
8959}
8960[Alter tests to work with the new APIs
8961Kevan Carstensen <kevan@isnotajoke.com>**20100708000031
8962 Ignore-this: 1f377904ac61ce40e9a04716fbd2ad95
8963] {
8964hunk ./src/allmydata/test/common.py 12
8965 from allmydata import uri, dirnode, client
8966 from allmydata.introducer.server import IntroducerNode
8967 from allmydata.interfaces import IMutableFileNode, IImmutableFileNode, \
8968-     FileTooLargeError, NotEnoughSharesError, ICheckable
8969+     FileTooLargeError, NotEnoughSharesError, ICheckable, \
8970+     IMutableUploadable
8971 from allmydata.check_results import CheckResults, CheckAndRepairResults, \
8972      DeepCheckResults, DeepCheckAndRepairResults
8973 from allmydata.mutable.common import CorruptShareError
8974hunk ./src/allmydata/test/common.py 18
8975 from allmydata.mutable.layout import unpack_header
8976+from allmydata.mutable.publish import MutableDataHandle
8977 from allmydata.storage.server import storage_index_to_dir
8978 from allmydata.storage.mutable import MutableShareFile
8979 from allmydata.util import hashutil, log, fileutil, pollmixin
8980hunk ./src/allmydata/test/common.py 182
8981         self.init_from_cap(make_mutable_file_cap())
8982     def create(self, contents, key_generator=None, keysize=None):
8983         initial_contents = self._get_initial_contents(contents)
8984-        if len(initial_contents) > self.MUTABLE_SIZELIMIT:
8985+        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
8986             raise FileTooLargeError("SDMF is limited to one segment, and "
8987hunk ./src/allmydata/test/common.py 184
8988-                                    "%d > %d" % (len(initial_contents),
8989+                                    "%d > %d" % (initial_contents.get_size(),
8990                                                  self.MUTABLE_SIZELIMIT))
8991hunk ./src/allmydata/test/common.py 186
8992-        self.all_contents[self.storage_index] = initial_contents
8993+        data = initial_contents.read(initial_contents.get_size())
8994+        data = "".join(data)
8995+        self.all_contents[self.storage_index] = data
8996         return defer.succeed(self)
8997     def _get_initial_contents(self, contents):
8998hunk ./src/allmydata/test/common.py 191
8999-        if isinstance(contents, str):
9000-            return contents
9001         if contents is None:
9002hunk ./src/allmydata/test/common.py 192
9003-            return ""
9004+            return MutableDataHandle("")
9005+
9006+        if IMutableUploadable.providedBy(contents):
9007+            return contents
9008+
9009         assert callable(contents), "%s should be callable, not %s" % \
9010                (contents, type(contents))
9011         return contents(self)
9012hunk ./src/allmydata/test/common.py 309
9013         return defer.succeed(self.all_contents[self.storage_index])
9014 
9015     def overwrite(self, new_contents):
9016-        if len(new_contents) > self.MUTABLE_SIZELIMIT:
9017+        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
9018             raise FileTooLargeError("SDMF is limited to one segment, and "
9019hunk ./src/allmydata/test/common.py 311
9020-                                    "%d > %d" % (len(new_contents),
9021+                                    "%d > %d" % (new_contents.get_size(),
9022                                                  self.MUTABLE_SIZELIMIT))
9023         assert not self.is_readonly()
9024hunk ./src/allmydata/test/common.py 314
9025-        self.all_contents[self.storage_index] = new_contents
9026+        new_data = new_contents.read(new_contents.get_size())
9027+        new_data = "".join(new_data)
9028+        self.all_contents[self.storage_index] = new_data
9029         return defer.succeed(None)
9030     def modify(self, modifier):
9031         # this does not implement FileTooLargeError, but the real one does
9032hunk ./src/allmydata/test/common.py 324
9033     def _modify(self, modifier):
9034         assert not self.is_readonly()
9035         old_contents = self.all_contents[self.storage_index]
9036-        self.all_contents[self.storage_index] = modifier(old_contents, None, True)
9037+        new_data = modifier(old_contents, None, True)
9038+        if new_data is not None:
9039+            new_data = new_data.read(new_data.get_size())
9040+            new_data = "".join(new_data)
9041+        self.all_contents[self.storage_index] = new_data
9042         return None
9043 
9044 def make_mutable_file_cap():
9045hunk ./src/allmydata/test/test_checker.py 11
9046 from allmydata.test.no_network import GridTestMixin
9047 from allmydata.immutable.upload import Data
9048 from allmydata.test.common_web import WebRenderingMixin
9049+from allmydata.mutable.publish import MutableDataHandle
9050 
9051 class FakeClient:
9052     def get_storage_broker(self):
9053hunk ./src/allmydata/test/test_checker.py 291
9054         def _stash_immutable(ur):
9055             self.imm = c0.create_node_from_uri(ur.uri)
9056         d.addCallback(_stash_immutable)
9057-        d.addCallback(lambda ign: c0.create_mutable_file("contents"))
9058+        d.addCallback(lambda ign:
9059+            c0.create_mutable_file(MutableDataHandle("contents")))
9060         def _stash_mutable(node):
9061             self.mut = node
9062         d.addCallback(_stash_mutable)
9063hunk ./src/allmydata/test/test_cli.py 12
9064 from allmydata.util import fileutil, hashutil, base32
9065 from allmydata import uri
9066 from allmydata.immutable import upload
9067+from allmydata.mutable.publish import MutableDataHandle
9068 from allmydata.dirnode import normalize
9069 
9070 # Test that the scripts can be imported -- although the actual tests of their
9071hunk ./src/allmydata/test/test_cli.py 1983
9072         self.set_up_grid()
9073         c0 = self.g.clients[0]
9074         DATA = "data" * 100
9075-        d = c0.create_mutable_file(DATA)
9076+        DATA_uploadable = MutableDataHandle(DATA)
9077+        d = c0.create_mutable_file(DATA_uploadable)
9078         def _stash_uri(n):
9079             self.uri = n.get_uri()
9080         d.addCallback(_stash_uri)
9081hunk ./src/allmydata/test/test_cli.py 2085
9082                                            upload.Data("literal",
9083                                                         convergence="")))
9084         d.addCallback(_stash_uri, "small")
9085-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"1"))
9086+        d.addCallback(lambda ign:
9087+            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
9088         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9089         d.addCallback(_stash_uri, "mutable")
9090 
9091hunk ./src/allmydata/test/test_cli.py 2104
9092         # root/small
9093         # root/mutable
9094 
9095+        # We haven't broken anything yet, so this should all be healthy.
9096         d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
9097                                               self.rooturi))
9098         def _check2((rc, out, err)):
9099hunk ./src/allmydata/test/test_cli.py 2119
9100                             in lines, out)
9101         d.addCallback(_check2)
9102 
9103+        # Similarly, all of these results should be as we expect them to
9104+        # be for a healthy file layout.
9105         d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
9106         def _check_stats((rc, out, err)):
9107             self.failUnlessReallyEqual(err, "")
9108hunk ./src/allmydata/test/test_cli.py 2136
9109             self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
9110         d.addCallback(_check_stats)
9111 
9112+        # Now we break things.
9113         def _clobber_shares(ignored):
9114             shares = self.find_shares(self.uris[u"gööd"])
9115             self.failUnlessReallyEqual(len(shares), 10)
9116hunk ./src/allmydata/test/test_cli.py 2155
9117         d.addCallback(_clobber_shares)
9118 
9119         # root
9120-        # root/gööd  [9 shares]
9121+        # root/gööd  [1 missing share]
9122         # root/small
9123         # root/mutable [1 corrupt share]
9124 
9125hunk ./src/allmydata/test/test_cli.py 2161
9126         d.addCallback(lambda ign:
9127                       self.do_cli("deep-check", "--verbose", self.rooturi))
9128+        # This should reveal the missing share, but not the corrupt
9129+        # share, since we didn't tell the deep check operation to also
9130+        # verify.
9131         def _check3((rc, out, err)):
9132             self.failUnlessReallyEqual(err, "")
9133             self.failUnlessReallyEqual(rc, 0)
9134hunk ./src/allmydata/test/test_cli.py 2212
9135                                   "--verbose", "--verify", "--repair",
9136                                   self.rooturi))
9137         def _check6((rc, out, err)):
9138+            # We've just repaired the directory. There is no reason for
9139+            # that repair to be unsuccessful.
9140             self.failUnlessReallyEqual(err, "")
9141             self.failUnlessReallyEqual(rc, 0)
9142             lines = out.splitlines()
9143hunk ./src/allmydata/test/test_deepcheck.py 9
9144 from twisted.internet import threads # CLI tests use deferToThread
9145 from allmydata.immutable import upload
9146 from allmydata.mutable.common import UnrecoverableFileError
9147+from allmydata.mutable.publish import MutableDataHandle
9148 from allmydata.util import idlib
9149 from allmydata.util import base32
9150 from allmydata.scripts import runner
9151hunk ./src/allmydata/test/test_deepcheck.py 38
9152         self.basedir = "deepcheck/MutableChecker/good"
9153         self.set_up_grid()
9154         CONTENTS = "a little bit of data"
9155-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9156+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9157+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9158         def _created(node):
9159             self.node = node
9160             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9161hunk ./src/allmydata/test/test_deepcheck.py 61
9162         self.basedir = "deepcheck/MutableChecker/corrupt"
9163         self.set_up_grid()
9164         CONTENTS = "a little bit of data"
9165-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9166+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9167+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9168         def _stash_and_corrupt(node):
9169             self.node = node
9170             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9171hunk ./src/allmydata/test/test_deepcheck.py 99
9172         self.basedir = "deepcheck/MutableChecker/delete_share"
9173         self.set_up_grid()
9174         CONTENTS = "a little bit of data"
9175-        d = self.g.clients[0].create_mutable_file(CONTENTS)
9176+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9177+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
9178         def _stash_and_delete(node):
9179             self.node = node
9180             self.fileurl = "uri/" + urllib.quote(node.get_uri())
9181hunk ./src/allmydata/test/test_deepcheck.py 223
9182             self.root = n
9183             self.root_uri = n.get_uri()
9184         d.addCallback(_created_root)
9185-        d.addCallback(lambda ign: c0.create_mutable_file("mutable file contents"))
9186+        d.addCallback(lambda ign:
9187+            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
9188         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
9189         def _created_mutable(n):
9190             self.mutable = n
9191hunk ./src/allmydata/test/test_deepcheck.py 965
9192     def create_mangled(self, ignored, name):
9193         nodetype, mangletype = name.split("-", 1)
9194         if nodetype == "mutable":
9195-            d = self.g.clients[0].create_mutable_file("mutable file contents")
9196+            mutable_uploadable = MutableDataHandle("mutable file contents")
9197+            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
9198             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
9199         elif nodetype == "large":
9200             large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
9201hunk ./src/allmydata/test/test_dirnode.py 1281
9202     implements(IMutableFileNode)
9203     counter = 0
9204     def __init__(self, initial_contents=""):
9205-        self.data = self._get_initial_contents(initial_contents)
9206+        data = self._get_initial_contents(initial_contents)
9207+        self.data = data.read(data.get_size())
9208+        self.data = "".join(self.data)
9209+
9210         counter = FakeMutableFile.counter
9211         FakeMutableFile.counter += 1
9212         writekey = hashutil.ssk_writekey_hash(str(counter))
9213hunk ./src/allmydata/test/test_dirnode.py 1331
9214         pass
9215 
9216     def modify(self, modifier):
9217-        self.data = modifier(self.data, None, True)
9218+        data = modifier(self.data, None, True)
9219+        self.data = data.read(data.get_size())
9220+        self.data = "".join(self.data)
9221         return defer.succeed(None)
9222 
9223 class FakeNodeMaker(NodeMaker):
9224hunk ./src/allmydata/test/test_hung_server.py 10
9225 from allmydata.util.consumer import download_to_data
9226 from allmydata.immutable import upload
9227 from allmydata.mutable.common import UnrecoverableFileError
9228+from allmydata.mutable.publish import MutableDataHandle
9229 from allmydata.storage.common import storage_index_to_dir
9230 from allmydata.test.no_network import GridTestMixin
9231 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
9232hunk ./src/allmydata/test/test_hung_server.py 96
9233         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
9234 
9235         if mutable:
9236-            d = nm.create_mutable_file(mutable_plaintext)
9237+            uploadable = MutableDataHandle(mutable_plaintext)
9238+            d = nm.create_mutable_file(uploadable)
9239             def _uploaded_mutable(node):
9240                 self.uri = node.get_uri()
9241                 self.shares = self.find_shares(self.uri)
9242hunk ./src/allmydata/test/test_mutable.py 297
9243             d.addCallback(lambda smap: smap.dump(StringIO()))
9244             d.addCallback(lambda sio:
9245                           self.failUnless("3-of-10" in sio.getvalue()))
9246-            d.addCallback(lambda res: n.overwrite("contents 1"))
9247+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9248             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9249             d.addCallback(lambda res: n.download_best_version())
9250             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9251hunk ./src/allmydata/test/test_mutable.py 304
9252             d.addCallback(lambda res: n.get_size_of_best_version())
9253             d.addCallback(lambda size:
9254                           self.failUnlessEqual(size, len("contents 1")))
9255-            d.addCallback(lambda res: n.overwrite("contents 2"))
9256+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9257             d.addCallback(lambda res: n.download_best_version())
9258             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9259             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9260hunk ./src/allmydata/test/test_mutable.py 308
9261-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9262+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9263             d.addCallback(lambda res: n.download_best_version())
9264             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9265             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9266hunk ./src/allmydata/test/test_mutable.py 320
9267             # mapupdate-to-retrieve data caching (i.e. make the shares larger
9268             # than the default readsize, which is 2000 bytes). A 15kB file
9269             # will have 5kB shares.
9270-            d.addCallback(lambda res: n.overwrite("large size file" * 1000))
9271+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
9272             d.addCallback(lambda res: n.download_best_version())
9273             d.addCallback(lambda res:
9274                           self.failUnlessEqual(res, "large size file" * 1000))
9275hunk ./src/allmydata/test/test_mutable.py 343
9276             # to make them big enough to force the file to be uploaded
9277             # in more than one segment.
9278             big_contents = "contents1" * 100000 # about 900 KiB
9279+            big_contents_uploadable = MutableDataHandle(big_contents)
9280             d.addCallback(lambda ignored:
9281hunk ./src/allmydata/test/test_mutable.py 345
9282-                n.overwrite(big_contents))
9283+                n.overwrite(big_contents_uploadable))
9284             d.addCallback(lambda ignored:
9285                 n.download_best_version())
9286             d.addCallback(lambda data:
9287hunk ./src/allmydata/test/test_mutable.py 355
9288             # segments, so that we make the downloader deal with
9289             # multiple segments.
9290             bigger_contents = "contents2" * 1000000 # about 9MiB
9291+            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
9292             d.addCallback(lambda ignored:
9293hunk ./src/allmydata/test/test_mutable.py 357
9294-                n.overwrite(bigger_contents))
9295+                n.overwrite(bigger_contents_uploadable))
9296             d.addCallback(lambda ignored:
9297                 n.download_best_version())
9298             d.addCallback(lambda data:
9299hunk ./src/allmydata/test/test_mutable.py 368
9300 
9301 
9302     def test_create_with_initial_contents(self):
9303-        d = self.nodemaker.create_mutable_file("contents 1")
9304+        upload1 = MutableDataHandle("contents 1")
9305+        d = self.nodemaker.create_mutable_file(upload1)
9306         def _created(n):
9307             d = n.download_best_version()
9308             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9309hunk ./src/allmydata/test/test_mutable.py 373
9310-            d.addCallback(lambda res: n.overwrite("contents 2"))
9311+            upload2 = MutableDataHandle("contents 2")
9312+            d.addCallback(lambda res: n.overwrite(upload2))
9313             d.addCallback(lambda res: n.download_best_version())
9314             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9315             return d
9316hunk ./src/allmydata/test/test_mutable.py 380
9317         d.addCallback(_created)
9318         return d
9319+    test_create_with_initial_contents.timeout = 15
9320 
9321 
9322     def test_create_mdmf_with_initial_contents(self):
9323hunk ./src/allmydata/test/test_mutable.py 385
9324         initial_contents = "foobarbaz" * 131072 # 900KiB
9325-        d = self.nodemaker.create_mutable_file(initial_contents,
9326+        initial_contents_uploadable = MutableDataHandle(initial_contents)
9327+        d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
9328                                                version=MDMF_VERSION)
9329         def _created(n):
9330             d = n.download_best_version()
9331hunk ./src/allmydata/test/test_mutable.py 392
9332             d.addCallback(lambda data:
9333                 self.failUnlessEqual(data, initial_contents))
9334+            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
9335             d.addCallback(lambda ignored:
9336hunk ./src/allmydata/test/test_mutable.py 394
9337-                n.overwrite(initial_contents + "foobarbaz"))
9338+                n.overwrite(uploadable2))
9339             d.addCallback(lambda ignored:
9340                 n.download_best_version())
9341             d.addCallback(lambda data:
9342hunk ./src/allmydata/test/test_mutable.py 413
9343             key = n.get_writekey()
9344             self.failUnless(isinstance(key, str), key)
9345             self.failUnlessEqual(len(key), 16) # AES key size
9346-            return data
9347+            return MutableDataHandle(data)
9348         d = self.nodemaker.create_mutable_file(_make_contents)
9349         def _created(n):
9350             return n.download_best_version()
9351hunk ./src/allmydata/test/test_mutable.py 429
9352             key = n.get_writekey()
9353             self.failUnless(isinstance(key, str), key)
9354             self.failUnlessEqual(len(key), 16)
9355-            return data
9356+            return MutableDataHandle(data)
9357         d = self.nodemaker.create_mutable_file(_make_contents,
9358                                                version=MDMF_VERSION)
9359         d.addCallback(lambda n:
9360hunk ./src/allmydata/test/test_mutable.py 441
9361 
9362     def test_create_with_too_large_contents(self):
9363         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9364-        d = self.nodemaker.create_mutable_file(BIG)
9365+        BIG_uploadable = MutableDataHandle(BIG)
9366+        d = self.nodemaker.create_mutable_file(BIG_uploadable)
9367         def _created(n):
9368hunk ./src/allmydata/test/test_mutable.py 444
9369-            d = n.overwrite(BIG)
9370+            other_BIG_uploadable = MutableDataHandle(BIG)
9371+            d = n.overwrite(other_BIG_uploadable)
9372             return d
9373         d.addCallback(_created)
9374         return d
9375hunk ./src/allmydata/test/test_mutable.py 459
9376 
9377     def test_modify(self):
9378         def _modifier(old_contents, servermap, first_time):
9379-            return old_contents + "line2"
9380+            new_contents = old_contents + "line2"
9381+            return MutableDataHandle(new_contents)
9382         def _non_modifier(old_contents, servermap, first_time):
9383hunk ./src/allmydata/test/test_mutable.py 462
9384-            return old_contents
9385+            return MutableDataHandle(old_contents)
9386         def _none_modifier(old_contents, servermap, first_time):
9387             return None
9388         def _error_modifier(old_contents, servermap, first_time):
9389hunk ./src/allmydata/test/test_mutable.py 468
9390             raise ValueError("oops")
9391         def _toobig_modifier(old_contents, servermap, first_time):
9392-            return "b" * (self.OLD_MAX_SEGMENT_SIZE+1)
9393+            new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
9394+            return MutableDataHandle(new_content)
9395         calls = []
9396         def _ucw_error_modifier(old_contents, servermap, first_time):
9397             # simulate an UncoordinatedWriteError once
9398hunk ./src/allmydata/test/test_mutable.py 476
9399             calls.append(1)
9400             if len(calls) <= 1:
9401                 raise UncoordinatedWriteError("simulated")
9402-            return old_contents + "line3"
9403+            new_contents = old_contents + "line3"
9404+            return MutableDataHandle(new_contents)
9405         def _ucw_error_non_modifier(old_contents, servermap, first_time):
9406             # simulate an UncoordinatedWriteError once, and don't actually
9407             # modify the contents on subsequent invocations
9408hunk ./src/allmydata/test/test_mutable.py 484
9409             calls.append(1)
9410             if len(calls) <= 1:
9411                 raise UncoordinatedWriteError("simulated")
9412-            return old_contents
9413+            return MutableDataHandle(old_contents)
9414 
9415hunk ./src/allmydata/test/test_mutable.py 486
9416-        d = self.nodemaker.create_mutable_file("line1")
9417+        initial_contents = "line1"
9418+        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
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 548
9423 
9424     def test_modify_backoffer(self):
9425         def _modifier(old_contents, servermap, first_time):
9426-            return old_contents + "line2"
9427+            return MutableDataHandle(old_contents + "line2")
9428         calls = []
9429         def _ucw_error_modifier(old_contents, servermap, first_time):
9430             # simulate an UncoordinatedWriteError once
9431hunk ./src/allmydata/test/test_mutable.py 555
9432             calls.append(1)
9433             if len(calls) <= 1:
9434                 raise UncoordinatedWriteError("simulated")
9435-            return old_contents + "line3"
9436+            return MutableDataHandle(old_contents + "line3")
9437         def _always_ucw_error_modifier(old_contents, servermap, first_time):
9438             raise UncoordinatedWriteError("simulated")
9439         def _backoff_stopper(node, f):
9440hunk ./src/allmydata/test/test_mutable.py 570
9441         giveuper._delay = 0.1
9442         giveuper.factor = 1
9443 
9444-        d = self.nodemaker.create_mutable_file("line1")
9445+        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
9446         def _created(n):
9447             d = n.modify(_modifier)
9448             d.addCallback(lambda res: n.download_best_version())
9449hunk ./src/allmydata/test/test_mutable.py 620
9450             d.addCallback(lambda smap: smap.dump(StringIO()))
9451             d.addCallback(lambda sio:
9452                           self.failUnless("3-of-10" in sio.getvalue()))
9453-            d.addCallback(lambda res: n.overwrite("contents 1"))
9454+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
9455             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
9456             d.addCallback(lambda res: n.download_best_version())
9457             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9458hunk ./src/allmydata/test/test_mutable.py 624
9459-            d.addCallback(lambda res: n.overwrite("contents 2"))
9460+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9461             d.addCallback(lambda res: n.download_best_version())
9462             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9463             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9464hunk ./src/allmydata/test/test_mutable.py 628
9465-            d.addCallback(lambda smap: n.upload("contents 3", smap))
9466+            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
9467             d.addCallback(lambda res: n.download_best_version())
9468             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
9469             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
9470hunk ./src/allmydata/test/test_mutable.py 646
9471         # publish a file and create shares, which can then be manipulated
9472         # later.
9473         self.CONTENTS = "New contents go here" * 1000
9474+        self.uploadable = MutableDataHandle(self.CONTENTS)
9475         self._storage = FakeStorage()
9476         self._nodemaker = make_nodemaker(self._storage)
9477         self._storage_broker = self._nodemaker.storage_broker
9478hunk ./src/allmydata/test/test_mutable.py 650
9479-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9480+        d = self._nodemaker.create_mutable_file(self.uploadable)
9481         def _created(node):
9482             self._fn = node
9483             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9484hunk ./src/allmydata/test/test_mutable.py 662
9485         # an MDMF file.
9486         # self.CONTENTS should have more than one segment.
9487         self.CONTENTS = "This is an MDMF file" * 100000
9488+        self.uploadable = MutableDataHandle(self.CONTENTS)
9489         self._storage = FakeStorage()
9490         self._nodemaker = make_nodemaker(self._storage)
9491         self._storage_broker = self._nodemaker.storage_broker
9492hunk ./src/allmydata/test/test_mutable.py 666
9493-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1)
9494+        d = self._nodemaker.create_mutable_file(self.uploadable, version=MDMF_VERSION)
9495         def _created(node):
9496             self._fn = node
9497             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9498hunk ./src/allmydata/test/test_mutable.py 678
9499         # like publish_one, except that the result is guaranteed to be
9500         # an SDMF file
9501         self.CONTENTS = "This is an SDMF file" * 1000
9502+        self.uploadable = MutableDataHandle(self.CONTENTS)
9503         self._storage = FakeStorage()
9504         self._nodemaker = make_nodemaker(self._storage)
9505         self._storage_broker = self._nodemaker.storage_broker
9506hunk ./src/allmydata/test/test_mutable.py 682
9507-        d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0)
9508+        d = self._nodemaker.create_mutable_file(self.uploadable, version=SDMF_VERSION)
9509         def _created(node):
9510             self._fn = node
9511             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
9512hunk ./src/allmydata/test/test_mutable.py 696
9513                          "Contents 2",
9514                          "Contents 3a",
9515                          "Contents 3b"]
9516+        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
9517         self._copied_shares = {}
9518         self._storage = FakeStorage()
9519         self._nodemaker = make_nodemaker(self._storage)
9520hunk ./src/allmydata/test/test_mutable.py 700
9521-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1
9522+        d = self._nodemaker.create_mutable_file(self.uploadables[0], version=version) # seqnum=1
9523         def _created(node):
9524             self._fn = node
9525             # now create multiple versions of the same file, and accumulate
9526hunk ./src/allmydata/test/test_mutable.py 707
9527             # their shares, so we can mix and match them later.
9528             d = defer.succeed(None)
9529             d.addCallback(self._copy_shares, 0)
9530-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[1])) #s2
9531+            d.addCallback(lambda res: node.overwrite(self.uploadables[1])) #s2
9532             d.addCallback(self._copy_shares, 1)
9533hunk ./src/allmydata/test/test_mutable.py 709
9534-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[2])) #s3
9535+            d.addCallback(lambda res: node.overwrite(self.uploadables[2])) #s3
9536             d.addCallback(self._copy_shares, 2)
9537hunk ./src/allmydata/test/test_mutable.py 711
9538-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[3])) #s4a
9539+            d.addCallback(lambda res: node.overwrite(self.uploadables[3])) #s4a
9540             d.addCallback(self._copy_shares, 3)
9541             # now we replace all the shares with version s3, and upload a new
9542             # version to get s4b.
9543hunk ./src/allmydata/test/test_mutable.py 717
9544             rollback = dict([(i,2) for i in range(10)])
9545             d.addCallback(lambda res: self._set_versions(rollback))
9546-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[4])) #s4b
9547+            d.addCallback(lambda res: node.overwrite(self.uploadables[4])) #s4b
9548             d.addCallback(self._copy_shares, 4)
9549             # we leave the storage in state 4
9550             return d
9551hunk ./src/allmydata/test/test_mutable.py 826
9552         # create a new file, which is large enough to knock the privkey out
9553         # of the early part of the file
9554         LARGE = "These are Larger contents" * 200 # about 5KB
9555-        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE))
9556+        LARGE_uploadable = MutableDataHandle(LARGE)
9557+        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
9558         def _created(large_fn):
9559             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
9560             return self.make_servermap(MODE_WRITE, large_fn2)
9561hunk ./src/allmydata/test/test_mutable.py 1842
9562 class MultipleEncodings(unittest.TestCase):
9563     def setUp(self):
9564         self.CONTENTS = "New contents go here"
9565+        self.uploadable = MutableDataHandle(self.CONTENTS)
9566         self._storage = FakeStorage()
9567         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
9568         self._storage_broker = self._nodemaker.storage_broker
9569hunk ./src/allmydata/test/test_mutable.py 1846
9570-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
9571+        d = self._nodemaker.create_mutable_file(self.uploadable)
9572         def _created(node):
9573             self._fn = node
9574         d.addCallback(_created)
9575hunk ./src/allmydata/test/test_mutable.py 1872
9576         s = self._storage
9577         s._peers = {} # clear existing storage
9578         p2 = Publish(fn2, self._storage_broker, None)
9579-        d = p2.publish(data)
9580+        uploadable = MutableDataHandle(data)
9581+        d = p2.publish(uploadable)
9582         def _published(res):
9583             shares = s._peers
9584             s._peers = {}
9585hunk ./src/allmydata/test/test_mutable.py 2049
9586         self._set_versions(target)
9587 
9588         def _modify(oldversion, servermap, first_time):
9589-            return oldversion + " modified"
9590+            return MutableDataHandle(oldversion + " modified")
9591         d = self._fn.modify(_modify)
9592         d.addCallback(lambda res: self._fn.download_best_version())
9593         expected = self.CONTENTS[2] + " modified"
9594hunk ./src/allmydata/test/test_mutable.py 2175
9595         self.basedir = "mutable/Problems/test_publish_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_WRITE))
9603hunk ./src/allmydata/test/test_mutable.py 2185
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 modify the file with the old servermap. This
9610             # will look just like an uncoordinated write, in which every
9611             # single share got updated between our mapupdate and our publish
9612hunk ./src/allmydata/test/test_mutable.py 2194
9613                           self.shouldFail(UncoordinatedWriteError,
9614                                           "test_publish_surprise", None,
9615                                           n.upload,
9616-                                          "contents 2a", self.old_map))
9617+                                          MutableDataHandle("contents 2a"), self.old_map))
9618             return d
9619         d.addCallback(_created)
9620         return d
9621hunk ./src/allmydata/test/test_mutable.py 2203
9622         self.basedir = "mutable/Problems/test_retrieve_surprise"
9623         self.set_up_grid()
9624         nm = self.g.clients[0].nodemaker
9625-        d = nm.create_mutable_file("contents 1")
9626+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9627         def _created(n):
9628             d = defer.succeed(None)
9629             d.addCallback(lambda res: n.get_servermap(MODE_READ))
9630hunk ./src/allmydata/test/test_mutable.py 2213
9631             d.addCallback(_got_smap1)
9632             # then modify the file, leaving the old map untouched
9633             d.addCallback(lambda res: log.msg("starting winning write"))
9634-            d.addCallback(lambda res: n.overwrite("contents 2"))
9635+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9636             # now attempt to retrieve the old version with the old servermap.
9637             # This will look like someone has changed the file since we
9638             # updated the servermap.
9639hunk ./src/allmydata/test/test_mutable.py 2241
9640         self.basedir = "mutable/Problems/test_unexpected_shares"
9641         self.set_up_grid()
9642         nm = self.g.clients[0].nodemaker
9643-        d = nm.create_mutable_file("contents 1")
9644+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9645         def _created(n):
9646             d = defer.succeed(None)
9647             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
9648hunk ./src/allmydata/test/test_mutable.py 2253
9649                 self.g.remove_server(peer0)
9650                 # then modify the file, leaving the old map untouched
9651                 log.msg("starting winning write")
9652-                return n.overwrite("contents 2")
9653+                return n.overwrite(MutableDataHandle("contents 2"))
9654             d.addCallback(_got_smap1)
9655             # now attempt to modify the file with the old servermap. This
9656             # will look just like an uncoordinated write, in which every
9657hunk ./src/allmydata/test/test_mutable.py 2263
9658                           self.shouldFail(UncoordinatedWriteError,
9659                                           "test_surprise", None,
9660                                           n.upload,
9661-                                          "contents 2a", self.old_map))
9662+                                          MutableDataHandle("contents 2a"), self.old_map))
9663             return d
9664         d.addCallback(_created)
9665         return d
9666hunk ./src/allmydata/test/test_mutable.py 2267
9667+    test_unexpected_shares.timeout = 15
9668 
9669     def test_bad_server(self):
9670         # Break one server, then create the file: the initial publish should
9671hunk ./src/allmydata/test/test_mutable.py 2303
9672         d.addCallback(_break_peer0)
9673         # now "create" the file, using the pre-established key, and let the
9674         # initial publish finally happen
9675-        d.addCallback(lambda res: nm.create_mutable_file("contents 1"))
9676+        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
9677         # that ought to work
9678         def _got_node(n):
9679             d = n.download_best_version()
9680hunk ./src/allmydata/test/test_mutable.py 2312
9681             def _break_peer1(res):
9682                 self.connection1.broken = True
9683             d.addCallback(_break_peer1)
9684-            d.addCallback(lambda res: n.overwrite("contents 2"))
9685+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9686             # that ought to work too
9687             d.addCallback(lambda res: n.download_best_version())
9688             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9689hunk ./src/allmydata/test/test_mutable.py 2344
9690         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
9691         self.g.break_server(peerids[0])
9692 
9693-        d = nm.create_mutable_file("contents 1")
9694+        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
9695         def _created(n):
9696             d = n.download_best_version()
9697             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
9698hunk ./src/allmydata/test/test_mutable.py 2352
9699             def _break_second_server(res):
9700                 self.g.break_server(peerids[1])
9701             d.addCallback(_break_second_server)
9702-            d.addCallback(lambda res: n.overwrite("contents 2"))
9703+            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
9704             # that ought to work too
9705             d.addCallback(lambda res: n.download_best_version())
9706             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
9707hunk ./src/allmydata/test/test_mutable.py 2371
9708         d = self.shouldFail(NotEnoughServersError,
9709                             "test_publish_all_servers_bad",
9710                             "Ran out of non-bad servers",
9711-                            nm.create_mutable_file, "contents")
9712+                            nm.create_mutable_file, MutableDataHandle("contents"))
9713         return d
9714 
9715     def test_publish_no_servers(self):
9716hunk ./src/allmydata/test/test_mutable.py 2383
9717         d = self.shouldFail(NotEnoughServersError,
9718                             "test_publish_no_servers",
9719                             "Ran out of non-bad servers",
9720-                            nm.create_mutable_file, "contents")
9721+                            nm.create_mutable_file, MutableDataHandle("contents"))
9722         return d
9723     test_publish_no_servers.timeout = 30
9724 
9725hunk ./src/allmydata/test/test_mutable.py 2401
9726         # we need some contents that are large enough to push the privkey out
9727         # of the early part of the file
9728         LARGE = "These are Larger contents" * 2000 # about 50KB
9729-        d = nm.create_mutable_file(LARGE)
9730+        LARGE_uploadable = MutableDataHandle(LARGE)
9731+        d = nm.create_mutable_file(LARGE_uploadable)
9732         def _created(n):
9733             self.uri = n.get_uri()
9734             self.n2 = nm.create_from_cap(self.uri)
9735hunk ./src/allmydata/test/test_mutable.py 2438
9736         self.set_up_grid(num_servers=20)
9737         nm = self.g.clients[0].nodemaker
9738         LARGE = "These are Larger contents" * 2000 # about 50KiB
9739+        LARGE_uploadable = MutableDataHandle(LARGE)
9740         nm._node_cache = DevNullDictionary() # disable the nodecache
9741 
9742hunk ./src/allmydata/test/test_mutable.py 2441
9743-        d = nm.create_mutable_file(LARGE)
9744+        d = nm.create_mutable_file(LARGE_uploadable)
9745         def _created(n):
9746             self.uri = n.get_uri()
9747             self.n2 = nm.create_from_cap(self.uri)
9748hunk ./src/allmydata/test/test_mutable.py 2464
9749         self.set_up_grid(num_servers=20)
9750         nm = self.g.clients[0].nodemaker
9751         CONTENTS = "contents" * 2000
9752-        d = nm.create_mutable_file(CONTENTS)
9753+        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
9754+        d = nm.create_mutable_file(CONTENTS_uploadable)
9755         def _created(node):
9756             self._node = node
9757         d.addCallback(_created)
9758hunk ./src/allmydata/test/test_system.py 22
9759 from allmydata.monitor import Monitor
9760 from allmydata.mutable.common import NotWriteableError
9761 from allmydata.mutable import layout as mutable_layout
9762+from allmydata.mutable.publish import MutableDataHandle
9763 from foolscap.api import DeadReferenceError
9764 from twisted.python.failure import Failure
9765 from twisted.web.client import getPage
9766hunk ./src/allmydata/test/test_system.py 460
9767     def test_mutable(self):
9768         self.basedir = "system/SystemTest/test_mutable"
9769         DATA = "initial contents go here."  # 25 bytes % 3 != 0
9770+        DATA_uploadable = MutableDataHandle(DATA)
9771         NEWDATA = "new contents yay"
9772hunk ./src/allmydata/test/test_system.py 462
9773+        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
9774         NEWERDATA = "this is getting old"
9775hunk ./src/allmydata/test/test_system.py 464
9776+        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
9777 
9778         d = self.set_up_nodes(use_key_generator=True)
9779 
9780hunk ./src/allmydata/test/test_system.py 471
9781         def _create_mutable(res):
9782             c = self.clients[0]
9783             log.msg("starting create_mutable_file")
9784-            d1 = c.create_mutable_file(DATA)
9785+            d1 = c.create_mutable_file(DATA_uploadable)
9786             def _done(res):
9787                 log.msg("DONE: %s" % (res,))
9788                 self._mutable_node_1 = res
9789hunk ./src/allmydata/test/test_system.py 558
9790             self.failUnlessEqual(res, DATA)
9791             # replace the data
9792             log.msg("starting replace1")
9793-            d1 = newnode.overwrite(NEWDATA)
9794+            d1 = newnode.overwrite(NEWDATA_uploadable)
9795             d1.addCallback(lambda res: newnode.download_best_version())
9796             return d1
9797         d.addCallback(_check_download_3)
9798hunk ./src/allmydata/test/test_system.py 572
9799             newnode2 = self.clients[3].create_node_from_uri(uri)
9800             self._newnode3 = self.clients[3].create_node_from_uri(uri)
9801             log.msg("starting replace2")
9802-            d1 = newnode1.overwrite(NEWERDATA)
9803+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
9804             d1.addCallback(lambda res: newnode2.download_best_version())
9805             return d1
9806         d.addCallback(_check_download_4)
9807hunk ./src/allmydata/test/test_system.py 642
9808         def _check_empty_file(res):
9809             # make sure we can create empty files, this usually screws up the
9810             # segsize math
9811-            d1 = self.clients[2].create_mutable_file("")
9812+            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
9813             d1.addCallback(lambda newnode: newnode.download_best_version())
9814             d1.addCallback(lambda res: self.failUnlessEqual("", res))
9815             return d1
9816hunk ./src/allmydata/test/test_system.py 673
9817                                  self.key_generator_svc.key_generator.pool_size + size_delta)
9818 
9819         d.addCallback(check_kg_poolsize, 0)
9820-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
9821+        d.addCallback(lambda junk:
9822+            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
9823         d.addCallback(check_kg_poolsize, -1)
9824         d.addCallback(lambda junk: self.clients[3].create_dirnode())
9825         d.addCallback(check_kg_poolsize, -2)
9826hunk ./src/allmydata/test/test_web.py 3181
9827         def _stash_mutable_uri(n, which):
9828             self.uris[which] = n.get_uri()
9829             assert isinstance(self.uris[which], str)
9830-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9831+        d.addCallback(lambda ign:
9832+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9833         d.addCallback(_stash_mutable_uri, "corrupt")
9834         d.addCallback(lambda ign:
9835                       c0.upload(upload.Data("literal", convergence="")))
9836hunk ./src/allmydata/test/test_web.py 3328
9837         def _stash_mutable_uri(n, which):
9838             self.uris[which] = n.get_uri()
9839             assert isinstance(self.uris[which], str)
9840-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
9841+        d.addCallback(lambda ign:
9842+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
9843         d.addCallback(_stash_mutable_uri, "corrupt")
9844 
9845         def _compute_fileurls(ignored):
9846hunk ./src/allmydata/test/test_web.py 3991
9847         def _stash_mutable_uri(n, which):
9848             self.uris[which] = n.get_uri()
9849             assert isinstance(self.uris[which], str)
9850-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
9851+        d.addCallback(lambda ign:
9852+            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
9853         d.addCallback(_stash_mutable_uri, "mutable")
9854 
9855         def _compute_fileurls(ignored):
9856hunk ./src/allmydata/test/test_web.py 4091
9857                                                         convergence="")))
9858         d.addCallback(_stash_uri, "small")
9859 
9860-        d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
9861+        d.addCallback(lambda ign:
9862+            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
9863         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
9864         d.addCallback(_stash_uri, "mutable")
9865 
9866}
9867[Alter mutable files to use file-like objects for publishing instead of strings.
9868Kevan Carstensen <kevan@isnotajoke.com>**20100708000732
9869 Ignore-this: 8dd07d95386b6d540bc21289f981ebd0
9870] {
9871hunk ./src/allmydata/dirnode.py 11
9872 from allmydata.mutable.common import NotWriteableError
9873 from allmydata.mutable.filenode import MutableFileNode
9874 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
9875+from allmydata.mutable.publish import MutableDataHandle
9876 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
9877      IImmutableFileNode, IMutableFileNode, \
9878      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
9879hunk ./src/allmydata/dirnode.py 104
9880 
9881         del children[self.name]
9882         new_contents = self.node._pack_contents(children)
9883-        return new_contents
9884+        uploadable = MutableDataHandle(new_contents)
9885+        return uploadable
9886 
9887 
9888 class MetadataSetter:
9889hunk ./src/allmydata/dirnode.py 130
9890 
9891         children[name] = (child, metadata)
9892         new_contents = self.node._pack_contents(children)
9893-        return new_contents
9894+        uploadable = MutableDataHandle(new_contents)
9895+        return uploadable
9896 
9897 
9898 class Adder:
9899hunk ./src/allmydata/dirnode.py 175
9900 
9901             children[name] = (child, metadata)
9902         new_contents = self.node._pack_contents(children)
9903-        return new_contents
9904+        uploadable = MutableDataHandle(new_contents)
9905+        return uploadable
9906 
9907 
9908 def _encrypt_rw_uri(filenode, rw_uri):
9909hunk ./src/allmydata/mutable/filenode.py 7
9910 from zope.interface import implements
9911 from twisted.internet import defer, reactor
9912 from foolscap.api import eventually
9913-from allmydata.interfaces import IMutableFileNode, \
9914-     ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION
9915+from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
9916+                                 NotEnoughSharesError, \
9917+                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
9918 from allmydata.util import hashutil, log
9919 from allmydata.util.assertutil import precondition
9920 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
9921hunk ./src/allmydata/mutable/filenode.py 16
9922 from allmydata.monitor import Monitor
9923 from pycryptopp.cipher.aes import AES
9924 
9925-from allmydata.mutable.publish import Publish
9926+from allmydata.mutable.publish import Publish, MutableFileHandle, \
9927+                                      MutableDataHandle
9928 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
9929      ResponseCache, UncoordinatedWriteError
9930 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
9931hunk ./src/allmydata/mutable/filenode.py 133
9932         return self._upload(initial_contents, None)
9933 
9934     def _get_initial_contents(self, contents):
9935-        if isinstance(contents, str):
9936-            return contents
9937         if contents is None:
9938hunk ./src/allmydata/mutable/filenode.py 134
9939-            return ""
9940+            return MutableDataHandle("")
9941+
9942+        if IMutableUploadable.providedBy(contents):
9943+            return contents
9944+
9945         assert callable(contents), "%s should be callable, not %s" % \
9946                (contents, type(contents))
9947         return contents(self)
9948hunk ./src/allmydata/mutable/filenode.py 353
9949     def overwrite(self, new_contents):
9950         return self._do_serialized(self._overwrite, new_contents)
9951     def _overwrite(self, new_contents):
9952+        assert IMutableUploadable.providedBy(new_contents)
9953+
9954         servermap = ServerMap()
9955         d = self._update_servermap(servermap, mode=MODE_WRITE)
9956         d.addCallback(lambda ignored: self._upload(new_contents, servermap))
9957hunk ./src/allmydata/mutable/filenode.py 431
9958                 # recovery when it observes UCWE, we need to do a second
9959                 # publish. See #551 for details. We'll basically loop until
9960                 # we managed an uncontested publish.
9961-                new_contents = old_contents
9962-            precondition(isinstance(new_contents, str),
9963-                         "Modifier function must return a string or None")
9964+                old_uploadable = MutableDataHandle(old_contents)
9965+                new_contents = old_uploadable
9966+            precondition((IMutableUploadable.providedBy(new_contents) or
9967+                          new_contents is None),
9968+                         "Modifier function must return an IMutableUploadable "
9969+                         "or None")
9970             return self._upload(new_contents, servermap)
9971         d.addCallback(_apply)
9972         return d
9973hunk ./src/allmydata/mutable/filenode.py 472
9974         return self._do_serialized(self._upload, new_contents, servermap)
9975     def _upload(self, new_contents, servermap):
9976         assert self._pubkey, "update_servermap must be called before publish"
9977+        assert IMutableUploadable.providedBy(new_contents)
9978+
9979         p = Publish(self, self._storage_broker, servermap)
9980         if self._history:
9981hunk ./src/allmydata/mutable/filenode.py 476
9982-            self._history.notify_publish(p.get_status(), len(new_contents))
9983+            self._history.notify_publish(p.get_status(), new_contents.get_size())
9984         d = p.publish(new_contents)
9985hunk ./src/allmydata/mutable/filenode.py 478
9986-        d.addCallback(self._did_upload, len(new_contents))
9987+        d.addCallback(self._did_upload, new_contents.get_size())
9988         return d
9989     def _did_upload(self, res, size):
9990         self._most_recent_size = size
9991hunk ./src/allmydata/mutable/publish.py 141
9992 
9993         # 0. Setup encoding parameters, encoder, and other such things.
9994         # 1. Encrypt, encode, and publish segments.
9995-        self.data = StringIO(newdata)
9996-        self.datalength = len(newdata)
9997+        assert IMutableUploadable.providedBy(newdata)
9998+
9999+        self.data = newdata
10000+        self.datalength = newdata.get_size()
10001 
10002         self.log("starting publish, datalen is %s" % self.datalength)
10003         self._status.set_size(self.datalength)
10004hunk ./src/allmydata/mutable/publish.py 442
10005 
10006         self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
10007         data = self.data.read(segsize)
10008+        # XXX: This is dumb. Why return a list?
10009+        data = "".join(data)
10010 
10011         assert len(data) == segsize
10012 
10013hunk ./src/allmydata/mutable/repairer.py 5
10014 from zope.interface import implements
10015 from twisted.internet import defer
10016 from allmydata.interfaces import IRepairResults, ICheckResults
10017+from allmydata.mutable.publish import MutableDataHandle
10018 
10019 class RepairResults:
10020     implements(IRepairResults)
10021hunk ./src/allmydata/mutable/repairer.py 108
10022             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
10023 
10024         d = self.node.download_version(smap, best_version, fetch_privkey=True)
10025+        d.addCallback(lambda data:
10026+            MutableDataHandle(data))
10027         d.addCallback(self.node.upload, smap)
10028         d.addCallback(self.get_results, smap)
10029         return d
10030hunk ./src/allmydata/nodemaker.py 9
10031 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
10032 from allmydata.immutable.upload import Data
10033 from allmydata.mutable.filenode import MutableFileNode
10034+from allmydata.mutable.publish import MutableDataHandle
10035 from allmydata.dirnode import DirectoryNode, pack_children
10036 from allmydata.unknown import UnknownNode
10037 from allmydata import uri
10038hunk ./src/allmydata/nodemaker.py 111
10039                          "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
10040             node.raise_error()
10041         d = self.create_mutable_file(lambda n:
10042-                                     pack_children(n, initial_children),
10043+                                     MutableDataHandle(
10044+                                        pack_children(n, initial_children)),
10045                                      version)
10046         d.addCallback(self._create_dirnode)
10047         return d
10048hunk ./src/allmydata/web/filenode.py 12
10049 from allmydata.interfaces import ExistingChildError
10050 from allmydata.monitor import Monitor
10051 from allmydata.immutable.upload import FileHandle
10052+from allmydata.mutable.publish import MutableFileHandle
10053 from allmydata.util import log, base32
10054 
10055 from allmydata.web.common import text_plain, WebError, RenderMixin, \
10056hunk ./src/allmydata/web/filenode.py 27
10057         # a new file is being uploaded in our place.
10058         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10059         if mutable:
10060-            req.content.seek(0)
10061-            data = req.content.read()
10062+            data = MutableFileHandle(req.content)
10063             d = client.create_mutable_file(data)
10064             def _uploaded(newnode):
10065                 d2 = self.parentnode.set_node(self.name, newnode,
10066hunk ./src/allmydata/web/filenode.py 61
10067         d.addCallback(lambda res: childnode.get_uri())
10068         return d
10069 
10070-    def _read_data_from_formpost(self, req):
10071-        # SDMF: files are small, and we can only upload data, so we read
10072-        # the whole file into memory before uploading.
10073-        contents = req.fields["file"]
10074-        contents.file.seek(0)
10075-        data = contents.file.read()
10076-        return data
10077 
10078     def replace_me_with_a_formpost(self, req, client, replace):
10079         # create a new file, maybe mutable, maybe immutable
10080hunk ./src/allmydata/web/filenode.py 66
10081         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
10082 
10083+        # create an immutable file
10084+        contents = req.fields["file"]
10085         if mutable:
10086hunk ./src/allmydata/web/filenode.py 69
10087-            data = self._read_data_from_formpost(req)
10088-            d = client.create_mutable_file(data)
10089+            uploadable = MutableFileHandle(contents.file)
10090+            d = client.create_mutable_file(uploadable)
10091             def _uploaded(newnode):
10092                 d2 = self.parentnode.set_node(self.name, newnode,
10093                                               overwrite=replace)
10094hunk ./src/allmydata/web/filenode.py 78
10095                 return d2
10096             d.addCallback(_uploaded)
10097             return d
10098-        # create an immutable file
10099-        contents = req.fields["file"]
10100+
10101         uploadable = FileHandle(contents.file, convergence=client.convergence)
10102         d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
10103         d.addCallback(lambda newnode: newnode.get_uri())
10104hunk ./src/allmydata/web/filenode.py 84
10105         return d
10106 
10107+
10108 class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
10109     def __init__(self, client, parentnode, name):
10110         rend.Page.__init__(self)
10111hunk ./src/allmydata/web/filenode.py 278
10112 
10113     def replace_my_contents(self, req):
10114         req.content.seek(0)
10115-        new_contents = req.content.read()
10116+        new_contents = MutableFileHandle(req.content)
10117         d = self.node.overwrite(new_contents)
10118         d.addCallback(lambda res: self.node.get_uri())
10119         return d
10120hunk ./src/allmydata/web/filenode.py 286
10121     def replace_my_contents_with_a_formpost(self, req):
10122         # we have a mutable file. Get the data from the formpost, and replace
10123         # the mutable file's contents with it.
10124-        new_contents = self._read_data_from_formpost(req)
10125+        new_contents = req.fields['file']
10126+        new_contents = MutableFileHandle(new_contents.file)
10127+
10128         d = self.node.overwrite(new_contents)
10129         d.addCallback(lambda res: self.node.get_uri())
10130         return d
10131hunk ./src/allmydata/web/unlinked.py 7
10132 from twisted.internet import defer
10133 from nevow import rend, url, tags as T
10134 from allmydata.immutable.upload import FileHandle
10135+from allmydata.mutable.publish import MutableFileHandle
10136 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
10137      convert_children_json, WebError
10138 from allmydata.web import status
10139hunk ./src/allmydata/web/unlinked.py 23
10140 def PUTUnlinkedSSK(req, client):
10141     # SDMF: files are small, and we can only upload data
10142     req.content.seek(0)
10143-    data = req.content.read()
10144+    data = MutableFileHandle(req.content)
10145     d = client.create_mutable_file(data)
10146     d.addCallback(lambda n: n.get_uri())
10147     return d
10148hunk ./src/allmydata/web/unlinked.py 87
10149     # "POST /uri", to create an unlinked file.
10150     # SDMF: files are small, and we can only upload data
10151     contents = req.fields["file"]
10152-    contents.file.seek(0)
10153-    data = contents.file.read()
10154+    data = MutableFileHandle(contents.file)
10155     d = client.create_mutable_file(data)
10156     d.addCallback(lambda n: n.get_uri())
10157     return d
10158}
10159[test/test_sftp.py: alter a setup routine to work with new mutable file APIs.
10160Kevan Carstensen <kevan@isnotajoke.com>**20100708193522
10161 Ignore-this: 434bbe1347072076c0836d26fca8ac8a
10162] {
10163hunk ./src/allmydata/test/test_sftp.py 32
10164 
10165 from allmydata.util.consumer import download_to_data
10166 from allmydata.immutable import upload
10167+from allmydata.mutable import publish
10168 from allmydata.test.no_network import GridTestMixin
10169 from allmydata.test.common import ShouldFailMixin
10170 from allmydata.test.common_util import ReallyEqualMixin
10171hunk ./src/allmydata/test/test_sftp.py 84
10172         return d
10173 
10174     def _set_up_tree(self):
10175-        d = self.client.create_mutable_file("mutable file contents")
10176+        u = publish.MutableDataHandle("mutable file contents")
10177+        d = self.client.create_mutable_file(u)
10178         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
10179         def _created_mutable(n):
10180             self.mutable = n
10181}
10182[mutable/publish.py: make MutableFileHandle seek to the beginning of its file handle before reading.
10183Kevan Carstensen <kevan@isnotajoke.com>**20100708193600
10184 Ignore-this: 453a737dc62a79c77b3d360fed9000ab
10185] hunk ./src/allmydata/mutable/publish.py 989
10186         assert hasattr(filehandle, "close")
10187 
10188         self._filehandle = filehandle
10189+        # We must start reading at the beginning of the file, or we risk
10190+        # encountering errors when the data read does not match the size
10191+        # reported to the uploader.
10192+        self._filehandle.seek(0)
10193 
10194 
10195     def get_size(self):
10196[Refactor download interfaces to be more uniform, per #993
10197Kevan Carstensen <kevan@isnotajoke.com>**20100709232912
10198 Ignore-this: 277c5699c4a2dd7c03ecfa0a28458f5b
10199] {
10200hunk ./src/allmydata/immutable/filenode.py 10
10201 from foolscap.api import eventually
10202 from allmydata.interfaces import IImmutableFileNode, ICheckable, \
10203      IDownloadTarget, IUploadResults
10204-from allmydata.util import dictutil, log, base32
10205+from allmydata.util import dictutil, log, base32, consumer
10206 from allmydata.uri import CHKFileURI, LiteralFileURI
10207 from allmydata.immutable.checker import Checker
10208 from allmydata.check_results import CheckResults, CheckAndRepairResults
10209hunk ./src/allmydata/immutable/filenode.py 318
10210                       self.download_cache.read(consumer, offset, size))
10211         return d
10212 
10213+    # IReadable, IFileNode
10214+
10215+    def get_best_readable_version(self):
10216+        """
10217+        Return an IReadable of the best version of this file. Since
10218+        immutable files can have only one version, we just return the
10219+        current filenode.
10220+        """
10221+        return self
10222+
10223+
10224+    def download_best_version(self):
10225+        """
10226+        Download the best version of this file, returning its contents
10227+        as a bytestring. Since there is only one version of an immutable
10228+        file, we download and return the contents of this file.
10229+        """
10230+        d = consumer.download_to_data(self)
10231+        return d
10232+
10233+    # for an immutable file, download_to_data (specified in IReadable)
10234+    # is the same as download_best_version (specified in IFileNode). For
10235+    # mutable files, the difference is more meaningful, since they can
10236+    # have multiple versions.
10237+    download_to_data = download_best_version
10238+
10239+
10240+    # get_size() (IReadable), get_current_size() (IFilesystemNode), and
10241+    # get_size_of_best_version(IFileNode) are all the same for immutable
10242+    # files.
10243+    get_size_of_best_version = get_current_size
10244+
10245+
10246 class LiteralProducer:
10247     implements(IPushProducer)
10248     def resumeProducing(self):
10249hunk ./src/allmydata/immutable/filenode.py 409
10250         d = basic.FileSender().beginFileTransfer(StringIO(data), consumer)
10251         d.addCallback(lambda lastSent: consumer)
10252         return d
10253+
10254+    # IReadable, IFileNode, IFilesystemNode
10255+    def get_best_readable_version(self):
10256+        return self
10257+
10258+
10259+    def download_best_version(self):
10260+        return defer.succeed(self.u.data)
10261+
10262+
10263+    download_to_data = download_best_version
10264+    get_size_of_best_version = get_current_size
10265hunk ./src/allmydata/interfaces.py 563
10266 class MustNotBeUnknownRWError(CapConstraintError):
10267     """Cannot add an unknown child cap specified in a rw_uri field."""
10268 
10269+
10270+class IReadable(Interface):
10271+    """I represent a readable object -- either an immutable file, or a
10272+    specific version of a mutable file.
10273+    """
10274+
10275+    def is_readonly():
10276+        """Return True if this reference provides mutable access to the given
10277+        file or directory (i.e. if you can modify it), or False if not. Note
10278+        that even if this reference is read-only, someone else may hold a
10279+        read-write reference to it.
10280+
10281+        For an IReadable returned by get_best_readable_version(), this will
10282+        always return True, but for instances of subinterfaces such as
10283+        IMutableFileVersion, it may return False."""
10284+
10285+    def is_mutable():
10286+        """Return True if this file or directory is mutable (by *somebody*,
10287+        not necessarily you), False if it is is immutable. Note that a file
10288+        might be mutable overall, but your reference to it might be
10289+        read-only. On the other hand, all references to an immutable file
10290+        will be read-only; there are no read-write references to an immutable
10291+        file."""
10292+
10293+    def get_storage_index():
10294+        """Return the storage index of the file."""
10295+
10296+    def get_size():
10297+        """Return the length (in bytes) of this readable object."""
10298+
10299+    def download_to_data():
10300+        """Download all of the file contents. I return a Deferred that fires
10301+        with the contents as a byte string."""
10302+
10303+    def read(consumer, offset=0, size=None):
10304+        """Download a portion (possibly all) of the file's contents, making
10305+        them available to the given IConsumer. Return a Deferred that fires
10306+        (with the consumer) when the consumer is unregistered (either because
10307+        the last byte has been given to it, or because the consumer threw an
10308+        exception during write(), possibly because it no longer wants to
10309+        receive data). The portion downloaded will start at 'offset' and
10310+        contain 'size' bytes (or the remainder of the file if size==None).
10311+
10312+        The consumer will be used in non-streaming mode: an IPullProducer
10313+        will be attached to it.
10314+
10315+        The consumer will not receive data right away: several network trips
10316+        must occur first. The order of events will be::
10317+
10318+         consumer.registerProducer(p, streaming)
10319+          (if streaming == False)::
10320+           consumer does p.resumeProducing()
10321+            consumer.write(data)
10322+           consumer does p.resumeProducing()
10323+            consumer.write(data).. (repeat until all data is written)
10324+         consumer.unregisterProducer()
10325+         deferred.callback(consumer)
10326+
10327+        If a download error occurs, or an exception is raised by
10328+        consumer.registerProducer() or consumer.write(), I will call
10329+        consumer.unregisterProducer() and then deliver the exception via
10330+        deferred.errback(). To cancel the download, the consumer should call
10331+        p.stopProducing(), which will result in an exception being delivered
10332+        via deferred.errback().
10333+
10334+        See src/allmydata/util/consumer.py for an example of a simple
10335+        download-to-memory consumer.
10336+        """
10337+
10338+
10339+class IMutableFileVersion(IReadable):
10340+    """I provide access to a particular version of a mutable file. The
10341+    access is read/write if I was obtained from a filenode derived from
10342+    a write cap, or read-only if the filenode was derived from a read cap.
10343+    """
10344+
10345+    def get_sequence_number():
10346+        """Return the sequence number of this version."""
10347+
10348+    def get_servermap():
10349+        """Return the IMutableFileServerMap instance that was used to create
10350+        this object.
10351+        """
10352+
10353+    def get_writekey():
10354+        """Return this filenode's writekey, or None if the node does not have
10355+        write-capability. This may be used to assist with data structures
10356+        that need to make certain data available only to writers, such as the
10357+        read-write child caps in dirnodes. The recommended process is to have
10358+        reader-visible data be submitted to the filenode in the clear (where
10359+        it will be encrypted by the filenode using the readkey), but encrypt
10360+        writer-visible data using this writekey.
10361+        """
10362+
10363+    # TODO: Can this be overwrite instead of replace?
10364+    def replace(new_contents):
10365+        """Replace the contents of the mutable file, provided that no other
10366+        node has published (or is attempting to publish, concurrently) a
10367+        newer version of the file than this one.
10368+
10369+        I will avoid modifying any share that is different than the version
10370+        given by get_sequence_number(). However, if another node is writing
10371+        to the file at the same time as me, I may manage to update some shares
10372+        while they update others. If I see any evidence of this, I will signal
10373+        UncoordinatedWriteError, and the file will be left in an inconsistent
10374+        state (possibly the version you provided, possibly the old version,
10375+        possibly somebody else's version, and possibly a mix of shares from
10376+        all of these).
10377+
10378+        The recommended response to UncoordinatedWriteError is to either
10379+        return it to the caller (since they failed to coordinate their
10380+        writes), or to attempt some sort of recovery. It may be sufficient to
10381+        wait a random interval (with exponential backoff) and repeat your
10382+        operation. If I do not signal UncoordinatedWriteError, then I was
10383+        able to write the new version without incident.
10384+
10385+        I return a Deferred that fires (with a PublishStatus object) when the
10386+        update has completed.
10387+        """
10388+
10389+    def modify(modifier_cb):
10390+        """Modify the contents of the file, by downloading this version,
10391+        applying the modifier function (or bound method), then uploading
10392+        the new version. This will succeed as long as no other node
10393+        publishes a version between the download and the upload.
10394+        I return a Deferred that fires (with a PublishStatus object) when
10395+        the update is complete.
10396+
10397+        The modifier callable will be given three arguments: a string (with
10398+        the old contents), a 'first_time' boolean, and a servermap. As with
10399+        download_to_data(), the old contents will be from this version,
10400+        but the modifier can use the servermap to make other decisions
10401+        (such as refusing to apply the delta if there are multiple parallel
10402+        versions, or if there is evidence of a newer unrecoverable version).
10403+        'first_time' will be True the first time the modifier is called,
10404+        and False on any subsequent calls.
10405+
10406+        The callable should return a string with the new contents. The
10407+        callable must be prepared to be called multiple times, and must
10408+        examine the input string to see if the change that it wants to make
10409+        is already present in the old version. If it does not need to make
10410+        any changes, it can either return None, or return its input string.
10411+
10412+        If the modifier raises an exception, it will be returned in the
10413+        errback.
10414+        """
10415+
10416+
10417 # The hierarchy looks like this:
10418 #  IFilesystemNode
10419 #   IFileNode
10420hunk ./src/allmydata/interfaces.py 801
10421     def raise_error():
10422         """Raise any error associated with this node."""
10423 
10424+    # XXX: These may not be appropriate outside the context of an IReadable.
10425     def get_size():
10426         """Return the length (in bytes) of the data this node represents. For
10427         directory nodes, I return the size of the backing store. I return
10428hunk ./src/allmydata/interfaces.py 818
10429 class IFileNode(IFilesystemNode):
10430     """I am a node which represents a file: a sequence of bytes. I am not a
10431     container, like IDirectoryNode."""
10432+    def get_best_readable_version():
10433+        """Return a Deferred that fires with an IReadable for the 'best'
10434+        available version of the file. The IReadable provides only read
10435+        access, even if this filenode was derived from a write cap.
10436 
10437hunk ./src/allmydata/interfaces.py 823
10438-class IImmutableFileNode(IFileNode):
10439-    def read(consumer, offset=0, size=None):
10440-        """Download a portion (possibly all) of the file's contents, making
10441-        them available to the given IConsumer. Return a Deferred that fires
10442-        (with the consumer) when the consumer is unregistered (either because
10443-        the last byte has been given to it, or because the consumer threw an
10444-        exception during write(), possibly because it no longer wants to
10445-        receive data). The portion downloaded will start at 'offset' and
10446-        contain 'size' bytes (or the remainder of the file if size==None).
10447-
10448-        The consumer will be used in non-streaming mode: an IPullProducer
10449-        will be attached to it.
10450+        For an immutable file, there is only one version. For a mutable
10451+        file, the 'best' version is the recoverable version with the
10452+        highest sequence number. If no uncoordinated writes have occurred,
10453+        and if enough shares are available, then this will be the most
10454+        recent version that has been uploaded. If no version is recoverable,
10455+        the Deferred will errback with an UnrecoverableFileError.
10456+        """
10457 
10458hunk ./src/allmydata/interfaces.py 831
10459-        The consumer will not receive data right away: several network trips
10460-        must occur first. The order of events will be::
10461+    def download_best_version():
10462+        """Download the contents of the version that would be returned
10463+        by get_best_readable_version(). This is equivalent to calling
10464+        download_to_data() on the IReadable given by that method.
10465 
10466hunk ./src/allmydata/interfaces.py 836
10467-         consumer.registerProducer(p, streaming)
10468-          (if streaming == False)::
10469-           consumer does p.resumeProducing()
10470-            consumer.write(data)
10471-           consumer does p.resumeProducing()
10472-            consumer.write(data).. (repeat until all data is written)
10473-         consumer.unregisterProducer()
10474-         deferred.callback(consumer)
10475+        I return a Deferred that fires with a byte string when the file
10476+        has been fully downloaded. To support streaming download, use
10477+        the 'read' method of IReadable. If no version is recoverable,
10478+        the Deferred will errback with an UnrecoverableFileError.
10479+        """
10480 
10481hunk ./src/allmydata/interfaces.py 842
10482-        If a download error occurs, or an exception is raised by
10483-        consumer.registerProducer() or consumer.write(), I will call
10484-        consumer.unregisterProducer() and then deliver the exception via
10485-        deferred.errback(). To cancel the download, the consumer should call
10486-        p.stopProducing(), which will result in an exception being delivered
10487-        via deferred.errback().
10488+    def get_size_of_best_version():
10489+        """Find the size of the version that would be returned by
10490+        get_best_readable_version().
10491 
10492hunk ./src/allmydata/interfaces.py 846
10493-        See src/allmydata/util/consumer.py for an example of a simple
10494-        download-to-memory consumer.
10495+        I return a Deferred that fires with an integer. If no version
10496+        is recoverable, the Deferred will errback with an
10497+        UnrecoverableFileError.
10498         """
10499 
10500hunk ./src/allmydata/interfaces.py 851
10501+
10502+class IImmutableFileNode(IFileNode, IReadable):
10503+    """I am a node representing an immutable file. Immutable files have
10504+    only one version"""
10505+
10506+
10507 class IMutableFileNode(IFileNode):
10508     """I provide access to a 'mutable file', which retains its identity
10509     regardless of what contents are put in it.
10510hunk ./src/allmydata/interfaces.py 916
10511     only be retrieved and updated all-at-once, as a single big string. Future
10512     versions of our mutable files will remove this restriction.
10513     """
10514-
10515-    def download_best_version():
10516-        """Download the 'best' available version of the file, meaning one of
10517-        the recoverable versions with the highest sequence number. If no
10518+    def get_best_mutable_version():
10519+        """Return a Deferred that fires with an IMutableFileVersion for
10520+        the 'best' available version of the file. The best version is
10521+        the recoverable version with the highest sequence number. If no
10522         uncoordinated writes have occurred, and if enough shares are
10523hunk ./src/allmydata/interfaces.py 921
10524-        available, then this will be the most recent version that has been
10525-        uploaded.
10526-
10527-        I update an internal servermap with MODE_READ, determine which
10528-        version of the file is indicated by
10529-        servermap.best_recoverable_version(), and return a Deferred that
10530-        fires with its contents. If no version is recoverable, the Deferred
10531-        will errback with UnrecoverableFileError.
10532-        """
10533-
10534-    def get_size_of_best_version():
10535-        """Find the size of the version that would be downloaded with
10536-        download_best_version(), without actually downloading the whole file.
10537+        available, then this will be the most recent version that has
10538+        been uploaded.
10539 
10540hunk ./src/allmydata/interfaces.py 924
10541-        I return a Deferred that fires with an integer.
10542+        If no version is recoverable, the Deferred will errback with an
10543+        UnrecoverableFileError.
10544         """
10545 
10546     def overwrite(new_contents):
10547hunk ./src/allmydata/interfaces.py 964
10548         errback.
10549         """
10550 
10551-
10552     def get_servermap(mode):
10553         """Return a Deferred that fires with an IMutableFileServerMap
10554         instance, updated using the given mode.
10555hunk ./src/allmydata/test/test_filenode.py 98
10556         def _check_segment(res):
10557             self.failUnlessEqual(res, DATA[1:1+5])
10558         d.addCallback(_check_segment)
10559+        d.addCallback(lambda ignored:
10560+            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
10561+        d.addCallback(lambda ignored:
10562+            fn1.get_size_of_best_version())
10563+        d.addCallback(lambda size:
10564+            self.failUnlessEqual(size, len(DATA)))
10565+        d.addCallback(lambda ignored:
10566+            fn1.download_to_data())
10567+        d.addCallback(lambda data:
10568+            self.failUnlessEqual(data, DATA))
10569+        d.addCallback(lambda ignored:
10570+            fn1.download_best_version())
10571+        d.addCallback(lambda data:
10572+            self.failUnlessEqual(data, DATA))
10573 
10574         return d
10575 
10576hunk ./src/allmydata/test/test_immutable.py 153
10577         return d
10578 
10579 
10580+    def test_download_to_data(self):
10581+        d = self.n.download_to_data()
10582+        d.addCallback(lambda data:
10583+            self.failUnlessEqual(data, common.TEST_DATA))
10584+        return d
10585+
10586+
10587+    def test_download_best_version(self):
10588+        d = self.n.download_best_version()
10589+        d.addCallback(lambda data:
10590+            self.failUnlessEqual(data, common.TEST_DATA))
10591+        return d
10592+
10593+
10594+    def test_get_best_readable_version(self):
10595+        n = self.n.get_best_readable_version()
10596+        self.failUnlessEqual(n, self.n)
10597+
10598+    def test_get_size_of_best_version(self):
10599+        d = self.n.get_size_of_best_version()
10600+        d.addCallback(lambda size:
10601+            self.failUnlessEqual(size, len(common.TEST_DATA)))
10602+        return d
10603+
10604+
10605 # XXX extend these tests to show bad behavior of various kinds from servers: raising exception from each remove_foo() method, for example
10606 
10607 # XXX test disconnect DeadReferenceError from get_buckets and get_block_whatsit
10608}
10609[frontends/sftpd.py: alter a mutable file overwrite to work with the new API
10610Kevan Carstensen <kevan@isnotajoke.com>**20100717014446
10611 Ignore-this: 2c57bbc8d9ab97a0e9af0634c00efc86
10612] {
10613hunk ./src/allmydata/frontends/sftpd.py 33
10614 from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, \
10615      NoSuchChildError, ChildOfWrongTypeError
10616 from allmydata.mutable.common import NotWriteableError
10617+from allmydata.mutable.publish import MutableFileHandle
10618 from allmydata.immutable.upload import FileHandle
10619 from allmydata.dirnode import update_metadata
10620 
10621merger 0.0 (
10622hunk ./src/allmydata/frontends/sftpd.py 715
10623-            # TODO: use download interface described in #993 when implemented.
10624hunk ./src/allmydata/frontends/sftpd.py 715
10625-            # TODO: use download interface described in #993 when implemented.
10626-            if filenode.is_mutable():
10627-                self.async.addCallback(lambda ign: filenode.download_best_version())
10628-                def _downloaded(data):
10629-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
10630-                    self.consumer.write(data)
10631-                    self.consumer.finish()
10632-                    return None
10633-                self.async.addCallback(_downloaded)
10634-            else:
10635-                download_size = filenode.get_size()
10636-                assert download_size is not None, "download_size is None"
10637+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
10638+
10639+            def _read(version):
10640+                download_size = version.get_size()
10641+                assert download_size is not None
10642+
10643)
10644hunk ./src/allmydata/frontends/sftpd.py 728
10645                 download_size = filenode.get_size()
10646                 assert download_size is not None, "download_size is None"
10647                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
10648-                def _read(ign):
10649-                    if noisy: self.log("_read immutable", level=NOISY)
10650-                    filenode.read(self.consumer, 0, None)
10651-                self.async.addCallback(_read)
10652+
10653+                if noisy: self.log("_read", level=NOISY)
10654+                version.read(self.consumer, 0, None)
10655+            self.async.addCallback(_read)
10656 
10657         eventually(self.async.callback, None)
10658 
10659hunk ./src/allmydata/frontends/sftpd.py 875
10660                     assert parent and childname, (parent, childname, self.metadata)
10661                     d2.addCallback(lambda ign: parent.set_metadata_for(childname, self.metadata))
10662 
10663-                d2.addCallback(lambda ign: self.consumer.get_current_size())
10664-                d2.addCallback(lambda size: self.consumer.read(0, size))
10665-                d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents))
10666+                d2.addCallback(lambda ign: self.filenode.overwrite(MutableFileHandle(self.consumer.get_file())))
10667             else:
10668                 def _add_file(ign):
10669                     self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL)
10670}
10671[mutable/filenode.py: implement most of IVersion, per #993
10672Kevan Carstensen <kevan@isnotajoke.com>**20100717014516
10673 Ignore-this: d4551142b32ea97040ce0e98a394fde5
10674] {
10675hunk ./src/allmydata/mutable/filenode.py 8
10676 from twisted.internet import defer, reactor
10677 from foolscap.api import eventually
10678 from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
10679-                                 NotEnoughSharesError, \
10680-                                 MDMF_VERSION, SDMF_VERSION, IMutableUploadable
10681-from allmydata.util import hashutil, log
10682+     NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
10683+     IMutableFileVersion
10684+from allmydata.util import hashutil, log, consumer
10685 from allmydata.util.assertutil import precondition
10686 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
10687 from allmydata.monitor import Monitor
10688hunk ./src/allmydata/mutable/filenode.py 17
10689 from pycryptopp.cipher.aes import AES
10690 
10691 from allmydata.mutable.publish import Publish, MutableFileHandle, \
10692-                                      MutableDataHandle
10693+                                      MutableData
10694 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
10695      ResponseCache, UncoordinatedWriteError
10696 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
10697hunk ./src/allmydata/mutable/filenode.py 134
10698 
10699     def _get_initial_contents(self, contents):
10700         if contents is None:
10701-            return MutableDataHandle("")
10702+            return MutableData("")
10703 
10704         if IMutableUploadable.providedBy(contents):
10705             return contents
10706hunk ./src/allmydata/mutable/filenode.py 208
10707 
10708     def get_size(self):
10709         return self._most_recent_size
10710+
10711     def get_current_size(self):
10712         d = self.get_size_of_best_version()
10713         d.addCallback(self._stash_size)
10714hunk ./src/allmydata/mutable/filenode.py 213
10715         return d
10716+
10717     def _stash_size(self, size):
10718         self._most_recent_size = size
10719         return size
10720hunk ./src/allmydata/mutable/filenode.py 272
10721             return cmp(self.__class__, them.__class__)
10722         return cmp(self._uri, them._uri)
10723 
10724-    def _do_serialized(self, cb, *args, **kwargs):
10725-        # note: to avoid deadlock, this callable is *not* allowed to invoke
10726-        # other serialized methods within this (or any other)
10727-        # MutableFileNode. The callable should be a bound method of this same
10728-        # MFN instance.
10729-        d = defer.Deferred()
10730-        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
10731-        # we need to put off d.callback until this Deferred is finished being
10732-        # processed. Otherwise the caller's subsequent activities (like,
10733-        # doing other things with this node) can cause reentrancy problems in
10734-        # the Deferred code itself
10735-        self._serializer.addBoth(lambda res: eventually(d.callback, res))
10736-        # add a log.err just in case something really weird happens, because
10737-        # self._serializer stays around forever, therefore we won't see the
10738-        # usual Unhandled Error in Deferred that would give us a hint.
10739-        self._serializer.addErrback(log.err)
10740-        return d
10741 
10742     #################################
10743     # ICheckable
10744hunk ./src/allmydata/mutable/filenode.py 297
10745 
10746 
10747     #################################
10748-    # IMutableFileNode
10749+    # IFileNode
10750+
10751+    def get_best_readable_version(self):
10752+        """
10753+        I return a Deferred that fires with a MutableFileVersion
10754+        representing the best readable version of the file that I
10755+        represent
10756+        """
10757+        return self.get_readable_version()
10758+
10759+
10760+    def get_readable_version(self, servermap=None, version=None):
10761+        """
10762+        I return a Deferred that fires with an MutableFileVersion for my
10763+        version argument, if there is a recoverable file of that version
10764+        on the grid. If there is no recoverable version, I fire with an
10765+        UnrecoverableFileError.
10766+
10767+        If a servermap is provided, I look in there for the requested
10768+        version. If no servermap is provided, I create and update a new
10769+        one.
10770+
10771+        If no version is provided, then I return a MutableFileVersion
10772+        representing the best recoverable version of the file.
10773+        """
10774+        d = self._get_version_from_servermap(MODE_READ, servermap, version)
10775+        def _build_version((servermap, their_version)):
10776+            assert their_version in servermap.recoverable_versions()
10777+            assert their_version in servermap.make_versionmap()
10778+
10779+            mfv = MutableFileVersion(self,
10780+                                     servermap,
10781+                                     their_version,
10782+                                     self._storage_index,
10783+                                     self._storage_broker,
10784+                                     self._readkey,
10785+                                     history=self._history)
10786+            assert mfv.is_readonly()
10787+            # our caller can use this to download the contents of the
10788+            # mutable file.
10789+            return mfv
10790+        return d.addCallback(_build_version)
10791+
10792+
10793+    def _get_version_from_servermap(self,
10794+                                    mode,
10795+                                    servermap=None,
10796+                                    version=None):
10797+        """
10798+        I return a Deferred that fires with (servermap, version).
10799+
10800+        This function performs validation and a servermap update. If it
10801+        returns (servermap, version), the caller can assume that:
10802+            - servermap was last updated in mode.
10803+            - version is recoverable, and corresponds to the servermap.
10804+
10805+        If version and servermap are provided to me, I will validate
10806+        that version exists in the servermap, and that the servermap was
10807+        updated correctly.
10808+
10809+        If version is not provided, but servermap is, I will validate
10810+        the servermap and return the best recoverable version that I can
10811+        find in the servermap.
10812+
10813+        If the version is provided but the servermap isn't, I will
10814+        obtain a servermap that has been updated in the correct mode and
10815+        validate that version is found and recoverable.
10816+
10817+        If neither servermap nor version are provided, I will obtain a
10818+        servermap updated in the correct mode, and return the best
10819+        recoverable version that I can find in there.
10820+        """
10821+        # XXX: wording ^^^^
10822+        if servermap and servermap.last_update_mode == mode:
10823+            d = defer.succeed(servermap)
10824+        else:
10825+            d = self._get_servermap(mode)
10826+
10827+        def _get_version(servermap, version):
10828+            if version and version not in servermap.recoverable_versions():
10829+                version = None
10830+            else:
10831+                version = servermap.best_recoverable_version()
10832+            if not version:
10833+                raise UnrecoverableFileError("no recoverable versions")
10834+            return (servermap, version)
10835+        return d.addCallback(_get_version, version)
10836+
10837 
10838     def download_best_version(self):
10839hunk ./src/allmydata/mutable/filenode.py 387
10840+        """
10841+        I return a Deferred that fires with the contents of the best
10842+        version of this mutable file.
10843+        """
10844         return self._do_serialized(self._download_best_version)
10845hunk ./src/allmydata/mutable/filenode.py 392
10846+
10847+
10848     def _download_best_version(self):
10849hunk ./src/allmydata/mutable/filenode.py 395
10850-        servermap = ServerMap()
10851-        d = self._try_once_to_download_best_version(servermap, MODE_READ)
10852-        def _maybe_retry(f):
10853-            f.trap(NotEnoughSharesError)
10854-            # the download is worth retrying once. Make sure to use the
10855-            # old servermap, since it is what remembers the bad shares,
10856-            # but use MODE_WRITE to make it look for even more shares.
10857-            # TODO: consider allowing this to retry multiple times.. this
10858-            # approach will let us tolerate about 8 bad shares, I think.
10859-            return self._try_once_to_download_best_version(servermap,
10860-                                                           MODE_WRITE)
10861+        """
10862+        I am the serialized sibling of download_best_version.
10863+        """
10864+        d = self.get_best_readable_version()
10865+        d.addCallback(self._record_size)
10866+        d.addCallback(lambda version: version.download_to_data())
10867+
10868+        # It is possible that the download will fail because there
10869+        # aren't enough shares to be had. If so, we will try again after
10870+        # updating the servermap in MODE_WRITE, which may find more
10871+        # shares than updating in MODE_READ, as we just did. We can do
10872+        # this by getting the best mutable version and downloading from
10873+        # that -- the best mutable version will be a MutableFileVersion
10874+        # with a servermap that was last updated in MODE_WRITE, as we
10875+        # want. If this fails, then we give up.
10876+        def _maybe_retry(failure):
10877+            failure.trap(NotEnoughSharesError)
10878+
10879+            d = self.get_best_mutable_version()
10880+            d.addCallback(self._record_size)
10881+            d.addCallback(lambda version: version.download_to_data())
10882+            return d
10883+
10884         d.addErrback(_maybe_retry)
10885         return d
10886hunk ./src/allmydata/mutable/filenode.py 420
10887-    def _try_once_to_download_best_version(self, servermap, mode):
10888-        d = self._update_servermap(servermap, mode)
10889-        d.addCallback(self._once_updated_download_best_version, servermap)
10890-        return d
10891-    def _once_updated_download_best_version(self, ignored, servermap):
10892-        goal = servermap.best_recoverable_version()
10893-        if not goal:
10894-            raise UnrecoverableFileError("no recoverable versions")
10895-        return self._try_once_to_download_version(servermap, goal)
10896+
10897+
10898+    def _record_size(self, mfv):
10899+        """
10900+        I record the size of a mutable file version.
10901+        """
10902+        self._most_recent_size = mfv.get_size()
10903+        return mfv
10904+
10905 
10906     def get_size_of_best_version(self):
10907hunk ./src/allmydata/mutable/filenode.py 431
10908-        d = self.get_servermap(MODE_READ)
10909-        def _got_servermap(smap):
10910-            ver = smap.best_recoverable_version()
10911-            if not ver:
10912-                raise UnrecoverableFileError("no recoverable version")
10913-            return smap.size_of_version(ver)
10914-        d.addCallback(_got_servermap)
10915-        return d
10916+        """
10917+        I return the size of the best version of this mutable file.
10918+
10919+        This is equivalent to calling get_size() on the result of
10920+        get_best_readable_version().
10921+        """
10922+        d = self.get_best_readable_version()
10923+        return d.addCallback(lambda mfv: mfv.get_size())
10924+
10925+
10926+    #################################
10927+    # IMutableFileNode
10928+
10929+    def get_best_mutable_version(self, servermap=None):
10930+        """
10931+        I return a Deferred that fires with a MutableFileVersion
10932+        representing the best readable version of the file that I
10933+        represent. I am like get_best_readable_version, except that I
10934+        will try to make a writable version if I can.
10935+        """
10936+        return self.get_mutable_version(servermap=servermap)
10937+
10938+
10939+    def get_mutable_version(self, servermap=None, version=None):
10940+        """
10941+        I return a version of this mutable file. I return a Deferred
10942+        that fires with a MutableFileVersion
10943+
10944+        If version is provided, the Deferred will fire with a
10945+        MutableFileVersion initailized with that version. Otherwise, it
10946+        will fire with the best version that I can recover.
10947+
10948+        If servermap is provided, I will use that to find versions
10949+        instead of performing my own servermap update.
10950+        """
10951+        if self.is_readonly():
10952+            return self.get_readable_version(servermap=servermap,
10953+                                             version=version)
10954+
10955+        # get_mutable_version => write intent, so we require that the
10956+        # servermap is updated in MODE_WRITE
10957+        d = self._get_version_from_servermap(MODE_WRITE, servermap, version)
10958+        def _build_version((servermap, smap_version)):
10959+            # these should have been set by the servermap update.
10960+            assert self._secret_holder
10961+            assert self._writekey
10962+
10963+            mfv = MutableFileVersion(self,
10964+                                     servermap,
10965+                                     smap_version,
10966+                                     self._storage_index,
10967+                                     self._storage_broker,
10968+                                     self._readkey,
10969+                                     self._writekey,
10970+                                     self._secret_holder,
10971+                                     history=self._history)
10972+            assert not mfv.is_readonly()
10973+            return mfv
10974+
10975+        return d.addCallback(_build_version)
10976+
10977+
10978+    # XXX: I'm uncomfortable with the difference between upload and
10979+    #      overwrite, which, FWICT, is basically that you don't have to
10980+    #      do a servermap update before you overwrite. We split them up
10981+    #      that way anyway, so I guess there's no real difficulty in
10982+    #      offering both ways to callers, but it also makes the
10983+    #      public-facing API cluttery, and makes it hard to discern the
10984+    #      right way of doing things.
10985 
10986hunk ./src/allmydata/mutable/filenode.py 501
10987+    # In general, we leave it to callers to ensure that they aren't
10988+    # going to cause UncoordinatedWriteErrors when working with
10989+    # MutableFileVersions. We know that the next three operations
10990+    # (upload, overwrite, and modify) will all operate on the same
10991+    # version, so we say that only one of them can be going on at once,
10992+    # and serialize them to ensure that that actually happens, since as
10993+    # the caller in this situation it is our job to do that.
10994     def overwrite(self, new_contents):
10995hunk ./src/allmydata/mutable/filenode.py 509
10996+        """
10997+        I overwrite the contents of the best recoverable version of this
10998+        mutable file with new_contents. This is equivalent to calling
10999+        overwrite on the result of get_best_mutable_version with
11000+        new_contents as an argument. I return a Deferred that eventually
11001+        fires with the results of my replacement process.
11002+        """
11003         return self._do_serialized(self._overwrite, new_contents)
11004hunk ./src/allmydata/mutable/filenode.py 517
11005+
11006+
11007     def _overwrite(self, new_contents):
11008hunk ./src/allmydata/mutable/filenode.py 520
11009-        assert IMutableUploadable.providedBy(new_contents)
11010+        """
11011+        I am the serialized sibling of overwrite.
11012+        """
11013+        d = self.get_best_mutable_version()
11014+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11015+
11016+
11017+
11018+    def upload(self, new_contents, servermap):
11019+        """
11020+        I overwrite the contents of the best recoverable version of this
11021+        mutable file with new_contents, using servermap instead of
11022+        creating/updating our own servermap. I return a Deferred that
11023+        fires with the results of my upload.
11024+        """
11025+        return self._do_serialized(self._upload, new_contents, servermap)
11026+
11027+
11028+    def _upload(self, new_contents, servermap):
11029+        """
11030+        I am the serialized sibling of upload.
11031+        """
11032+        d = self.get_best_mutable_version(servermap)
11033+        return d.addCallback(lambda mfv: mfv.overwrite(new_contents))
11034+
11035+
11036+    def modify(self, modifier, backoffer=None):
11037+        """
11038+        I modify the contents of the best recoverable version of this
11039+        mutable file with the modifier. This is equivalent to calling
11040+        modify on the result of get_best_mutable_version. I return a
11041+        Deferred that eventually fires with an UploadResults instance
11042+        describing this process.
11043+        """
11044+        return self._do_serialized(self._modify, modifier, backoffer)
11045+
11046+
11047+    def _modify(self, modifier, backoffer):
11048+        """
11049+        I am the serialized sibling of modify.
11050+        """
11051+        d = self.get_best_mutable_version()
11052+        return d.addCallback(lambda mfv: mfv.modify(modifier, backoffer))
11053+
11054+
11055+    def download_version(self, servermap, version, fetch_privkey=False):
11056+        """
11057+        Download the specified version of this mutable file. I return a
11058+        Deferred that fires with the contents of the specified version
11059+        as a bytestring, or errbacks if the file is not recoverable.
11060+        """
11061+        d = self.get_readable_version(servermap, version)
11062+        return d.addCallback(lambda mfv: mfv.download_to_data(fetch_privkey))
11063+
11064+
11065+    def get_servermap(self, mode):
11066+        """
11067+        I return a servermap that has been updated in mode.
11068+
11069+        mode should be one of MODE_READ, MODE_WRITE, MODE_CHECK or
11070+        MODE_ANYTHING. See servermap.py for more on what these mean.
11071+        """
11072+        return self._do_serialized(self._get_servermap, mode)
11073+
11074 
11075hunk ./src/allmydata/mutable/filenode.py 585
11076+    def _get_servermap(self, mode):
11077+        """
11078+        I am a serialized twin to get_servermap.
11079+        """
11080         servermap = ServerMap()
11081hunk ./src/allmydata/mutable/filenode.py 590
11082-        d = self._update_servermap(servermap, mode=MODE_WRITE)
11083-        d.addCallback(lambda ignored: self._upload(new_contents, servermap))
11084+        return self._update_servermap(servermap, mode)
11085+
11086+
11087+    def _update_servermap(self, servermap, mode):
11088+        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11089+                             mode)
11090+        if self._history:
11091+            self._history.notify_mapupdate(u.get_status())
11092+        return u.update()
11093+
11094+
11095+    def set_version(self, version):
11096+        # I can be set in two ways:
11097+        #  1. When the node is created.
11098+        #  2. (for an existing share) when the Servermap is updated
11099+        #     before I am read.
11100+        assert version in (MDMF_VERSION, SDMF_VERSION)
11101+        self._protocol_version = version
11102+
11103+
11104+    def get_version(self):
11105+        return self._protocol_version
11106+
11107+
11108+    def _do_serialized(self, cb, *args, **kwargs):
11109+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11110+        # other serialized methods within this (or any other)
11111+        # MutableFileNode. The callable should be a bound method of this same
11112+        # MFN instance.
11113+        d = defer.Deferred()
11114+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11115+        # we need to put off d.callback until this Deferred is finished being
11116+        # processed. Otherwise the caller's subsequent activities (like,
11117+        # doing other things with this node) can cause reentrancy problems in
11118+        # the Deferred code itself
11119+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11120+        # add a log.err just in case something really weird happens, because
11121+        # self._serializer stays around forever, therefore we won't see the
11122+        # usual Unhandled Error in Deferred that would give us a hint.
11123+        self._serializer.addErrback(log.err)
11124         return d
11125 
11126 
11127hunk ./src/allmydata/mutable/filenode.py 633
11128+    def _upload(self, new_contents, servermap):
11129+        """
11130+        A MutableFileNode still has to have some way of getting
11131+        published initially, which is what I am here for. After that,
11132+        all publishing, updating, modifying and so on happens through
11133+        MutableFileVersions.
11134+        """
11135+        assert self._pubkey, "update_servermap must be called before publish"
11136+
11137+        p = Publish(self, self._storage_broker, servermap)
11138+        if self._history:
11139+            self._history.notify_publish(p.get_status(),
11140+                                         new_contents.get_size())
11141+        d = p.publish(new_contents)
11142+        d.addCallback(self._did_upload, new_contents.get_size())
11143+        return d
11144+
11145+
11146+    def _did_upload(self, res, size):
11147+        self._most_recent_size = size
11148+        return res
11149+
11150+
11151+class MutableFileVersion:
11152+    """
11153+    I represent a specific version (most likely the best version) of a
11154+    mutable file.
11155+
11156+    Since I implement IReadable, instances which hold a
11157+    reference to an instance of me are guaranteed the ability (absent
11158+    connection difficulties or unrecoverable versions) to read the file
11159+    that I represent. Depending on whether I was initialized with a
11160+    write capability or not, I may also provide callers the ability to
11161+    overwrite or modify the contents of the mutable file that I
11162+    reference.
11163+    """
11164+    implements(IMutableFileVersion)
11165+
11166+    def __init__(self,
11167+                 node,
11168+                 servermap,
11169+                 version,
11170+                 storage_index,
11171+                 storage_broker,
11172+                 readcap,
11173+                 writekey=None,
11174+                 write_secrets=None,
11175+                 history=None):
11176+
11177+        self._node = node
11178+        self._servermap = servermap
11179+        self._version = version
11180+        self._storage_index = storage_index
11181+        self._write_secrets = write_secrets
11182+        self._history = history
11183+        self._storage_broker = storage_broker
11184+
11185+        #assert isinstance(readcap, IURI)
11186+        self._readcap = readcap
11187+
11188+        self._writekey = writekey
11189+        self._serializer = defer.succeed(None)
11190+        self._size = None
11191+
11192+
11193+    def get_sequence_number(self):
11194+        """
11195+        Get the sequence number of the mutable version that I represent.
11196+        """
11197+        return 0
11198+
11199+
11200+    # TODO: Terminology?
11201+    def get_writekey(self):
11202+        """
11203+        I return a writekey or None if I don't have a writekey.
11204+        """
11205+        return self._writekey
11206+
11207+
11208+    def overwrite(self, new_contents):
11209+        """
11210+        I overwrite the contents of this mutable file version with the
11211+        data in new_contents.
11212+        """
11213+        assert not self.is_readonly()
11214+
11215+        return self._do_serialized(self._overwrite, new_contents)
11216+
11217+
11218+    def _overwrite(self, new_contents):
11219+        assert IMutableUploadable.providedBy(new_contents)
11220+        assert self._servermap.last_update_mode == MODE_WRITE
11221+
11222+        return self._upload(new_contents)
11223+
11224+
11225     def modify(self, modifier, backoffer=None):
11226         """I use a modifier callback to apply a change to the mutable file.
11227         I implement the following pseudocode::
11228hunk ./src/allmydata/mutable/filenode.py 770
11229         backoffer should not invoke any methods on this MutableFileNode
11230         instance, and it needs to be highly conscious of deadlock issues.
11231         """
11232+        assert not self.is_readonly()
11233+
11234         return self._do_serialized(self._modify, modifier, backoffer)
11235hunk ./src/allmydata/mutable/filenode.py 773
11236+
11237+
11238     def _modify(self, modifier, backoffer):
11239hunk ./src/allmydata/mutable/filenode.py 776
11240-        servermap = ServerMap()
11241         if backoffer is None:
11242             backoffer = BackoffAgent().delay
11243hunk ./src/allmydata/mutable/filenode.py 778
11244-        return self._modify_and_retry(servermap, modifier, backoffer, True)
11245-    def _modify_and_retry(self, servermap, modifier, backoffer, first_time):
11246-        d = self._modify_once(servermap, modifier, first_time)
11247+        return self._modify_and_retry(modifier, backoffer, True)
11248+
11249+
11250+    def _modify_and_retry(self, modifier, backoffer, first_time):
11251+        """
11252+        I try to apply modifier to the contents of this version of the
11253+        mutable file. If I succeed, I return an UploadResults instance
11254+        describing my success. If I fail, I try again after waiting for
11255+        a little bit.
11256+        """
11257+        log.msg("doing modify")
11258+        d = self._modify_once(modifier, first_time)
11259         def _retry(f):
11260             f.trap(UncoordinatedWriteError)
11261             d2 = defer.maybeDeferred(backoffer, self, f)
11262hunk ./src/allmydata/mutable/filenode.py 794
11263             d2.addCallback(lambda ignored:
11264-                           self._modify_and_retry(servermap, modifier,
11265+                           self._modify_and_retry(modifier,
11266                                                   backoffer, False))
11267             return d2
11268         d.addErrback(_retry)
11269hunk ./src/allmydata/mutable/filenode.py 799
11270         return d
11271-    def _modify_once(self, servermap, modifier, first_time):
11272-        d = self._update_servermap(servermap, MODE_WRITE)
11273-        d.addCallback(self._once_updated_download_best_version, servermap)
11274+
11275+
11276+    def _modify_once(self, modifier, first_time):
11277+        """
11278+        I attempt to apply a modifier to the contents of the mutable
11279+        file.
11280+        """
11281+        assert self._servermap.last_update_mode == MODE_WRITE
11282+
11283+        # download_to_data is serialized, so we have to call this to
11284+        # avoid deadlock.
11285+        d = self._try_to_download_data()
11286         def _apply(old_contents):
11287hunk ./src/allmydata/mutable/filenode.py 812
11288-            new_contents = modifier(old_contents, servermap, first_time)
11289+            new_contents = modifier(old_contents, self._servermap, first_time)
11290             if new_contents is None or new_contents == old_contents:
11291hunk ./src/allmydata/mutable/filenode.py 814
11292+                log.msg("no changes")
11293                 # no changes need to be made
11294                 if first_time:
11295                     return
11296hunk ./src/allmydata/mutable/filenode.py 822
11297                 # recovery when it observes UCWE, we need to do a second
11298                 # publish. See #551 for details. We'll basically loop until
11299                 # we managed an uncontested publish.
11300-                old_uploadable = MutableDataHandle(old_contents)
11301+                old_uploadable = MutableData(old_contents)
11302                 new_contents = old_uploadable
11303             precondition((IMutableUploadable.providedBy(new_contents) or
11304                           new_contents is None),
11305hunk ./src/allmydata/mutable/filenode.py 828
11306                          "Modifier function must return an IMutableUploadable "
11307                          "or None")
11308-            return self._upload(new_contents, servermap)
11309+            return self._upload(new_contents)
11310         d.addCallback(_apply)
11311         return d
11312 
11313hunk ./src/allmydata/mutable/filenode.py 832
11314-    def get_servermap(self, mode):
11315-        return self._do_serialized(self._get_servermap, mode)
11316-    def _get_servermap(self, mode):
11317-        servermap = ServerMap()
11318-        return self._update_servermap(servermap, mode)
11319-    def _update_servermap(self, servermap, mode):
11320-        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
11321-                             mode)
11322-        if self._history:
11323-            self._history.notify_mapupdate(u.get_status())
11324-        return u.update()
11325 
11326hunk ./src/allmydata/mutable/filenode.py 833
11327-    def download_version(self, servermap, version, fetch_privkey=False):
11328-        return self._do_serialized(self._try_once_to_download_version,
11329-                                   servermap, version, fetch_privkey)
11330-    def _try_once_to_download_version(self, servermap, version,
11331-                                      fetch_privkey=False):
11332-        r = Retrieve(self, servermap, version, fetch_privkey)
11333+    def is_readonly(self):
11334+        """
11335+        I return True if this MutableFileVersion provides no write
11336+        access to the file that it encapsulates, and False if it
11337+        provides the ability to modify the file.
11338+        """
11339+        return self._writekey is None
11340+
11341+
11342+    def is_mutable(self):
11343+        """
11344+        I return True, since mutable files are always mutable by
11345+        somebody.
11346+        """
11347+        return True
11348+
11349+
11350+    def get_storage_index(self):
11351+        """
11352+        I return the storage index of the reference that I encapsulate.
11353+        """
11354+        return self._storage_index
11355+
11356+
11357+    def get_size(self):
11358+        """
11359+        I return the length, in bytes, of this readable object.
11360+        """
11361+        return self._servermap.size_of_version(self._version)
11362+
11363+
11364+    def download_to_data(self, fetch_privkey=False):
11365+        """
11366+        I return a Deferred that fires with the contents of this
11367+        readable object as a byte string.
11368+
11369+        """
11370+        c = consumer.MemoryConsumer()
11371+        d = self.read(c, fetch_privkey=fetch_privkey)
11372+        d.addCallback(lambda mc: "".join(mc.chunks))
11373+        return d
11374+
11375+
11376+    def _try_to_download_data(self):
11377+        """
11378+        I am an unserialized cousin of download_to_data; I am called
11379+        from the children of modify() to download the data associated
11380+        with this mutable version.
11381+        """
11382+        c = consumer.MemoryConsumer()
11383+        # modify will almost certainly write, so we need the privkey.
11384+        d = self._read(c, fetch_privkey=True)
11385+        d.addCallback(lambda mc: "".join(mc.chunks))
11386+        return d
11387+
11388+
11389+    def _update_servermap(self, mode=MODE_READ):
11390+        """
11391+        I update our Servermap according to my mode argument. I return a
11392+        Deferred that fires with None when this has finished. The
11393+        updated Servermap will be at self._servermap in that case.
11394+        """
11395+        d = self._node.get_servermap(mode)
11396+
11397+        def _got_servermap(servermap):
11398+            assert servermap.last_update_mode == mode
11399+
11400+            self._servermap = servermap
11401+        d.addCallback(_got_servermap)
11402+        return d
11403+
11404+
11405+    def read(self, consumer, offset=0, size=None, fetch_privkey=False):
11406+        """
11407+        I read a portion (possibly all) of the mutable file that I
11408+        reference into consumer.
11409+        """
11410+        return self._do_serialized(self._read, consumer, offset, size,
11411+                                   fetch_privkey)
11412+
11413+
11414+    def _read(self, consumer, offset=0, size=None, fetch_privkey=False):
11415+        """
11416+        I am the serialized companion of read.
11417+        """
11418+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
11419         if self._history:
11420             self._history.notify_retrieve(r.get_status())
11421hunk ./src/allmydata/mutable/filenode.py 921
11422-        d = r.download()
11423-        d.addCallback(self._downloaded_version)
11424+        d = r.download(consumer, offset, size)
11425+        return d
11426+
11427+
11428+    def _do_serialized(self, cb, *args, **kwargs):
11429+        # note: to avoid deadlock, this callable is *not* allowed to invoke
11430+        # other serialized methods within this (or any other)
11431+        # MutableFileNode. The callable should be a bound method of this same
11432+        # MFN instance.
11433+        d = defer.Deferred()
11434+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
11435+        # we need to put off d.callback until this Deferred is finished being
11436+        # processed. Otherwise the caller's subsequent activities (like,
11437+        # doing other things with this node) can cause reentrancy problems in
11438+        # the Deferred code itself
11439+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
11440+        # add a log.err just in case something really weird happens, because
11441+        # self._serializer stays around forever, therefore we won't see the
11442+        # usual Unhandled Error in Deferred that would give us a hint.
11443+        self._serializer.addErrback(log.err)
11444         return d
11445hunk ./src/allmydata/mutable/filenode.py 942
11446-    def _downloaded_version(self, data):
11447-        self._most_recent_size = len(data)
11448-        return data
11449 
11450hunk ./src/allmydata/mutable/filenode.py 943
11451-    def upload(self, new_contents, servermap):
11452-        return self._do_serialized(self._upload, new_contents, servermap)
11453-    def _upload(self, new_contents, servermap):
11454-        assert self._pubkey, "update_servermap must be called before publish"
11455-        assert IMutableUploadable.providedBy(new_contents)
11456 
11457hunk ./src/allmydata/mutable/filenode.py 944
11458-        p = Publish(self, self._storage_broker, servermap)
11459+    def _upload(self, new_contents):
11460+        #assert self._pubkey, "update_servermap must be called before publish"
11461+        p = Publish(self._node, self._storage_broker, self._servermap)
11462         if self._history:
11463hunk ./src/allmydata/mutable/filenode.py 948
11464-            self._history.notify_publish(p.get_status(), new_contents.get_size())
11465+            self._history.notify_publish(p.get_status(),
11466+                                         new_contents.get_size())
11467         d = p.publish(new_contents)
11468         d.addCallback(self._did_upload, new_contents.get_size())
11469         return d
11470hunk ./src/allmydata/mutable/filenode.py 953
11471-    def _did_upload(self, res, size):
11472-        self._most_recent_size = size
11473-        return res
11474-
11475-
11476-    def set_version(self, version):
11477-        # I can be set in two ways:
11478-        #  1. When the node is created.
11479-        #  2. (for an existing share) when the Servermap is updated
11480-        #     before I am read.
11481-        assert version in (MDMF_VERSION, SDMF_VERSION)
11482-        self._protocol_version = version
11483 
11484 
11485hunk ./src/allmydata/mutable/filenode.py 955
11486-    def get_version(self):
11487-        return self._protocol_version
11488+    def _did_upload(self, res, size):
11489+        self._size = size
11490+        return res
11491}
11492[mutable/publish.py: enable segmented uploading for big files, change constants and wording, change MutableDataHandle to MutableData
11493Kevan Carstensen <kevan@isnotajoke.com>**20100717014549
11494 Ignore-this: f736c60c90ff09c98544af17146cf654
11495] {
11496hunk ./src/allmydata/mutable/publish.py 145
11497 
11498         self.data = newdata
11499         self.datalength = newdata.get_size()
11500+        if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
11501+            self._version = MDMF_VERSION
11502+        else:
11503+            self._version = SDMF_VERSION
11504 
11505         self.log("starting publish, datalen is %s" % self.datalength)
11506         self._status.set_size(self.datalength)
11507hunk ./src/allmydata/mutable/publish.py 1007
11508             old_position = self._filehandle.tell()
11509             # Seek to the end of the file by seeking 0 bytes from the
11510             # file's end
11511-            self._filehandle.seek(0, os.SEEK_END)
11512+            self._filehandle.seek(0, 2) # 2 == os.SEEK_END in 2.5+
11513             self._size = self._filehandle.tell()
11514             # Restore the previous position, in case this was called
11515             # after a read.
11516hunk ./src/allmydata/mutable/publish.py 1022
11517         """
11518         I return some data (up to length bytes) from my filehandle.
11519 
11520-        In most cases, I return length bytes. If I don't, it is because
11521-        length is longer than the distance between my current position
11522-        in the file that I represent and its end. In that case, I return
11523-        as many bytes as I can before going over the EOF.
11524+        In most cases, I return length bytes, but sometimes I won't --
11525+        for example, if I am asked to read beyond the end of a file, or
11526+        an error occurs.
11527         """
11528         return [self._filehandle.read(length)]
11529 
11530hunk ./src/allmydata/mutable/publish.py 1037
11531         self._filehandle.close()
11532 
11533 
11534-class MutableDataHandle(MutableFileHandle):
11535+class MutableData(MutableFileHandle):
11536     """
11537     I am a mutable uploadable built around a string, which I then cast
11538     into a StringIO and treat as a filehandle.
11539}
11540[immutable/filenode.py: fix broken implementation of #993 interfaces
11541Kevan Carstensen <kevan@isnotajoke.com>**20100717015049
11542 Ignore-this: 19ac8cf5d31ac88d4a1998ac342db004
11543] {
11544hunk ./src/allmydata/immutable/filenode.py 326
11545         immutable files can have only one version, we just return the
11546         current filenode.
11547         """
11548-        return self
11549+        return defer.succeed(self)
11550 
11551 
11552     def download_best_version(self):
11553hunk ./src/allmydata/immutable/filenode.py 412
11554 
11555     # IReadable, IFileNode, IFilesystemNode
11556     def get_best_readable_version(self):
11557-        return self
11558+        return defer.succeed(self)
11559 
11560 
11561     def download_best_version(self):
11562}
11563[mutable/retrieve.py: alter Retrieve so that it can download parts of mutable files
11564Kevan Carstensen <kevan@isnotajoke.com>**20100717015123
11565 Ignore-this: f49a6d3c05afc784aff0a4c63964a3e5
11566] {
11567hunk ./src/allmydata/mutable/retrieve.py 7
11568 from zope.interface import implements
11569 from twisted.internet import defer
11570 from twisted.python import failure
11571+from twisted.internet.interfaces import IPushProducer, IConsumer
11572 from foolscap.api import DeadReferenceError, eventually, fireEventually
11573 from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
11574                                  MDMF_VERSION, SDMF_VERSION
11575hunk ./src/allmydata/mutable/retrieve.py 86
11576     # times, and each will have a separate response chain. However the
11577     # Retrieve object will remain tied to a specific version of the file, and
11578     # will use a single ServerMap instance.
11579+    implements(IPushProducer)
11580 
11581     def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
11582                  verify=False):
11583hunk ./src/allmydata/mutable/retrieve.py 129
11584         # 3. When we are validating readers, we need to validate the
11585         #    signature on the prefix. Do we? We already do this in the
11586         #    servermap update?
11587-        #
11588-        # (just work on 1 and 2 for now, I guess)
11589         self._verify = False
11590         if verify:
11591             self._verify = True
11592hunk ./src/allmydata/mutable/retrieve.py 143
11593         self._status.set_size(datalength)
11594         self._status.set_encoding(k, N)
11595         self.readers = {}
11596+        self._paused = False
11597+        self._paused_deferred = None
11598+
11599 
11600     def get_status(self):
11601         return self._status
11602hunk ./src/allmydata/mutable/retrieve.py 157
11603             kwargs["facility"] = "tahoe.mutable.retrieve"
11604         return log.msg(*args, **kwargs)
11605 
11606-    def download(self):
11607+
11608+    ###################
11609+    # IPushProducer
11610+
11611+    def pauseProducing(self):
11612+        """
11613+        I am called by my download target if we have produced too much
11614+        data for it to handle. I make the downloader stop producing new
11615+        data until my resumeProducing method is called.
11616+        """
11617+        if self._paused:
11618+            return
11619+
11620+        # fired when the download is unpaused.
11621+        self._pause_deferred = defer.Deferred()
11622+        self._paused = True
11623+
11624+
11625+    def resumeProducing(self):
11626+        """
11627+        I am called by my download target once it is ready to begin
11628+        receiving data again.
11629+        """
11630+        if not self._paused:
11631+            return
11632+
11633+        self._paused = False
11634+        p = self._pause_deferred
11635+        self._pause_deferred = None
11636+        eventually(p.callback, None)
11637+
11638+
11639+    def _check_for_paused(self, res):
11640+        """
11641+        I am called just before a write to the consumer. I return a
11642+        Deferred that eventually fires with the data that is to be
11643+        written to the consumer. If the download has not been paused,
11644+        the Deferred fires immediately. Otherwise, the Deferred fires
11645+        when the downloader is unpaused.
11646+        """
11647+        if self._paused:
11648+            d = defer.Deferred()
11649+            self._pause_defered.addCallback(lambda ignored: d.callback(res))
11650+            return d
11651+        return defer.succeed(res)
11652+
11653+
11654+    def download(self, consumer=None, offset=0, size=None):
11655+        assert IConsumer.providedBy(consumer) or self._verify
11656+
11657+        if consumer:
11658+            self._consumer = consumer
11659+            # we provide IPushProducer, so streaming=True, per
11660+            # IConsumer.
11661+            self._consumer.registerProducer(self, streaming=True)
11662+
11663         self._done_deferred = defer.Deferred()
11664         self._started = time.time()
11665         self._status.set_status("Retrieving Shares")
11666hunk ./src/allmydata/mutable/retrieve.py 217
11667 
11668+        self._offset = offset
11669+        self._read_length = size
11670+
11671         # first, which servers can we use?
11672         versionmap = self.servermap.make_versionmap()
11673         shares = versionmap[self.verinfo]
11674hunk ./src/allmydata/mutable/retrieve.py 278
11675         assert len(self.remaining_sharemap) >= k
11676 
11677         self.log("starting download")
11678+        self._paused = False
11679         self._add_active_peers()
11680         # The download process beyond this is a state machine.
11681         # _add_active_peers will select the peers that we want to use
11682hunk ./src/allmydata/mutable/retrieve.py 324
11683 
11684         self._segment_decoder = codec.CRSDecoder()
11685         self._segment_decoder.set_params(segsize, k, n)
11686-        self._current_segment = 0
11687 
11688         if  not self._tail_data_size:
11689             self._tail_data_size = segsize
11690hunk ./src/allmydata/mutable/retrieve.py 349
11691             # So we don't have to do this later.
11692             self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
11693 
11694-        # If we have more than one segment, we are an SDMF file, which
11695-        # means that we need to validate the salts as we receive them.
11696-        self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments)
11697-        self._salt_hash_tree[0] = IV # from the prefix.
11698+        # Our last task is to tell the downloader where to start and
11699+        # where to stop. We use three parameters for that:
11700+        #   - self._start_segment: the segment that we need to start
11701+        #     downloading from.
11702+        #   - self._current_segment: the next segment that we need to
11703+        #     download.
11704+        #   - self._last_segment: The last segment that we were asked to
11705+        #     download.
11706+        #
11707+        #  We say that the download is complete when
11708+        #  self._current_segment > self._last_segment. We use
11709+        #  self._start_segment and self._last_segment to know when to
11710+        #  strip things off of segments, and how much to strip.
11711+        if self._offset:
11712+            self.log("got offset: %d" % self._offset)
11713+            # our start segment is the first segment containing the
11714+            # offset we were given.
11715+            start = mathutil.div_ceil(self._offset,
11716+                                      self._segment_size)
11717+            # this gets us the first segment after self._offset. Then
11718+            # our start segment is the one before it.
11719+            start -= 1
11720+
11721+            assert start < self._num_segments
11722+            self._start_segment = start
11723+            self.log("got start segment: %d" % self._start_segment)
11724+        else:
11725+            self._start_segment = 0
11726+
11727+
11728+        if self._read_length:
11729+            # our end segment is the last segment containing part of the
11730+            # segment that we were asked to read.
11731+            self.log("got read length %d" % self._read_length)
11732+            end_data = self._offset + self._read_length
11733+            end = mathutil.div_ceil(end_data,
11734+                                    self._segment_size)
11735+            end -= 1
11736+            assert end < self._num_segments
11737+            self._last_segment = end
11738+            self.log("got end segment: %d" % self._last_segment)
11739+        else:
11740+            self._last_segment = self._num_segments - 1
11741 
11742hunk ./src/allmydata/mutable/retrieve.py 393
11743+        self._current_segment = self._start_segment
11744 
11745     def _add_active_peers(self):
11746         """
11747hunk ./src/allmydata/mutable/retrieve.py 637
11748         that this Retrieve is currently responsible for downloading.
11749         """
11750         assert len(self._active_readers) >= self._required_shares
11751-        if self._current_segment < self._num_segments:
11752+        if self._current_segment <= self._last_segment:
11753             d = self._process_segment(self._current_segment)
11754         else:
11755             d = defer.succeed(None)
11756hunk ./src/allmydata/mutable/retrieve.py 701
11757             d.addCallback(self._decrypt_segment)
11758             d.addErrback(self._validation_or_decoding_failed,
11759                          self._active_readers)
11760+            # check to see whether we've been paused before writing
11761+            # anything.
11762+            d.addCallback(self._check_for_paused)
11763             d.addCallback(self._set_segment)
11764             return d
11765         else:
11766hunk ./src/allmydata/mutable/retrieve.py 716
11767         target that is handling the file download.
11768         """
11769         self.log("got plaintext for segment %d" % self._current_segment)
11770-        self._plaintext += segment
11771+        if self._current_segment == self._start_segment:
11772+            # We're on the first segment. It's possible that we want
11773+            # only some part of the end of this segment, and that we
11774+            # just downloaded the whole thing to get that part. If so,
11775+            # we need to account for that and give the reader just the
11776+            # data that they want.
11777+            n = self._offset % self._segment_size
11778+            self.log("stripping %d bytes off of the first segment" % n)
11779+            self.log("original segment length: %d" % len(segment))
11780+            segment = segment[n:]
11781+            self.log("new segment length: %d" % len(segment))
11782+
11783+        if self._current_segment == self._last_segment and self._read_length is not None:
11784+            # We're on the last segment. It's possible that we only want
11785+            # part of the beginning of this segment, and that we
11786+            # downloaded the whole thing anyway. Make sure to give the
11787+            # caller only the portion of the segment that they want to
11788+            # receive.
11789+            extra = self._read_length
11790+            if self._start_segment != self._last_segment:
11791+                extra -= self._segment_size - \
11792+                            (self._offset % self._segment_size)
11793+            extra %= self._segment_size
11794+            self.log("original segment length: %d" % len(segment))
11795+            segment = segment[:extra]
11796+            self.log("new segment length: %d" % len(segment))
11797+            self.log("only taking %d bytes of the last segment" % extra)
11798+
11799+        if not self._verify:
11800+            self._consumer.write(segment)
11801+        else:
11802+            # we don't care about the plaintext if we are doing a verify.
11803+            segment = None
11804         self._current_segment += 1
11805 
11806 
11807hunk ./src/allmydata/mutable/retrieve.py 848
11808                                         reader.shnum,
11809                                         "corrupt hashes: %s" % e)
11810 
11811-        # TODO: Validate the salt, too.
11812         self.log('share %d is valid for segment %d' % (reader.shnum,
11813                                                        segnum))
11814         return {reader.shnum: (block, salt)}
11815hunk ./src/allmydata/mutable/retrieve.py 1014
11816               _done_deferred to errback.
11817         """
11818         self.log("checking for doneness")
11819-        if self._current_segment == self._num_segments:
11820+        if self._current_segment > self._last_segment:
11821             # No more segments to download, we're done.
11822             self.log("got plaintext, done")
11823             return self._done()
11824hunk ./src/allmydata/mutable/retrieve.py 1043
11825             ret = list(self._bad_shares)
11826             self.log("done verifying, found %d bad shares" % len(ret))
11827         else:
11828-            ret = self._plaintext
11829+            # TODO: upload status here?
11830+            ret = self._consumer
11831+            self._consumer.unregisterProducer()
11832         eventually(self._done_deferred.callback, ret)
11833 
11834 
11835hunk ./src/allmydata/mutable/retrieve.py 1066
11836                       "encoding %(k)d-of-%(n)d")
11837             args = {"have": self._current_segment,
11838                     "total": self._num_segments,
11839+                    "need": self._last_segment,
11840                     "k": self._required_shares,
11841                     "n": self._total_shares,
11842                     "bad": len(self._bad_shares)}
11843}
11844[change MutableDataHandle to MutableData in code.
11845Kevan Carstensen <kevan@isnotajoke.com>**20100717015210
11846 Ignore-this: f85ae425eabc21b47ad60bd6bf1f7dec
11847] {
11848hunk ./src/allmydata/dirnode.py 11
11849 from allmydata.mutable.common import NotWriteableError
11850 from allmydata.mutable.filenode import MutableFileNode
11851 from allmydata.unknown import UnknownNode, strip_prefix_for_ro
11852-from allmydata.mutable.publish import MutableDataHandle
11853+from allmydata.mutable.publish import MutableData
11854 from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \
11855      IImmutableFileNode, IMutableFileNode, \
11856      ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \
11857hunk ./src/allmydata/dirnode.py 104
11858 
11859         del children[self.name]
11860         new_contents = self.node._pack_contents(children)
11861-        uploadable = MutableDataHandle(new_contents)
11862+        uploadable = MutableData(new_contents)
11863         return uploadable
11864 
11865 
11866hunk ./src/allmydata/dirnode.py 130
11867 
11868         children[name] = (child, metadata)
11869         new_contents = self.node._pack_contents(children)
11870-        uploadable = MutableDataHandle(new_contents)
11871+        uploadable = MutableData(new_contents)
11872         return uploadable
11873 
11874 
11875hunk ./src/allmydata/dirnode.py 175
11876 
11877             children[name] = (child, metadata)
11878         new_contents = self.node._pack_contents(children)
11879-        uploadable = MutableDataHandle(new_contents)
11880+        uploadable = MutableData(new_contents)
11881         return uploadable
11882 
11883 
11884hunk ./src/allmydata/mutable/repairer.py 5
11885 from zope.interface import implements
11886 from twisted.internet import defer
11887 from allmydata.interfaces import IRepairResults, ICheckResults
11888-from allmydata.mutable.publish import MutableDataHandle
11889+from allmydata.mutable.publish import MutableData
11890 
11891 class RepairResults:
11892     implements(IRepairResults)
11893hunk ./src/allmydata/mutable/repairer.py 109
11894 
11895         d = self.node.download_version(smap, best_version, fetch_privkey=True)
11896         d.addCallback(lambda data:
11897-            MutableDataHandle(data))
11898+            MutableData(data))
11899         d.addCallback(self.node.upload, smap)
11900         d.addCallback(self.get_results, smap)
11901         return d
11902hunk ./src/allmydata/nodemaker.py 9
11903 from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode
11904 from allmydata.immutable.upload import Data
11905 from allmydata.mutable.filenode import MutableFileNode
11906-from allmydata.mutable.publish import MutableDataHandle
11907+from allmydata.mutable.publish import MutableData
11908 from allmydata.dirnode import DirectoryNode, pack_children
11909 from allmydata.unknown import UnknownNode
11910 from allmydata import uri
11911hunk ./src/allmydata/nodemaker.py 111
11912                          "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
11913             node.raise_error()
11914         d = self.create_mutable_file(lambda n:
11915-                                     MutableDataHandle(
11916+                                     MutableData(
11917                                         pack_children(n, initial_children)),
11918                                      version)
11919         d.addCallback(self._create_dirnode)
11920hunk ./src/allmydata/test/common.py 18
11921      DeepCheckResults, DeepCheckAndRepairResults
11922 from allmydata.mutable.common import CorruptShareError
11923 from allmydata.mutable.layout import unpack_header
11924-from allmydata.mutable.publish import MutableDataHandle
11925+from allmydata.mutable.publish import MutableData
11926 from allmydata.storage.server import storage_index_to_dir
11927 from allmydata.storage.mutable import MutableShareFile
11928 from allmydata.util import hashutil, log, fileutil, pollmixin
11929hunk ./src/allmydata/test/common.py 192
11930         return defer.succeed(self)
11931     def _get_initial_contents(self, contents):
11932         if contents is None:
11933-            return MutableDataHandle("")
11934+            return MutableData("")
11935 
11936         if IMutableUploadable.providedBy(contents):
11937             return contents
11938hunk ./src/allmydata/test/test_checker.py 11
11939 from allmydata.test.no_network import GridTestMixin
11940 from allmydata.immutable.upload import Data
11941 from allmydata.test.common_web import WebRenderingMixin
11942-from allmydata.mutable.publish import MutableDataHandle
11943+from allmydata.mutable.publish import MutableData
11944 
11945 class FakeClient:
11946     def get_storage_broker(self):
11947hunk ./src/allmydata/test/test_checker.py 292
11948             self.imm = c0.create_node_from_uri(ur.uri)
11949         d.addCallback(_stash_immutable)
11950         d.addCallback(lambda ign:
11951-            c0.create_mutable_file(MutableDataHandle("contents")))
11952+            c0.create_mutable_file(MutableData("contents")))
11953         def _stash_mutable(node):
11954             self.mut = node
11955         d.addCallback(_stash_mutable)
11956hunk ./src/allmydata/test/test_cli.py 12
11957 from allmydata.util import fileutil, hashutil, base32
11958 from allmydata import uri
11959 from allmydata.immutable import upload
11960-from allmydata.mutable.publish import MutableDataHandle
11961+from allmydata.mutable.publish import MutableData
11962 from allmydata.dirnode import normalize
11963 
11964 # Test that the scripts can be imported -- although the actual tests of their
11965hunk ./src/allmydata/test/test_cli.py 1983
11966         self.set_up_grid()
11967         c0 = self.g.clients[0]
11968         DATA = "data" * 100
11969-        DATA_uploadable = MutableDataHandle(DATA)
11970+        DATA_uploadable = MutableData(DATA)
11971         d = c0.create_mutable_file(DATA_uploadable)
11972         def _stash_uri(n):
11973             self.uri = n.get_uri()
11974hunk ./src/allmydata/test/test_cli.py 2086
11975                                                         convergence="")))
11976         d.addCallback(_stash_uri, "small")
11977         d.addCallback(lambda ign:
11978-            c0.create_mutable_file(MutableDataHandle(DATA+"1")))
11979+            c0.create_mutable_file(MutableData(DATA+"1")))
11980         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
11981         d.addCallback(_stash_uri, "mutable")
11982 
11983hunk ./src/allmydata/test/test_deepcheck.py 9
11984 from twisted.internet import threads # CLI tests use deferToThread
11985 from allmydata.immutable import upload
11986 from allmydata.mutable.common import UnrecoverableFileError
11987-from allmydata.mutable.publish import MutableDataHandle
11988+from allmydata.mutable.publish import MutableData
11989 from allmydata.util import idlib
11990 from allmydata.util import base32
11991 from allmydata.scripts import runner
11992hunk ./src/allmydata/test/test_deepcheck.py 38
11993         self.basedir = "deepcheck/MutableChecker/good"
11994         self.set_up_grid()
11995         CONTENTS = "a little bit of data"
11996-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
11997+        CONTENTS_uploadable = MutableData(CONTENTS)
11998         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
11999         def _created(node):
12000             self.node = node
12001hunk ./src/allmydata/test/test_deepcheck.py 61
12002         self.basedir = "deepcheck/MutableChecker/corrupt"
12003         self.set_up_grid()
12004         CONTENTS = "a little bit of data"
12005-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12006+        CONTENTS_uploadable = MutableData(CONTENTS)
12007         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12008         def _stash_and_corrupt(node):
12009             self.node = node
12010hunk ./src/allmydata/test/test_deepcheck.py 99
12011         self.basedir = "deepcheck/MutableChecker/delete_share"
12012         self.set_up_grid()
12013         CONTENTS = "a little bit of data"
12014-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12015+        CONTENTS_uploadable = MutableData(CONTENTS)
12016         d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
12017         def _stash_and_delete(node):
12018             self.node = node
12019hunk ./src/allmydata/test/test_deepcheck.py 224
12020             self.root_uri = n.get_uri()
12021         d.addCallback(_created_root)
12022         d.addCallback(lambda ign:
12023-            c0.create_mutable_file(MutableDataHandle("mutable file contents")))
12024+            c0.create_mutable_file(MutableData("mutable file contents")))
12025         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
12026         def _created_mutable(n):
12027             self.mutable = n
12028hunk ./src/allmydata/test/test_deepcheck.py 965
12029     def create_mangled(self, ignored, name):
12030         nodetype, mangletype = name.split("-", 1)
12031         if nodetype == "mutable":
12032-            mutable_uploadable = MutableDataHandle("mutable file contents")
12033+            mutable_uploadable = MutableData("mutable file contents")
12034             d = self.g.clients[0].create_mutable_file(mutable_uploadable)
12035             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
12036         elif nodetype == "large":
12037hunk ./src/allmydata/test/test_hung_server.py 10
12038 from allmydata.util.consumer import download_to_data
12039 from allmydata.immutable import upload
12040 from allmydata.mutable.common import UnrecoverableFileError
12041-from allmydata.mutable.publish import MutableDataHandle
12042+from allmydata.mutable.publish import MutableData
12043 from allmydata.storage.common import storage_index_to_dir
12044 from allmydata.test.no_network import GridTestMixin
12045 from allmydata.test.common import ShouldFailMixin, _corrupt_share_data
12046hunk ./src/allmydata/test/test_hung_server.py 96
12047         self.servers = [(id, ss) for (id, ss) in nm.storage_broker.get_all_servers()]
12048 
12049         if mutable:
12050-            uploadable = MutableDataHandle(mutable_plaintext)
12051+            uploadable = MutableData(mutable_plaintext)
12052             d = nm.create_mutable_file(uploadable)
12053             def _uploaded_mutable(node):
12054                 self.uri = node.get_uri()
12055hunk ./src/allmydata/test/test_mutable.py 27
12056      NotEnoughServersError, CorruptShareError
12057 from allmydata.mutable.retrieve import Retrieve
12058 from allmydata.mutable.publish import Publish, MutableFileHandle, \
12059-                                      MutableDataHandle
12060+                                      MutableData
12061 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
12062 from allmydata.mutable.layout import unpack_header, unpack_share, \
12063                                      MDMFSlotReadProxy
12064hunk ./src/allmydata/test/test_mutable.py 297
12065             d.addCallback(lambda smap: smap.dump(StringIO()))
12066             d.addCallback(lambda sio:
12067                           self.failUnless("3-of-10" in sio.getvalue()))
12068-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12069+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12070             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12071             d.addCallback(lambda res: n.download_best_version())
12072             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12073hunk ./src/allmydata/test/test_mutable.py 304
12074             d.addCallback(lambda res: n.get_size_of_best_version())
12075             d.addCallback(lambda size:
12076                           self.failUnlessEqual(size, len("contents 1")))
12077-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12078+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12079             d.addCallback(lambda res: n.download_best_version())
12080             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12081             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12082hunk ./src/allmydata/test/test_mutable.py 308
12083-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12084+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12085             d.addCallback(lambda res: n.download_best_version())
12086             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12087             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12088hunk ./src/allmydata/test/test_mutable.py 320
12089             # mapupdate-to-retrieve data caching (i.e. make the shares larger
12090             # than the default readsize, which is 2000 bytes). A 15kB file
12091             # will have 5kB shares.
12092-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("large size file" * 1000)))
12093+            d.addCallback(lambda res: n.overwrite(MutableData("large size file" * 1000)))
12094             d.addCallback(lambda res: n.download_best_version())
12095             d.addCallback(lambda res:
12096                           self.failUnlessEqual(res, "large size file" * 1000))
12097hunk ./src/allmydata/test/test_mutable.py 343
12098             # to make them big enough to force the file to be uploaded
12099             # in more than one segment.
12100             big_contents = "contents1" * 100000 # about 900 KiB
12101-            big_contents_uploadable = MutableDataHandle(big_contents)
12102+            big_contents_uploadable = MutableData(big_contents)
12103             d.addCallback(lambda ignored:
12104                 n.overwrite(big_contents_uploadable))
12105             d.addCallback(lambda ignored:
12106hunk ./src/allmydata/test/test_mutable.py 355
12107             # segments, so that we make the downloader deal with
12108             # multiple segments.
12109             bigger_contents = "contents2" * 1000000 # about 9MiB
12110-            bigger_contents_uploadable = MutableDataHandle(bigger_contents)
12111+            bigger_contents_uploadable = MutableData(bigger_contents)
12112             d.addCallback(lambda ignored:
12113                 n.overwrite(bigger_contents_uploadable))
12114             d.addCallback(lambda ignored:
12115hunk ./src/allmydata/test/test_mutable.py 368
12116 
12117 
12118     def test_create_with_initial_contents(self):
12119-        upload1 = MutableDataHandle("contents 1")
12120+        upload1 = MutableData("contents 1")
12121         d = self.nodemaker.create_mutable_file(upload1)
12122         def _created(n):
12123             d = n.download_best_version()
12124hunk ./src/allmydata/test/test_mutable.py 373
12125             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12126-            upload2 = MutableDataHandle("contents 2")
12127+            upload2 = MutableData("contents 2")
12128             d.addCallback(lambda res: n.overwrite(upload2))
12129             d.addCallback(lambda res: n.download_best_version())
12130             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12131hunk ./src/allmydata/test/test_mutable.py 385
12132 
12133     def test_create_mdmf_with_initial_contents(self):
12134         initial_contents = "foobarbaz" * 131072 # 900KiB
12135-        initial_contents_uploadable = MutableDataHandle(initial_contents)
12136+        initial_contents_uploadable = MutableData(initial_contents)
12137         d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
12138                                                version=MDMF_VERSION)
12139         def _created(n):
12140hunk ./src/allmydata/test/test_mutable.py 392
12141             d = n.download_best_version()
12142             d.addCallback(lambda data:
12143                 self.failUnlessEqual(data, initial_contents))
12144-            uploadable2 = MutableDataHandle(initial_contents + "foobarbaz")
12145+            uploadable2 = MutableData(initial_contents + "foobarbaz")
12146             d.addCallback(lambda ignored:
12147                 n.overwrite(uploadable2))
12148             d.addCallback(lambda ignored:
12149hunk ./src/allmydata/test/test_mutable.py 413
12150             key = n.get_writekey()
12151             self.failUnless(isinstance(key, str), key)
12152             self.failUnlessEqual(len(key), 16) # AES key size
12153-            return MutableDataHandle(data)
12154+            return MutableData(data)
12155         d = self.nodemaker.create_mutable_file(_make_contents)
12156         def _created(n):
12157             return n.download_best_version()
12158hunk ./src/allmydata/test/test_mutable.py 429
12159             key = n.get_writekey()
12160             self.failUnless(isinstance(key, str), key)
12161             self.failUnlessEqual(len(key), 16)
12162-            return MutableDataHandle(data)
12163+            return MutableData(data)
12164         d = self.nodemaker.create_mutable_file(_make_contents,
12165                                                version=MDMF_VERSION)
12166         d.addCallback(lambda n:
12167hunk ./src/allmydata/test/test_mutable.py 441
12168 
12169     def test_create_with_too_large_contents(self):
12170         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12171-        BIG_uploadable = MutableDataHandle(BIG)
12172+        BIG_uploadable = MutableData(BIG)
12173         d = self.nodemaker.create_mutable_file(BIG_uploadable)
12174         def _created(n):
12175hunk ./src/allmydata/test/test_mutable.py 444
12176-            other_BIG_uploadable = MutableDataHandle(BIG)
12177+            other_BIG_uploadable = MutableData(BIG)
12178             d = n.overwrite(other_BIG_uploadable)
12179             return d
12180         d.addCallback(_created)
12181hunk ./src/allmydata/test/test_mutable.py 460
12182     def test_modify(self):
12183         def _modifier(old_contents, servermap, first_time):
12184             new_contents = old_contents + "line2"
12185-            return MutableDataHandle(new_contents)
12186+            return MutableData(new_contents)
12187         def _non_modifier(old_contents, servermap, first_time):
12188hunk ./src/allmydata/test/test_mutable.py 462
12189-            return MutableDataHandle(old_contents)
12190+            return MutableData(old_contents)
12191         def _none_modifier(old_contents, servermap, first_time):
12192             return None
12193         def _error_modifier(old_contents, servermap, first_time):
12194hunk ./src/allmydata/test/test_mutable.py 469
12195             raise ValueError("oops")
12196         def _toobig_modifier(old_contents, servermap, first_time):
12197             new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
12198-            return MutableDataHandle(new_content)
12199+            return MutableData(new_content)
12200         calls = []
12201         def _ucw_error_modifier(old_contents, servermap, first_time):
12202             # simulate an UncoordinatedWriteError once
12203hunk ./src/allmydata/test/test_mutable.py 477
12204             if len(calls) <= 1:
12205                 raise UncoordinatedWriteError("simulated")
12206             new_contents = old_contents + "line3"
12207-            return MutableDataHandle(new_contents)
12208+            return MutableData(new_contents)
12209         def _ucw_error_non_modifier(old_contents, servermap, first_time):
12210             # simulate an UncoordinatedWriteError once, and don't actually
12211             # modify the contents on subsequent invocations
12212hunk ./src/allmydata/test/test_mutable.py 484
12213             calls.append(1)
12214             if len(calls) <= 1:
12215                 raise UncoordinatedWriteError("simulated")
12216-            return MutableDataHandle(old_contents)
12217+            return MutableData(old_contents)
12218 
12219         initial_contents = "line1"
12220hunk ./src/allmydata/test/test_mutable.py 487
12221-        d = self.nodemaker.create_mutable_file(MutableDataHandle(initial_contents))
12222+        d = self.nodemaker.create_mutable_file(MutableData(initial_contents))
12223         def _created(n):
12224             d = n.modify(_modifier)
12225             d.addCallback(lambda res: n.download_best_version())
12226hunk ./src/allmydata/test/test_mutable.py 548
12227 
12228     def test_modify_backoffer(self):
12229         def _modifier(old_contents, servermap, first_time):
12230-            return MutableDataHandle(old_contents + "line2")
12231+            return MutableData(old_contents + "line2")
12232         calls = []
12233         def _ucw_error_modifier(old_contents, servermap, first_time):
12234             # simulate an UncoordinatedWriteError once
12235hunk ./src/allmydata/test/test_mutable.py 555
12236             calls.append(1)
12237             if len(calls) <= 1:
12238                 raise UncoordinatedWriteError("simulated")
12239-            return MutableDataHandle(old_contents + "line3")
12240+            return MutableData(old_contents + "line3")
12241         def _always_ucw_error_modifier(old_contents, servermap, first_time):
12242             raise UncoordinatedWriteError("simulated")
12243         def _backoff_stopper(node, f):
12244hunk ./src/allmydata/test/test_mutable.py 570
12245         giveuper._delay = 0.1
12246         giveuper.factor = 1
12247 
12248-        d = self.nodemaker.create_mutable_file(MutableDataHandle("line1"))
12249+        d = self.nodemaker.create_mutable_file(MutableData("line1"))
12250         def _created(n):
12251             d = n.modify(_modifier)
12252             d.addCallback(lambda res: n.download_best_version())
12253hunk ./src/allmydata/test/test_mutable.py 620
12254             d.addCallback(lambda smap: smap.dump(StringIO()))
12255             d.addCallback(lambda sio:
12256                           self.failUnless("3-of-10" in sio.getvalue()))
12257-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 1")))
12258+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
12259             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
12260             d.addCallback(lambda res: n.download_best_version())
12261             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12262hunk ./src/allmydata/test/test_mutable.py 624
12263-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12264+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12265             d.addCallback(lambda res: n.download_best_version())
12266             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12267             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12268hunk ./src/allmydata/test/test_mutable.py 628
12269-            d.addCallback(lambda smap: n.upload(MutableDataHandle("contents 3"), smap))
12270+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
12271             d.addCallback(lambda res: n.download_best_version())
12272             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
12273             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
12274hunk ./src/allmydata/test/test_mutable.py 646
12275         # publish a file and create shares, which can then be manipulated
12276         # later.
12277         self.CONTENTS = "New contents go here" * 1000
12278-        self.uploadable = MutableDataHandle(self.CONTENTS)
12279+        self.uploadable = MutableData(self.CONTENTS)
12280         self._storage = FakeStorage()
12281         self._nodemaker = make_nodemaker(self._storage)
12282         self._storage_broker = self._nodemaker.storage_broker
12283hunk ./src/allmydata/test/test_mutable.py 662
12284         # an MDMF file.
12285         # self.CONTENTS should have more than one segment.
12286         self.CONTENTS = "This is an MDMF file" * 100000
12287-        self.uploadable = MutableDataHandle(self.CONTENTS)
12288+        self.uploadable = MutableData(self.CONTENTS)
12289         self._storage = FakeStorage()
12290         self._nodemaker = make_nodemaker(self._storage)
12291         self._storage_broker = self._nodemaker.storage_broker
12292hunk ./src/allmydata/test/test_mutable.py 678
12293         # like publish_one, except that the result is guaranteed to be
12294         # an SDMF file
12295         self.CONTENTS = "This is an SDMF file" * 1000
12296-        self.uploadable = MutableDataHandle(self.CONTENTS)
12297+        self.uploadable = MutableData(self.CONTENTS)
12298         self._storage = FakeStorage()
12299         self._nodemaker = make_nodemaker(self._storage)
12300         self._storage_broker = self._nodemaker.storage_broker
12301hunk ./src/allmydata/test/test_mutable.py 696
12302                          "Contents 2",
12303                          "Contents 3a",
12304                          "Contents 3b"]
12305-        self.uploadables = [MutableDataHandle(d) for d in self.CONTENTS]
12306+        self.uploadables = [MutableData(d) for d in self.CONTENTS]
12307         self._copied_shares = {}
12308         self._storage = FakeStorage()
12309         self._nodemaker = make_nodemaker(self._storage)
12310hunk ./src/allmydata/test/test_mutable.py 826
12311         # create a new file, which is large enough to knock the privkey out
12312         # of the early part of the file
12313         LARGE = "These are Larger contents" * 200 # about 5KB
12314-        LARGE_uploadable = MutableDataHandle(LARGE)
12315+        LARGE_uploadable = MutableData(LARGE)
12316         d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
12317         def _created(large_fn):
12318             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
12319hunk ./src/allmydata/test/test_mutable.py 1842
12320 class MultipleEncodings(unittest.TestCase):
12321     def setUp(self):
12322         self.CONTENTS = "New contents go here"
12323-        self.uploadable = MutableDataHandle(self.CONTENTS)
12324+        self.uploadable = MutableData(self.CONTENTS)
12325         self._storage = FakeStorage()
12326         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
12327         self._storage_broker = self._nodemaker.storage_broker
12328hunk ./src/allmydata/test/test_mutable.py 1872
12329         s = self._storage
12330         s._peers = {} # clear existing storage
12331         p2 = Publish(fn2, self._storage_broker, None)
12332-        uploadable = MutableDataHandle(data)
12333+        uploadable = MutableData(data)
12334         d = p2.publish(uploadable)
12335         def _published(res):
12336             shares = s._peers
12337hunk ./src/allmydata/test/test_mutable.py 2049
12338         self._set_versions(target)
12339 
12340         def _modify(oldversion, servermap, first_time):
12341-            return MutableDataHandle(oldversion + " modified")
12342+            return MutableData(oldversion + " modified")
12343         d = self._fn.modify(_modify)
12344         d.addCallback(lambda res: self._fn.download_best_version())
12345         expected = self.CONTENTS[2] + " modified"
12346hunk ./src/allmydata/test/test_mutable.py 2175
12347         self.basedir = "mutable/Problems/test_publish_surprise"
12348         self.set_up_grid()
12349         nm = self.g.clients[0].nodemaker
12350-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12351+        d = nm.create_mutable_file(MutableData("contents 1"))
12352         def _created(n):
12353             d = defer.succeed(None)
12354             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12355hunk ./src/allmydata/test/test_mutable.py 2185
12356             d.addCallback(_got_smap1)
12357             # then modify the file, leaving the old map untouched
12358             d.addCallback(lambda res: log.msg("starting winning write"))
12359-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12360+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12361             # now attempt to modify the file with the old servermap. This
12362             # will look just like an uncoordinated write, in which every
12363             # single share got updated between our mapupdate and our publish
12364hunk ./src/allmydata/test/test_mutable.py 2194
12365                           self.shouldFail(UncoordinatedWriteError,
12366                                           "test_publish_surprise", None,
12367                                           n.upload,
12368-                                          MutableDataHandle("contents 2a"), self.old_map))
12369+                                          MutableData("contents 2a"), self.old_map))
12370             return d
12371         d.addCallback(_created)
12372         return d
12373hunk ./src/allmydata/test/test_mutable.py 2203
12374         self.basedir = "mutable/Problems/test_retrieve_surprise"
12375         self.set_up_grid()
12376         nm = self.g.clients[0].nodemaker
12377-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12378+        d = nm.create_mutable_file(MutableData("contents 1"))
12379         def _created(n):
12380             d = defer.succeed(None)
12381             d.addCallback(lambda res: n.get_servermap(MODE_READ))
12382hunk ./src/allmydata/test/test_mutable.py 2213
12383             d.addCallback(_got_smap1)
12384             # then modify the file, leaving the old map untouched
12385             d.addCallback(lambda res: log.msg("starting winning write"))
12386-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12387+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12388             # now attempt to retrieve the old version with the old servermap.
12389             # This will look like someone has changed the file since we
12390             # updated the servermap.
12391hunk ./src/allmydata/test/test_mutable.py 2241
12392         self.basedir = "mutable/Problems/test_unexpected_shares"
12393         self.set_up_grid()
12394         nm = self.g.clients[0].nodemaker
12395-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12396+        d = nm.create_mutable_file(MutableData("contents 1"))
12397         def _created(n):
12398             d = defer.succeed(None)
12399             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
12400hunk ./src/allmydata/test/test_mutable.py 2253
12401                 self.g.remove_server(peer0)
12402                 # then modify the file, leaving the old map untouched
12403                 log.msg("starting winning write")
12404-                return n.overwrite(MutableDataHandle("contents 2"))
12405+                return n.overwrite(MutableData("contents 2"))
12406             d.addCallback(_got_smap1)
12407             # now attempt to modify the file with the old servermap. This
12408             # will look just like an uncoordinated write, in which every
12409hunk ./src/allmydata/test/test_mutable.py 2263
12410                           self.shouldFail(UncoordinatedWriteError,
12411                                           "test_surprise", None,
12412                                           n.upload,
12413-                                          MutableDataHandle("contents 2a"), self.old_map))
12414+                                          MutableData("contents 2a"), self.old_map))
12415             return d
12416         d.addCallback(_created)
12417         return d
12418hunk ./src/allmydata/test/test_mutable.py 2303
12419         d.addCallback(_break_peer0)
12420         # now "create" the file, using the pre-established key, and let the
12421         # initial publish finally happen
12422-        d.addCallback(lambda res: nm.create_mutable_file(MutableDataHandle("contents 1")))
12423+        d.addCallback(lambda res: nm.create_mutable_file(MutableData("contents 1")))
12424         # that ought to work
12425         def _got_node(n):
12426             d = n.download_best_version()
12427hunk ./src/allmydata/test/test_mutable.py 2312
12428             def _break_peer1(res):
12429                 self.connection1.broken = True
12430             d.addCallback(_break_peer1)
12431-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12432+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12433             # that ought to work too
12434             d.addCallback(lambda res: n.download_best_version())
12435             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12436hunk ./src/allmydata/test/test_mutable.py 2344
12437         peerids = [serverid for (serverid,ss) in sb.get_all_servers()]
12438         self.g.break_server(peerids[0])
12439 
12440-        d = nm.create_mutable_file(MutableDataHandle("contents 1"))
12441+        d = nm.create_mutable_file(MutableData("contents 1"))
12442         def _created(n):
12443             d = n.download_best_version()
12444             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
12445hunk ./src/allmydata/test/test_mutable.py 2352
12446             def _break_second_server(res):
12447                 self.g.break_server(peerids[1])
12448             d.addCallback(_break_second_server)
12449-            d.addCallback(lambda res: n.overwrite(MutableDataHandle("contents 2")))
12450+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
12451             # that ought to work too
12452             d.addCallback(lambda res: n.download_best_version())
12453             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
12454hunk ./src/allmydata/test/test_mutable.py 2371
12455         d = self.shouldFail(NotEnoughServersError,
12456                             "test_publish_all_servers_bad",
12457                             "Ran out of non-bad servers",
12458-                            nm.create_mutable_file, MutableDataHandle("contents"))
12459+                            nm.create_mutable_file, MutableData("contents"))
12460         return d
12461 
12462     def test_publish_no_servers(self):
12463hunk ./src/allmydata/test/test_mutable.py 2383
12464         d = self.shouldFail(NotEnoughServersError,
12465                             "test_publish_no_servers",
12466                             "Ran out of non-bad servers",
12467-                            nm.create_mutable_file, MutableDataHandle("contents"))
12468+                            nm.create_mutable_file, MutableData("contents"))
12469         return d
12470     test_publish_no_servers.timeout = 30
12471 
12472hunk ./src/allmydata/test/test_mutable.py 2401
12473         # we need some contents that are large enough to push the privkey out
12474         # of the early part of the file
12475         LARGE = "These are Larger contents" * 2000 # about 50KB
12476-        LARGE_uploadable = MutableDataHandle(LARGE)
12477+        LARGE_uploadable = MutableData(LARGE)
12478         d = nm.create_mutable_file(LARGE_uploadable)
12479         def _created(n):
12480             self.uri = n.get_uri()
12481hunk ./src/allmydata/test/test_mutable.py 2438
12482         self.set_up_grid(num_servers=20)
12483         nm = self.g.clients[0].nodemaker
12484         LARGE = "These are Larger contents" * 2000 # about 50KiB
12485-        LARGE_uploadable = MutableDataHandle(LARGE)
12486+        LARGE_uploadable = MutableData(LARGE)
12487         nm._node_cache = DevNullDictionary() # disable the nodecache
12488 
12489         d = nm.create_mutable_file(LARGE_uploadable)
12490hunk ./src/allmydata/test/test_mutable.py 2464
12491         self.set_up_grid(num_servers=20)
12492         nm = self.g.clients[0].nodemaker
12493         CONTENTS = "contents" * 2000
12494-        CONTENTS_uploadable = MutableDataHandle(CONTENTS)
12495+        CONTENTS_uploadable = MutableData(CONTENTS)
12496         d = nm.create_mutable_file(CONTENTS_uploadable)
12497         def _created(node):
12498             self._node = node
12499hunk ./src/allmydata/test/test_mutable.py 2565
12500 class DataHandle(unittest.TestCase):
12501     def setUp(self):
12502         self.test_data = "Test Data" * 50000
12503-        self.uploadable = MutableDataHandle(self.test_data)
12504+        self.uploadable = MutableData(self.test_data)
12505 
12506 
12507     def test_datahandle_read(self):
12508hunk ./src/allmydata/test/test_sftp.py 84
12509         return d
12510 
12511     def _set_up_tree(self):
12512-        u = publish.MutableDataHandle("mutable file contents")
12513+        u = publish.MutableData("mutable file contents")
12514         d = self.client.create_mutable_file(u)
12515         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
12516         def _created_mutable(n):
12517hunk ./src/allmydata/test/test_system.py 22
12518 from allmydata.monitor import Monitor
12519 from allmydata.mutable.common import NotWriteableError
12520 from allmydata.mutable import layout as mutable_layout
12521-from allmydata.mutable.publish import MutableDataHandle
12522+from allmydata.mutable.publish import MutableData
12523 from foolscap.api import DeadReferenceError
12524 from twisted.python.failure import Failure
12525 from twisted.web.client import getPage
12526hunk ./src/allmydata/test/test_system.py 460
12527     def test_mutable(self):
12528         self.basedir = "system/SystemTest/test_mutable"
12529         DATA = "initial contents go here."  # 25 bytes % 3 != 0
12530-        DATA_uploadable = MutableDataHandle(DATA)
12531+        DATA_uploadable = MutableData(DATA)
12532         NEWDATA = "new contents yay"
12533hunk ./src/allmydata/test/test_system.py 462
12534-        NEWDATA_uploadable = MutableDataHandle(NEWDATA)
12535+        NEWDATA_uploadable = MutableData(NEWDATA)
12536         NEWERDATA = "this is getting old"
12537hunk ./src/allmydata/test/test_system.py 464
12538-        NEWERDATA_uploadable = MutableDataHandle(NEWERDATA)
12539+        NEWERDATA_uploadable = MutableData(NEWERDATA)
12540 
12541         d = self.set_up_nodes(use_key_generator=True)
12542 
12543hunk ./src/allmydata/test/test_system.py 642
12544         def _check_empty_file(res):
12545             # make sure we can create empty files, this usually screws up the
12546             # segsize math
12547-            d1 = self.clients[2].create_mutable_file(MutableDataHandle(""))
12548+            d1 = self.clients[2].create_mutable_file(MutableData(""))
12549             d1.addCallback(lambda newnode: newnode.download_best_version())
12550             d1.addCallback(lambda res: self.failUnlessEqual("", res))
12551             return d1
12552hunk ./src/allmydata/test/test_system.py 674
12553 
12554         d.addCallback(check_kg_poolsize, 0)
12555         d.addCallback(lambda junk:
12556-            self.clients[3].create_mutable_file(MutableDataHandle('hello, world')))
12557+            self.clients[3].create_mutable_file(MutableData('hello, world')))
12558         d.addCallback(check_kg_poolsize, -1)
12559         d.addCallback(lambda junk: self.clients[3].create_dirnode())
12560         d.addCallback(check_kg_poolsize, -2)
12561hunk ./src/allmydata/test/test_web.py 3182
12562             self.uris[which] = n.get_uri()
12563             assert isinstance(self.uris[which], str)
12564         d.addCallback(lambda ign:
12565-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12566+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12567         d.addCallback(_stash_mutable_uri, "corrupt")
12568         d.addCallback(lambda ign:
12569                       c0.upload(upload.Data("literal", convergence="")))
12570hunk ./src/allmydata/test/test_web.py 3329
12571             self.uris[which] = n.get_uri()
12572             assert isinstance(self.uris[which], str)
12573         d.addCallback(lambda ign:
12574-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"3")))
12575+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12576         d.addCallback(_stash_mutable_uri, "corrupt")
12577 
12578         def _compute_fileurls(ignored):
12579hunk ./src/allmydata/test/test_web.py 3992
12580             self.uris[which] = n.get_uri()
12581             assert isinstance(self.uris[which], str)
12582         d.addCallback(lambda ign:
12583-            c0.create_mutable_file(publish.MutableDataHandle(DATA+"2")))
12584+            c0.create_mutable_file(publish.MutableData(DATA+"2")))
12585         d.addCallback(_stash_mutable_uri, "mutable")
12586 
12587         def _compute_fileurls(ignored):
12588hunk ./src/allmydata/test/test_web.py 4092
12589         d.addCallback(_stash_uri, "small")
12590 
12591         d.addCallback(lambda ign:
12592-            c0.create_mutable_file(publish.MutableDataHandle("mutable")))
12593+            c0.create_mutable_file(publish.MutableData("mutable")))
12594         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
12595         d.addCallback(_stash_uri, "mutable")
12596 
12597}
12598[tests: fix tests that were broken by #993
12599Kevan Carstensen <kevan@isnotajoke.com>**20100717015230
12600 Ignore-this: f0ace6538c6d824b7fe271c40a7ebf8d
12601] {
12602hunk ./src/allmydata/test/common.py 152
12603         consumer.write(data[start:end])
12604         return consumer
12605 
12606+
12607+    def get_best_readable_version(self):
12608+        return defer.succeed(self)
12609+
12610+
12611+    download_best_version = download_to_data
12612+
12613+
12614+    def download_to_data(self):
12615+        return download_to_data(self)
12616+
12617+
12618+    def get_size_of_best_version(self):
12619+        return defer.succeed(self.get_size)
12620+
12621+
12622 def make_chk_file_cap(size):
12623     return uri.CHKFileURI(key=os.urandom(16),
12624                           uri_extension_hash=os.urandom(32),
12625hunk ./src/allmydata/test/common.py 318
12626         return d
12627 
12628     def download_best_version(self):
12629+        return defer.succeed(self._download_best_version())
12630+
12631+
12632+    def _download_best_version(self, ignored=None):
12633         if isinstance(self.my_uri, uri.LiteralFileURI):
12634hunk ./src/allmydata/test/common.py 323
12635-            return defer.succeed(self.my_uri.data)
12636+            return self.my_uri.data
12637         if self.storage_index not in self.all_contents:
12638hunk ./src/allmydata/test/common.py 325
12639-            return defer.fail(NotEnoughSharesError(None, 0, 3))
12640-        return defer.succeed(self.all_contents[self.storage_index])
12641+            raise NotEnoughSharesError(None, 0, 3)
12642+        return self.all_contents[self.storage_index]
12643+
12644 
12645     def overwrite(self, new_contents):
12646         if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
12647hunk ./src/allmydata/test/common.py 352
12648         self.all_contents[self.storage_index] = new_data
12649         return None
12650 
12651+    # As actually implemented, MutableFilenode and MutableFileVersion
12652+    # are distinct. However, nothing in the webapi uses (yet) that
12653+    # distinction -- it just uses the unified download interface
12654+    # provided by get_best_readable_version and read. When we start
12655+    # doing cooler things like LDMF, we will want to revise this code to
12656+    # be less simplistic.
12657+    def get_best_readable_version(self):
12658+        return defer.succeed(self)
12659+
12660+
12661+    def read(self, consumer, offset=0, size=None):
12662+        data = self._download_best_version()
12663+        if size:
12664+            data = data[offset:offset+size]
12665+        consumer.write(data)
12666+        return defer.succeed(consumer)
12667+
12668+
12669 def make_mutable_file_cap():
12670     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
12671                                    fingerprint=os.urandom(32))
12672hunk ./src/allmydata/test/test_filenode.py 98
12673         def _check_segment(res):
12674             self.failUnlessEqual(res, DATA[1:1+5])
12675         d.addCallback(_check_segment)
12676-        d.addCallback(lambda ignored:
12677-            self.failUnlessEqual(fn1.get_best_readable_version(), fn1))
12678+        d.addCallback(lambda ignored: fn1.get_best_readable_version())
12679+        d.addCallback(lambda fn2: self.failUnlessEqual(fn1, fn2))
12680         d.addCallback(lambda ignored:
12681             fn1.get_size_of_best_version())
12682         d.addCallback(lambda size:
12683hunk ./src/allmydata/test/test_immutable.py 168
12684 
12685 
12686     def test_get_best_readable_version(self):
12687-        n = self.n.get_best_readable_version()
12688-        self.failUnlessEqual(n, self.n)
12689+        d = self.n.get_best_readable_version()
12690+        d.addCallback(lambda n2:
12691+            self.failUnlessEqual(n2, self.n))
12692+        return d
12693 
12694     def test_get_size_of_best_version(self):
12695         d = self.n.get_size_of_best_version()
12696hunk ./src/allmydata/test/test_mutable.py 8
12697 from twisted.internet import defer, reactor
12698 from allmydata import uri, client
12699 from allmydata.nodemaker import NodeMaker
12700-from allmydata.util import base32
12701+from allmydata.util import base32, consumer
12702 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
12703      ssk_pubkey_fingerprint_hash
12704hunk ./src/allmydata/test/test_mutable.py 11
12705+from allmydata.util.deferredutil import gatherResults
12706 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
12707      NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
12708 from allmydata.monitor import Monitor
12709hunk ./src/allmydata/test/test_mutable.py 1000
12710         if version is None:
12711             version = servermap.best_recoverable_version()
12712         r = Retrieve(self._fn, servermap, version)
12713-        return r.download()
12714+        c = consumer.MemoryConsumer()
12715+        d = r.download(consumer=c)
12716+        d.addCallback(lambda mc: "".join(mc.chunks))
12717+        return d
12718+
12719 
12720     def test_basic(self):
12721         d = self.make_servermap()
12722hunk ./src/allmydata/test/test_mutable.py 1263
12723                             in str(servermap.problems[0]))
12724             ver = servermap.best_recoverable_version()
12725             r = Retrieve(self._fn, servermap, ver)
12726-            return r.download()
12727+            c = consumer.MemoryConsumer()
12728+            return r.download(c)
12729         d.addCallback(_do_retrieve)
12730hunk ./src/allmydata/test/test_mutable.py 1266
12731+        d.addCallback(lambda mc: "".join(mc.chunks))
12732         d.addCallback(lambda new_contents:
12733                       self.failUnlessEqual(new_contents, self.CONTENTS))
12734         return d
12735}
12736[test/test_immutable.py: add tests for #993-related modifications
12737Kevan Carstensen <kevan@isnotajoke.com>**20100717015402
12738 Ignore-this: d94ad98bd0d322ead85af2e0aa95be38
12739] hunk ./src/allmydata/test/test_mutable.py 2607
12740         start = chunk_size
12741         end = chunk_size * 2
12742         self.failUnlessEqual("".join(more_data), self.test_data[start:end])
12743+
12744+
12745+class Version(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
12746+    def setUp(self):
12747+        GridTestMixin.setUp(self)
12748+        self.basedir = self.mktemp()
12749+        self.set_up_grid()
12750+        self.c = self.g.clients[0]
12751+        self.nm = self.c.nodemaker
12752+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12753+        self.small_data = "test data" * 10 # about 90 B; SDMF
12754+        return self.do_upload()
12755+
12756+
12757+    def do_upload(self):
12758+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12759+                                         version=MDMF_VERSION)
12760+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12761+        dl = gatherResults([d1, d2])
12762+        def _then((n1, n2)):
12763+            assert isinstance(n1, MutableFileNode)
12764+            assert isinstance(n2, MutableFileNode)
12765+
12766+            self.mdmf_node = n1
12767+            self.sdmf_node = n2
12768+        dl.addCallback(_then)
12769+        return dl
12770+
12771+
12772+    def test_get_readonly_mutable_version(self):
12773+        # Attempting to get a mutable version of a mutable file from a
12774+        # filenode initialized with a readcap should return a readonly
12775+        # version of that same node.
12776+        ro = self.mdmf_node.get_readonly()
12777+        d = ro.get_best_mutable_version()
12778+        d.addCallback(lambda version:
12779+            self.failUnless(version.is_readonly()))
12780+        d.addCallback(lambda ignored:
12781+            self.sdmf_node.get_readonly())
12782+        d.addCallback(lambda version:
12783+            self.failUnless(version.is_readonly()))
12784+        return d
12785+
12786+
12787+    def test_get_sequence_number(self):
12788+        d = self.mdmf_node.get_best_readable_version()
12789+        d.addCallback(lambda bv:
12790+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12791+        d.addCallback(lambda ignored:
12792+            self.sdmf_node.get_best_readable_version())
12793+        d.addCallback(lambda bv:
12794+            self.failUnlessEqual(bv.get_sequence_number(), 0))
12795+        # Now update. The sequence number in both cases should be 1 in
12796+        # both cases.
12797+        def _do_update(ignored):
12798+            new_data = MutableData("foo bar baz" * 100000)
12799+            new_small_data = MutableData("foo bar baz" * 10)
12800+            d1 = self.mdmf_node.overwrite(new_data)
12801+            d2 = self.sdmf_node.overwrite(new_small_data)
12802+            dl = gatherResults([d1, d2])
12803+            return dl
12804+        d.addCallback(_do_update)
12805+        d.addCallback(lambda ignored:
12806+            self.mdmf_node.get_best_readable_version())
12807+        d.addCallback(lambda bv:
12808+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12809+        d.addCallback(lambda ignored:
12810+            self.sdmf_node.get_best_readable_version())
12811+        d.addCallback(lambda bv:
12812+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12813+        return d
12814+
12815+
12816+    def test_get_writekey(self):
12817+        d = self.mdmf_node.get_best_mutable_version()
12818+        d.addCallback(lambda bv:
12819+            self.failUnlessEqual(bv.get_writekey(),
12820+                                 self.mdmf_node.get_writekey()))
12821+        d.addCallback(lambda ignored:
12822+            self.sdmf_node.get_best_mutable_version())
12823+        d.addCallback(lambda bv:
12824+            self.failUnlessEqual(bv.get_writekey(),
12825+                                 self.sdmf_node.get_writekey()))
12826+        return d
12827+
12828+
12829+    def test_get_storage_index(self):
12830+        d = self.mdmf_node.get_best_mutable_version()
12831+        d.addCallback(lambda bv:
12832+            self.failUnlessEqual(bv.get_storage_index(),
12833+                                 self.mdmf_node.get_storage_index()))
12834+        d.addCallback(lambda ignored:
12835+            self.sdmf_node.get_best_mutable_version())
12836+        d.addCallback(lambda bv:
12837+            self.failUnlessEqual(bv.get_storage_index(),
12838+                                 self.sdmf_node.get_storage_index()))
12839+        return d
12840+
12841+
12842+    def test_get_readonly_version(self):
12843+        d = self.mdmf_node.get_best_readable_version()
12844+        d.addCallback(lambda bv:
12845+            self.failUnless(bv.is_readonly()))
12846+        d.addCallback(lambda ignored:
12847+            self.sdmf_node.get_best_readable_version())
12848+        d.addCallback(lambda bv:
12849+            self.failUnless(bv.is_readonly()))
12850+        return d
12851+
12852+
12853+    def test_get_mutable_version(self):
12854+        d = self.mdmf_node.get_best_mutable_version()
12855+        d.addCallback(lambda bv:
12856+            self.failIf(bv.is_readonly()))
12857+        d.addCallback(lambda ignored:
12858+            self.sdmf_node.get_best_mutable_version())
12859+        d.addCallback(lambda bv:
12860+            self.failIf(bv.is_readonly()))
12861+        return d
12862+
12863+
12864+    def test_toplevel_overwrite(self):
12865+        new_data = MutableData("foo bar baz" * 100000)
12866+        new_small_data = MutableData("foo bar baz" * 10)
12867+        d = self.mdmf_node.overwrite(new_data)
12868+        d.addCallback(lambda ignored:
12869+            self.mdmf_node.download_best_version())
12870+        d.addCallback(lambda data:
12871+            self.failUnlessEqual(data, "foo bar baz" * 100000))
12872+        d.addCallback(lambda ignored:
12873+            self.sdmf_node.overwrite(new_small_data))
12874+        d.addCallback(lambda ignored:
12875+            self.sdmf_node.download_best_version())
12876+        d.addCallback(lambda data:
12877+            self.failUnlessEqual(data, "foo bar baz" * 10))
12878+        return d
12879+
12880+
12881+    def test_toplevel_modify(self):
12882+        def modifier(old_contents, servermap, first_time):
12883+            return MutableData(old_contents + "modified")
12884+        d = self.mdmf_node.modify(modifier)
12885+        d.addCallback(lambda ignored:
12886+            self.mdmf_node.download_best_version())
12887+        d.addCallback(lambda data:
12888+            self.failUnlessIn("modified", data))
12889+        d.addCallback(lambda ignored:
12890+            self.sdmf_node.modify(modifier))
12891+        d.addCallback(lambda ignored:
12892+            self.sdmf_node.download_best_version())
12893+        d.addCallback(lambda data:
12894+            self.failUnlessIn("modified", data))
12895+        return d
12896+
12897+
12898+    def test_version_modify(self):
12899+        # TODO: When we can publish multiple versions, alter this test
12900+        # to modify a version other than the best usable version, then
12901+        # test to see that the best recoverable version is that.
12902+        def modifier(old_contents, servermap, first_time):
12903+            return MutableData(old_contents + "modified")
12904+        d = self.mdmf_node.modify(modifier)
12905+        d.addCallback(lambda ignored:
12906+            self.mdmf_node.download_best_version())
12907+        d.addCallback(lambda data:
12908+            self.failUnlessIn("modified", data))
12909+        d.addCallback(lambda ignored:
12910+            self.sdmf_node.modify(modifier))
12911+        d.addCallback(lambda ignored:
12912+            self.sdmf_node.download_best_version())
12913+        d.addCallback(lambda data:
12914+            self.failUnlessIn("modified", data))
12915+        return d
12916+
12917+
12918+    def test_download_version(self):
12919+        # This will only pass once we get the ability to publish
12920+        # multiple recoverable versions.
12921+        self.failUnless(False)
12922+
12923+
12924+    def test_partial_read(self):
12925+        # read only a few bytes at a time, and see that the results are
12926+        # what we expect.
12927+        d = self.mdmf_node.get_best_readable_version()
12928+        def _read_data(version):
12929+            c = consumer.MemoryConsumer()
12930+            d2 = defer.succeed(None)
12931+            for i in xrange(0, len(self.data), 10000):
12932+                d2.addCallback(lambda ignored, i=i: version.read(c, i, 10000))
12933+            d2.addCallback(lambda ignored:
12934+                self.failUnlessEqual(self.data, "".join(c.chunks)))
12935+            return d2
12936+        d.addCallback(_read_data)
12937+        return d
12938+
12939+
12940+    def test_read(self):
12941+        d = self.mdmf_node.get_best_readable_version()
12942+        def _read_data(version):
12943+            c = consumer.MemoryConsumer()
12944+            d2 = defer.succeed(None)
12945+            d2.addCallback(lambda ignored: version.read(c))
12946+            d2.addCallback(lambda ignored:
12947+                self.failUnlessEqual("".join(c.chunks), self.data))
12948+            return d2
12949+        d.addCallback(_read_data)
12950+        return d
12951+
12952+
12953+    def test_download_best_version(self):
12954+        d = self.mdmf_node.download_best_version()
12955+        d.addCallback(lambda data:
12956+            self.failUnlessEqual(data, self.data))
12957+        d.addCallback(lambda ignored:
12958+            self.sdmf_node.download_best_version())
12959+        d.addCallback(lambda data:
12960+            self.failUnlessEqual(data, self.small_data))
12961+        return d
12962[web/filenode.py: alter download code to use the new #993 interface.
12963Kevan Carstensen <kevan@isnotajoke.com>**20100717015426
12964 Ignore-this: 4e8b89f9b616755f1f644ecea4cda0bb
12965] {
12966hunk ./src/allmydata/web/filenode.py 167
12967             # properly. So we assume that at least the browser will agree
12968             # with itself, and echo back the same bytes that we were given.
12969             filename = get_arg(req, "filename", self.name) or "unknown"
12970-            if self.node.is_mutable():
12971-                # some day: d = self.node.get_best_version()
12972-                d = makeMutableDownloadable(self.node)
12973-            else:
12974-                d = defer.succeed(self.node)
12975+            d = self.node.get_best_readable_version()
12976             d.addCallback(lambda dn: FileDownloader(dn, filename))
12977             return d
12978         if t == "json":
12979hunk ./src/allmydata/web/filenode.py 191
12980         if t:
12981             raise WebError("GET file: bad t=%s" % t)
12982         filename = get_arg(req, "filename", self.name) or "unknown"
12983-        if self.node.is_mutable():
12984-            # some day: d = self.node.get_best_version()
12985-            d = makeMutableDownloadable(self.node)
12986-        else:
12987-            d = defer.succeed(self.node)
12988+        d = self.node.get_best_readable_version()
12989         d.addCallback(lambda dn: FileDownloader(dn, filename))
12990         return d
12991 
12992hunk ./src/allmydata/web/filenode.py 285
12993         d.addCallback(lambda res: self.node.get_uri())
12994         return d
12995 
12996-class MutableDownloadable:
12997-    #implements(IDownloadable)
12998-    def __init__(self, size, node):
12999-        self.size = size
13000-        self.node = node
13001-    def get_size(self):
13002-        return self.size
13003-    def is_mutable(self):
13004-        return True
13005-    def read(self, consumer, offset=0, size=None):
13006-        d = self.node.download_best_version()
13007-        d.addCallback(self._got_data, consumer, offset, size)
13008-        return d
13009-    def _got_data(self, contents, consumer, offset, size):
13010-        start = offset
13011-        if size is not None:
13012-            end = offset+size
13013-        else:
13014-            end = self.size
13015-        # SDMF: we can write the whole file in one big chunk
13016-        consumer.write(contents[start:end])
13017-        return consumer
13018-
13019-def makeMutableDownloadable(n):
13020-    d = defer.maybeDeferred(n.get_size_of_best_version)
13021-    d.addCallback(MutableDownloadable, n)
13022-    return d
13023 
13024 class FileDownloader(rend.Page):
13025     # since we override the rendering process (to let the tahoe Downloader
13026}
13027[test/common.py: remove FileTooLargeErrors that tested for an SDMF limitation that no longer exists
13028Kevan Carstensen <kevan@isnotajoke.com>**20100717015501
13029 Ignore-this: 8b17689d9391a4870a327c1d7c0b3225
13030] {
13031hunk ./src/allmydata/test/common.py 198
13032         self.init_from_cap(make_mutable_file_cap())
13033     def create(self, contents, key_generator=None, keysize=None):
13034         initial_contents = self._get_initial_contents(contents)
13035-        if initial_contents.get_size() > self.MUTABLE_SIZELIMIT:
13036-            raise FileTooLargeError("SDMF is limited to one segment, and "
13037-                                    "%d > %d" % (initial_contents.get_size(),
13038-                                                 self.MUTABLE_SIZELIMIT))
13039         data = initial_contents.read(initial_contents.get_size())
13040         data = "".join(data)
13041         self.all_contents[self.storage_index] = data
13042hunk ./src/allmydata/test/common.py 326
13043 
13044 
13045     def overwrite(self, new_contents):
13046-        if new_contents.get_size() > self.MUTABLE_SIZELIMIT:
13047-            raise FileTooLargeError("SDMF is limited to one segment, and "
13048-                                    "%d > %d" % (new_contents.get_size(),
13049-                                                 self.MUTABLE_SIZELIMIT))
13050         assert not self.is_readonly()
13051         new_data = new_contents.read(new_contents.get_size())
13052         new_data = "".join(new_data)
13053}
13054[frontends/sftpd.py: fix conflicts with trunk
13055Kevan Carstensen <kevan@isnotajoke.com>**20100717020240
13056 Ignore-this: 27fae8655794db5c907eb0fa657c0f9a
13057] hunk ./src/allmydata/frontends/sftpd.py 715
13058         else:
13059             assert IFileNode.providedBy(filenode), filenode
13060 
13061-            # TODO: use download interface described in #993 when implemented.
13062-            if filenode.is_mutable():
13063-                self.async.addCallback(lambda ign: filenode.download_best_version())
13064-                def _downloaded(data):
13065-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
13066-                    self.consumer.write(data)
13067-                    self.consumer.finish()
13068-                    return None
13069-                self.async.addCallback(_downloaded)
13070-            else:
13071-                download_size = filenode.get_size()
13072-                assert download_size is not None, "download_size is None"
13073+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
13074+
13075+            def _read(version):
13076+                download_size = version.get_size()
13077+                assert download_size is not None
13078+
13079                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
13080 
13081                 if noisy: self.log("_read", level=NOISY)
13082
13083Context:
13084
13085[docs: CREDITS and NEWS
13086zooko@zooko.com**20100714060150
13087 Ignore-this: dc83e612f77d69e50ee975f07f6b16fe
13088] 
13089[CREDITS: more creds for Kevan, plus utf-8 BOM
13090zooko@zooko.com**20100619045503
13091 Ignore-this: 72d02bdd7a0f324f1cee8cd399c7c6de
13092] 
13093[cli.py: make command descriptions consistently end with a full stop.
13094david-sarah@jacaranda.org**20100714014538
13095 Ignore-this: 9ee7fa29ca2d1631db4049c2a389a97a
13096] 
13097[SFTP: address some of the comments in zooko's review (#1106).
13098david-sarah@jacaranda.org**20100712025537
13099 Ignore-this: c3921638a2d4f1de2a776ae78e4dc37e
13100] 
13101[docs/logging.txt: note that setting flogging vars might affect tests with race conditions.
13102david-sarah@jacaranda.org**20100712050721
13103 Ignore-this: fc1609d215fcd5561a57fd1226206f27
13104] 
13105[test_storage.py: potential fix for failures when logging is enabled.
13106david-sarah@jacaranda.org**19700713040546
13107 Ignore-this: 5815693a0df3e64c52c3c6b7be2846c7
13108] 
13109[upcase_since_on_welcome
13110terrellrussell@gmail.com**20100708193903] 
13111[server_version_on_welcome_page.dpatch.txt
13112freestorm77@gmail.com**20100605191721
13113 Ignore-this: b450c76dc875f5ac8cca229a666cbd0a
13114 
13115 
13116 - The storage server version is 0 for all storage nodes in the Welcome Page
13117 
13118 
13119] 
13120[NEWS: add NEWS snippets about two recent patches
13121zooko@zooko.com**20100708162058
13122 Ignore-this: 6c9da6a0ad7351a960bdd60f81532899
13123] 
13124[directory_html_top_banner.dpatch
13125freestorm77@gmail.com**20100622205301
13126 Ignore-this: 1d770d975e0c414c996564774f049bca
13127 
13128 The div tag with the link "Return to Welcome page" on the directory.xhtml page is not correct
13129 
13130] 
13131[tahoe_css_toolbar.dpatch
13132freestorm77@gmail.com**20100622210046
13133 Ignore-this: 5b3ebb2e0f52bbba718a932f80c246c0
13134 
13135 CSS modification to be correctly diplayed with Internet Explorer 8
13136 
13137 The links on the top of page directory.xhtml are not diplayed in the same line as display with Firefox.
13138 
13139] 
13140[runnin_test_tahoe_css.dpatch
13141freestorm77@gmail.com**20100622214714
13142 Ignore-this: e0db73d68740aad09a7b9ae60a08c05c
13143 
13144 Runnin test for changes in tahoe.css file
13145 
13146] 
13147[runnin_test_directory_xhtml.dpatch
13148freestorm77@gmail.com**20100622201403
13149 Ignore-this: f8962463fce50b9466405cb59fe11d43
13150 
13151 Runnin test for diretory.xhtml top banner
13152 
13153] 
13154[stringutils.py: tolerate sys.stdout having no 'encoding' attribute.
13155david-sarah@jacaranda.org**20100626040817
13156 Ignore-this: f42cad81cef645ee38ac1df4660cc850
13157] 
13158[quickstart.html: python 2.5 -> 2.6 as recommended version
13159david-sarah@jacaranda.org**20100705175858
13160 Ignore-this: bc3a14645ea1d5435002966ae903199f
13161] 
13162[SFTP: don't call .stopProducing on the producer registered with OverwriteableFileConsumer (which breaks with warner's new downloader).
13163david-sarah@jacaranda.org**20100628231926
13164 Ignore-this: 131b7a5787bc85a9a356b5740d9d996f
13165] 
13166[docs/how_to_make_a_tahoe-lafs_release.txt: trivial correction, install.html should now be quickstart.html.
13167david-sarah@jacaranda.org**20100625223929
13168 Ignore-this: 99a5459cac51bd867cc11ad06927ff30
13169] 
13170[setup: in the Makefile, refuse to upload tarballs unless someone has passed the environment variable "BB_BRANCH" with value "trunk"
13171zooko@zooko.com**20100619034928
13172 Ignore-this: 276ddf9b6ad7ec79e27474862e0f7d6
13173] 
13174[trivial: tiny update to in-line comment
13175zooko@zooko.com**20100614045715
13176 Ignore-this: 10851b0ed2abfed542c97749e5d280bc
13177 (I'm actually committing this patch as a test of the new eager-annotation-computation of trac-darcs.)
13178] 
13179[docs: about.html link to home page early on, and be decentralized storage instead of cloud storage this time around
13180zooko@zooko.com**20100619065318
13181 Ignore-this: dc6db03f696e5b6d2848699e754d8053
13182] 
13183[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"
13184zooko@zooko.com**20100619065124
13185 Ignore-this: e292c7f51c337a84ebfeb366fbd24d6c
13186] 
13187[TAG allmydata-tahoe-1.7.0
13188zooko@zooko.com**20100619052631
13189 Ignore-this: d21e27afe6d85e2e3ba6a3292ba2be1
13190] 
13191Patch bundle hash:
13192bbbb9ed3df5818033ed85f2a7274e1beb7b7885a