Ticket #393: 393status44.dpatch

File 393status44.dpatch, 847.9 KB (added by kevan, at 2011-05-31T01:52:19Z)

add more tests; append hints to caps when we have hints to give; initial work on MDMF directories

Line 
1Mon Aug  9 16:32:44 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
2  * interfaces.py: Add #993 interfaces
3
4Mon Aug  9 16:35:35 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
5  * frontends/sftpd.py: Modify the sftp frontend to work with the MDMF changes
6
7Mon Aug  9 17:06:19 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
8  * immutable/filenode.py: Make the immutable file node implement the same interfaces as the mutable one
9
10Mon Aug  9 17:06:33 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
11  * immutable/literal.py: implement the same interfaces as other filenodes
12
13Fri Aug 13 16:49:57 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
14  * scripts: tell 'tahoe put' about MDMF
15
16Sat Aug 14 01:10:12 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
17  * web: Alter the webapi to get along with and take advantage of the MDMF changes
18 
19  The main benefit that the webapi gets from MDMF, at least initially, is
20  the ability to do a streaming download of an MDMF mutable file. It also
21  exposes a way (through the PUT verb) to append to or otherwise modify
22  (in-place) an MDMF mutable file.
23
24Sat Aug 14 15:57:11 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
25  * client.py: learn how to create different kinds of mutable files
26
27Wed Aug 18 17:32:16 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
28  * mutable/checker.py and mutable/repair.py: Modify checker and repairer to work with MDMF
29 
30  The checker and repairer required minimal changes to work with the MDMF
31  modifications made elsewhere. The checker duplicated a lot of the code
32  that was already in the downloader, so I modified the downloader
33  slightly to expose this functionality to the checker and removed the
34  duplicated code. The repairer only required a minor change to deal with
35  data representation.
36
37Wed Aug 18 17:32:31 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
38  * mutable/filenode.py: add versions and partial-file updates to the mutable file node
39 
40  One of the goals of MDMF as a GSoC project is to lay the groundwork for
41  LDMF, a format that will allow Tahoe-LAFS to deal with and encourage
42  multiple versions of a single cap on the grid. In line with this, there
43  is a now a distinction between an overriding mutable file (which can be
44  thought to correspond to the cap/unique identifier for that mutable
45  file) and versions of the mutable file (which we can download, update,
46  and so on). All download, upload, and modification operations end up
47  happening on a particular version of a mutable file, but there are
48  shortcut methods on the object representing the overriding mutable file
49  that perform these operations on the best version of the mutable file
50  (which is what code should be doing until we have LDMF and better
51  support for other paradigms).
52 
53  Another goal of MDMF was to take advantage of segmentation to give
54  callers more efficient partial file updates or appends. This patch
55  implements methods that do that, too.
56 
57
58Wed Aug 18 17:33:42 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
59  * mutable/publish.py: Modify the publish process to support MDMF
60 
61  The inner workings of the publishing process needed to be reworked to a
62  large extend to cope with segmented mutable files, and to cope with
63  partial-file updates of mutable files. This patch does that. It also
64  introduces wrappers for uploadable data, allowing the use of
65  filehandle-like objects as data sources, in addition to strings. This
66  reduces memory inefficiency when dealing with large files through the
67  webapi, and clarifies update code there.
68
69Wed Aug 18 17:35:09 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
70  * nodemaker.py: Make nodemaker expose a way to create MDMF files
71
72Sat Aug 14 15:56:44 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
73  * docs: update docs to mention MDMF
74
75Wed Aug 18 17:33:04 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
76  * mutable/layout.py and interfaces.py: add MDMF writer and reader
77 
78  The MDMF writer is responsible for keeping state as plaintext is
79  gradually processed into share data by the upload process. When the
80  upload finishes, it will write all of its share data to a remote server,
81  reporting its status back to the publisher.
82 
83  The MDMF reader is responsible for abstracting an MDMF file as it sits
84  on the grid from the downloader; specifically, by receiving and
85  responding to requests for arbitrary data within the MDMF file.
86 
87  The interfaces.py file has also been modified to contain an interface
88  for the writer.
89
90Wed Aug 18 17:34:09 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
91  * mutable/retrieve.py: Modify the retrieval process to support MDMF
92 
93  The logic behind a mutable file download had to be adapted to work with
94  segmented mutable files; this patch performs those adaptations. It also
95  exposes some decoding and decrypting functionality to make partial-file
96  updates a little easier, and supports efficient random-access downloads
97  of parts of an MDMF file.
98
99Wed Aug 18 17:34:39 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
100  * mutable/servermap.py: Alter the servermap updater to work with MDMF files
101 
102  These modifications were basically all to the end of having the
103  servermap updater use the unified MDMF + SDMF read interface whenever
104  possible -- this reduces the complexity of the code, making it easier to
105  read and maintain. To do this, I needed to modify the process of
106  updating the servermap a little bit.
107 
108  To support partial-file updates, I also modified the servermap updater
109  to fetch the block hash trees and certain segments of files while it
110  performed a servermap update (this can be done without adding any new
111  roundtrips because of batch-read functionality that the read proxy has).
112 
113
114Wed Aug 18 17:35:31 PDT 2010  Kevan Carstensen <kevan@isnotajoke.com>
115  * tests:
116 
117      - A lot of existing tests relied on aspects of the mutable file
118        implementation that were changed. This patch updates those tests
119        to work with the changes.
120      - This patch also adds tests for new features.
121
122Sun Feb 20 15:02:01 PST 2011  "Brian Warner <warner@lothar.com>"
123  * resolve conflicts between 393-MDMF patches and trunk as of 1.8.2
124
125Sun Feb 20 17:46:59 PST 2011  "Brian Warner <warner@lothar.com>"
126  * mutable/filenode.py: fix create_mutable_file('string')
127
128Sun Feb 20 21:56:00 PST 2011  "Brian Warner <warner@lothar.com>"
129  * resolve more conflicts with current trunk
130
131Sun Feb 20 22:10:04 PST 2011  "Brian Warner <warner@lothar.com>"
132  * update MDMF code with StorageFarmBroker changes
133
134Fri Feb 25 17:04:33 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
135  * mutable/filenode: Clean up servermap handling in MutableFileVersion
136 
137  We want to update the servermap before attempting to modify a file,
138  which we now do. This introduced code duplication, which was addressed
139  by refactoring the servermap update into its own method, and then
140  eliminating duplicate servermap updates throughout the
141  MutableFileVersion.
142
143Sun Feb 27 15:16:43 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
144  * web: Use the string "replace" to trigger whole-file replacement when processing an offset parameter.
145
146Sun Feb 27 16:34:26 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
147  * docs/configuration.rst: fix more conflicts between #393 and trunk
148
149Sun Feb 27 17:06:37 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
150  * mutable/layout: remove references to the salt hash tree.
151
152Sun Feb 27 18:10:56 PST 2011  warner@lothar.com
153  * test_mutable.py: add test to exercise fencepost bug
154
155Mon Feb 28 00:33:27 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
156  * mutable/publish: account for offsets on segment boundaries.
157
158Mon Feb 28 19:08:07 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
159  * tahoe-put: raise UsageError when given a nonsensical mutable type, move option validation code to the option parser.
160
161Fri Mar  4 17:08:58 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
162  * web: use None instead of False in the case of no offset, use object identity comparison to check whether or not an offset was specified.
163
164Mon Mar  7 00:17:13 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
165  * mutable/filenode: remove incorrect comments about segment boundaries
166
167Mon Mar  7 00:22:29 PST 2011  Kevan Carstensen <kevan@isnotajoke.com>
168  * mutable: use integer division where appropriate
169
170Sun May  1 15:41:25 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
171  * mutable/layout.py: reorder on-disk format to aput variable-length fields at the end of the share, after a predictably long preamble
172
173Sun May  1 15:42:49 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
174  * uri.py: Add MDMF cap
175
176Sun May  1 15:45:23 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
177  * nodemaker, mutable/filenode: train nodemaker and filenode to handle MDMF caps
178
179Sun May 15 15:59:46 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
180  * mutable/retrieve: fix typo in paused check
181
182Sun May 15 16:00:08 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
183  * scripts/tahoe_put.py: teach tahoe put about MDMF caps
184
185Sun May 15 16:00:38 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
186  * test/common.py: fix some MDMF-related bugs in common test fixtures
187
188Sun May 15 16:00:54 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
189  * test/test_cli: Alter existing MDMF tests to test for MDMF caps
190
191Sun May 15 16:02:07 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
192  * test/test_mutable.py: write a test for pausing during retrieval, write support structure for that test
193
194Sun May 15 16:03:26 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
195  * test/test_mutable.py: implement cap type checking
196
197Sun May 15 16:03:58 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
198  * test/test_web: add MDMF cap tests
199
200Sun May 15 16:04:21 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
201  * web/filenode.py: complain if a PUT is requested with a readonly cap
202
203Sun May 15 16:04:44 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
204  * web/info.py: Display mutable type information when describing a mutable file
205
206Mon May 30 18:17:07 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
207  * Add MDMF dirnodes
208
209Mon May 30 18:17:58 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
210  * Add tests for MDMF directories
211
212Mon May 30 18:20:36 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
213  * uri: teach mutable URI objects how to allow other objects to give them extension parameters
214
215Mon May 30 18:22:01 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
216  * interfaces: working update to interfaces.py for extension handling
217
218Mon May 30 18:24:47 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
219  * mutable/publish: tell filenodes about encoding parameters so they can be put in the cap
220
221Mon May 30 18:25:57 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
222  * mutable/servermap: caps imply protocol version, so the servermap doesn't need to tell the filenode what it is anymore.
223
224Mon May 30 18:26:41 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
225  * mutable/filenode: pass downloader hints between publisher, MutableFileNode, and MutableFileVersion as convenient
226 
227  We still need to work on making this more thorough; i.e., passing hints
228  when other operations change encoding parameters.
229
230Mon May 30 18:27:39 PDT 2011  Kevan Carstensen <kevan@isnotajoke.com>
231  * test: change test fixtures to work with our new extension passing API; add, change, and delete tests as appropriate to reflect the fact that caps without hints are now the exception rather than the norm
232
233New patches:
234
235[interfaces.py: Add #993 interfaces
236Kevan Carstensen <kevan@isnotajoke.com>**20100809233244
237 Ignore-this: b58621ac5cc86f1b4b4149f9e6c6a1ce
238] {
239hunk ./src/allmydata/interfaces.py 499
240 class MustNotBeUnknownRWError(CapConstraintError):
241     """Cannot add an unknown child cap specified in a rw_uri field."""
242 
243+
244+class IReadable(Interface):
245+    """I represent a readable object -- either an immutable file, or a
246+    specific version of a mutable file.
247+    """
248+
249+    def is_readonly():
250+        """Return True if this reference provides mutable access to the given
251+        file or directory (i.e. if you can modify it), or False if not. Note
252+        that even if this reference is read-only, someone else may hold a
253+        read-write reference to it.
254+
255+        For an IReadable returned by get_best_readable_version(), this will
256+        always return True, but for instances of subinterfaces such as
257+        IMutableFileVersion, it may return False."""
258+
259+    def is_mutable():
260+        """Return True if this file or directory is mutable (by *somebody*,
261+        not necessarily you), False if it is is immutable. Note that a file
262+        might be mutable overall, but your reference to it might be
263+        read-only. On the other hand, all references to an immutable file
264+        will be read-only; there are no read-write references to an immutable
265+        file."""
266+
267+    def get_storage_index():
268+        """Return the storage index of the file."""
269+
270+    def get_size():
271+        """Return the length (in bytes) of this readable object."""
272+
273+    def download_to_data():
274+        """Download all of the file contents. I return a Deferred that fires
275+        with the contents as a byte string."""
276+
277+    def read(consumer, offset=0, size=None):
278+        """Download a portion (possibly all) of the file's contents, making
279+        them available to the given IConsumer. Return a Deferred that fires
280+        (with the consumer) when the consumer is unregistered (either because
281+        the last byte has been given to it, or because the consumer threw an
282+        exception during write(), possibly because it no longer wants to
283+        receive data). The portion downloaded will start at 'offset' and
284+        contain 'size' bytes (or the remainder of the file if size==None).
285+
286+        The consumer will be used in non-streaming mode: an IPullProducer
287+        will be attached to it.
288+
289+        The consumer will not receive data right away: several network trips
290+        must occur first. The order of events will be::
291+
292+         consumer.registerProducer(p, streaming)
293+          (if streaming == False)::
294+           consumer does p.resumeProducing()
295+            consumer.write(data)
296+           consumer does p.resumeProducing()
297+            consumer.write(data).. (repeat until all data is written)
298+         consumer.unregisterProducer()
299+         deferred.callback(consumer)
300+
301+        If a download error occurs, or an exception is raised by
302+        consumer.registerProducer() or consumer.write(), I will call
303+        consumer.unregisterProducer() and then deliver the exception via
304+        deferred.errback(). To cancel the download, the consumer should call
305+        p.stopProducing(), which will result in an exception being delivered
306+        via deferred.errback().
307+
308+        See src/allmydata/util/consumer.py for an example of a simple
309+        download-to-memory consumer.
310+        """
311+
312+
313+class IWritable(Interface):
314+    """
315+    I define methods that callers can use to update SDMF and MDMF
316+    mutable files on a Tahoe-LAFS grid.
317+    """
318+    # XXX: For the moment, we have only this. It is possible that we
319+    #      want to move overwrite() and modify() in here too.
320+    def update(data, offset):
321+        """
322+        I write the data from my data argument to the MDMF file,
323+        starting at offset. I continue writing data until my data
324+        argument is exhausted, appending data to the file as necessary.
325+        """
326+        # assert IMutableUploadable.providedBy(data)
327+        # to append data: offset=node.get_size_of_best_version()
328+        # do we want to support compacting MDMF?
329+        # for an MDMF file, this can be done with O(data.get_size())
330+        # memory. For an SDMF file, any modification takes
331+        # O(node.get_size_of_best_version()).
332+
333+
334+class IMutableFileVersion(IReadable):
335+    """I provide access to a particular version of a mutable file. The
336+    access is read/write if I was obtained from a filenode derived from
337+    a write cap, or read-only if the filenode was derived from a read cap.
338+    """
339+
340+    def get_sequence_number():
341+        """Return the sequence number of this version."""
342+
343+    def get_servermap():
344+        """Return the IMutableFileServerMap instance that was used to create
345+        this object.
346+        """
347+
348+    def get_writekey():
349+        """Return this filenode's writekey, or None if the node does not have
350+        write-capability. This may be used to assist with data structures
351+        that need to make certain data available only to writers, such as the
352+        read-write child caps in dirnodes. The recommended process is to have
353+        reader-visible data be submitted to the filenode in the clear (where
354+        it will be encrypted by the filenode using the readkey), but encrypt
355+        writer-visible data using this writekey.
356+        """
357+
358+    # TODO: Can this be overwrite instead of replace?
359+    def replace(new_contents):
360+        """Replace the contents of the mutable file, provided that no other
361+        node has published (or is attempting to publish, concurrently) a
362+        newer version of the file than this one.
363+
364+        I will avoid modifying any share that is different than the version
365+        given by get_sequence_number(). However, if another node is writing
366+        to the file at the same time as me, I may manage to update some shares
367+        while they update others. If I see any evidence of this, I will signal
368+        UncoordinatedWriteError, and the file will be left in an inconsistent
369+        state (possibly the version you provided, possibly the old version,
370+        possibly somebody else's version, and possibly a mix of shares from
371+        all of these).
372+
373+        The recommended response to UncoordinatedWriteError is to either
374+        return it to the caller (since they failed to coordinate their
375+        writes), or to attempt some sort of recovery. It may be sufficient to
376+        wait a random interval (with exponential backoff) and repeat your
377+        operation. If I do not signal UncoordinatedWriteError, then I was
378+        able to write the new version without incident.
379+
380+        I return a Deferred that fires (with a PublishStatus object) when the
381+        update has completed.
382+        """
383+
384+    def modify(modifier_cb):
385+        """Modify the contents of the file, by downloading this version,
386+        applying the modifier function (or bound method), then uploading
387+        the new version. This will succeed as long as no other node
388+        publishes a version between the download and the upload.
389+        I return a Deferred that fires (with a PublishStatus object) when
390+        the update is complete.
391+
392+        The modifier callable will be given three arguments: a string (with
393+        the old contents), a 'first_time' boolean, and a servermap. As with
394+        download_to_data(), the old contents will be from this version,
395+        but the modifier can use the servermap to make other decisions
396+        (such as refusing to apply the delta if there are multiple parallel
397+        versions, or if there is evidence of a newer unrecoverable version).
398+        'first_time' will be True the first time the modifier is called,
399+        and False on any subsequent calls.
400+
401+        The callable should return a string with the new contents. The
402+        callable must be prepared to be called multiple times, and must
403+        examine the input string to see if the change that it wants to make
404+        is already present in the old version. If it does not need to make
405+        any changes, it can either return None, or return its input string.
406+
407+        If the modifier raises an exception, it will be returned in the
408+        errback.
409+        """
410+
411+
412 # The hierarchy looks like this:
413 #  IFilesystemNode
414 #   IFileNode
415hunk ./src/allmydata/interfaces.py 758
416     def raise_error():
417         """Raise any error associated with this node."""
418 
419+    # XXX: These may not be appropriate outside the context of an IReadable.
420     def get_size():
421         """Return the length (in bytes) of the data this node represents. For
422         directory nodes, I return the size of the backing store. I return
423hunk ./src/allmydata/interfaces.py 775
424 class IFileNode(IFilesystemNode):
425     """I am a node which represents a file: a sequence of bytes. I am not a
426     container, like IDirectoryNode."""
427+    def get_best_readable_version():
428+        """Return a Deferred that fires with an IReadable for the 'best'
429+        available version of the file. The IReadable provides only read
430+        access, even if this filenode was derived from a write cap.
431 
432hunk ./src/allmydata/interfaces.py 780
433-class IImmutableFileNode(IFileNode):
434-    def read(consumer, offset=0, size=None):
435-        """Download a portion (possibly all) of the file's contents, making
436-        them available to the given IConsumer. Return a Deferred that fires
437-        (with the consumer) when the consumer is unregistered (either because
438-        the last byte has been given to it, or because the consumer threw an
439-        exception during write(), possibly because it no longer wants to
440-        receive data). The portion downloaded will start at 'offset' and
441-        contain 'size' bytes (or the remainder of the file if size==None).
442-
443-        The consumer will be used in non-streaming mode: an IPullProducer
444-        will be attached to it.
445+        For an immutable file, there is only one version. For a mutable
446+        file, the 'best' version is the recoverable version with the
447+        highest sequence number. If no uncoordinated writes have occurred,
448+        and if enough shares are available, then this will be the most
449+        recent version that has been uploaded. If no version is recoverable,
450+        the Deferred will errback with an UnrecoverableFileError.
451+        """
452 
453hunk ./src/allmydata/interfaces.py 788
454-        The consumer will not receive data right away: several network trips
455-        must occur first. The order of events will be::
456+    def download_best_version():
457+        """Download the contents of the version that would be returned
458+        by get_best_readable_version(). This is equivalent to calling
459+        download_to_data() on the IReadable given by that method.
460 
461hunk ./src/allmydata/interfaces.py 793
462-         consumer.registerProducer(p, streaming)
463-          (if streaming == False)::
464-           consumer does p.resumeProducing()
465-            consumer.write(data)
466-           consumer does p.resumeProducing()
467-            consumer.write(data).. (repeat until all data is written)
468-         consumer.unregisterProducer()
469-         deferred.callback(consumer)
470+        I return a Deferred that fires with a byte string when the file
471+        has been fully downloaded. To support streaming download, use
472+        the 'read' method of IReadable. If no version is recoverable,
473+        the Deferred will errback with an UnrecoverableFileError.
474+        """
475 
476hunk ./src/allmydata/interfaces.py 799
477-        If a download error occurs, or an exception is raised by
478-        consumer.registerProducer() or consumer.write(), I will call
479-        consumer.unregisterProducer() and then deliver the exception via
480-        deferred.errback(). To cancel the download, the consumer should call
481-        p.stopProducing(), which will result in an exception being delivered
482-        via deferred.errback().
483+    def get_size_of_best_version():
484+        """Find the size of the version that would be returned by
485+        get_best_readable_version().
486 
487hunk ./src/allmydata/interfaces.py 803
488-        See src/allmydata/util/consumer.py for an example of a simple
489-        download-to-memory consumer.
490+        I return a Deferred that fires with an integer. If no version
491+        is recoverable, the Deferred will errback with an
492+        UnrecoverableFileError.
493         """
494 
495hunk ./src/allmydata/interfaces.py 808
496+
497+class IImmutableFileNode(IFileNode, IReadable):
498+    """I am a node representing an immutable file. Immutable files have
499+    only one version"""
500+
501+
502 class IMutableFileNode(IFileNode):
503     """I provide access to a 'mutable file', which retains its identity
504     regardless of what contents are put in it.
505hunk ./src/allmydata/interfaces.py 873
506     only be retrieved and updated all-at-once, as a single big string. Future
507     versions of our mutable files will remove this restriction.
508     """
509-
510-    def download_best_version():
511-        """Download the 'best' available version of the file, meaning one of
512-        the recoverable versions with the highest sequence number. If no
513+    def get_best_mutable_version():
514+        """Return a Deferred that fires with an IMutableFileVersion for
515+        the 'best' available version of the file. The best version is
516+        the recoverable version with the highest sequence number. If no
517         uncoordinated writes have occurred, and if enough shares are
518hunk ./src/allmydata/interfaces.py 878
519-        available, then this will be the most recent version that has been
520-        uploaded.
521+        available, then this will be the most recent version that has
522+        been uploaded.
523 
524hunk ./src/allmydata/interfaces.py 881
525-        I update an internal servermap with MODE_READ, determine which
526-        version of the file is indicated by
527-        servermap.best_recoverable_version(), and return a Deferred that
528-        fires with its contents. If no version is recoverable, the Deferred
529-        will errback with UnrecoverableFileError.
530-        """
531-
532-    def get_size_of_best_version():
533-        """Find the size of the version that would be downloaded with
534-        download_best_version(), without actually downloading the whole file.
535-
536-        I return a Deferred that fires with an integer.
537+        If no version is recoverable, the Deferred will errback with an
538+        UnrecoverableFileError.
539         """
540 
541     def overwrite(new_contents):
542hunk ./src/allmydata/interfaces.py 921
543         errback.
544         """
545 
546-
547     def get_servermap(mode):
548         """Return a Deferred that fires with an IMutableFileServerMap
549         instance, updated using the given mode.
550hunk ./src/allmydata/interfaces.py 974
551         writer-visible data using this writekey.
552         """
553 
554+    def set_version(version):
555+        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
556+        we upload in SDMF for reasons of compatibility. If you want to
557+        change this, set_version will let you do that.
558+
559+        To say that this file should be uploaded in SDMF, pass in a 0. To
560+        say that the file should be uploaded as MDMF, pass in a 1.
561+        """
562+
563+    def get_version():
564+        """Returns the mutable file protocol version."""
565+
566 class NotEnoughSharesError(Exception):
567     """Download was unable to get enough shares"""
568 
569hunk ./src/allmydata/interfaces.py 1822
570         """The upload is finished, and whatever filehandle was in use may be
571         closed."""
572 
573+
574+class IMutableUploadable(Interface):
575+    """
576+    I represent content that is due to be uploaded to a mutable filecap.
577+    """
578+    # This is somewhat simpler than the IUploadable interface above
579+    # because mutable files do not need to be concerned with possibly
580+    # generating a CHK, nor with per-file keys. It is a subset of the
581+    # methods in IUploadable, though, so we could just as well implement
582+    # the mutable uploadables as IUploadables that don't happen to use
583+    # those methods (with the understanding that the unused methods will
584+    # never be called on such objects)
585+    def get_size():
586+        """
587+        Returns a Deferred that fires with the size of the content held
588+        by the uploadable.
589+        """
590+
591+    def read(length):
592+        """
593+        Returns a list of strings which, when concatenated, are the next
594+        length bytes of the file, or fewer if there are fewer bytes
595+        between the current location and the end of the file.
596+        """
597+
598+    def close():
599+        """
600+        The process that used the Uploadable is finished using it, so
601+        the uploadable may be closed.
602+        """
603+
604 class IUploadResults(Interface):
605     """I am returned by upload() methods. I contain a number of public
606     attributes which can be read to determine the results of the upload. Some
607}
608[frontends/sftpd.py: Modify the sftp frontend to work with the MDMF changes
609Kevan Carstensen <kevan@isnotajoke.com>**20100809233535
610 Ignore-this: 2d25e2cfcd0d7bbcbba660c7e1da12f
611] {
612hunk ./src/allmydata/frontends/sftpd.py 33
613 from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, \
614      NoSuchChildError, ChildOfWrongTypeError
615 from allmydata.mutable.common import NotWriteableError
616+from allmydata.mutable.publish import MutableFileHandle
617 from allmydata.immutable.upload import FileHandle
618 from allmydata.dirnode import update_metadata
619 from allmydata.util.fileutil import EncryptedTemporaryFile
620hunk ./src/allmydata/frontends/sftpd.py 667
621         else:
622             assert IFileNode.providedBy(filenode), filenode
623 
624-            if filenode.is_mutable():
625-                self.async.addCallback(lambda ign: filenode.download_best_version())
626-                def _downloaded(data):
627-                    self.consumer = OverwriteableFileConsumer(len(data), tempfile_maker)
628-                    self.consumer.write(data)
629-                    self.consumer.finish()
630-                    return None
631-                self.async.addCallback(_downloaded)
632-            else:
633-                download_size = filenode.get_size()
634-                assert download_size is not None, "download_size is None"
635+            self.async.addCallback(lambda ignored: filenode.get_best_readable_version())
636+
637+            def _read(version):
638+                if noisy: self.log("_read", level=NOISY)
639+                download_size = version.get_size()
640+                assert download_size is not None
641+
642                 self.consumer = OverwriteableFileConsumer(download_size, tempfile_maker)
643hunk ./src/allmydata/frontends/sftpd.py 675
644-                def _read(ign):
645-                    if noisy: self.log("_read immutable", level=NOISY)
646-                    filenode.read(self.consumer, 0, None)
647-                self.async.addCallback(_read)
648+
649+                version.read(self.consumer, 0, None)
650+            self.async.addCallback(_read)
651 
652         eventually(self.async.callback, None)
653 
654hunk ./src/allmydata/frontends/sftpd.py 821
655                     assert parent and childname, (parent, childname, self.metadata)
656                     d2.addCallback(lambda ign: parent.set_metadata_for(childname, self.metadata))
657 
658-                d2.addCallback(lambda ign: self.consumer.get_current_size())
659-                d2.addCallback(lambda size: self.consumer.read(0, size))
660-                d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents))
661+                d2.addCallback(lambda ign: self.filenode.overwrite(MutableFileHandle(self.consumer.get_file())))
662             else:
663                 def _add_file(ign):
664                     self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL)
665}
666[immutable/filenode.py: Make the immutable file node implement the same interfaces as the mutable one
667Kevan Carstensen <kevan@isnotajoke.com>**20100810000619
668 Ignore-this: 93e536c0f8efb705310f13ff64621527
669] {
670hunk ./src/allmydata/immutable/filenode.py 8
671 now = time.time
672 from zope.interface import implements, Interface
673 from twisted.internet import defer
674-from twisted.internet.interfaces import IConsumer
675 
676hunk ./src/allmydata/immutable/filenode.py 9
677-from allmydata.interfaces import IImmutableFileNode, IUploadResults
678 from allmydata import uri
679hunk ./src/allmydata/immutable/filenode.py 10
680+from twisted.internet.interfaces import IConsumer
681+from twisted.protocols import basic
682+from foolscap.api import eventually
683+from allmydata.interfaces import IImmutableFileNode, ICheckable, \
684+     IDownloadTarget, IUploadResults
685+from allmydata.util import dictutil, log, base32, consumer
686+from allmydata.immutable.checker import Checker
687 from allmydata.check_results import CheckResults, CheckAndRepairResults
688 from allmydata.util.dictutil import DictOfSets
689 from pycryptopp.cipher.aes import AES
690hunk ./src/allmydata/immutable/filenode.py 296
691         return self._cnode.check_and_repair(monitor, verify, add_lease)
692     def check(self, monitor, verify=False, add_lease=False):
693         return self._cnode.check(monitor, verify, add_lease)
694+
695+    def get_best_readable_version(self):
696+        """
697+        Return an IReadable of the best version of this file. Since
698+        immutable files can have only one version, we just return the
699+        current filenode.
700+        """
701+        return defer.succeed(self)
702+
703+
704+    def download_best_version(self):
705+        """
706+        Download the best version of this file, returning its contents
707+        as a bytestring. Since there is only one version of an immutable
708+        file, we download and return the contents of this file.
709+        """
710+        d = consumer.download_to_data(self)
711+        return d
712+
713+    # for an immutable file, download_to_data (specified in IReadable)
714+    # is the same as download_best_version (specified in IFileNode). For
715+    # mutable files, the difference is more meaningful, since they can
716+    # have multiple versions.
717+    download_to_data = download_best_version
718+
719+
720+    # get_size() (IReadable), get_current_size() (IFilesystemNode), and
721+    # get_size_of_best_version(IFileNode) are all the same for immutable
722+    # files.
723+    get_size_of_best_version = get_current_size
724}
725[immutable/literal.py: implement the same interfaces as other filenodes
726Kevan Carstensen <kevan@isnotajoke.com>**20100810000633
727 Ignore-this: b50dd5df2d34ecd6477b8499a27aef13
728] hunk ./src/allmydata/immutable/literal.py 106
729         d.addCallback(lambda lastSent: consumer)
730         return d
731 
732+    # IReadable, IFileNode, IFilesystemNode
733+    def get_best_readable_version(self):
734+        return defer.succeed(self)
735+
736+
737+    def download_best_version(self):
738+        return defer.succeed(self.u.data)
739+
740+
741+    download_to_data = download_best_version
742+    get_size_of_best_version = get_current_size
743+
744[scripts: tell 'tahoe put' about MDMF
745Kevan Carstensen <kevan@isnotajoke.com>**20100813234957
746 Ignore-this: c106b3384fc676bd3c0fb466d2a52b1b
747] {
748hunk ./src/allmydata/scripts/cli.py 160
749     optFlags = [
750         ("mutable", "m", "Create a mutable file instead of an immutable one."),
751         ]
752+    optParameters = [
753+        ("mutable-type", None, False, "Create a mutable file in the given format. Valid formats are 'sdmf' for SDMF and 'mdmf' for MDMF"),
754+        ]
755 
756     def parseArgs(self, arg1=None, arg2=None):
757         # see Examples below
758hunk ./src/allmydata/scripts/tahoe_put.py 21
759     from_file = options.from_file
760     to_file = options.to_file
761     mutable = options['mutable']
762+    mutable_type = False
763+
764+    if mutable:
765+        mutable_type = options['mutable-type']
766     if options['quiet']:
767         verbosity = 0
768     else:
769hunk ./src/allmydata/scripts/tahoe_put.py 33
770     stdout = options.stdout
771     stderr = options.stderr
772 
773+    if mutable_type and mutable_type not in ('sdmf', 'mdmf'):
774+        # Don't try to pass unsupported types to the webapi
775+        print >>stderr, "error: %s is an invalid format" % mutable_type
776+        return 1
777+
778     if nodeurl[-1] != "/":
779         nodeurl += "/"
780     if to_file:
781hunk ./src/allmydata/scripts/tahoe_put.py 76
782         url = nodeurl + "uri"
783     if mutable:
784         url += "?mutable=true"
785+    if mutable_type:
786+        assert mutable
787+        url += "&mutable-type=%s" % mutable_type
788+
789     if from_file:
790         infileobj = open(os.path.expanduser(from_file), "rb")
791     else:
792}
793[web: Alter the webapi to get along with and take advantage of the MDMF changes
794Kevan Carstensen <kevan@isnotajoke.com>**20100814081012
795 Ignore-this: 96c2ed4e4a9f450fb84db5d711d10bd6
796 
797 The main benefit that the webapi gets from MDMF, at least initially, is
798 the ability to do a streaming download of an MDMF mutable file. It also
799 exposes a way (through the PUT verb) to append to or otherwise modify
800 (in-place) an MDMF mutable file.
801] {
802hunk ./src/allmydata/web/common.py 12
803 from allmydata.interfaces import ExistingChildError, NoSuchChildError, \
804      FileTooLargeError, NotEnoughSharesError, NoSharesError, \
805      EmptyPathnameComponentError, MustBeDeepImmutableError, \
806-     MustBeReadonlyError, MustNotBeUnknownRWError
807+     MustBeReadonlyError, MustNotBeUnknownRWError, SDMF_VERSION, MDMF_VERSION
808 from allmydata.mutable.common import UnrecoverableFileError
809 from allmydata.util import abbreviate
810 from allmydata.util.encodingutil import to_str, quote_output
811hunk ./src/allmydata/web/common.py 35
812     else:
813         return boolean_of_arg(replace)
814 
815+
816+def parse_mutable_type_arg(arg):
817+    if not arg:
818+        return None # interpreted by the caller as "let the nodemaker decide"
819+
820+    arg = arg.lower()
821+    assert arg in ("mdmf", "sdmf")
822+
823+    if arg == "mdmf":
824+        return MDMF_VERSION
825+
826+    return SDMF_VERSION
827+
828+
829+def parse_offset_arg(offset):
830+    # XXX: This will raise a ValueError when invoked on something that
831+    # is not an integer. Is that okay? Or do we want a better error
832+    # message? Since this call is going to be used by programmers and
833+    # their tools rather than users (through the wui), it is not
834+    # inconsistent to return that, I guess.
835+    offset = int(offset)
836+    return offset
837+
838+
839 def get_root(ctx_or_req):
840     req = IRequest(ctx_or_req)
841     # the addSlash=True gives us one extra (empty) segment
842hunk ./src/allmydata/web/directory.py 19
843 from allmydata.uri import from_string_dirnode
844 from allmydata.interfaces import IDirectoryNode, IFileNode, IFilesystemNode, \
845      IImmutableFileNode, IMutableFileNode, ExistingChildError, \
846-     NoSuchChildError, EmptyPathnameComponentError
847+     NoSuchChildError, EmptyPathnameComponentError, SDMF_VERSION, MDMF_VERSION
848 from allmydata.monitor import Monitor, OperationCancelledError
849 from allmydata import dirnode
850 from allmydata.web.common import text_plain, WebError, \
851hunk ./src/allmydata/web/directory.py 153
852         if not t:
853             # render the directory as HTML, using the docFactory and Nevow's
854             # whole templating thing.
855-            return DirectoryAsHTML(self.node)
856+            return DirectoryAsHTML(self.node,
857+                                   self.client.mutable_file_default)
858 
859         if t == "json":
860             return DirectoryJSONMetadata(ctx, self.node)
861hunk ./src/allmydata/web/directory.py 556
862     docFactory = getxmlfile("directory.xhtml")
863     addSlash = True
864 
865-    def __init__(self, node):
866+    def __init__(self, node, default_mutable_format):
867         rend.Page.__init__(self)
868         self.node = node
869 
870hunk ./src/allmydata/web/directory.py 560
871+        assert default_mutable_format in (MDMF_VERSION, SDMF_VERSION)
872+        self.default_mutable_format = default_mutable_format
873+
874     def beforeRender(self, ctx):
875         # attempt to get the dirnode's children, stashing them (or the
876         # failure that results) for later use
877hunk ./src/allmydata/web/directory.py 780
878             ]]
879         forms.append(T.div(class_="freeform-form")[mkdir])
880 
881+        # Build input elements for mutable file type. We do this outside
882+        # of the list so we can check the appropriate format, based on
883+        # the default configured in the client (which reflects the
884+        # default configured in tahoe.cfg)
885+        if self.default_mutable_format == MDMF_VERSION:
886+            mdmf_input = T.input(type='radio', name='mutable-type',
887+                                 id='mutable-type-mdmf', value='mdmf',
888+                                 checked='checked')
889+        else:
890+            mdmf_input = T.input(type='radio', name='mutable-type',
891+                                 id='mutable-type-mdmf', value='mdmf')
892+
893+        if self.default_mutable_format == SDMF_VERSION:
894+            sdmf_input = T.input(type='radio', name='mutable-type',
895+                                 id='mutable-type-sdmf', value='sdmf',
896+                                 checked="checked")
897+        else:
898+            sdmf_input = T.input(type='radio', name='mutable-type',
899+                                 id='mutable-type-sdmf', value='sdmf')
900+
901         upload = T.form(action=".", method="post",
902                         enctype="multipart/form-data")[
903             T.fieldset[
904hunk ./src/allmydata/web/directory.py 812
905             T.input(type="submit", value="Upload"),
906             " Mutable?:",
907             T.input(type="checkbox", name="mutable"),
908+            sdmf_input, T.label(for_="mutable-type-sdmf")["SDMF"],
909+            mdmf_input,
910+            T.label(for_="mutable-type-mdmf")["MDMF (experimental)"],
911             ]]
912         forms.append(T.div(class_="freeform-form")[upload])
913 
914hunk ./src/allmydata/web/directory.py 850
915                 kiddata = ("filenode", {'size': childnode.get_size(),
916                                         'mutable': childnode.is_mutable(),
917                                         })
918+                if childnode.is_mutable() and \
919+                    childnode.get_version() is not None:
920+                    mutable_type = childnode.get_version()
921+                    assert mutable_type in (SDMF_VERSION, MDMF_VERSION)
922+
923+                    if mutable_type == MDMF_VERSION:
924+                        mutable_type = "mdmf"
925+                    else:
926+                        mutable_type = "sdmf"
927+                    kiddata[1]['mutable-type'] = mutable_type
928+
929             elif IDirectoryNode.providedBy(childnode):
930                 kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
931             else:
932hunk ./src/allmydata/web/filenode.py 9
933 from nevow import url, rend
934 from nevow.inevow import IRequest
935 
936-from allmydata.interfaces import ExistingChildError
937+from allmydata.interfaces import ExistingChildError, SDMF_VERSION, MDMF_VERSION
938 from allmydata.monitor import Monitor
939 from allmydata.immutable.upload import FileHandle
940hunk ./src/allmydata/web/filenode.py 12
941+from allmydata.mutable.publish import MutableFileHandle
942+from allmydata.mutable.common import MODE_READ
943 from allmydata.util import log, base32
944 
945 from allmydata.web.common import text_plain, WebError, RenderMixin, \
946hunk ./src/allmydata/web/filenode.py 18
947      boolean_of_arg, get_arg, should_create_intermediate_directories, \
948-     MyExceptionHandler, parse_replace_arg
949+     MyExceptionHandler, parse_replace_arg, parse_offset_arg, \
950+     parse_mutable_type_arg
951 from allmydata.web.check_results import CheckResults, \
952      CheckAndRepairResults, LiteralCheckResults
953 from allmydata.web.info import MoreInfo
954hunk ./src/allmydata/web/filenode.py 29
955         # a new file is being uploaded in our place.
956         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
957         if mutable:
958-            req.content.seek(0)
959-            data = req.content.read()
960-            d = client.create_mutable_file(data)
961+            mutable_type = parse_mutable_type_arg(get_arg(req,
962+                                                          "mutable-type",
963+                                                          None))
964+            data = MutableFileHandle(req.content)
965+            d = client.create_mutable_file(data, version=mutable_type)
966             def _uploaded(newnode):
967                 d2 = self.parentnode.set_node(self.name, newnode,
968                                               overwrite=replace)
969hunk ./src/allmydata/web/filenode.py 66
970         d.addCallback(lambda res: childnode.get_uri())
971         return d
972 
973-    def _read_data_from_formpost(self, req):
974-        # SDMF: files are small, and we can only upload data, so we read
975-        # the whole file into memory before uploading.
976-        contents = req.fields["file"]
977-        contents.file.seek(0)
978-        data = contents.file.read()
979-        return data
980 
981     def replace_me_with_a_formpost(self, req, client, replace):
982         # create a new file, maybe mutable, maybe immutable
983hunk ./src/allmydata/web/filenode.py 71
984         mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
985 
986+        # create an immutable file
987+        contents = req.fields["file"]
988         if mutable:
989hunk ./src/allmydata/web/filenode.py 74
990-            data = self._read_data_from_formpost(req)
991-            d = client.create_mutable_file(data)
992+            mutable_type = parse_mutable_type_arg(get_arg(req, "mutable-type",
993+                                                          None))
994+            uploadable = MutableFileHandle(contents.file)
995+            d = client.create_mutable_file(uploadable, version=mutable_type)
996             def _uploaded(newnode):
997                 d2 = self.parentnode.set_node(self.name, newnode,
998                                               overwrite=replace)
999hunk ./src/allmydata/web/filenode.py 85
1000                 return d2
1001             d.addCallback(_uploaded)
1002             return d
1003-        # create an immutable file
1004-        contents = req.fields["file"]
1005+
1006         uploadable = FileHandle(contents.file, convergence=client.convergence)
1007         d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
1008         d.addCallback(lambda newnode: newnode.get_uri())
1009hunk ./src/allmydata/web/filenode.py 91
1010         return d
1011 
1012+
1013 class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
1014     def __init__(self, client, parentnode, name):
1015         rend.Page.__init__(self)
1016hunk ./src/allmydata/web/filenode.py 174
1017             # properly. So we assume that at least the browser will agree
1018             # with itself, and echo back the same bytes that we were given.
1019             filename = get_arg(req, "filename", self.name) or "unknown"
1020-            if self.node.is_mutable():
1021-                # some day: d = self.node.get_best_version()
1022-                d = makeMutableDownloadable(self.node)
1023-            else:
1024-                d = defer.succeed(self.node)
1025+            d = self.node.get_best_readable_version()
1026             d.addCallback(lambda dn: FileDownloader(dn, filename))
1027             return d
1028         if t == "json":
1029hunk ./src/allmydata/web/filenode.py 178
1030-            if self.parentnode and self.name:
1031-                d = self.parentnode.get_metadata_for(self.name)
1032+            # We do this to make sure that fields like size and
1033+            # mutable-type (which depend on the file on the grid and not
1034+            # just on the cap) are filled in. The latter gets used in
1035+            # tests, in particular.
1036+            #
1037+            # TODO: Make it so that the servermap knows how to update in
1038+            # a mode specifically designed to fill in these fields, and
1039+            # then update it in that mode.
1040+            if self.node.is_mutable():
1041+                d = self.node.get_servermap(MODE_READ)
1042             else:
1043                 d = defer.succeed(None)
1044hunk ./src/allmydata/web/filenode.py 190
1045+            if self.parentnode and self.name:
1046+                d.addCallback(lambda ignored:
1047+                    self.parentnode.get_metadata_for(self.name))
1048+            else:
1049+                d.addCallback(lambda ignored: None)
1050             d.addCallback(lambda md: FileJSONMetadata(ctx, self.node, md))
1051             return d
1052         if t == "info":
1053hunk ./src/allmydata/web/filenode.py 211
1054         if t:
1055             raise WebError("GET file: bad t=%s" % t)
1056         filename = get_arg(req, "filename", self.name) or "unknown"
1057-        if self.node.is_mutable():
1058-            # some day: d = self.node.get_best_version()
1059-            d = makeMutableDownloadable(self.node)
1060-        else:
1061-            d = defer.succeed(self.node)
1062+        d = self.node.get_best_readable_version()
1063         d.addCallback(lambda dn: FileDownloader(dn, filename))
1064         return d
1065 
1066hunk ./src/allmydata/web/filenode.py 219
1067         req = IRequest(ctx)
1068         t = get_arg(req, "t", "").strip()
1069         replace = parse_replace_arg(get_arg(req, "replace", "true"))
1070+        offset = parse_offset_arg(get_arg(req, "offset", -1))
1071 
1072         if not t:
1073hunk ./src/allmydata/web/filenode.py 222
1074-            if self.node.is_mutable():
1075+            if self.node.is_mutable() and offset >= 0:
1076+                return self.update_my_contents(req, offset)
1077+
1078+            elif self.node.is_mutable():
1079                 return self.replace_my_contents(req)
1080             if not replace:
1081                 # this is the early trap: if someone else modifies the
1082hunk ./src/allmydata/web/filenode.py 232
1083                 # directory while we're uploading, the add_file(overwrite=)
1084                 # call in replace_me_with_a_child will do the late trap.
1085                 raise ExistingChildError()
1086+            if offset >= 0:
1087+                raise WebError("PUT to a file: append operation invoked "
1088+                               "on an immutable cap")
1089+
1090+
1091             assert self.parentnode and self.name
1092             return self.replace_me_with_a_child(req, self.client, replace)
1093         if t == "uri":
1094hunk ./src/allmydata/web/filenode.py 299
1095 
1096     def replace_my_contents(self, req):
1097         req.content.seek(0)
1098-        new_contents = req.content.read()
1099+        new_contents = MutableFileHandle(req.content)
1100         d = self.node.overwrite(new_contents)
1101         d.addCallback(lambda res: self.node.get_uri())
1102         return d
1103hunk ./src/allmydata/web/filenode.py 304
1104 
1105+
1106+    def update_my_contents(self, req, offset):
1107+        req.content.seek(0)
1108+        added_contents = MutableFileHandle(req.content)
1109+
1110+        d = self.node.get_best_mutable_version()
1111+        d.addCallback(lambda mv:
1112+            mv.update(added_contents, offset))
1113+        d.addCallback(lambda ignored:
1114+            self.node.get_uri())
1115+        return d
1116+
1117+
1118     def replace_my_contents_with_a_formpost(self, req):
1119         # we have a mutable file. Get the data from the formpost, and replace
1120         # the mutable file's contents with it.
1121hunk ./src/allmydata/web/filenode.py 320
1122-        new_contents = self._read_data_from_formpost(req)
1123+        new_contents = req.fields['file']
1124+        new_contents = MutableFileHandle(new_contents.file)
1125+
1126         d = self.node.overwrite(new_contents)
1127         d.addCallback(lambda res: self.node.get_uri())
1128         return d
1129hunk ./src/allmydata/web/filenode.py 327
1130 
1131-class MutableDownloadable:
1132-    #implements(IDownloadable)
1133-    def __init__(self, size, node):
1134-        self.size = size
1135-        self.node = node
1136-    def get_size(self):
1137-        return self.size
1138-    def is_mutable(self):
1139-        return True
1140-    def read(self, consumer, offset=0, size=None):
1141-        d = self.node.download_best_version()
1142-        d.addCallback(self._got_data, consumer, offset, size)
1143-        return d
1144-    def _got_data(self, contents, consumer, offset, size):
1145-        start = offset
1146-        if size is not None:
1147-            end = offset+size
1148-        else:
1149-            end = self.size
1150-        # SDMF: we can write the whole file in one big chunk
1151-        consumer.write(contents[start:end])
1152-        return consumer
1153-
1154-def makeMutableDownloadable(n):
1155-    d = defer.maybeDeferred(n.get_size_of_best_version)
1156-    d.addCallback(MutableDownloadable, n)
1157-    return d
1158 
1159 class FileDownloader(rend.Page):
1160     # since we override the rendering process (to let the tahoe Downloader
1161hunk ./src/allmydata/web/filenode.py 509
1162     data[1]['mutable'] = filenode.is_mutable()
1163     if edge_metadata is not None:
1164         data[1]['metadata'] = edge_metadata
1165+
1166+    if filenode.is_mutable() and filenode.get_version() is not None:
1167+        mutable_type = filenode.get_version()
1168+        assert mutable_type in (MDMF_VERSION, SDMF_VERSION)
1169+        if mutable_type == MDMF_VERSION:
1170+            mutable_type = "mdmf"
1171+        else:
1172+            mutable_type = "sdmf"
1173+        data[1]['mutable-type'] = mutable_type
1174+
1175     return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx)
1176 
1177 def FileURI(ctx, filenode):
1178hunk ./src/allmydata/web/root.py 15
1179 from allmydata import get_package_versions_string
1180 from allmydata import provisioning
1181 from allmydata.util import idlib, log
1182-from allmydata.interfaces import IFileNode
1183+from allmydata.interfaces import IFileNode, MDMF_VERSION, SDMF_VERSION
1184 from allmydata.web import filenode, directory, unlinked, status, operations
1185 from allmydata.web import reliability, storage
1186 from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
1187hunk ./src/allmydata/web/root.py 19
1188-     get_arg, RenderMixin, boolean_of_arg
1189+     get_arg, RenderMixin, boolean_of_arg, parse_mutable_type_arg
1190 
1191 
1192 class URIHandler(RenderMixin, rend.Page):
1193hunk ./src/allmydata/web/root.py 50
1194         if t == "":
1195             mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip())
1196             if mutable:
1197-                return unlinked.PUTUnlinkedSSK(req, self.client)
1198+                version = parse_mutable_type_arg(get_arg(req, "mutable-type",
1199+                                                 None))
1200+                return unlinked.PUTUnlinkedSSK(req, self.client, version)
1201             else:
1202                 return unlinked.PUTUnlinkedCHK(req, self.client)
1203         if t == "mkdir":
1204hunk ./src/allmydata/web/root.py 70
1205         if t in ("", "upload"):
1206             mutable = bool(get_arg(req, "mutable", "").strip())
1207             if mutable:
1208-                return unlinked.POSTUnlinkedSSK(req, self.client)
1209+                version = parse_mutable_type_arg(get_arg(req, "mutable-type",
1210+                                                         None))
1211+                return unlinked.POSTUnlinkedSSK(req, self.client, version)
1212             else:
1213                 return unlinked.POSTUnlinkedCHK(req, self.client)
1214         if t == "mkdir":
1215hunk ./src/allmydata/web/root.py 324
1216 
1217     def render_upload_form(self, ctx, data):
1218         # this is a form where users can upload unlinked files
1219+        #
1220+        # for mutable files, users can choose the format by selecting
1221+        # MDMF or SDMF from a radio button. They can also configure a
1222+        # default format in tahoe.cfg, which they rightly expect us to
1223+        # obey. we convey to them that we are obeying their choice by
1224+        # ensuring that the one that they've chosen is selected in the
1225+        # interface.
1226+        if self.client.mutable_file_default == MDMF_VERSION:
1227+            mdmf_input = T.input(type='radio', name='mutable-type',
1228+                                 value='mdmf', id='mutable-type-mdmf',
1229+                                 checked='checked')
1230+        else:
1231+            mdmf_input = T.input(type='radio', name='mutable-type',
1232+                                 value='mdmf', id='mutable-type-mdmf')
1233+
1234+        if self.client.mutable_file_default == SDMF_VERSION:
1235+            sdmf_input = T.input(type='radio', name='mutable-type',
1236+                                 value='sdmf', id='mutable-type-sdmf',
1237+                                 checked='checked')
1238+        else:
1239+            sdmf_input = T.input(type='radio', name='mutable-type',
1240+                                 value='sdmf', id='mutable-type-sdmf')
1241+
1242+
1243         form = T.form(action="uri", method="post",
1244                       enctype="multipart/form-data")[
1245             T.fieldset[
1246hunk ./src/allmydata/web/root.py 356
1247                   T.input(type="file", name="file", class_="freeform-input-file")],
1248             T.input(type="hidden", name="t", value="upload"),
1249             T.div[T.input(type="checkbox", name="mutable"), T.label(for_="mutable")["Create mutable file"],
1250+                  sdmf_input, T.label(for_="mutable-type-sdmf")["SDMF"],
1251+                  mdmf_input,
1252+                  T.label(for_='mutable-type-mdmf')['MDMF (experimental)'],
1253                   " ", T.input(type="submit", value="Upload!")],
1254             ]]
1255         return T.div[form]
1256hunk ./src/allmydata/web/unlinked.py 7
1257 from twisted.internet import defer
1258 from nevow import rend, url, tags as T
1259 from allmydata.immutable.upload import FileHandle
1260+from allmydata.mutable.publish import MutableFileHandle
1261 from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
1262      convert_children_json, WebError
1263 from allmydata.web import status
1264hunk ./src/allmydata/web/unlinked.py 20
1265     # that fires with the URI of the new file
1266     return d
1267 
1268-def PUTUnlinkedSSK(req, client):
1269+def PUTUnlinkedSSK(req, client, version):
1270     # SDMF: files are small, and we can only upload data
1271     req.content.seek(0)
1272hunk ./src/allmydata/web/unlinked.py 23
1273-    data = req.content.read()
1274-    d = client.create_mutable_file(data)
1275+    data = MutableFileHandle(req.content)
1276+    d = client.create_mutable_file(data, version=version)
1277     d.addCallback(lambda n: n.get_uri())
1278     return d
1279 
1280hunk ./src/allmydata/web/unlinked.py 83
1281                       ["/uri/" + res.uri])
1282         return d
1283 
1284-def POSTUnlinkedSSK(req, client):
1285+def POSTUnlinkedSSK(req, client, version):
1286     # "POST /uri", to create an unlinked file.
1287     # SDMF: files are small, and we can only upload data
1288hunk ./src/allmydata/web/unlinked.py 86
1289-    contents = req.fields["file"]
1290-    contents.file.seek(0)
1291-    data = contents.file.read()
1292-    d = client.create_mutable_file(data)
1293+    contents = req.fields["file"].file
1294+    data = MutableFileHandle(contents)
1295+    d = client.create_mutable_file(data, version=version)
1296     d.addCallback(lambda n: n.get_uri())
1297     return d
1298 
1299}
1300[client.py: learn how to create different kinds of mutable files
1301Kevan Carstensen <kevan@isnotajoke.com>**20100814225711
1302 Ignore-this: 61ff665bc050cba5f58bf2ed779d692b
1303] {
1304hunk ./src/allmydata/client.py 25
1305 from allmydata.util.time_format import parse_duration, parse_date
1306 from allmydata.stats import StatsProvider
1307 from allmydata.history import History
1308-from allmydata.interfaces import IStatsProducer, RIStubClient
1309+from allmydata.interfaces import IStatsProducer, RIStubClient, \
1310+                                 SDMF_VERSION, MDMF_VERSION
1311 from allmydata.nodemaker import NodeMaker
1312 
1313 
1314hunk ./src/allmydata/client.py 357
1315                                    self.terminator,
1316                                    self.get_encoding_parameters(),
1317                                    self._key_generator)
1318+        default = self.get_config("client", "mutable.format", default="sdmf")
1319+        if default == "mdmf":
1320+            self.mutable_file_default = MDMF_VERSION
1321+        else:
1322+            self.mutable_file_default = SDMF_VERSION
1323 
1324     def get_history(self):
1325         return self.history
1326hunk ./src/allmydata/client.py 500
1327     def create_immutable_dirnode(self, children, convergence=None):
1328         return self.nodemaker.create_immutable_directory(children, convergence)
1329 
1330-    def create_mutable_file(self, contents=None, keysize=None):
1331-        return self.nodemaker.create_mutable_file(contents, keysize)
1332+    def create_mutable_file(self, contents=None, keysize=None, version=None):
1333+        if not version:
1334+            version = self.mutable_file_default
1335+        return self.nodemaker.create_mutable_file(contents, keysize,
1336+                                                  version=version)
1337 
1338     def upload(self, uploadable):
1339         uploader = self.getServiceNamed("uploader")
1340}
1341[mutable/checker.py and mutable/repair.py: Modify checker and repairer to work with MDMF
1342Kevan Carstensen <kevan@isnotajoke.com>**20100819003216
1343 Ignore-this: d3bd3260742be8964877f0a53543b01b
1344 
1345 The checker and repairer required minimal changes to work with the MDMF
1346 modifications made elsewhere. The checker duplicated a lot of the code
1347 that was already in the downloader, so I modified the downloader
1348 slightly to expose this functionality to the checker and removed the
1349 duplicated code. The repairer only required a minor change to deal with
1350 data representation.
1351] {
1352hunk ./src/allmydata/mutable/checker.py 2
1353 
1354-from twisted.internet import defer
1355-from twisted.python import failure
1356-from allmydata import hashtree
1357 from allmydata.uri import from_string
1358hunk ./src/allmydata/mutable/checker.py 3
1359-from allmydata.util import hashutil, base32, idlib, log
1360+from allmydata.util import base32, idlib, log
1361 from allmydata.check_results import CheckAndRepairResults, CheckResults
1362 
1363 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
1364hunk ./src/allmydata/mutable/checker.py 8
1365 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
1366-from allmydata.mutable.layout import unpack_share, SIGNED_PREFIX_LENGTH
1367+from allmydata.mutable.retrieve import Retrieve # for verifying
1368 
1369 class MutableChecker:
1370 
1371hunk ./src/allmydata/mutable/checker.py 25
1372 
1373     def check(self, verify=False, add_lease=False):
1374         servermap = ServerMap()
1375+        # Updating the servermap in MODE_CHECK will stand a good chance
1376+        # of finding all of the shares, and getting a good idea of
1377+        # recoverability, etc, without verifying.
1378         u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
1379                              servermap, MODE_CHECK, add_lease=add_lease)
1380         if self._history:
1381hunk ./src/allmydata/mutable/checker.py 51
1382         if num_recoverable:
1383             self.best_version = servermap.best_recoverable_version()
1384 
1385+        # The file is unhealthy and needs to be repaired if:
1386+        # - There are unrecoverable versions.
1387         if servermap.unrecoverable_versions():
1388             self.need_repair = True
1389hunk ./src/allmydata/mutable/checker.py 55
1390+        # - There isn't a recoverable version.
1391         if num_recoverable != 1:
1392             self.need_repair = True
1393hunk ./src/allmydata/mutable/checker.py 58
1394+        # - The best recoverable version is missing some shares.
1395         if self.best_version:
1396             available_shares = servermap.shares_available()
1397             (num_distinct_shares, k, N) = available_shares[self.best_version]
1398hunk ./src/allmydata/mutable/checker.py 69
1399 
1400     def _verify_all_shares(self, servermap):
1401         # read every byte of each share
1402+        #
1403+        # This logic is going to be very nearly the same as the
1404+        # downloader. I bet we could pass the downloader a flag that
1405+        # makes it do this, and piggyback onto that instead of
1406+        # duplicating a bunch of code.
1407+        #
1408+        # Like:
1409+        #  r = Retrieve(blah, blah, blah, verify=True)
1410+        #  d = r.download()
1411+        #  (wait, wait, wait, d.callback)
1412+        # 
1413+        #  Then, when it has finished, we can check the servermap (which
1414+        #  we provided to Retrieve) to figure out which shares are bad,
1415+        #  since the Retrieve process will have updated the servermap as
1416+        #  it went along.
1417+        #
1418+        #  By passing the verify=True flag to the constructor, we are
1419+        #  telling the downloader a few things.
1420+        #
1421+        #  1. It needs to download all N shares, not just K shares.
1422+        #  2. It doesn't need to decrypt or decode the shares, only
1423+        #     verify them.
1424         if not self.best_version:
1425             return
1426hunk ./src/allmydata/mutable/checker.py 93
1427-        versionmap = servermap.make_versionmap()
1428-        shares = versionmap[self.best_version]
1429-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1430-         offsets_tuple) = self.best_version
1431-        offsets = dict(offsets_tuple)
1432-        readv = [ (0, offsets["EOF"]) ]
1433-        dl = []
1434-        for (shnum, peerid, timestamp) in shares:
1435-            ss = servermap.connections[peerid]
1436-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
1437-            d.addCallback(self._got_answer, peerid, servermap)
1438-            dl.append(d)
1439-        return defer.DeferredList(dl, fireOnOneErrback=True, consumeErrors=True)
1440 
1441hunk ./src/allmydata/mutable/checker.py 94
1442-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
1443-        # isolate the callRemote to a separate method, so tests can subclass
1444-        # Publish and override it
1445-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
1446+        r = Retrieve(self._node, servermap, self.best_version, verify=True)
1447+        d = r.download()
1448+        d.addCallback(self._process_bad_shares)
1449         return d
1450 
1451hunk ./src/allmydata/mutable/checker.py 99
1452-    def _got_answer(self, datavs, peerid, servermap):
1453-        for shnum,datav in datavs.items():
1454-            data = datav[0]
1455-            try:
1456-                self._got_results_one_share(shnum, peerid, data)
1457-            except CorruptShareError:
1458-                f = failure.Failure()
1459-                self.need_repair = True
1460-                self.bad_shares.append( (peerid, shnum, f) )
1461-                prefix = data[:SIGNED_PREFIX_LENGTH]
1462-                servermap.mark_bad_share(peerid, shnum, prefix)
1463-                ss = servermap.connections[peerid]
1464-                self.notify_server_corruption(ss, shnum, str(f.value))
1465-
1466-    def check_prefix(self, peerid, shnum, data):
1467-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
1468-         offsets_tuple) = self.best_version
1469-        got_prefix = data[:SIGNED_PREFIX_LENGTH]
1470-        if got_prefix != prefix:
1471-            raise CorruptShareError(peerid, shnum,
1472-                                    "prefix mismatch: share changed while we were reading it")
1473-
1474-    def _got_results_one_share(self, shnum, peerid, data):
1475-        self.check_prefix(peerid, shnum, data)
1476-
1477-        # the [seqnum:signature] pieces are validated by _compare_prefix,
1478-        # which checks their signature against the pubkey known to be
1479-        # associated with this file.
1480 
1481hunk ./src/allmydata/mutable/checker.py 100
1482-        (seqnum, root_hash, IV, k, N, segsize, datalen, pubkey, signature,
1483-         share_hash_chain, block_hash_tree, share_data,
1484-         enc_privkey) = unpack_share(data)
1485-
1486-        # validate [share_hash_chain,block_hash_tree,share_data]
1487-
1488-        leaves = [hashutil.block_hash(share_data)]
1489-        t = hashtree.HashTree(leaves)
1490-        if list(t) != block_hash_tree:
1491-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
1492-        share_hash_leaf = t[0]
1493-        t2 = hashtree.IncompleteHashTree(N)
1494-        # root_hash was checked by the signature
1495-        t2.set_hashes({0: root_hash})
1496-        try:
1497-            t2.set_hashes(hashes=share_hash_chain,
1498-                          leaves={shnum: share_hash_leaf})
1499-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
1500-                IndexError), e:
1501-            msg = "corrupt hashes: %s" % (e,)
1502-            raise CorruptShareError(peerid, shnum, msg)
1503-
1504-        # validate enc_privkey: only possible if we have a write-cap
1505-        if not self._node.is_readonly():
1506-            alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
1507-            alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
1508-            if alleged_writekey != self._node.get_writekey():
1509-                raise CorruptShareError(peerid, shnum, "invalid privkey")
1510+    def _process_bad_shares(self, bad_shares):
1511+        if bad_shares:
1512+            self.need_repair = True
1513+        self.bad_shares = bad_shares
1514 
1515hunk ./src/allmydata/mutable/checker.py 105
1516-    def notify_server_corruption(self, ss, shnum, reason):
1517-        ss.callRemoteOnly("advise_corrupt_share",
1518-                          "mutable", self._storage_index, shnum, reason)
1519 
1520     def _count_shares(self, smap, version):
1521         available_shares = smap.shares_available()
1522hunk ./src/allmydata/mutable/repairer.py 5
1523 from zope.interface import implements
1524 from twisted.internet import defer
1525 from allmydata.interfaces import IRepairResults, ICheckResults
1526+from allmydata.mutable.publish import MutableData
1527 
1528 class RepairResults:
1529     implements(IRepairResults)
1530hunk ./src/allmydata/mutable/repairer.py 108
1531             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
1532 
1533         d = self.node.download_version(smap, best_version, fetch_privkey=True)
1534+        d.addCallback(lambda data:
1535+            MutableData(data))
1536         d.addCallback(self.node.upload, smap)
1537         d.addCallback(self.get_results, smap)
1538         return d
1539}
1540[mutable/filenode.py: add versions and partial-file updates to the mutable file node
1541Kevan Carstensen <kevan@isnotajoke.com>**20100819003231
1542 Ignore-this: b7b5434201fdb9b48f902d7ab25ef45c
1543 
1544 One of the goals of MDMF as a GSoC project is to lay the groundwork for
1545 LDMF, a format that will allow Tahoe-LAFS to deal with and encourage
1546 multiple versions of a single cap on the grid. In line with this, there
1547 is a now a distinction between an overriding mutable file (which can be
1548 thought to correspond to the cap/unique identifier for that mutable
1549 file) and versions of the mutable file (which we can download, update,
1550 and so on). All download, upload, and modification operations end up
1551 happening on a particular version of a mutable file, but there are
1552 shortcut methods on the object representing the overriding mutable file
1553 that perform these operations on the best version of the mutable file
1554 (which is what code should be doing until we have LDMF and better
1555 support for other paradigms).
1556 
1557 Another goal of MDMF was to take advantage of segmentation to give
1558 callers more efficient partial file updates or appends. This patch
1559 implements methods that do that, too.
1560 
1561] {
1562hunk ./src/allmydata/mutable/filenode.py 7
1563 from zope.interface import implements
1564 from twisted.internet import defer, reactor
1565 from foolscap.api import eventually
1566-from allmydata.interfaces import IMutableFileNode, \
1567-     ICheckable, ICheckResults, NotEnoughSharesError
1568-from allmydata.util import hashutil, log
1569+from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
1570+     NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
1571+     IMutableFileVersion, IWritable
1572+from allmydata.util import hashutil, log, consumer, deferredutil, mathutil
1573 from allmydata.util.assertutil import precondition
1574 from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
1575 from allmydata.monitor import Monitor
1576hunk ./src/allmydata/mutable/filenode.py 16
1577 from pycryptopp.cipher.aes import AES
1578 
1579-from allmydata.mutable.publish import Publish
1580+from allmydata.mutable.publish import Publish, MutableData,\
1581+                                      DEFAULT_MAX_SEGMENT_SIZE, \
1582+                                      TransformingUploadable
1583 from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
1584      ResponseCache, UncoordinatedWriteError
1585 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
1586hunk ./src/allmydata/mutable/filenode.py 70
1587         self._sharemap = {} # known shares, shnum-to-[nodeids]
1588         self._cache = ResponseCache()
1589         self._most_recent_size = None
1590+        # filled in after __init__ if we're being created for the first time;
1591+        # filled in by the servermap updater before publishing, otherwise.
1592+        # set to this default value in case neither of those things happen,
1593+        # or in case the servermap can't find any shares to tell us what
1594+        # to publish as.
1595+        # TODO: Set this back to None, and find out why the tests fail
1596+        #       with it set to None.
1597+        self._protocol_version = None
1598 
1599         # all users of this MutableFileNode go through the serializer. This
1600         # takes advantage of the fact that Deferreds discard the callbacks
1601hunk ./src/allmydata/mutable/filenode.py 134
1602         return self._upload(initial_contents, None)
1603 
1604     def _get_initial_contents(self, contents):
1605-        if isinstance(contents, str):
1606-            return contents
1607         if contents is None:
1608hunk ./src/allmydata/mutable/filenode.py 135
1609-            return ""
1610+            return MutableData("")
1611+
1612+        if IMutableUploadable.providedBy(contents):
1613+            return contents
1614+
1615         assert callable(contents), "%s should be callable, not %s" % \
1616                (contents, type(contents))
1617         return contents(self)
1618hunk ./src/allmydata/mutable/filenode.py 209
1619 
1620     def get_size(self):
1621         return self._most_recent_size
1622+
1623     def get_current_size(self):
1624         d = self.get_size_of_best_version()
1625         d.addCallback(self._stash_size)
1626hunk ./src/allmydata/mutable/filenode.py 214
1627         return d
1628+
1629     def _stash_size(self, size):
1630         self._most_recent_size = size
1631         return size
1632hunk ./src/allmydata/mutable/filenode.py 273
1633             return cmp(self.__class__, them.__class__)
1634         return cmp(self._uri, them._uri)
1635 
1636-    def _do_serialized(self, cb, *args, **kwargs):
1637-        # note: to avoid deadlock, this callable is *not* allowed to invoke
1638-        # other serialized methods within this (or any other)
1639-        # MutableFileNode. The callable should be a bound method of this same
1640-        # MFN instance.
1641-        d = defer.Deferred()
1642-        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
1643-        # we need to put off d.callback until this Deferred is finished being
1644-        # processed. Otherwise the caller's subsequent activities (like,
1645-        # doing other things with this node) can cause reentrancy problems in
1646-        # the Deferred code itself
1647-        self._serializer.addBoth(lambda res: eventually(d.callback, res))
1648-        # add a log.err just in case something really weird happens, because
1649-        # self._serializer stays around forever, therefore we won't see the
1650-        # usual Unhandled Error in Deferred that would give us a hint.
1651-        self._serializer.addErrback(log.err)
1652-        return d
1653 
1654     #################################
1655     # ICheckable
1656hunk ./src/allmydata/mutable/filenode.py 298
1657 
1658 
1659     #################################
1660-    # IMutableFileNode
1661+    # IFileNode
1662+
1663+    def get_best_readable_version(self):
1664+        """
1665+        I return a Deferred that fires with a MutableFileVersion
1666+        representing the best readable version of the file that I
1667+        represent
1668+        """
1669+        return self.get_readable_version()
1670+
1671+
1672+    def get_readable_version(self, servermap=None, version=None):
1673+        """
1674+        I return a Deferred that fires with an MutableFileVersion for my
1675+        version argument, if there is a recoverable file of that version
1676+        on the grid. If there is no recoverable version, I fire with an
1677+        UnrecoverableFileError.
1678+
1679+        If a servermap is provided, I look in there for the requested
1680+        version. If no servermap is provided, I create and update a new
1681+        one.
1682+
1683+        If no version is provided, then I return a MutableFileVersion
1684+        representing the best recoverable version of the file.
1685+        """
1686+        d = self._get_version_from_servermap(MODE_READ, servermap, version)
1687+        def _build_version((servermap, their_version)):
1688+            assert their_version in servermap.recoverable_versions()
1689+            assert their_version in servermap.make_versionmap()
1690+
1691+            mfv = MutableFileVersion(self,
1692+                                     servermap,
1693+                                     their_version,
1694+                                     self._storage_index,
1695+                                     self._storage_broker,
1696+                                     self._readkey,
1697+                                     history=self._history)
1698+            assert mfv.is_readonly()
1699+            # our caller can use this to download the contents of the
1700+            # mutable file.
1701+            return mfv
1702+        return d.addCallback(_build_version)
1703+
1704+
1705+    def _get_version_from_servermap(self,
1706+                                    mode,
1707+                                    servermap=None,
1708+                                    version=None):
1709+        """
1710+        I return a Deferred that fires with (servermap, version).
1711+
1712+        This function performs validation and a servermap update. If it
1713+        returns (servermap, version), the caller can assume that:
1714+            - servermap was last updated in mode.
1715+            - version is recoverable, and corresponds to the servermap.
1716+
1717+        If version and servermap are provided to me, I will validate
1718+        that version exists in the servermap, and that the servermap was
1719+        updated correctly.
1720+
1721+        If version is not provided, but servermap is, I will validate
1722+        the servermap and return the best recoverable version that I can
1723+        find in the servermap.
1724+
1725+        If the version is provided but the servermap isn't, I will
1726+        obtain a servermap that has been updated in the correct mode and
1727+        validate that version is found and recoverable.
1728+
1729+        If neither servermap nor version are provided, I will obtain a
1730+        servermap updated in the correct mode, and return the best
1731+        recoverable version that I can find in there.
1732+        """
1733+        # XXX: wording ^^^^
1734+        if servermap and servermap.last_update_mode == mode:
1735+            d = defer.succeed(servermap)
1736+        else:
1737+            d = self._get_servermap(mode)
1738+
1739+        def _get_version(servermap, v):
1740+            if v and v not in servermap.recoverable_versions():
1741+                v = None
1742+            elif not v:
1743+                v = servermap.best_recoverable_version()
1744+            if not v:
1745+                raise UnrecoverableFileError("no recoverable versions")
1746+
1747+            return (servermap, v)
1748+        return d.addCallback(_get_version, version)
1749+
1750 
1751     def download_best_version(self):
1752hunk ./src/allmydata/mutable/filenode.py 389
1753+        """
1754+        I return a Deferred that fires with the contents of the best
1755+        version of this mutable file.
1756+        """
1757         return self._do_serialized(self._download_best_version)
1758hunk ./src/allmydata/mutable/filenode.py 394
1759+
1760+
1761     def _download_best_version(self):
1762hunk ./src/allmydata/mutable/filenode.py 397
1763-        servermap = ServerMap()
1764-        d = self._try_once_to_download_best_version(servermap, MODE_READ)
1765-        def _maybe_retry(f):
1766-            f.trap(NotEnoughSharesError)
1767-            # the download is worth retrying once. Make sure to use the
1768-            # old servermap, since it is what remembers the bad shares,
1769-            # but use MODE_WRITE to make it look for even more shares.
1770-            # TODO: consider allowing this to retry multiple times.. this
1771-            # approach will let us tolerate about 8 bad shares, I think.
1772-            return self._try_once_to_download_best_version(servermap,
1773-                                                           MODE_WRITE)
1774+        """
1775+        I am the serialized sibling of download_best_version.
1776+        """
1777+        d = self.get_best_readable_version()
1778+        d.addCallback(self._record_size)
1779+        d.addCallback(lambda version: version.download_to_data())
1780+
1781+        # It is possible that the download will fail because there
1782+        # aren't enough shares to be had. If so, we will try again after
1783+        # updating the servermap in MODE_WRITE, which may find more
1784+        # shares than updating in MODE_READ, as we just did. We can do
1785+        # this by getting the best mutable version and downloading from
1786+        # that -- the best mutable version will be a MutableFileVersion
1787+        # with a servermap that was last updated in MODE_WRITE, as we
1788+        # want. If this fails, then we give up.
1789+        def _maybe_retry(failure):
1790+            failure.trap(NotEnoughSharesError)
1791+
1792+            d = self.get_best_mutable_version()
1793+            d.addCallback(self._record_size)
1794+            d.addCallback(lambda version: version.download_to_data())
1795+            return d
1796+
1797         d.addErrback(_maybe_retry)
1798         return d
1799hunk ./src/allmydata/mutable/filenode.py 422
1800-    def _try_once_to_download_best_version(self, servermap, mode):
1801-        d = self._update_servermap(servermap, mode)
1802-        d.addCallback(self._once_updated_download_best_version, servermap)
1803-        return d
1804-    def _once_updated_download_best_version(self, ignored, servermap):
1805-        goal = servermap.best_recoverable_version()
1806-        if not goal:
1807-            raise UnrecoverableFileError("no recoverable versions")
1808-        return self._try_once_to_download_version(servermap, goal)
1809+
1810+
1811+    def _record_size(self, mfv):
1812+        """
1813+        I record the size of a mutable file version.
1814+        """
1815+        self._most_recent_size = mfv.get_size()
1816+        return mfv
1817+
1818 
1819     def get_size_of_best_version(self):
1820hunk ./src/allmydata/mutable/filenode.py 433
1821-        d = self.get_servermap(MODE_READ)
1822-        def _got_servermap(smap):
1823-            ver = smap.best_recoverable_version()
1824-            if not ver:
1825-                raise UnrecoverableFileError("no recoverable version")
1826-            return smap.size_of_version(ver)
1827-        d.addCallback(_got_servermap)
1828-        return d
1829+        """
1830+        I return the size of the best version of this mutable file.
1831 
1832hunk ./src/allmydata/mutable/filenode.py 436
1833+        This is equivalent to calling get_size() on the result of
1834+        get_best_readable_version().
1835+        """
1836+        d = self.get_best_readable_version()
1837+        return d.addCallback(lambda mfv: mfv.get_size())
1838+
1839+
1840+    #################################
1841+    # IMutableFileNode
1842+
1843+    def get_best_mutable_version(self, servermap=None):
1844+        """
1845+        I return a Deferred that fires with a MutableFileVersion
1846+        representing the best readable version of the file that I
1847+        represent. I am like get_best_readable_version, except that I
1848+        will try to make a writable version if I can.
1849+        """
1850+        return self.get_mutable_version(servermap=servermap)
1851+
1852+
1853+    def get_mutable_version(self, servermap=None, version=None):
1854+        """
1855+        I return a version of this mutable file. I return a Deferred
1856+        that fires with a MutableFileVersion
1857+
1858+        If version is provided, the Deferred will fire with a
1859+        MutableFileVersion initailized with that version. Otherwise, it
1860+        will fire with the best version that I can recover.
1861+
1862+        If servermap is provided, I will use that to find versions
1863+        instead of performing my own servermap update.
1864+        """
1865+        if self.is_readonly():
1866+            return self.get_readable_version(servermap=servermap,
1867+                                             version=version)
1868+
1869+        # get_mutable_version => write intent, so we require that the
1870+        # servermap is updated in MODE_WRITE
1871+        d = self._get_version_from_servermap(MODE_WRITE, servermap, version)
1872+        def _build_version((servermap, smap_version)):
1873+            # these should have been set by the servermap update.
1874+            assert self._secret_holder
1875+            assert self._writekey
1876+
1877+            mfv = MutableFileVersion(self,
1878+                                     servermap,
1879+                                     smap_version,
1880+                                     self._storage_index,
1881+                                     self._storage_broker,
1882+                                     self._readkey,
1883+                                     self._writekey,
1884+                                     self._secret_holder,
1885+                                     history=self._history)
1886+            assert not mfv.is_readonly()
1887+            return mfv
1888+
1889+        return d.addCallback(_build_version)
1890+
1891+
1892+    # XXX: I'm uncomfortable with the difference between upload and
1893+    #      overwrite, which, FWICT, is basically that you don't have to
1894+    #      do a servermap update before you overwrite. We split them up
1895+    #      that way anyway, so I guess there's no real difficulty in
1896+    #      offering both ways to callers, but it also makes the
1897+    #      public-facing API cluttery, and makes it hard to discern the
1898+    #      right way of doing things.
1899+
1900+    # In general, we leave it to callers to ensure that they aren't
1901+    # going to cause UncoordinatedWriteErrors when working with
1902+    # MutableFileVersions. We know that the next three operations
1903+    # (upload, overwrite, and modify) will all operate on the same
1904+    # version, so we say that only one of them can be going on at once,
1905+    # and serialize them to ensure that that actually happens, since as
1906+    # the caller in this situation it is our job to do that.
1907     def overwrite(self, new_contents):
1908hunk ./src/allmydata/mutable/filenode.py 511
1909+        """
1910+        I overwrite the contents of the best recoverable version of this
1911+        mutable file with new_contents. This is equivalent to calling
1912+        overwrite on the result of get_best_mutable_version with
1913+        new_contents as an argument. I return a Deferred that eventually
1914+        fires with the results of my replacement process.
1915+        """
1916         return self._do_serialized(self._overwrite, new_contents)
1917hunk ./src/allmydata/mutable/filenode.py 519
1918+
1919+
1920     def _overwrite(self, new_contents):
1921hunk ./src/allmydata/mutable/filenode.py 522
1922+        """
1923+        I am the serialized sibling of overwrite.
1924+        """
1925+        d = self.get_best_mutable_version()
1926+        d.addCallback(lambda mfv: mfv.overwrite(new_contents))
1927+        d.addCallback(self._did_upload, new_contents.get_size())
1928+        return d
1929+
1930+
1931+
1932+    def upload(self, new_contents, servermap):
1933+        """
1934+        I overwrite the contents of the best recoverable version of this
1935+        mutable file with new_contents, using servermap instead of
1936+        creating/updating our own servermap. I return a Deferred that
1937+        fires with the results of my upload.
1938+        """
1939+        return self._do_serialized(self._upload, new_contents, servermap)
1940+
1941+
1942+    def modify(self, modifier, backoffer=None):
1943+        """
1944+        I modify the contents of the best recoverable version of this
1945+        mutable file with the modifier. This is equivalent to calling
1946+        modify on the result of get_best_mutable_version. I return a
1947+        Deferred that eventually fires with an UploadResults instance
1948+        describing this process.
1949+        """
1950+        return self._do_serialized(self._modify, modifier, backoffer)
1951+
1952+
1953+    def _modify(self, modifier, backoffer):
1954+        """
1955+        I am the serialized sibling of modify.
1956+        """
1957+        d = self.get_best_mutable_version()
1958+        d.addCallback(lambda mfv: mfv.modify(modifier, backoffer))
1959+        return d
1960+
1961+
1962+    def download_version(self, servermap, version, fetch_privkey=False):
1963+        """
1964+        Download the specified version of this mutable file. I return a
1965+        Deferred that fires with the contents of the specified version
1966+        as a bytestring, or errbacks if the file is not recoverable.
1967+        """
1968+        d = self.get_readable_version(servermap, version)
1969+        return d.addCallback(lambda mfv: mfv.download_to_data(fetch_privkey))
1970+
1971+
1972+    def get_servermap(self, mode):
1973+        """
1974+        I return a servermap that has been updated in mode.
1975+
1976+        mode should be one of MODE_READ, MODE_WRITE, MODE_CHECK or
1977+        MODE_ANYTHING. See servermap.py for more on what these mean.
1978+        """
1979+        return self._do_serialized(self._get_servermap, mode)
1980+
1981+
1982+    def _get_servermap(self, mode):
1983+        """
1984+        I am a serialized twin to get_servermap.
1985+        """
1986         servermap = ServerMap()
1987hunk ./src/allmydata/mutable/filenode.py 587
1988-        d = self._update_servermap(servermap, mode=MODE_WRITE)
1989-        d.addCallback(lambda ignored: self._upload(new_contents, servermap))
1990+        d = self._update_servermap(servermap, mode)
1991+        # The servermap will tell us about the most recent size of the
1992+        # file, so we may as well set that so that callers might get
1993+        # more data about us.
1994+        if not self._most_recent_size:
1995+            d.addCallback(self._get_size_from_servermap)
1996+        return d
1997+
1998+
1999+    def _get_size_from_servermap(self, servermap):
2000+        """
2001+        I extract the size of the best version of this file and record
2002+        it in self._most_recent_size. I return the servermap that I was
2003+        given.
2004+        """
2005+        if servermap.recoverable_versions():
2006+            v = servermap.best_recoverable_version()
2007+            size = v[4] # verinfo[4] == size
2008+            self._most_recent_size = size
2009+        return servermap
2010+
2011+
2012+    def _update_servermap(self, servermap, mode):
2013+        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
2014+                             mode)
2015+        if self._history:
2016+            self._history.notify_mapupdate(u.get_status())
2017+        return u.update()
2018+
2019+
2020+    def set_version(self, version):
2021+        # I can be set in two ways:
2022+        #  1. When the node is created.
2023+        #  2. (for an existing share) when the Servermap is updated
2024+        #     before I am read.
2025+        assert version in (MDMF_VERSION, SDMF_VERSION)
2026+        self._protocol_version = version
2027+
2028+
2029+    def get_version(self):
2030+        return self._protocol_version
2031+
2032+
2033+    def _do_serialized(self, cb, *args, **kwargs):
2034+        # note: to avoid deadlock, this callable is *not* allowed to invoke
2035+        # other serialized methods within this (or any other)
2036+        # MutableFileNode. The callable should be a bound method of this same
2037+        # MFN instance.
2038+        d = defer.Deferred()
2039+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
2040+        # we need to put off d.callback until this Deferred is finished being
2041+        # processed. Otherwise the caller's subsequent activities (like,
2042+        # doing other things with this node) can cause reentrancy problems in
2043+        # the Deferred code itself
2044+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
2045+        # add a log.err just in case something really weird happens, because
2046+        # self._serializer stays around forever, therefore we won't see the
2047+        # usual Unhandled Error in Deferred that would give us a hint.
2048+        self._serializer.addErrback(log.err)
2049         return d
2050 
2051 
2052hunk ./src/allmydata/mutable/filenode.py 649
2053+    def _upload(self, new_contents, servermap):
2054+        """
2055+        A MutableFileNode still has to have some way of getting
2056+        published initially, which is what I am here for. After that,
2057+        all publishing, updating, modifying and so on happens through
2058+        MutableFileVersions.
2059+        """
2060+        assert self._pubkey, "update_servermap must be called before publish"
2061+
2062+        p = Publish(self, self._storage_broker, servermap)
2063+        if self._history:
2064+            self._history.notify_publish(p.get_status(),
2065+                                         new_contents.get_size())
2066+        d = p.publish(new_contents)
2067+        d.addCallback(self._did_upload, new_contents.get_size())
2068+        return d
2069+
2070+
2071+    def _did_upload(self, res, size):
2072+        self._most_recent_size = size
2073+        return res
2074+
2075+
2076+class MutableFileVersion:
2077+    """
2078+    I represent a specific version (most likely the best version) of a
2079+    mutable file.
2080+
2081+    Since I implement IReadable, instances which hold a
2082+    reference to an instance of me are guaranteed the ability (absent
2083+    connection difficulties or unrecoverable versions) to read the file
2084+    that I represent. Depending on whether I was initialized with a
2085+    write capability or not, I may also provide callers the ability to
2086+    overwrite or modify the contents of the mutable file that I
2087+    reference.
2088+    """
2089+    implements(IMutableFileVersion, IWritable)
2090+
2091+    def __init__(self,
2092+                 node,
2093+                 servermap,
2094+                 version,
2095+                 storage_index,
2096+                 storage_broker,
2097+                 readcap,
2098+                 writekey=None,
2099+                 write_secrets=None,
2100+                 history=None):
2101+
2102+        self._node = node
2103+        self._servermap = servermap
2104+        self._version = version
2105+        self._storage_index = storage_index
2106+        self._write_secrets = write_secrets
2107+        self._history = history
2108+        self._storage_broker = storage_broker
2109+
2110+        #assert isinstance(readcap, IURI)
2111+        self._readcap = readcap
2112+
2113+        self._writekey = writekey
2114+        self._serializer = defer.succeed(None)
2115+
2116+
2117+    def get_sequence_number(self):
2118+        """
2119+        Get the sequence number of the mutable version that I represent.
2120+        """
2121+        return self._version[0] # verinfo[0] == the sequence number
2122+
2123+
2124+    # TODO: Terminology?
2125+    def get_writekey(self):
2126+        """
2127+        I return a writekey or None if I don't have a writekey.
2128+        """
2129+        return self._writekey
2130+
2131+
2132+    def overwrite(self, new_contents):
2133+        """
2134+        I overwrite the contents of this mutable file version with the
2135+        data in new_contents.
2136+        """
2137+        assert not self.is_readonly()
2138+
2139+        return self._do_serialized(self._overwrite, new_contents)
2140+
2141+
2142+    def _overwrite(self, new_contents):
2143+        assert IMutableUploadable.providedBy(new_contents)
2144+        assert self._servermap.last_update_mode == MODE_WRITE
2145+
2146+        return self._upload(new_contents)
2147+
2148+
2149     def modify(self, modifier, backoffer=None):
2150         """I use a modifier callback to apply a change to the mutable file.
2151         I implement the following pseudocode::
2152hunk ./src/allmydata/mutable/filenode.py 785
2153         backoffer should not invoke any methods on this MutableFileNode
2154         instance, and it needs to be highly conscious of deadlock issues.
2155         """
2156+        assert not self.is_readonly()
2157+
2158         return self._do_serialized(self._modify, modifier, backoffer)
2159hunk ./src/allmydata/mutable/filenode.py 788
2160+
2161+
2162     def _modify(self, modifier, backoffer):
2163hunk ./src/allmydata/mutable/filenode.py 791
2164-        servermap = ServerMap()
2165         if backoffer is None:
2166             backoffer = BackoffAgent().delay
2167hunk ./src/allmydata/mutable/filenode.py 793
2168-        return self._modify_and_retry(servermap, modifier, backoffer, True)
2169-    def _modify_and_retry(self, servermap, modifier, backoffer, first_time):
2170-        d = self._modify_once(servermap, modifier, first_time)
2171+        return self._modify_and_retry(modifier, backoffer, True)
2172+
2173+
2174+    def _modify_and_retry(self, modifier, backoffer, first_time):
2175+        """
2176+        I try to apply modifier to the contents of this version of the
2177+        mutable file. If I succeed, I return an UploadResults instance
2178+        describing my success. If I fail, I try again after waiting for
2179+        a little bit.
2180+        """
2181+        log.msg("doing modify")
2182+        d = self._modify_once(modifier, first_time)
2183         def _retry(f):
2184             f.trap(UncoordinatedWriteError)
2185             d2 = defer.maybeDeferred(backoffer, self, f)
2186hunk ./src/allmydata/mutable/filenode.py 809
2187             d2.addCallback(lambda ignored:
2188-                           self._modify_and_retry(servermap, modifier,
2189+                           self._modify_and_retry(modifier,
2190                                                   backoffer, False))
2191             return d2
2192         d.addErrback(_retry)
2193hunk ./src/allmydata/mutable/filenode.py 814
2194         return d
2195-    def _modify_once(self, servermap, modifier, first_time):
2196-        d = self._update_servermap(servermap, MODE_WRITE)
2197-        d.addCallback(self._once_updated_download_best_version, servermap)
2198+
2199+
2200+    def _modify_once(self, modifier, first_time):
2201+        """
2202+        I attempt to apply a modifier to the contents of the mutable
2203+        file.
2204+        """
2205+        # XXX: This is wrong -- we could get more servers if we updated
2206+        # in MODE_ANYTHING and possibly MODE_CHECK. Probably we want to
2207+        # assert that the last update wasn't MODE_READ
2208+        assert self._servermap.last_update_mode == MODE_WRITE
2209+
2210+        # download_to_data is serialized, so we have to call this to
2211+        # avoid deadlock.
2212+        d = self._try_to_download_data()
2213         def _apply(old_contents):
2214hunk ./src/allmydata/mutable/filenode.py 830
2215-            new_contents = modifier(old_contents, servermap, first_time)
2216+            new_contents = modifier(old_contents, self._servermap, first_time)
2217+            precondition((isinstance(new_contents, str) or
2218+                          new_contents is None),
2219+                         "Modifier function must return a string "
2220+                         "or None")
2221+
2222             if new_contents is None or new_contents == old_contents:
2223hunk ./src/allmydata/mutable/filenode.py 837
2224+                log.msg("no changes")
2225                 # no changes need to be made
2226                 if first_time:
2227                     return
2228hunk ./src/allmydata/mutable/filenode.py 845
2229                 # recovery when it observes UCWE, we need to do a second
2230                 # publish. See #551 for details. We'll basically loop until
2231                 # we managed an uncontested publish.
2232-                new_contents = old_contents
2233-            precondition(isinstance(new_contents, str),
2234-                         "Modifier function must return a string or None")
2235-            return self._upload(new_contents, servermap)
2236+                old_uploadable = MutableData(old_contents)
2237+                new_contents = old_uploadable
2238+            else:
2239+                new_contents = MutableData(new_contents)
2240+
2241+            return self._upload(new_contents)
2242         d.addCallback(_apply)
2243         return d
2244 
2245hunk ./src/allmydata/mutable/filenode.py 854
2246-    def get_servermap(self, mode):
2247-        return self._do_serialized(self._get_servermap, mode)
2248-    def _get_servermap(self, mode):
2249-        servermap = ServerMap()
2250-        return self._update_servermap(servermap, mode)
2251-    def _update_servermap(self, servermap, mode):
2252-        u = ServermapUpdater(self, self._storage_broker, Monitor(), servermap,
2253-                             mode)
2254-        if self._history:
2255-            self._history.notify_mapupdate(u.get_status())
2256-        return u.update()
2257 
2258hunk ./src/allmydata/mutable/filenode.py 855
2259-    def download_version(self, servermap, version, fetch_privkey=False):
2260-        return self._do_serialized(self._try_once_to_download_version,
2261-                                   servermap, version, fetch_privkey)
2262-    def _try_once_to_download_version(self, servermap, version,
2263-                                      fetch_privkey=False):
2264-        r = Retrieve(self, servermap, version, fetch_privkey)
2265+    def is_readonly(self):
2266+        """
2267+        I return True if this MutableFileVersion provides no write
2268+        access to the file that it encapsulates, and False if it
2269+        provides the ability to modify the file.
2270+        """
2271+        return self._writekey is None
2272+
2273+
2274+    def is_mutable(self):
2275+        """
2276+        I return True, since mutable files are always mutable by
2277+        somebody.
2278+        """
2279+        return True
2280+
2281+
2282+    def get_storage_index(self):
2283+        """
2284+        I return the storage index of the reference that I encapsulate.
2285+        """
2286+        return self._storage_index
2287+
2288+
2289+    def get_size(self):
2290+        """
2291+        I return the length, in bytes, of this readable object.
2292+        """
2293+        return self._servermap.size_of_version(self._version)
2294+
2295+
2296+    def download_to_data(self, fetch_privkey=False):
2297+        """
2298+        I return a Deferred that fires with the contents of this
2299+        readable object as a byte string.
2300+
2301+        """
2302+        c = consumer.MemoryConsumer()
2303+        d = self.read(c, fetch_privkey=fetch_privkey)
2304+        d.addCallback(lambda mc: "".join(mc.chunks))
2305+        return d
2306+
2307+
2308+    def _try_to_download_data(self):
2309+        """
2310+        I am an unserialized cousin of download_to_data; I am called
2311+        from the children of modify() to download the data associated
2312+        with this mutable version.
2313+        """
2314+        c = consumer.MemoryConsumer()
2315+        # modify will almost certainly write, so we need the privkey.
2316+        d = self._read(c, fetch_privkey=True)
2317+        d.addCallback(lambda mc: "".join(mc.chunks))
2318+        return d
2319+
2320+
2321+    def read(self, consumer, offset=0, size=None, fetch_privkey=False):
2322+        """
2323+        I read a portion (possibly all) of the mutable file that I
2324+        reference into consumer.
2325+        """
2326+        return self._do_serialized(self._read, consumer, offset, size,
2327+                                   fetch_privkey)
2328+
2329+
2330+    def _read(self, consumer, offset=0, size=None, fetch_privkey=False):
2331+        """
2332+        I am the serialized companion of read.
2333+        """
2334+        r = Retrieve(self._node, self._servermap, self._version, fetch_privkey)
2335         if self._history:
2336             self._history.notify_retrieve(r.get_status())
2337hunk ./src/allmydata/mutable/filenode.py 927
2338-        d = r.download()
2339-        d.addCallback(self._downloaded_version)
2340+        d = r.download(consumer, offset, size)
2341         return d
2342hunk ./src/allmydata/mutable/filenode.py 929
2343-    def _downloaded_version(self, data):
2344-        self._most_recent_size = len(data)
2345-        return data
2346 
2347hunk ./src/allmydata/mutable/filenode.py 930
2348-    def upload(self, new_contents, servermap):
2349-        return self._do_serialized(self._upload, new_contents, servermap)
2350-    def _upload(self, new_contents, servermap):
2351-        assert self._pubkey, "update_servermap must be called before publish"
2352-        p = Publish(self, self._storage_broker, servermap)
2353+
2354+    def _do_serialized(self, cb, *args, **kwargs):
2355+        # note: to avoid deadlock, this callable is *not* allowed to invoke
2356+        # other serialized methods within this (or any other)
2357+        # MutableFileNode. The callable should be a bound method of this same
2358+        # MFN instance.
2359+        d = defer.Deferred()
2360+        self._serializer.addCallback(lambda ignore: cb(*args, **kwargs))
2361+        # we need to put off d.callback until this Deferred is finished being
2362+        # processed. Otherwise the caller's subsequent activities (like,
2363+        # doing other things with this node) can cause reentrancy problems in
2364+        # the Deferred code itself
2365+        self._serializer.addBoth(lambda res: eventually(d.callback, res))
2366+        # add a log.err just in case something really weird happens, because
2367+        # self._serializer stays around forever, therefore we won't see the
2368+        # usual Unhandled Error in Deferred that would give us a hint.
2369+        self._serializer.addErrback(log.err)
2370+        return d
2371+
2372+
2373+    def _upload(self, new_contents):
2374+        #assert self._pubkey, "update_servermap must be called before publish"
2375+        p = Publish(self._node, self._storage_broker, self._servermap)
2376         if self._history:
2377hunk ./src/allmydata/mutable/filenode.py 954
2378-            self._history.notify_publish(p.get_status(), len(new_contents))
2379+            self._history.notify_publish(p.get_status(),
2380+                                         new_contents.get_size())
2381         d = p.publish(new_contents)
2382hunk ./src/allmydata/mutable/filenode.py 957
2383-        d.addCallback(self._did_upload, len(new_contents))
2384+        d.addCallback(self._did_upload, new_contents.get_size())
2385         return d
2386hunk ./src/allmydata/mutable/filenode.py 959
2387+
2388+
2389     def _did_upload(self, res, size):
2390         self._most_recent_size = size
2391         return res
2392hunk ./src/allmydata/mutable/filenode.py 964
2393+
2394+    def update(self, data, offset):
2395+        """
2396+        Do an update of this mutable file version by inserting data at
2397+        offset within the file. If offset is the EOF, this is an append
2398+        operation. I return a Deferred that fires with the results of
2399+        the update operation when it has completed.
2400+
2401+        In cases where update does not append any data, or where it does
2402+        not append so many blocks that the block count crosses a
2403+        power-of-two boundary, this operation will use roughly
2404+        O(data.get_size()) memory/bandwidth/CPU to perform the update.
2405+        Otherwise, it must download, re-encode, and upload the entire
2406+        file again, which will use O(filesize) resources.
2407+        """
2408+        return self._do_serialized(self._update, data, offset)
2409+
2410+
2411+    def _update(self, data, offset):
2412+        """
2413+        I update the mutable file version represented by this particular
2414+        IMutableVersion by inserting the data in data at the offset
2415+        offset. I return a Deferred that fires when this has been
2416+        completed.
2417+        """
2418+        # We have two cases here:
2419+        # 1. The new data will add few enough segments so that it does
2420+        #    not cross into the next power-of-two boundary.
2421+        # 2. It doesn't.
2422+        #
2423+        # In the former case, we can modify the file in place. In the
2424+        # latter case, we need to re-encode the file.
2425+        new_size = data.get_size() + offset
2426+        old_size = self.get_size()
2427+        segment_size = self._version[3]
2428+        num_old_segments = mathutil.div_ceil(old_size,
2429+                                             segment_size)
2430+        num_new_segments = mathutil.div_ceil(new_size,
2431+                                             segment_size)
2432+        log.msg("got %d old segments, %d new segments" % \
2433+                        (num_old_segments, num_new_segments))
2434+
2435+        # We also do a whole file re-encode if the file is an SDMF file.
2436+        if self._version[2]: # version[2] == SDMF salt, which MDMF lacks
2437+            log.msg("doing re-encode instead of in-place update")
2438+            return self._do_modify_update(data, offset)
2439+
2440+        log.msg("updating in place")
2441+        d = self._do_update_update(data, offset)
2442+        d.addCallback(self._decode_and_decrypt_segments, data, offset)
2443+        d.addCallback(self._build_uploadable_and_finish, data, offset)
2444+        return d
2445+
2446+
2447+    def _do_modify_update(self, data, offset):
2448+        """
2449+        I perform a file update by modifying the contents of the file
2450+        after downloading it, then reuploading it. I am less efficient
2451+        than _do_update_update, but am necessary for certain updates.
2452+        """
2453+        def m(old, servermap, first_time):
2454+            start = offset
2455+            rest = offset + data.get_size()
2456+            new = old[:start]
2457+            new += "".join(data.read(data.get_size()))
2458+            new += old[rest:]
2459+            return new
2460+        return self._modify(m, None)
2461+
2462+
2463+    def _do_update_update(self, data, offset):
2464+        """
2465+        I start the Servermap update that gets us the data we need to
2466+        continue the update process. I return a Deferred that fires when
2467+        the servermap update is done.
2468+        """
2469+        assert IMutableUploadable.providedBy(data)
2470+        assert self.is_mutable()
2471+        # offset == self.get_size() is valid and means that we are
2472+        # appending data to the file.
2473+        assert offset <= self.get_size()
2474+
2475+        # We'll need the segment that the data starts in, regardless of
2476+        # what we'll do later.
2477+        start_segment = mathutil.div_ceil(offset, DEFAULT_MAX_SEGMENT_SIZE)
2478+        start_segment -= 1
2479+
2480+        # We only need the end segment if the data we append does not go
2481+        # beyond the current end-of-file.
2482+        end_segment = start_segment
2483+        if offset + data.get_size() < self.get_size():
2484+            end_data = offset + data.get_size()
2485+            end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
2486+            end_segment -= 1
2487+        self._start_segment = start_segment
2488+        self._end_segment = end_segment
2489+
2490+        # Now ask for the servermap to be updated in MODE_WRITE with
2491+        # this update range.
2492+        u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
2493+                             self._servermap,
2494+                             mode=MODE_WRITE,
2495+                             update_range=(start_segment, end_segment))
2496+        return u.update()
2497+
2498+
2499+    def _decode_and_decrypt_segments(self, ignored, data, offset):
2500+        """
2501+        After the servermap update, I take the encrypted and encoded
2502+        data that the servermap fetched while doing its update and
2503+        transform it into decoded-and-decrypted plaintext that can be
2504+        used by the new uploadable. I return a Deferred that fires with
2505+        the segments.
2506+        """
2507+        r = Retrieve(self._node, self._servermap, self._version)
2508+        # decode: takes in our blocks and salts from the servermap,
2509+        # returns a Deferred that fires with the corresponding plaintext
2510+        # segments. Does not download -- simply takes advantage of
2511+        # existing infrastructure within the Retrieve class to avoid
2512+        # duplicating code.
2513+        sm = self._servermap
2514+        # XXX: If the methods in the servermap don't work as
2515+        # abstractions, you should rewrite them instead of going around
2516+        # them.
2517+        update_data = sm.update_data
2518+        start_segments = {} # shnum -> start segment
2519+        end_segments = {} # shnum -> end segment
2520+        blockhashes = {} # shnum -> blockhash tree
2521+        for (shnum, data) in update_data.iteritems():
2522+            data = [d[1] for d in data if d[0] == self._version]
2523+
2524+            # Every data entry in our list should now be share shnum for
2525+            # a particular version of the mutable file, so all of the
2526+            # entries should be identical.
2527+            datum = data[0]
2528+            assert filter(lambda x: x != datum, data) == []
2529+
2530+            blockhashes[shnum] = datum[0]
2531+            start_segments[shnum] = datum[1]
2532+            end_segments[shnum] = datum[2]
2533+
2534+        d1 = r.decode(start_segments, self._start_segment)
2535+        d2 = r.decode(end_segments, self._end_segment)
2536+        d3 = defer.succeed(blockhashes)
2537+        return deferredutil.gatherResults([d1, d2, d3])
2538+
2539+
2540+    def _build_uploadable_and_finish(self, segments_and_bht, data, offset):
2541+        """
2542+        After the process has the plaintext segments, I build the
2543+        TransformingUploadable that the publisher will eventually
2544+        re-upload to the grid. I then invoke the publisher with that
2545+        uploadable, and return a Deferred when the publish operation has
2546+        completed without issue.
2547+        """
2548+        u = TransformingUploadable(data, offset,
2549+                                   self._version[3],
2550+                                   segments_and_bht[0],
2551+                                   segments_and_bht[1])
2552+        p = Publish(self._node, self._storage_broker, self._servermap)
2553+        return p.update(u, offset, segments_and_bht[2], self._version)
2554}
2555[mutable/publish.py: Modify the publish process to support MDMF
2556Kevan Carstensen <kevan@isnotajoke.com>**20100819003342
2557 Ignore-this: 2bb379974927e2e20cff75bae8302d1d
2558 
2559 The inner workings of the publishing process needed to be reworked to a
2560 large extend to cope with segmented mutable files, and to cope with
2561 partial-file updates of mutable files. This patch does that. It also
2562 introduces wrappers for uploadable data, allowing the use of
2563 filehandle-like objects as data sources, in addition to strings. This
2564 reduces memory inefficiency when dealing with large files through the
2565 webapi, and clarifies update code there.
2566] {
2567hunk ./src/allmydata/mutable/publish.py 3
2568 
2569 
2570-import os, struct, time
2571+import os, time
2572+from StringIO import StringIO
2573 from itertools import count
2574 from zope.interface import implements
2575 from twisted.internet import defer
2576hunk ./src/allmydata/mutable/publish.py 9
2577 from twisted.python import failure
2578-from allmydata.interfaces import IPublishStatus
2579+from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
2580+                                 IMutableUploadable
2581 from allmydata.util import base32, hashutil, mathutil, idlib, log
2582 from allmydata.util.dictutil import DictOfSets
2583 from allmydata import hashtree, codec
2584hunk ./src/allmydata/mutable/publish.py 21
2585 from allmydata.mutable.common import MODE_WRITE, MODE_CHECK, \
2586      UncoordinatedWriteError, NotEnoughServersError
2587 from allmydata.mutable.servermap import ServerMap
2588-from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \
2589-     unpack_checkstring, SIGNED_PREFIX
2590+from allmydata.mutable.layout import unpack_checkstring, MDMFSlotWriteProxy, \
2591+                                     SDMFSlotWriteProxy
2592+
2593+KiB = 1024
2594+DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB
2595+PUSHING_BLOCKS_STATE = 0
2596+PUSHING_EVERYTHING_ELSE_STATE = 1
2597+DONE_STATE = 2
2598 
2599 class PublishStatus:
2600     implements(IPublishStatus)
2601hunk ./src/allmydata/mutable/publish.py 118
2602         self._status.set_helper(False)
2603         self._status.set_progress(0.0)
2604         self._status.set_active(True)
2605+        self._version = self._node.get_version()
2606+        assert self._version in (SDMF_VERSION, MDMF_VERSION)
2607+
2608 
2609     def get_status(self):
2610         return self._status
2611hunk ./src/allmydata/mutable/publish.py 132
2612             kwargs["facility"] = "tahoe.mutable.publish"
2613         return log.msg(*args, **kwargs)
2614 
2615+
2616+    def update(self, data, offset, blockhashes, version):
2617+        """
2618+        I replace the contents of this file with the contents of data,
2619+        starting at offset. I return a Deferred that fires with None
2620+        when the replacement has been completed, or with an error if
2621+        something went wrong during the process.
2622+
2623+        Note that this process will not upload new shares. If the file
2624+        being updated is in need of repair, callers will have to repair
2625+        it on their own.
2626+        """
2627+        # How this works:
2628+        # 1: Make peer assignments. We'll assign each share that we know
2629+        # about on the grid to that peer that currently holds that
2630+        # share, and will not place any new shares.
2631+        # 2: Setup encoding parameters. Most of these will stay the same
2632+        # -- datalength will change, as will some of the offsets.
2633+        # 3. Upload the new segments.
2634+        # 4. Be done.
2635+        assert IMutableUploadable.providedBy(data)
2636+
2637+        self.data = data
2638+
2639+        # XXX: Use the MutableFileVersion instead.
2640+        self.datalength = self._node.get_size()
2641+        if data.get_size() > self.datalength:
2642+            self.datalength = data.get_size()
2643+
2644+        self.log("starting update")
2645+        self.log("adding new data of length %d at offset %d" % \
2646+                    (data.get_size(), offset))
2647+        self.log("new data length is %d" % self.datalength)
2648+        self._status.set_size(self.datalength)
2649+        self._status.set_status("Started")
2650+        self._started = time.time()
2651+
2652+        self.done_deferred = defer.Deferred()
2653+
2654+        self._writekey = self._node.get_writekey()
2655+        assert self._writekey, "need write capability to publish"
2656+
2657+        # first, which servers will we publish to? We require that the
2658+        # servermap was updated in MODE_WRITE, so we can depend upon the
2659+        # peerlist computed by that process instead of computing our own.
2660+        assert self._servermap
2661+        assert self._servermap.last_update_mode in (MODE_WRITE, MODE_CHECK)
2662+        # we will push a version that is one larger than anything present
2663+        # in the grid, according to the servermap.
2664+        self._new_seqnum = self._servermap.highest_seqnum() + 1
2665+        self._status.set_servermap(self._servermap)
2666+
2667+        self.log(format="new seqnum will be %(seqnum)d",
2668+                 seqnum=self._new_seqnum, level=log.NOISY)
2669+
2670+        # We're updating an existing file, so all of the following
2671+        # should be available.
2672+        self.readkey = self._node.get_readkey()
2673+        self.required_shares = self._node.get_required_shares()
2674+        assert self.required_shares is not None
2675+        self.total_shares = self._node.get_total_shares()
2676+        assert self.total_shares is not None
2677+        self._status.set_encoding(self.required_shares, self.total_shares)
2678+
2679+        self._pubkey = self._node.get_pubkey()
2680+        assert self._pubkey
2681+        self._privkey = self._node.get_privkey()
2682+        assert self._privkey
2683+        self._encprivkey = self._node.get_encprivkey()
2684+
2685+        sb = self._storage_broker
2686+        full_peerlist = sb.get_servers_for_index(self._storage_index)
2687+        self.full_peerlist = full_peerlist # for use later, immutable
2688+        self.bad_peers = set() # peerids who have errbacked/refused requests
2689+
2690+        # This will set self.segment_size, self.num_segments, and
2691+        # self.fec. TODO: Does it know how to do the offset? Probably
2692+        # not. So do that part next.
2693+        self.setup_encoding_parameters(offset=offset)
2694+
2695+        # if we experience any surprises (writes which were rejected because
2696+        # our test vector did not match, or shares which we didn't expect to
2697+        # see), we set this flag and report an UncoordinatedWriteError at the
2698+        # end of the publish process.
2699+        self.surprised = False
2700+
2701+        # we keep track of three tables. The first is our goal: which share
2702+        # we want to see on which servers. This is initially populated by the
2703+        # existing servermap.
2704+        self.goal = set() # pairs of (peerid, shnum) tuples
2705+
2706+        # the second table is our list of outstanding queries: those which
2707+        # are in flight and may or may not be delivered, accepted, or
2708+        # acknowledged. Items are added to this table when the request is
2709+        # sent, and removed when the response returns (or errbacks).
2710+        self.outstanding = set() # (peerid, shnum) tuples
2711+
2712+        # the third is a table of successes: share which have actually been
2713+        # placed. These are populated when responses come back with success.
2714+        # When self.placed == self.goal, we're done.
2715+        self.placed = set() # (peerid, shnum) tuples
2716+
2717+        # we also keep a mapping from peerid to RemoteReference. Each time we
2718+        # pull a connection out of the full peerlist, we add it to this for
2719+        # use later.
2720+        self.connections = {}
2721+
2722+        self.bad_share_checkstrings = {}
2723+
2724+        # This is set at the last step of the publishing process.
2725+        self.versioninfo = ""
2726+
2727+        # we use the servermap to populate the initial goal: this way we will
2728+        # try to update each existing share in place. Since we're
2729+        # updating, we ignore damaged and missing shares -- callers must
2730+        # do a repair to repair and recreate these.
2731+        for (peerid, shnum) in self._servermap.servermap:
2732+            self.goal.add( (peerid, shnum) )
2733+            self.connections[peerid] = self._servermap.connections[peerid]
2734+        self.writers = {}
2735+
2736+        # SDMF files are updated differently.
2737+        self._version = MDMF_VERSION
2738+        writer_class = MDMFSlotWriteProxy
2739+
2740+        # For each (peerid, shnum) in self.goal, we make a
2741+        # write proxy for that peer. We'll use this to write
2742+        # shares to the peer.
2743+        for key in self.goal:
2744+            peerid, shnum = key
2745+            write_enabler = self._node.get_write_enabler(peerid)
2746+            renew_secret = self._node.get_renewal_secret(peerid)
2747+            cancel_secret = self._node.get_cancel_secret(peerid)
2748+            secrets = (write_enabler, renew_secret, cancel_secret)
2749+
2750+            self.writers[shnum] =  writer_class(shnum,
2751+                                                self.connections[peerid],
2752+                                                self._storage_index,
2753+                                                secrets,
2754+                                                self._new_seqnum,
2755+                                                self.required_shares,
2756+                                                self.total_shares,
2757+                                                self.segment_size,
2758+                                                self.datalength)
2759+            self.writers[shnum].peerid = peerid
2760+            assert (peerid, shnum) in self._servermap.servermap
2761+            old_versionid, old_timestamp = self._servermap.servermap[key]
2762+            (old_seqnum, old_root_hash, old_salt, old_segsize,
2763+             old_datalength, old_k, old_N, old_prefix,
2764+             old_offsets_tuple) = old_versionid
2765+            self.writers[shnum].set_checkstring(old_seqnum,
2766+                                                old_root_hash,
2767+                                                old_salt)
2768+
2769+        # Our remote shares will not have a complete checkstring until
2770+        # after we are done writing share data and have started to write
2771+        # blocks. In the meantime, we need to know what to look for when
2772+        # writing, so that we can detect UncoordinatedWriteErrors.
2773+        self._checkstring = self.writers.values()[0].get_checkstring()
2774+
2775+        # Now, we start pushing shares.
2776+        self._status.timings["setup"] = time.time() - self._started
2777+        # First, we encrypt, encode, and publish the shares that we need
2778+        # to encrypt, encode, and publish.
2779+
2780+        # Our update process fetched these for us. We need to update
2781+        # them in place as publishing happens.
2782+        self.blockhashes = {} # (shnum, [blochashes])
2783+        for (i, bht) in blockhashes.iteritems():
2784+            # We need to extract the leaves from our old hash tree.
2785+            old_segcount = mathutil.div_ceil(version[4],
2786+                                             version[3])
2787+            h = hashtree.IncompleteHashTree(old_segcount)
2788+            bht = dict(enumerate(bht))
2789+            h.set_hashes(bht)
2790+            leaves = h[h.get_leaf_index(0):]
2791+            for j in xrange(self.num_segments - len(leaves)):
2792+                leaves.append(None)
2793+
2794+            assert len(leaves) >= self.num_segments
2795+            self.blockhashes[i] = leaves
2796+            # This list will now be the leaves that were set during the
2797+            # initial upload + enough empty hashes to make it a
2798+            # power-of-two. If we exceed a power of two boundary, we
2799+            # should be encoding the file over again, and should not be
2800+            # here. So, we have
2801+            #assert len(self.blockhashes[i]) == \
2802+            #    hashtree.roundup_pow2(self.num_segments), \
2803+            #        len(self.blockhashes[i])
2804+            # XXX: Except this doesn't work. Figure out why.
2805+
2806+        # These are filled in later, after we've modified the block hash
2807+        # tree suitably.
2808+        self.sharehash_leaves = None # eventually [sharehashes]
2809+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
2810+                              # validate the share]
2811+
2812+        self.log("Starting push")
2813+
2814+        self._state = PUSHING_BLOCKS_STATE
2815+        self._push()
2816+
2817+        return self.done_deferred
2818+
2819+
2820     def publish(self, newdata):
2821         """Publish the filenode's current contents.  Returns a Deferred that
2822         fires (with None) when the publish has done as much work as it's ever
2823hunk ./src/allmydata/mutable/publish.py 344
2824         simultaneous write.
2825         """
2826 
2827-        # 1: generate shares (SDMF: files are small, so we can do it in RAM)
2828-        # 2: perform peer selection, get candidate servers
2829-        #  2a: send queries to n+epsilon servers, to determine current shares
2830-        #  2b: based upon responses, create target map
2831-        # 3: send slot_testv_and_readv_and_writev messages
2832-        # 4: as responses return, update share-dispatch table
2833-        # 4a: may need to run recovery algorithm
2834-        # 5: when enough responses are back, we're done
2835+        # 0. Setup encoding parameters, encoder, and other such things.
2836+        # 1. Encrypt, encode, and publish segments.
2837+        assert IMutableUploadable.providedBy(newdata)
2838 
2839hunk ./src/allmydata/mutable/publish.py 348
2840-        self.log("starting publish, datalen is %s" % len(newdata))
2841-        self._status.set_size(len(newdata))
2842+        self.data = newdata
2843+        self.datalength = newdata.get_size()
2844+        #if self.datalength >= DEFAULT_MAX_SEGMENT_SIZE:
2845+        #    self._version = MDMF_VERSION
2846+        #else:
2847+        #    self._version = SDMF_VERSION
2848+
2849+        self.log("starting publish, datalen is %s" % self.datalength)
2850+        self._status.set_size(self.datalength)
2851         self._status.set_status("Started")
2852         self._started = time.time()
2853 
2854hunk ./src/allmydata/mutable/publish.py 405
2855         self.full_peerlist = full_peerlist # for use later, immutable
2856         self.bad_peers = set() # peerids who have errbacked/refused requests
2857 
2858-        self.newdata = newdata
2859-        self.salt = os.urandom(16)
2860-
2861+        # This will set self.segment_size, self.num_segments, and
2862+        # self.fec.
2863         self.setup_encoding_parameters()
2864 
2865         # if we experience any surprises (writes which were rejected because
2866hunk ./src/allmydata/mutable/publish.py 415
2867         # end of the publish process.
2868         self.surprised = False
2869 
2870-        # as a failsafe, refuse to iterate through self.loop more than a
2871-        # thousand times.
2872-        self.looplimit = 1000
2873-
2874         # we keep track of three tables. The first is our goal: which share
2875         # we want to see on which servers. This is initially populated by the
2876         # existing servermap.
2877hunk ./src/allmydata/mutable/publish.py 438
2878 
2879         self.bad_share_checkstrings = {}
2880 
2881+        # This is set at the last step of the publishing process.
2882+        self.versioninfo = ""
2883+
2884         # we use the servermap to populate the initial goal: this way we will
2885         # try to update each existing share in place.
2886         for (peerid, shnum) in self._servermap.servermap:
2887hunk ./src/allmydata/mutable/publish.py 454
2888             self.bad_share_checkstrings[key] = old_checkstring
2889             self.connections[peerid] = self._servermap.connections[peerid]
2890 
2891-        # create the shares. We'll discard these as they are delivered. SDMF:
2892-        # we're allowed to hold everything in memory.
2893+        # TODO: Make this part do peer selection.
2894+        self.update_goal()
2895+        self.writers = {}
2896+        if self._version == MDMF_VERSION:
2897+            writer_class = MDMFSlotWriteProxy
2898+        else:
2899+            writer_class = SDMFSlotWriteProxy
2900 
2901hunk ./src/allmydata/mutable/publish.py 462
2902+        # For each (peerid, shnum) in self.goal, we make a
2903+        # write proxy for that peer. We'll use this to write
2904+        # shares to the peer.
2905+        for key in self.goal:
2906+            peerid, shnum = key
2907+            write_enabler = self._node.get_write_enabler(peerid)
2908+            renew_secret = self._node.get_renewal_secret(peerid)
2909+            cancel_secret = self._node.get_cancel_secret(peerid)
2910+            secrets = (write_enabler, renew_secret, cancel_secret)
2911+
2912+            self.writers[shnum] =  writer_class(shnum,
2913+                                                self.connections[peerid],
2914+                                                self._storage_index,
2915+                                                secrets,
2916+                                                self._new_seqnum,
2917+                                                self.required_shares,
2918+                                                self.total_shares,
2919+                                                self.segment_size,
2920+                                                self.datalength)
2921+            self.writers[shnum].peerid = peerid
2922+            if (peerid, shnum) in self._servermap.servermap:
2923+                old_versionid, old_timestamp = self._servermap.servermap[key]
2924+                (old_seqnum, old_root_hash, old_salt, old_segsize,
2925+                 old_datalength, old_k, old_N, old_prefix,
2926+                 old_offsets_tuple) = old_versionid
2927+                self.writers[shnum].set_checkstring(old_seqnum,
2928+                                                    old_root_hash,
2929+                                                    old_salt)
2930+            elif (peerid, shnum) in self.bad_share_checkstrings:
2931+                old_checkstring = self.bad_share_checkstrings[(peerid, shnum)]
2932+                self.writers[shnum].set_checkstring(old_checkstring)
2933+
2934+        # Our remote shares will not have a complete checkstring until
2935+        # after we are done writing share data and have started to write
2936+        # blocks. In the meantime, we need to know what to look for when
2937+        # writing, so that we can detect UncoordinatedWriteErrors.
2938+        self._checkstring = self.writers.values()[0].get_checkstring()
2939+
2940+        # Now, we start pushing shares.
2941         self._status.timings["setup"] = time.time() - self._started
2942hunk ./src/allmydata/mutable/publish.py 502
2943-        d = self._encrypt_and_encode()
2944-        d.addCallback(self._generate_shares)
2945-        def _start_pushing(res):
2946-            self._started_pushing = time.time()
2947-            return res
2948-        d.addCallback(_start_pushing)
2949-        d.addCallback(self.loop) # trigger delivery
2950-        d.addErrback(self._fatal_error)
2951+        # First, we encrypt, encode, and publish the shares that we need
2952+        # to encrypt, encode, and publish.
2953+
2954+        # This will eventually hold the block hash chain for each share
2955+        # that we publish. We define it this way so that empty publishes
2956+        # will still have something to write to the remote slot.
2957+        self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)])
2958+        for i in xrange(self.total_shares):
2959+            blocks = self.blockhashes[i]
2960+            for j in xrange(self.num_segments):
2961+                blocks.append(None)
2962+        self.sharehash_leaves = None # eventually [sharehashes]
2963+        self.sharehashes = {} # shnum -> [sharehash leaves necessary to
2964+                              # validate the share]
2965+
2966+        self.log("Starting push")
2967+
2968+        self._state = PUSHING_BLOCKS_STATE
2969+        self._push()
2970 
2971         return self.done_deferred
2972 
2973hunk ./src/allmydata/mutable/publish.py 524
2974-    def setup_encoding_parameters(self):
2975-        segment_size = len(self.newdata)
2976+
2977+    def _update_status(self):
2978+        self._status.set_status("Sending Shares: %d placed out of %d, "
2979+                                "%d messages outstanding" %
2980+                                (len(self.placed),
2981+                                 len(self.goal),
2982+                                 len(self.outstanding)))
2983+        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
2984+
2985+
2986+    def setup_encoding_parameters(self, offset=0):
2987+        if self._version == MDMF_VERSION:
2988+            segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default
2989+        else:
2990+            segment_size = self.datalength # SDMF is only one segment
2991         # this must be a multiple of self.required_shares
2992         segment_size = mathutil.next_multiple(segment_size,
2993                                               self.required_shares)
2994hunk ./src/allmydata/mutable/publish.py 543
2995         self.segment_size = segment_size
2996+
2997+        # Calculate the starting segment for the upload.
2998         if segment_size:
2999hunk ./src/allmydata/mutable/publish.py 546
3000-            self.num_segments = mathutil.div_ceil(len(self.newdata),
3001+            self.num_segments = mathutil.div_ceil(self.datalength,
3002                                                   segment_size)
3003hunk ./src/allmydata/mutable/publish.py 548
3004+            self.starting_segment = mathutil.div_ceil(offset,
3005+                                                      segment_size)
3006+            self.starting_segment -= 1
3007+            if offset == 0:
3008+                self.starting_segment = 0
3009+
3010         else:
3011             self.num_segments = 0
3012hunk ./src/allmydata/mutable/publish.py 556
3013-        assert self.num_segments in [0, 1,] # SDMF restrictions
3014+            self.starting_segment = 0
3015+
3016+
3017+        self.log("building encoding parameters for file")
3018+        self.log("got segsize %d" % self.segment_size)
3019+        self.log("got %d segments" % self.num_segments)
3020+
3021+        if self._version == SDMF_VERSION:
3022+            assert self.num_segments in (0, 1) # SDMF
3023+        # calculate the tail segment size.
3024+
3025+        if segment_size and self.datalength:
3026+            self.tail_segment_size = self.datalength % segment_size
3027+            self.log("got tail segment size %d" % self.tail_segment_size)
3028+        else:
3029+            self.tail_segment_size = 0
3030+
3031+        if self.tail_segment_size == 0 and segment_size:
3032+            # The tail segment is the same size as the other segments.
3033+            self.tail_segment_size = segment_size
3034+
3035+        # Make FEC encoders
3036+        fec = codec.CRSEncoder()
3037+        fec.set_params(self.segment_size,
3038+                       self.required_shares, self.total_shares)
3039+        self.piece_size = fec.get_block_size()
3040+        self.fec = fec
3041+
3042+        if self.tail_segment_size == self.segment_size:
3043+            self.tail_fec = self.fec
3044+        else:
3045+            tail_fec = codec.CRSEncoder()
3046+            tail_fec.set_params(self.tail_segment_size,
3047+                                self.required_shares,
3048+                                self.total_shares)
3049+            self.tail_fec = tail_fec
3050+
3051+        self._current_segment = self.starting_segment
3052+        self.end_segment = self.num_segments - 1
3053+        # Now figure out where the last segment should be.
3054+        if self.data.get_size() != self.datalength:
3055+            end = self.data.get_size()
3056+            self.end_segment = mathutil.div_ceil(end,
3057+                                                 segment_size)
3058+            self.end_segment -= 1
3059+        self.log("got start segment %d" % self.starting_segment)
3060+        self.log("got end segment %d" % self.end_segment)
3061+
3062+
3063+    def _push(self, ignored=None):
3064+        """
3065+        I manage state transitions. In particular, I see that we still
3066+        have a good enough number of writers to complete the upload
3067+        successfully.
3068+        """
3069+        # Can we still successfully publish this file?
3070+        # TODO: Keep track of outstanding queries before aborting the
3071+        #       process.
3072+        if len(self.writers) <= self.required_shares or self.surprised:
3073+            return self._failure()
3074+
3075+        # Figure out what we need to do next. Each of these needs to
3076+        # return a deferred so that we don't block execution when this
3077+        # is first called in the upload method.
3078+        if self._state == PUSHING_BLOCKS_STATE:
3079+            return self.push_segment(self._current_segment)
3080+
3081+        elif self._state == PUSHING_EVERYTHING_ELSE_STATE:
3082+            return self.push_everything_else()
3083+
3084+        # If we make it to this point, we were successful in placing the
3085+        # file.
3086+        return self._done(None)
3087+
3088+
3089+    def push_segment(self, segnum):
3090+        if self.num_segments == 0 and self._version == SDMF_VERSION:
3091+            self._add_dummy_salts()
3092 
3093hunk ./src/allmydata/mutable/publish.py 635
3094-    def _fatal_error(self, f):
3095-        self.log("error during loop", failure=f, level=log.UNUSUAL)
3096-        self._done(f)
3097+        if segnum > self.end_segment:
3098+            # We don't have any more segments to push.
3099+            self._state = PUSHING_EVERYTHING_ELSE_STATE
3100+            return self._push()
3101+
3102+        d = self._encode_segment(segnum)
3103+        d.addCallback(self._push_segment, segnum)
3104+        def _increment_segnum(ign):
3105+            self._current_segment += 1
3106+        # XXX: I don't think we need to do addBoth here -- any errBacks
3107+        # should be handled within push_segment.
3108+        d.addBoth(_increment_segnum)
3109+        d.addBoth(self._turn_barrier)
3110+        d.addBoth(self._push)
3111+
3112+
3113+    def _turn_barrier(self, result):
3114+        """
3115+        I help the publish process avoid the recursion limit issues
3116+        described in #237.
3117+        """
3118+        return fireEventually(result)
3119+
3120+
3121+    def _add_dummy_salts(self):
3122+        """
3123+        SDMF files need a salt even if they're empty, or the signature
3124+        won't make sense. This method adds a dummy salt to each of our
3125+        SDMF writers so that they can write the signature later.
3126+        """
3127+        salt = os.urandom(16)
3128+        assert self._version == SDMF_VERSION
3129+
3130+        for writer in self.writers.itervalues():
3131+            writer.put_salt(salt)
3132+
3133+
3134+    def _encode_segment(self, segnum):
3135+        """
3136+        I encrypt and encode the segment segnum.
3137+        """
3138+        started = time.time()
3139+
3140+        if segnum + 1 == self.num_segments:
3141+            segsize = self.tail_segment_size
3142+        else:
3143+            segsize = self.segment_size
3144+
3145+
3146+        self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments))
3147+        data = self.data.read(segsize)
3148+        # XXX: This is dumb. Why return a list?
3149+        data = "".join(data)
3150+
3151+        assert len(data) == segsize, len(data)
3152+
3153+        salt = os.urandom(16)
3154+
3155+        key = hashutil.ssk_readkey_data_hash(salt, self.readkey)
3156+        self._status.set_status("Encrypting")
3157+        enc = AES(key)
3158+        crypttext = enc.process(data)
3159+        assert len(crypttext) == len(data)
3160+
3161+        now = time.time()
3162+        self._status.timings["encrypt"] = now - started
3163+        started = now
3164+
3165+        # now apply FEC
3166+        if segnum + 1 == self.num_segments:
3167+            fec = self.tail_fec
3168+        else:
3169+            fec = self.fec
3170+
3171+        self._status.set_status("Encoding")
3172+        crypttext_pieces = [None] * self.required_shares
3173+        piece_size = fec.get_block_size()
3174+        for i in range(len(crypttext_pieces)):
3175+            offset = i * piece_size
3176+            piece = crypttext[offset:offset+piece_size]
3177+            piece = piece + "\x00"*(piece_size - len(piece)) # padding
3178+            crypttext_pieces[i] = piece
3179+            assert len(piece) == piece_size
3180+        d = fec.encode(crypttext_pieces)
3181+        def _done_encoding(res):
3182+            elapsed = time.time() - started
3183+            self._status.timings["encode"] = elapsed
3184+            return (res, salt)
3185+        d.addCallback(_done_encoding)
3186+        return d
3187+
3188+
3189+    def _push_segment(self, encoded_and_salt, segnum):
3190+        """
3191+        I push (data, salt) as segment number segnum.
3192+        """
3193+        results, salt = encoded_and_salt
3194+        shares, shareids = results
3195+        self._status.set_status("Pushing segment")
3196+        for i in xrange(len(shares)):
3197+            sharedata = shares[i]
3198+            shareid = shareids[i]
3199+            if self._version == MDMF_VERSION:
3200+                hashed = salt + sharedata
3201+            else:
3202+                hashed = sharedata
3203+            block_hash = hashutil.block_hash(hashed)
3204+            self.blockhashes[shareid][segnum] = block_hash
3205+            # find the writer for this share
3206+            writer = self.writers[shareid]
3207+            writer.put_block(sharedata, segnum, salt)
3208+
3209+
3210+    def push_everything_else(self):
3211+        """
3212+        I put everything else associated with a share.
3213+        """
3214+        self._pack_started = time.time()
3215+        self.push_encprivkey()
3216+        self.push_blockhashes()
3217+        self.push_sharehashes()
3218+        self.push_toplevel_hashes_and_signature()
3219+        d = self.finish_publishing()
3220+        def _change_state(ignored):
3221+            self._state = DONE_STATE
3222+        d.addCallback(_change_state)
3223+        d.addCallback(self._push)
3224+        return d
3225+
3226+
3227+    def push_encprivkey(self):
3228+        encprivkey = self._encprivkey
3229+        self._status.set_status("Pushing encrypted private key")
3230+        for writer in self.writers.itervalues():
3231+            writer.put_encprivkey(encprivkey)
3232+
3233+
3234+    def push_blockhashes(self):
3235+        self.sharehash_leaves = [None] * len(self.blockhashes)
3236+        self._status.set_status("Building and pushing block hash tree")
3237+        for shnum, blockhashes in self.blockhashes.iteritems():
3238+            t = hashtree.HashTree(blockhashes)
3239+            self.blockhashes[shnum] = list(t)
3240+            # set the leaf for future use.
3241+            self.sharehash_leaves[shnum] = t[0]
3242+
3243+            writer = self.writers[shnum]
3244+            writer.put_blockhashes(self.blockhashes[shnum])
3245+
3246+
3247+    def push_sharehashes(self):
3248+        self._status.set_status("Building and pushing share hash chain")
3249+        share_hash_tree = hashtree.HashTree(self.sharehash_leaves)
3250+        for shnum in xrange(len(self.sharehash_leaves)):
3251+            needed_indices = share_hash_tree.needed_hashes(shnum)
3252+            self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i])
3253+                                             for i in needed_indices] )
3254+            writer = self.writers[shnum]
3255+            writer.put_sharehashes(self.sharehashes[shnum])
3256+        self.root_hash = share_hash_tree[0]
3257+
3258+
3259+    def push_toplevel_hashes_and_signature(self):
3260+        # We need to to three things here:
3261+        #   - Push the root hash and salt hash
3262+        #   - Get the checkstring of the resulting layout; sign that.
3263+        #   - Push the signature
3264+        self._status.set_status("Pushing root hashes and signature")
3265+        for shnum in xrange(self.total_shares):
3266+            writer = self.writers[shnum]
3267+            writer.put_root_hash(self.root_hash)
3268+        self._update_checkstring()
3269+        self._make_and_place_signature()
3270+
3271+
3272+    def _update_checkstring(self):
3273+        """
3274+        After putting the root hash, MDMF files will have the
3275+        checkstring written to the storage server. This means that we
3276+        can update our copy of the checkstring so we can detect
3277+        uncoordinated writes. SDMF files will have the same checkstring,
3278+        so we need not do anything.
3279+        """
3280+        self._checkstring = self.writers.values()[0].get_checkstring()
3281+
3282+
3283+    def _make_and_place_signature(self):
3284+        """
3285+        I create and place the signature.
3286+        """
3287+        started = time.time()
3288+        self._status.set_status("Signing prefix")
3289+        signable = self.writers[0].get_signable()
3290+        self.signature = self._privkey.sign(signable)
3291+
3292+        for (shnum, writer) in self.writers.iteritems():
3293+            writer.put_signature(self.signature)
3294+        self._status.timings['sign'] = time.time() - started
3295+
3296+
3297+    def finish_publishing(self):
3298+        # We're almost done -- we just need to put the verification key
3299+        # and the offsets
3300+        started = time.time()
3301+        self._status.set_status("Pushing shares")
3302+        self._started_pushing = started
3303+        ds = []
3304+        verification_key = self._pubkey.serialize()
3305+
3306+
3307+        # TODO: Bad, since we remove from this same dict. We need to
3308+        # make a copy, or just use a non-iterated value.
3309+        for (shnum, writer) in self.writers.iteritems():
3310+            writer.put_verification_key(verification_key)
3311+            d = writer.finish_publishing()
3312+            # Add the (peerid, shnum) tuple to our list of outstanding
3313+            # queries. This gets used by _loop if some of our queries
3314+            # fail to place shares.
3315+            self.outstanding.add((writer.peerid, writer.shnum))
3316+            d.addCallback(self._got_write_answer, writer, started)
3317+            d.addErrback(self._connection_problem, writer)
3318+            ds.append(d)
3319+        self._record_verinfo()
3320+        self._status.timings['pack'] = time.time() - started
3321+        return defer.DeferredList(ds)
3322+
3323+
3324+    def _record_verinfo(self):
3325+        self.versioninfo = self.writers.values()[0].get_verinfo()
3326+
3327+
3328+    def _connection_problem(self, f, writer):
3329+        """
3330+        We ran into a connection problem while working with writer, and
3331+        need to deal with that.
3332+        """
3333+        self.log("found problem: %s" % str(f))
3334+        self._last_failure = f
3335+        del(self.writers[writer.shnum])
3336 
3337hunk ./src/allmydata/mutable/publish.py 875
3338-    def _update_status(self):
3339-        self._status.set_status("Sending Shares: %d placed out of %d, "
3340-                                "%d messages outstanding" %
3341-                                (len(self.placed),
3342-                                 len(self.goal),
3343-                                 len(self.outstanding)))
3344-        self._status.set_progress(1.0 * len(self.placed) / len(self.goal))
3345 
3346hunk ./src/allmydata/mutable/publish.py 876
3347-    def loop(self, ignored=None):
3348-        self.log("entering loop", level=log.NOISY)
3349-        if not self._running:
3350-            return
3351-
3352-        self.looplimit -= 1
3353-        if self.looplimit <= 0:
3354-            raise LoopLimitExceededError("loop limit exceeded")
3355-
3356-        if self.surprised:
3357-            # don't send out any new shares, just wait for the outstanding
3358-            # ones to be retired.
3359-            self.log("currently surprised, so don't send any new shares",
3360-                     level=log.NOISY)
3361-        else:
3362-            self.update_goal()
3363-            # how far are we from our goal?
3364-            needed = self.goal - self.placed - self.outstanding
3365-            self._update_status()
3366-
3367-            if needed:
3368-                # we need to send out new shares
3369-                self.log(format="need to send %(needed)d new shares",
3370-                         needed=len(needed), level=log.NOISY)
3371-                self._send_shares(needed)
3372-                return
3373-
3374-        if self.outstanding:
3375-            # queries are still pending, keep waiting
3376-            self.log(format="%(outstanding)d queries still outstanding",
3377-                     outstanding=len(self.outstanding),
3378-                     level=log.NOISY)
3379-            return
3380-
3381-        # no queries outstanding, no placements needed: we're done
3382-        self.log("no queries outstanding, no placements needed: done",
3383-                 level=log.OPERATIONAL)
3384-        now = time.time()
3385-        elapsed = now - self._started_pushing
3386-        self._status.timings["push"] = elapsed
3387-        return self._done(None)
3388-
3389     def log_goal(self, goal, message=""):
3390         logmsg = [message]
3391         for (shnum, peerid) in sorted([(s,p) for (p,s) in goal]):
3392hunk ./src/allmydata/mutable/publish.py 957
3393             self.log_goal(self.goal, "after update: ")
3394 
3395 
3396+    def _got_write_answer(self, answer, writer, started):
3397+        if not answer:
3398+            # SDMF writers only pretend to write when readers set their
3399+            # blocks, salts, and so on -- they actually just write once,
3400+            # at the end of the upload process. In fake writes, they
3401+            # return defer.succeed(None). If we see that, we shouldn't
3402+            # bother checking it.
3403+            return
3404 
3405hunk ./src/allmydata/mutable/publish.py 966
3406-    def _encrypt_and_encode(self):
3407-        # this returns a Deferred that fires with a list of (sharedata,
3408-        # sharenum) tuples. TODO: cache the ciphertext, only produce the
3409-        # shares that we care about.
3410-        self.log("_encrypt_and_encode")
3411-
3412-        self._status.set_status("Encrypting")
3413-        started = time.time()
3414-
3415-        key = hashutil.ssk_readkey_data_hash(self.salt, self.readkey)
3416-        enc = AES(key)
3417-        crypttext = enc.process(self.newdata)
3418-        assert len(crypttext) == len(self.newdata)
3419+        peerid = writer.peerid
3420+        lp = self.log("_got_write_answer from %s, share %d" %
3421+                      (idlib.shortnodeid_b2a(peerid), writer.shnum))
3422 
3423         now = time.time()
3424hunk ./src/allmydata/mutable/publish.py 971
3425-        self._status.timings["encrypt"] = now - started
3426-        started = now
3427-
3428-        # now apply FEC
3429-
3430-        self._status.set_status("Encoding")
3431-        fec = codec.CRSEncoder()
3432-        fec.set_params(self.segment_size,
3433-                       self.required_shares, self.total_shares)
3434-        piece_size = fec.get_block_size()
3435-        crypttext_pieces = [None] * self.required_shares
3436-        for i in range(len(crypttext_pieces)):
3437-            offset = i * piece_size
3438-            piece = crypttext[offset:offset+piece_size]
3439-            piece = piece + "\x00"*(piece_size - len(piece)) # padding
3440-            crypttext_pieces[i] = piece
3441-            assert len(piece) == piece_size
3442-
3443-        d = fec.encode(crypttext_pieces)
3444-        def _done_encoding(res):
3445-            elapsed = time.time() - started
3446-            self._status.timings["encode"] = elapsed
3447-            return res
3448-        d.addCallback(_done_encoding)
3449-        return d
3450-
3451-    def _generate_shares(self, shares_and_shareids):
3452-        # this sets self.shares and self.root_hash
3453-        self.log("_generate_shares")
3454-        self._status.set_status("Generating Shares")
3455-        started = time.time()
3456-
3457-        # we should know these by now
3458-        privkey = self._privkey
3459-        encprivkey = self._encprivkey
3460-        pubkey = self._pubkey
3461-
3462-        (shares, share_ids) = shares_and_shareids
3463-
3464-        assert len(shares) == len(share_ids)
3465-        assert len(shares) == self.total_shares
3466-        all_shares = {}
3467-        block_hash_trees = {}
3468-        share_hash_leaves = [None] * len(shares)
3469-        for i in range(len(shares)):
3470-            share_data = shares[i]
3471-            shnum = share_ids[i]
3472-            all_shares[shnum] = share_data
3473-
3474-            # build the block hash tree. SDMF has only one leaf.
3475-            leaves = [hashutil.block_hash(share_data)]
3476-            t = hashtree.HashTree(leaves)
3477-            block_hash_trees[shnum] = list(t)
3478-            share_hash_leaves[shnum] = t[0]
3479-        for leaf in share_hash_leaves:
3480-            assert leaf is not None
3481-        share_hash_tree = hashtree.HashTree(share_hash_leaves)
3482-        share_hash_chain = {}
3483-        for shnum in range(self.total_shares):
3484-            needed_hashes = share_hash_tree.needed_hashes(shnum)
3485-            share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i])
3486-                                              for i in needed_hashes ] )
3487-        root_hash = share_hash_tree[0]
3488-        assert len(root_hash) == 32
3489-        self.log("my new root_hash is %s" % base32.b2a(root_hash))
3490-        self._new_version_info = (self._new_seqnum, root_hash, self.salt)
3491-
3492-        prefix = pack_prefix(self._new_seqnum, root_hash, self.salt,
3493-                             self.required_shares, self.total_shares,
3494-                             self.segment_size, len(self.newdata))
3495-
3496-        # now pack the beginning of the share. All shares are the same up
3497-        # to the signature, then they have divergent share hash chains,
3498-        # then completely different block hash trees + salt + share data,
3499-        # then they all share the same encprivkey at the end. The sizes
3500-        # of everything are the same for all shares.
3501-
3502-        sign_started = time.time()
3503-        signature = privkey.sign(prefix)
3504-        self._status.timings["sign"] = time.time() - sign_started
3505-
3506-        verification_key = pubkey.serialize()
3507-
3508-        final_shares = {}
3509-        for shnum in range(self.total_shares):
3510-            final_share = pack_share(prefix,
3511-                                     verification_key,
3512-                                     signature,
3513-                                     share_hash_chain[shnum],
3514-                                     block_hash_trees[shnum],
3515-                                     all_shares[shnum],
3516-                                     encprivkey)
3517-            final_shares[shnum] = final_share
3518-        elapsed = time.time() - started
3519-        self._status.timings["pack"] = elapsed
3520-        self.shares = final_shares
3521-        self.root_hash = root_hash
3522-
3523-        # we also need to build up the version identifier for what we're
3524-        # pushing. Extract the offsets from one of our shares.
3525-        assert final_shares
3526-        offsets = unpack_header(final_shares.values()[0])[-1]
3527-        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
3528-        verinfo = (self._new_seqnum, root_hash, self.salt,
3529-                   self.segment_size, len(self.newdata),
3530-                   self.required_shares, self.total_shares,
3531-                   prefix, offsets_tuple)
3532-        self.versioninfo = verinfo
3533-
3534-
3535-
3536-    def _send_shares(self, needed):
3537-        self.log("_send_shares")
3538-
3539-        # we're finally ready to send out our shares. If we encounter any
3540-        # surprises here, it's because somebody else is writing at the same
3541-        # time. (Note: in the future, when we remove the _query_peers() step
3542-        # and instead speculate about [or remember] which shares are where,
3543-        # surprises here are *not* indications of UncoordinatedWriteError,
3544-        # and we'll need to respond to them more gracefully.)
3545-
3546-        # needed is a set of (peerid, shnum) tuples. The first thing we do is
3547-        # organize it by peerid.
3548-
3549-        peermap = DictOfSets()
3550-        for (peerid, shnum) in needed:
3551-            peermap.add(peerid, shnum)
3552-
3553-        # the next thing is to build up a bunch of test vectors. The
3554-        # semantics of Publish are that we perform the operation if the world
3555-        # hasn't changed since the ServerMap was constructed (more or less).
3556-        # For every share we're trying to place, we create a test vector that
3557-        # tests to see if the server*share still corresponds to the
3558-        # map.
3559-
3560-        all_tw_vectors = {} # maps peerid to tw_vectors
3561-        sm = self._servermap.servermap
3562-
3563-        for key in needed:
3564-            (peerid, shnum) = key
3565-
3566-            if key in sm:
3567-                # an old version of that share already exists on the
3568-                # server, according to our servermap. We will create a
3569-                # request that attempts to replace it.
3570-                old_versionid, old_timestamp = sm[key]
3571-                (old_seqnum, old_root_hash, old_salt, old_segsize,
3572-                 old_datalength, old_k, old_N, old_prefix,
3573-                 old_offsets_tuple) = old_versionid
3574-                old_checkstring = pack_checkstring(old_seqnum,
3575-                                                   old_root_hash,
3576-                                                   old_salt)
3577-                testv = (0, len(old_checkstring), "eq", old_checkstring)
3578-
3579-            elif key in self.bad_share_checkstrings:
3580-                old_checkstring = self.bad_share_checkstrings[key]
3581-                testv = (0, len(old_checkstring), "eq", old_checkstring)
3582-
3583-            else:
3584-                # add a testv that requires the share not exist
3585-
3586-                # Unfortunately, foolscap-0.2.5 has a bug in the way inbound
3587-                # constraints are handled. If the same object is referenced
3588-                # multiple times inside the arguments, foolscap emits a
3589-                # 'reference' token instead of a distinct copy of the
3590-                # argument. The bug is that these 'reference' tokens are not
3591-                # accepted by the inbound constraint code. To work around
3592-                # this, we need to prevent python from interning the
3593-                # (constant) tuple, by creating a new copy of this vector
3594-                # each time.
3595-
3596-                # This bug is fixed in foolscap-0.2.6, and even though this
3597-                # version of Tahoe requires foolscap-0.3.1 or newer, we are
3598-                # supposed to be able to interoperate with older versions of
3599-                # Tahoe which are allowed to use older versions of foolscap,
3600-                # including foolscap-0.2.5 . In addition, I've seen other
3601-                # foolscap problems triggered by 'reference' tokens (see #541
3602-                # for details). So we must keep this workaround in place.
3603-
3604-                #testv = (0, 1, 'eq', "")
3605-                testv = tuple([0, 1, 'eq', ""])
3606-
3607-            testvs = [testv]
3608-            # the write vector is simply the share
3609-            writev = [(0, self.shares[shnum])]
3610-
3611-            if peerid not in all_tw_vectors:
3612-                all_tw_vectors[peerid] = {}
3613-                # maps shnum to (testvs, writevs, new_length)
3614-            assert shnum not in all_tw_vectors[peerid]
3615-
3616-            all_tw_vectors[peerid][shnum] = (testvs, writev, None)
3617-
3618-        # we read the checkstring back from each share, however we only use
3619-        # it to detect whether there was a new share that we didn't know
3620-        # about. The success or failure of the write will tell us whether
3621-        # there was a collision or not. If there is a collision, the first
3622-        # thing we'll do is update the servermap, which will find out what
3623-        # happened. We could conceivably reduce a roundtrip by using the
3624-        # readv checkstring to populate the servermap, but really we'd have
3625-        # to read enough data to validate the signatures too, so it wouldn't
3626-        # be an overall win.
3627-        read_vector = [(0, struct.calcsize(SIGNED_PREFIX))]
3628-
3629-        # ok, send the messages!
3630-        self.log("sending %d shares" % len(all_tw_vectors), level=log.NOISY)
3631-        started = time.time()
3632-        for (peerid, tw_vectors) in all_tw_vectors.items():
3633-
3634-            write_enabler = self._node.get_write_enabler(peerid)
3635-            renew_secret = self._node.get_renewal_secret(peerid)
3636-            cancel_secret = self._node.get_cancel_secret(peerid)
3637-            secrets = (write_enabler, renew_secret, cancel_secret)
3638-            shnums = tw_vectors.keys()
3639-
3640-            for shnum in shnums:
3641-                self.outstanding.add( (peerid, shnum) )
3642+        elapsed = now - started
3643 
3644hunk ./src/allmydata/mutable/publish.py 973
3645-            d = self._do_testreadwrite(peerid, secrets,
3646-                                       tw_vectors, read_vector)
3647-            d.addCallbacks(self._got_write_answer, self._got_write_error,
3648-                           callbackArgs=(peerid, shnums, started),
3649-                           errbackArgs=(peerid, shnums, started))
3650-            # tolerate immediate errback, like with DeadReferenceError
3651-            d.addBoth(fireEventually)
3652-            d.addCallback(self.loop)
3653-            d.addErrback(self._fatal_error)
3654+        self._status.add_per_server_time(peerid, elapsed)
3655 
3656hunk ./src/allmydata/mutable/publish.py 975
3657-        self._update_status()
3658-        self.log("%d shares sent" % len(all_tw_vectors), level=log.NOISY)
3659+        wrote, read_data = answer
3660 
3661hunk ./src/allmydata/mutable/publish.py 977
3662-    def _do_testreadwrite(self, peerid, secrets,
3663-                          tw_vectors, read_vector):
3664-        storage_index = self._storage_index
3665-        ss = self.connections[peerid]
3666+        surprise_shares = set(read_data.keys()) - set([writer.shnum])
3667 
3668hunk ./src/allmydata/mutable/publish.py 979
3669-        #print "SS[%s] is %s" % (idlib.shortnodeid_b2a(peerid), ss), ss.tracker.interfaceName
3670-        d = ss.callRemote("slot_testv_and_readv_and_writev",
3671-                          storage_index,
3672-                          secrets,
3673-                          tw_vectors,
3674-                          read_vector)
3675-        return d
3676+        # We need to remove from surprise_shares any shares that we are
3677+        # knowingly also writing to that peer from other writers.
3678 
3679hunk ./src/allmydata/mutable/publish.py 982
3680-    def _got_write_answer(self, answer, peerid, shnums, started):
3681-        lp = self.log("_got_write_answer from %s" %
3682-                      idlib.shortnodeid_b2a(peerid))
3683-        for shnum in shnums:
3684-            self.outstanding.discard( (peerid, shnum) )
3685+        # TODO: Precompute this.
3686+        known_shnums = [x.shnum for x in self.writers.values()
3687+                        if x.peerid == peerid]
3688+        surprise_shares -= set(known_shnums)
3689+        self.log("found the following surprise shares: %s" %
3690+                 str(surprise_shares))
3691 
3692hunk ./src/allmydata/mutable/publish.py 989
3693-        now = time.time()
3694-        elapsed = now - started
3695-        self._status.add_per_server_time(peerid, elapsed)
3696-
3697-        wrote, read_data = answer
3698-
3699-        surprise_shares = set(read_data.keys()) - set(shnums)
3700+        # Now surprise shares contains all of the shares that we did not
3701+        # expect to be there.
3702 
3703         surprised = False
3704         for shnum in surprise_shares:
3705hunk ./src/allmydata/mutable/publish.py 996
3706             # read_data is a dict mapping shnum to checkstring (SIGNED_PREFIX)
3707             checkstring = read_data[shnum][0]
3708-            their_version_info = unpack_checkstring(checkstring)
3709-            if their_version_info == self._new_version_info:
3710+            # What we want to do here is to see if their (seqnum,
3711+            # roothash, salt) is the same as our (seqnum, roothash,
3712+            # salt), or the equivalent for MDMF. The best way to do this
3713+            # is to store a packed representation of our checkstring
3714+            # somewhere, then not bother unpacking the other
3715+            # checkstring.
3716+            if checkstring == self._checkstring:
3717                 # they have the right share, somehow
3718 
3719                 if (peerid,shnum) in self.goal:
3720hunk ./src/allmydata/mutable/publish.py 1081
3721             self.log("our testv failed, so the write did not happen",
3722                      parent=lp, level=log.WEIRD, umid="8sc26g")
3723             self.surprised = True
3724-            self.bad_peers.add(peerid) # don't ask them again
3725+            self.bad_peers.add(writer) # don't ask them again
3726             # use the checkstring to add information to the log message
3727             for (shnum,readv) in read_data.items():
3728                 checkstring = readv[0]
3729hunk ./src/allmydata/mutable/publish.py 1103
3730                 # if expected_version==None, then we didn't expect to see a
3731                 # share on that peer, and the 'surprise_shares' clause above
3732                 # will have logged it.
3733-            # self.loop() will take care of finding new homes
3734             return
3735 
3736hunk ./src/allmydata/mutable/publish.py 1105
3737-        for shnum in shnums:
3738-            self.placed.add( (peerid, shnum) )
3739-            # and update the servermap
3740-            self._servermap.add_new_share(peerid, shnum,
3741+        # and update the servermap
3742+        # self.versioninfo is set during the last phase of publishing.
3743+        # If we get there, we know that responses correspond to placed
3744+        # shares, and can safely execute these statements.
3745+        if self.versioninfo:
3746+            self.log("wrote successfully: adding new share to servermap")
3747+            self._servermap.add_new_share(peerid, writer.shnum,
3748                                           self.versioninfo, started)
3749hunk ./src/allmydata/mutable/publish.py 1113
3750-
3751-        # self.loop() will take care of checking to see if we're done
3752+            self.placed.add( (peerid, writer.shnum) )
3753+        self._update_status()
3754+        # the next method in the deferred chain will check to see if
3755+        # we're done and successful.
3756         return
3757 
3758hunk ./src/allmydata/mutable/publish.py 1119
3759-    def _got_write_error(self, f, peerid, shnums, started):
3760-        for shnum in shnums:
3761-            self.outstanding.discard( (peerid, shnum) )
3762-        self.bad_peers.add(peerid)
3763-        if self._first_write_error is None:
3764-            self._first_write_error = f
3765-        self.log(format="error while writing shares %(shnums)s to peerid %(peerid)s",
3766-                 shnums=list(shnums), peerid=idlib.shortnodeid_b2a(peerid),
3767-                 failure=f,
3768-                 level=log.UNUSUAL)
3769-        # self.loop() will take care of checking to see if we're done
3770-        return
3771-
3772 
3773     def _done(self, res):
3774         if not self._running:
3775hunk ./src/allmydata/mutable/publish.py 1126
3776         self._running = False
3777         now = time.time()
3778         self._status.timings["total"] = now - self._started
3779+
3780+        elapsed = now - self._started_pushing
3781+        self._status.timings['push'] = elapsed
3782+
3783         self._status.set_active(False)
3784hunk ./src/allmydata/mutable/publish.py 1131
3785-        if isinstance(res, failure.Failure):
3786-            self.log("Publish done, with failure", failure=res,
3787-                     level=log.WEIRD, umid="nRsR9Q")
3788-            self._status.set_status("Failed")
3789-        elif self.surprised:
3790-            self.log("Publish done, UncoordinatedWriteError", level=log.UNUSUAL)
3791-            self._status.set_status("UncoordinatedWriteError")
3792-            # deliver a failure
3793-            res = failure.Failure(UncoordinatedWriteError())
3794-            # TODO: recovery
3795-        else:
3796-            self.log("Publish done, success")
3797-            self._status.set_status("Finished")
3798-            self._status.set_progress(1.0)
3799+        self.log("Publish done, success")
3800+        self._status.set_status("Finished")
3801+        self._status.set_progress(1.0)
3802         eventually(self.done_deferred.callback, res)
3803 
3804hunk ./src/allmydata/mutable/publish.py 1136
3805+    def _failure(self):
3806+
3807+        if not self.surprised:
3808+            # We ran out of servers
3809+            self.log("Publish ran out of good servers, "
3810+                     "last failure was: %s" % str(self._last_failure))
3811+            e = NotEnoughServersError("Ran out of non-bad servers, "
3812+                                      "last failure was %s" %
3813+                                      str(self._last_failure))
3814+        else:
3815+            # We ran into shares that we didn't recognize, which means
3816+            # that we need to return an UncoordinatedWriteError.
3817+            self.log("Publish failed with UncoordinatedWriteError")
3818+            e = UncoordinatedWriteError()
3819+        f = failure.Failure(e)
3820+        eventually(self.done_deferred.callback, f)
3821+
3822+
3823+class MutableFileHandle:
3824+    """
3825+    I am a mutable uploadable built around a filehandle-like object,
3826+    usually either a StringIO instance or a handle to an actual file.
3827+    """
3828+    implements(IMutableUploadable)
3829+
3830+    def __init__(self, filehandle):
3831+        # The filehandle is defined as a generally file-like object that
3832+        # has these two methods. We don't care beyond that.
3833+        assert hasattr(filehandle, "read")
3834+        assert hasattr(filehandle, "close")
3835+
3836+        self._filehandle = filehandle
3837+        # We must start reading at the beginning of the file, or we risk
3838+        # encountering errors when the data read does not match the size
3839+        # reported to the uploader.
3840+        self._filehandle.seek(0)
3841+
3842+        # We have not yet read anything, so our position is 0.
3843+        self._marker = 0
3844+
3845+
3846+    def get_size(self):
3847+        """
3848+        I return the amount of data in my filehandle.
3849+        """
3850+        if not hasattr(self, "_size"):
3851+            old_position = self._filehandle.tell()
3852+            # Seek to the end of the file by seeking 0 bytes from the
3853+            # file's end
3854+            self._filehandle.seek(0, 2) # 2 == os.SEEK_END in 2.5+
3855+            self._size = self._filehandle.tell()
3856+            # Restore the previous position, in case this was called
3857+            # after a read.
3858+            self._filehandle.seek(old_position)
3859+            assert self._filehandle.tell() == old_position
3860+
3861+        assert hasattr(self, "_size")
3862+        return self._size
3863+
3864+
3865+    def pos(self):
3866+        """
3867+        I return the position of my read marker -- i.e., how much data I
3868+        have already read and returned to callers.
3869+        """
3870+        return self._marker
3871+
3872+
3873+    def read(self, length):
3874+        """
3875+        I return some data (up to length bytes) from my filehandle.
3876+
3877+        In most cases, I return length bytes, but sometimes I won't --
3878+        for example, if I am asked to read beyond the end of a file, or
3879+        an error occurs.
3880+        """
3881+        results = self._filehandle.read(length)
3882+        self._marker += len(results)
3883+        return [results]
3884+
3885+
3886+    def close(self):
3887+        """
3888+        I close the underlying filehandle. Any further operations on the
3889+        filehandle fail at this point.
3890+        """
3891+        self._filehandle.close()
3892+
3893+
3894+class MutableData(MutableFileHandle):
3895+    """
3896+    I am a mutable uploadable built around a string, which I then cast
3897+    into a StringIO and treat as a filehandle.
3898+    """
3899+
3900+    def __init__(self, s):
3901+        # Take a string and return a file-like uploadable.
3902+        assert isinstance(s, str)
3903+
3904+        MutableFileHandle.__init__(self, StringIO(s))
3905+
3906+
3907+class TransformingUploadable:
3908+    """
3909+    I am an IMutableUploadable that wraps another IMutableUploadable,
3910+    and some segments that are already on the grid. When I am called to
3911+    read, I handle merging of boundary segments.
3912+    """
3913+    implements(IMutableUploadable)
3914+
3915+
3916+    def __init__(self, data, offset, segment_size, start, end):
3917+        assert IMutableUploadable.providedBy(data)
3918+
3919+        self._newdata = data
3920+        self._offset = offset
3921+        self._segment_size = segment_size
3922+        self._start = start
3923+        self._end = end
3924+
3925+        self._read_marker = 0
3926+
3927+        self._first_segment_offset = offset % segment_size
3928+
3929+        num = self.log("TransformingUploadable: starting", parent=None)
3930+        self._log_number = num
3931+        self.log("got fso: %d" % self._first_segment_offset)
3932+        self.log("got offset: %d" % self._offset)
3933+
3934+
3935+    def log(self, *args, **kwargs):
3936+        if 'parent' not in kwargs:
3937+            kwargs['parent'] = self._log_number
3938+        if "facility" not in kwargs:
3939+            kwargs["facility"] = "tahoe.mutable.transforminguploadable"
3940+        return log.msg(*args, **kwargs)
3941+
3942+
3943+    def get_size(self):
3944+        return self._offset + self._newdata.get_size()
3945+
3946+
3947+    def read(self, length):
3948+        # We can get data from 3 sources here.
3949+        #   1. The first of the segments provided to us.
3950+        #   2. The data that we're replacing things with.
3951+        #   3. The last of the segments provided to us.
3952+
3953+        # are we in state 0?
3954+        self.log("reading %d bytes" % length)
3955+
3956+        old_start_data = ""
3957+        old_data_length = self._first_segment_offset - self._read_marker
3958+        if old_data_length > 0:
3959+            if old_data_length > length:
3960+                old_data_length = length
3961+            self.log("returning %d bytes of old start data" % old_data_length)
3962+
3963+            old_data_end = old_data_length + self._read_marker
3964+            old_start_data = self._start[self._read_marker:old_data_end]
3965+            length -= old_data_length
3966+        else:
3967+            # otherwise calculations later get screwed up.
3968+            old_data_length = 0
3969+
3970+        # Is there enough new data to satisfy this read? If not, we need
3971+        # to pad the end of the data with data from our last segment.
3972+        old_end_length = length - \
3973+            (self._newdata.get_size() - self._newdata.pos())
3974+        old_end_data = ""
3975+        if old_end_length > 0:
3976+            self.log("reading %d bytes of old end data" % old_end_length)
3977+
3978+            # TODO: We're not explicitly checking for tail segment size
3979+            # here. Is that a problem?
3980+            old_data_offset = (length - old_end_length + \
3981+                               old_data_length) % self._segment_size
3982+            self.log("reading at offset %d" % old_data_offset)
3983+            old_end = old_data_offset + old_end_length
3984+            old_end_data = self._end[old_data_offset:old_end]
3985+            length -= old_end_length
3986+            assert length == self._newdata.get_size() - self._newdata.pos()
3987+
3988+        self.log("reading %d bytes of new data" % length)
3989+        new_data = self._newdata.read(length)
3990+        new_data = "".join(new_data)
3991+
3992+        self._read_marker += len(old_start_data + new_data + old_end_data)
3993+
3994+        return old_start_data + new_data + old_end_data
3995 
3996hunk ./src/allmydata/mutable/publish.py 1327
3997+    def close(self):
3998+        pass
3999}
4000[nodemaker.py: Make nodemaker expose a way to create MDMF files
4001Kevan Carstensen <kevan@isnotajoke.com>**20100819003509
4002 Ignore-this: a6701746d6b992fc07bc0556a2b4a61d
4003] {
4004hunk ./src/allmydata/nodemaker.py 3
4005 import weakref
4006 from zope.interface import implements
4007-from allmydata.interfaces import INodeMaker
4008+from allmydata.util.assertutil import precondition
4009+from allmydata.interfaces import INodeMaker, SDMF_VERSION
4010 from allmydata.immutable.literal import LiteralFileNode
4011 from allmydata.immutable.filenode import ImmutableFileNode, CiphertextFileNode
4012 from allmydata.immutable.upload import Data
4013hunk ./src/allmydata/nodemaker.py 9
4014 from allmydata.mutable.filenode import MutableFileNode
4015+from allmydata.mutable.publish import MutableData
4016 from allmydata.dirnode import DirectoryNode, pack_children
4017 from allmydata.unknown import UnknownNode
4018 from allmydata import uri
4019hunk ./src/allmydata/nodemaker.py 92
4020             return self._create_dirnode(filenode)
4021         return None
4022 
4023-    def create_mutable_file(self, contents=None, keysize=None):
4024+    def create_mutable_file(self, contents=None, keysize=None,
4025+                            version=SDMF_VERSION):
4026         n = MutableFileNode(self.storage_broker, self.secret_holder,
4027                             self.default_encoding_parameters, self.history)
4028hunk ./src/allmydata/nodemaker.py 96
4029+        n.set_version(version)
4030         d = self.key_generator.generate(keysize)
4031         d.addCallback(n.create_with_keys, contents)
4032         d.addCallback(lambda res: n)
4033hunk ./src/allmydata/nodemaker.py 103
4034         return d
4035 
4036     def create_new_mutable_directory(self, initial_children={}):
4037+        # mutable directories will always be SDMF for now, to help
4038+        # compatibility with older clients.
4039+        version = SDMF_VERSION
4040+        # initial_children must have metadata (i.e. {} instead of None)
4041+        for (name, (node, metadata)) in initial_children.iteritems():
4042+            precondition(isinstance(metadata, dict),
4043+                         "create_new_mutable_directory requires metadata to be a dict, not None", metadata)
4044+            node.raise_error()
4045         d = self.create_mutable_file(lambda n:
4046hunk ./src/allmydata/nodemaker.py 112
4047-                                     pack_children(initial_children, n.get_writekey()))
4048+                                     MutableData(pack_children(initial_children,
4049+                                                    n.get_writekey())),
4050+                                     version=version)
4051         d.addCallback(self._create_dirnode)
4052         return d
4053 
4054}
4055[docs: update docs to mention MDMF
4056Kevan Carstensen <kevan@isnotajoke.com>**20100814225644
4057 Ignore-this: 1c3caa3cd44831007dcfbef297814308
4058] {
4059merger 0.0 (
4060hunk ./docs/configuration.rst 324
4061+Frontend Configuration
4062+======================
4063+
4064+The Tahoe client process can run a variety of frontend file-access protocols.
4065+You will use these to create and retrieve files from the virtual filesystem.
4066+Configuration details for each are documented in the following
4067+protocol-specific guides:
4068+
4069+HTTP
4070+
4071+    Tahoe runs a webserver by default on port 3456. This interface provides a
4072+    human-oriented "WUI", with pages to create, modify, and browse
4073+    directories and files, as well as a number of pages to check on the
4074+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
4075+    with a REST-ful HTTP interface that can be used by other programs
4076+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
4077+    details, and the ``web.port`` and ``web.static`` config variables above.
4078+    The `<frontends/download-status.rst>`_ document also describes a few WUI
4079+    status pages.
4080+
4081+CLI
4082+
4083+    The main "bin/tahoe" executable includes subcommands for manipulating the
4084+    filesystem, uploading/downloading files, and creating/running Tahoe
4085+    nodes. See `<frontends/CLI.rst>`_ for details.
4086+
4087+FTP, SFTP
4088+
4089+    Tahoe can also run both FTP and SFTP servers, and map a username/password
4090+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
4091+    for instructions on configuring these services, and the ``[ftpd]`` and
4092+    ``[sftpd]`` sections of ``tahoe.cfg``.
4093+
4094merger 0.0 (
4095replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
4096merger 0.0 (
4097hunk ./docs/configuration.rst 384
4098-shares.needed = (int, optional) aka "k", default 3
4099-shares.total = (int, optional) aka "N", N >= k, default 10
4100-shares.happy = (int, optional) 1 <= happy <= N, default 7
4101-
4102- These three values set the default encoding parameters. Each time a new file
4103- is uploaded, erasure-coding is used to break the ciphertext into separate
4104- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
4105- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
4106- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
4107- Setting k to 1 is equivalent to simple replication (uploading N copies of
4108- the file).
4109-
4110- These values control the tradeoff between storage overhead, performance, and
4111- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
4112- backend storage space (the actual value will be a bit more, because of other
4113- forms of overhead). Up to N-k shares can be lost before the file becomes
4114- unrecoverable, so assuming there are at least N servers, up to N-k servers
4115- can be offline without losing the file. So large N/k ratios are more
4116- reliable, and small N/k ratios use less disk space. Clearly, k must never be
4117- smaller than N.
4118-
4119- Large values of N will slow down upload operations slightly, since more
4120- servers must be involved, and will slightly increase storage overhead due to
4121- the hash trees that are created. Large values of k will cause downloads to
4122- be marginally slower, because more servers must be involved. N cannot be
4123- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
4124- uses.
4125-
4126- shares.happy allows you control over the distribution of your immutable file.
4127- For a successful upload, shares are guaranteed to be initially placed on
4128- at least 'shares.happy' distinct servers, the correct functioning of any
4129- k of which is sufficient to guarantee the availability of the uploaded file.
4130- This value should not be larger than the number of servers on your grid.
4131-
4132- A value of shares.happy <= k is allowed, but does not provide any redundancy
4133- if some servers fail or lose shares.
4134-
4135- (Mutable files use a different share placement algorithm that does not
4136-  consider this parameter.)
4137-
4138-
4139-== Storage Server Configuration ==
4140-
4141-[storage]
4142-enabled = (boolean, optional)
4143-
4144- If this is True, the node will run a storage server, offering space to other
4145- clients. If it is False, the node will not run a storage server, meaning
4146- that no shares will be stored on this node. Use False this for clients who
4147- do not wish to provide storage service. The default value is True.
4148-
4149-readonly = (boolean, optional)
4150-
4151- If True, the node will run a storage server but will not accept any shares,
4152- making it effectively read-only. Use this for storage servers which are
4153- being decommissioned: the storage/ directory could be mounted read-only,
4154- while shares are moved to other servers. Note that this currently only
4155- affects immutable shares. Mutable shares (used for directories) will be
4156- written and modified anyway. See ticket #390 for the current status of this
4157- bug. The default value is False.
4158-
4159-reserved_space = (str, optional)
4160-
4161- If provided, this value defines how much disk space is reserved: the storage
4162- server will not accept any share which causes the amount of free disk space
4163- to drop below this value. (The free space is measured by a call to statvfs(2)
4164- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
4165- user account under which the storage server runs.)
4166-
4167- This string contains a number, with an optional case-insensitive scale
4168- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
4169- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
4170- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
4171-
4172-expire.enabled =
4173-expire.mode =
4174-expire.override_lease_duration =
4175-expire.cutoff_date =
4176-expire.immutable =
4177-expire.mutable =
4178-
4179- These settings control garbage-collection, in which the server will delete
4180- shares that no longer have an up-to-date lease on them. Please see the
4181- neighboring "garbage-collection.txt" document for full details.
4182-
4183-
4184-== Running A Helper ==
4185+Running A Helper
4186+================
4187hunk ./docs/configuration.rst 424
4188+mutable.format = sdmf or mdmf
4189+
4190+ This value tells Tahoe-LAFS what the default mutable file format should
4191+ be. If mutable.format=sdmf, then newly created mutable files will be in
4192+ the old SDMF format. This is desirable for clients that operate on
4193+ grids where some peers run older versions of Tahoe-LAFS, as these older
4194+ versions cannot read the new MDMF mutable file format. If
4195+ mutable.format = mdmf, then newly created mutable files will use the
4196+ new MDMF format, which supports efficient in-place modification and
4197+ streaming downloads. You can overwrite this value using a special
4198+ mutable-type parameter in the webapi. If you do not specify a value
4199+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
4200+
4201+ Note that this parameter only applies to mutable files. Mutable
4202+ directories, which are stored as mutable files, are not controlled by
4203+ this parameter and will always use SDMF. We may revisit this decision
4204+ in future versions of Tahoe-LAFS.
4205)
4206)
4207)
4208hunk ./docs/frontends/webapi.rst 363
4209  writeable mutable file, that file's contents will be overwritten in-place. If
4210  it is a read-cap for a mutable file, an error will occur. If it is an
4211  immutable file, the old file will be discarded, and a new one will be put in
4212- its place.
4213+ its place. If the target file is a writable mutable file, you may also
4214+ specify an "offset" parameter -- a byte offset that determines where in
4215+ the mutable file the data from the HTTP request body is placed. This
4216+ operation is relatively efficient for MDMF mutable files, and is
4217+ relatively inefficient (but still supported) for SDMF mutable files.
4218 
4219  When creating a new file, if "mutable=true" is in the query arguments, the
4220  operation will create a mutable file instead of an immutable one.
4221hunk ./docs/frontends/webapi.rst 388
4222 
4223  If "mutable=true" is in the query arguments, the operation will create a
4224  mutable file, and return its write-cap in the HTTP respose. The default is
4225- to create an immutable file, returning the read-cap as a response.
4226+ to create an immutable file, returning the read-cap as a response. If
4227+ you create a mutable file, you can also use the "mutable-type" query
4228+ parameter. If "mutable-type=sdmf", then the mutable file will be created
4229+ in the old SDMF mutable file format. This is desirable for files that
4230+ need to be read by old clients. If "mutable-type=mdmf", then the file
4231+ will be created in the new MDMF mutable file format. MDMF mutable files
4232+ can be downloaded more efficiently, and modified in-place efficiently,
4233+ but are not compatible with older versions of Tahoe-LAFS. If no
4234+ "mutable-type" argument is given, the file is created in whatever
4235+ format was configured in tahoe.cfg.
4236 
4237 Creating A New Directory
4238 ------------------------
4239hunk ./docs/frontends/webapi.rst 1082
4240  If a "mutable=true" argument is provided, the operation will create a
4241  mutable file, and the response body will contain the write-cap instead of
4242  the upload results page. The default is to create an immutable file,
4243- returning the upload results page as a response.
4244+ returning the upload results page as a response. If you create a
4245+ mutable file, you may choose to specify the format of that mutable file
4246+ with the "mutable-type" parameter. If "mutable-type=mdmf", then the
4247+ file will be created as an MDMF mutable file. If "mutable-type=sdmf",
4248+ then the file will be created as an SDMF mutable file. If no value is
4249+ specified, the file will be created in whatever format is specified in
4250+ tahoe.cfg.
4251 
4252 
4253 ``POST /uri/$DIRCAP/[SUBDIRS../]?t=upload``
4254}
4255[mutable/layout.py and interfaces.py: add MDMF writer and reader
4256Kevan Carstensen <kevan@isnotajoke.com>**20100819003304
4257 Ignore-this: 44400fec923987b62830da2ed5075fb4
4258 
4259 The MDMF writer is responsible for keeping state as plaintext is
4260 gradually processed into share data by the upload process. When the
4261 upload finishes, it will write all of its share data to a remote server,
4262 reporting its status back to the publisher.
4263 
4264 The MDMF reader is responsible for abstracting an MDMF file as it sits
4265 on the grid from the downloader; specifically, by receiving and
4266 responding to requests for arbitrary data within the MDMF file.
4267 
4268 The interfaces.py file has also been modified to contain an interface
4269 for the writer.
4270] {
4271hunk ./src/allmydata/interfaces.py 7
4272      ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
4273 
4274 HASH_SIZE=32
4275+SALT_SIZE=16
4276+
4277+SDMF_VERSION=0
4278+MDMF_VERSION=1
4279 
4280 Hash = StringConstraint(maxLength=HASH_SIZE,
4281                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
4282hunk ./src/allmydata/interfaces.py 424
4283         """
4284 
4285 
4286+class IMutableSlotWriter(Interface):
4287+    """
4288+    The interface for a writer around a mutable slot on a remote server.
4289+    """
4290+    def set_checkstring(checkstring, *args):
4291+        """
4292+        Set the checkstring that I will pass to the remote server when
4293+        writing.
4294+
4295+            @param checkstring A packed checkstring to use.
4296+
4297+        Note that implementations can differ in which semantics they
4298+        wish to support for set_checkstring -- they can, for example,
4299+        build the checkstring themselves from its constituents, or
4300+        some other thing.
4301+        """
4302+
4303+    def get_checkstring():
4304+        """
4305+        Get the checkstring that I think currently exists on the remote
4306+        server.
4307+        """
4308+
4309+    def put_block(data, segnum, salt):
4310+        """
4311+        Add a block and salt to the share.
4312+        """
4313+
4314+    def put_encprivey(encprivkey):
4315+        """
4316+        Add the encrypted private key to the share.
4317+        """
4318+
4319+    def put_blockhashes(blockhashes=list):
4320+        """
4321+        Add the block hash tree to the share.
4322+        """
4323+
4324+    def put_sharehashes(sharehashes=dict):
4325+        """
4326+        Add the share hash chain to the share.
4327+        """
4328+
4329+    def get_signable():
4330+        """
4331+        Return the part of the share that needs to be signed.
4332+        """
4333+
4334+    def put_signature(signature):
4335+        """
4336+        Add the signature to the share.
4337+        """
4338+
4339+    def put_verification_key(verification_key):
4340+        """
4341+        Add the verification key to the share.
4342+        """
4343+
4344+    def finish_publishing():
4345+        """
4346+        Do anything necessary to finish writing the share to a remote
4347+        server. I require that no further publishing needs to take place
4348+        after this method has been called.
4349+        """
4350+
4351+
4352 class IURI(Interface):
4353     def init_from_string(uri):
4354         """Accept a string (as created by my to_string() method) and populate
4355hunk ./src/allmydata/mutable/layout.py 4
4356 
4357 import struct
4358 from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError
4359+from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \
4360+                                 MDMF_VERSION, IMutableSlotWriter
4361+from allmydata.util import mathutil, observer
4362+from twisted.python import failure
4363+from twisted.internet import defer
4364+from zope.interface import implements
4365+
4366+
4367+# These strings describe the format of the packed structs they help process
4368+# Here's what they mean:
4369+#
4370+#  PREFIX:
4371+#    >: Big-endian byte order; the most significant byte is first (leftmost).
4372+#    B: The version information; an 8 bit version identifier. Stored as
4373+#       an unsigned char. This is currently 00 00 00 00; our modifications
4374+#       will turn it into 00 00 00 01.
4375+#    Q: The sequence number; this is sort of like a revision history for
4376+#       mutable files; they start at 1 and increase as they are changed after
4377+#       being uploaded. Stored as an unsigned long long, which is 8 bytes in
4378+#       length.
4379+#  32s: The root hash of the share hash tree. We use sha-256d, so we use 32
4380+#       characters = 32 bytes to store the value.
4381+#  16s: The salt for the readkey. This is a 16-byte random value, stored as
4382+#       16 characters.
4383+#
4384+#  SIGNED_PREFIX additions, things that are covered by the signature:
4385+#    B: The "k" encoding parameter. We store this as an 8-bit character,
4386+#       which is convenient because our erasure coding scheme cannot
4387+#       encode if you ask for more than 255 pieces.
4388+#    B: The "N" encoding parameter. Stored as an 8-bit character for the
4389+#       same reasons as above.
4390+#    Q: The segment size of the uploaded file. This will essentially be the
4391+#       length of the file in SDMF. An unsigned long long, so we can store
4392+#       files of quite large size.
4393+#    Q: The data length of the uploaded file. Modulo padding, this will be
4394+#       the same of the data length field. Like the data length field, it is
4395+#       an unsigned long long and can be quite large.
4396+#
4397+#   HEADER additions:
4398+#     L: The offset of the signature of this. An unsigned long.
4399+#     L: The offset of the share hash chain. An unsigned long.
4400+#     L: The offset of the block hash tree. An unsigned long.
4401+#     L: The offset of the share data. An unsigned long.
4402+#     Q: The offset of the encrypted private key. An unsigned long long, to
4403+#        account for the possibility of a lot of share data.
4404+#     Q: The offset of the EOF. An unsigned long long, to account for the
4405+#        possibility of a lot of share data.
4406+#
4407+#  After all of these, we have the following:
4408+#    - The verification key: Occupies the space between the end of the header
4409+#      and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']].
4410+#    - The signature, which goes from the signature offset to the share hash
4411+#      chain offset.
4412+#    - The share hash chain, which goes from the share hash chain offset to
4413+#      the block hash tree offset.
4414+#    - The share data, which goes from the share data offset to the encrypted
4415+#      private key offset.
4416+#    - The encrypted private key offset, which goes until the end of the file.
4417+#
4418+#  The block hash tree in this encoding has only one share, so the offset of
4419+#  the share data will be 32 bits more than the offset of the block hash tree.
4420+#  Given this, we may need to check to see how many bytes a reasonably sized
4421+#  block hash tree will take up.
4422 
4423 PREFIX = ">BQ32s16s" # each version has a different prefix
4424 SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature
4425hunk ./src/allmydata/mutable/layout.py 73
4426 SIGNED_PREFIX_LENGTH = struct.calcsize(SIGNED_PREFIX)
4427 HEADER = ">BQ32s16s BBQQ LLLLQQ" # includes offsets
4428 HEADER_LENGTH = struct.calcsize(HEADER)
4429+OFFSETS = ">LLLLQQ"
4430+OFFSETS_LENGTH = struct.calcsize(OFFSETS)
4431 
4432hunk ./src/allmydata/mutable/layout.py 76
4433+# These are still used for some tests.
4434 def unpack_header(data):
4435     o = {}
4436     (version,
4437hunk ./src/allmydata/mutable/layout.py 92
4438      o['EOF']) = struct.unpack(HEADER, data[:HEADER_LENGTH])
4439     return (version, seqnum, root_hash, IV, k, N, segsize, datalen, o)
4440 
4441-def unpack_prefix_and_signature(data):
4442-    assert len(data) >= HEADER_LENGTH, len(data)
4443-    prefix = data[:SIGNED_PREFIX_LENGTH]
4444-
4445-    (version,
4446-     seqnum,
4447-     root_hash,
4448-     IV,
4449-     k, N, segsize, datalen,
4450-     o) = unpack_header(data)
4451-
4452-    if version != 0:
4453-        raise UnknownVersionError("got mutable share version %d, but I only understand version 0" % version)
4454-
4455-    if len(data) < o['share_hash_chain']:
4456-        raise NeedMoreDataError(o['share_hash_chain'],
4457-                                o['enc_privkey'], o['EOF']-o['enc_privkey'])
4458-
4459-    pubkey_s = data[HEADER_LENGTH:o['signature']]
4460-    signature = data[o['signature']:o['share_hash_chain']]
4461-
4462-    return (seqnum, root_hash, IV, k, N, segsize, datalen,
4463-            pubkey_s, signature, prefix)
4464-
4465 def unpack_share(data):
4466     assert len(data) >= HEADER_LENGTH
4467     o = {}
4468hunk ./src/allmydata/mutable/layout.py 139
4469             pubkey, signature, share_hash_chain, block_hash_tree,
4470             share_data, enc_privkey)
4471 
4472-def unpack_share_data(verinfo, hash_and_data):
4473-    (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, o_t) = verinfo
4474-
4475-    # hash_and_data starts with the share_hash_chain, so figure out what the
4476-    # offsets really are
4477-    o = dict(o_t)
4478-    o_share_hash_chain = 0
4479-    o_block_hash_tree = o['block_hash_tree'] - o['share_hash_chain']
4480-    o_share_data = o['share_data'] - o['share_hash_chain']
4481-    o_enc_privkey = o['enc_privkey'] - o['share_hash_chain']
4482-
4483-    share_hash_chain_s = hash_and_data[o_share_hash_chain:o_block_hash_tree]
4484-    share_hash_format = ">H32s"
4485-    hsize = struct.calcsize(share_hash_format)
4486-    assert len(share_hash_chain_s) % hsize == 0, len(share_hash_chain_s)
4487-    share_hash_chain = []
4488-    for i in range(0, len(share_hash_chain_s), hsize):
4489-        chunk = share_hash_chain_s[i:i+hsize]
4490-        (hid, h) = struct.unpack(share_hash_format, chunk)
4491-        share_hash_chain.append( (hid, h) )
4492-    share_hash_chain = dict(share_hash_chain)
4493-    block_hash_tree_s = hash_and_data[o_block_hash_tree:o_share_data]
4494-    assert len(block_hash_tree_s) % 32 == 0, len(block_hash_tree_s)
4495-    block_hash_tree = []
4496-    for i in range(0, len(block_hash_tree_s), 32):
4497-        block_hash_tree.append(block_hash_tree_s[i:i+32])
4498-
4499-    share_data = hash_and_data[o_share_data:o_enc_privkey]
4500-
4501-    return (share_hash_chain, block_hash_tree, share_data)
4502-
4503-
4504-def pack_checkstring(seqnum, root_hash, IV):
4505-    return struct.pack(PREFIX,
4506-                       0, # version,
4507-                       seqnum,
4508-                       root_hash,
4509-                       IV)
4510-
4511 def unpack_checkstring(checkstring):
4512     cs_len = struct.calcsize(PREFIX)
4513     version, seqnum, root_hash, IV = struct.unpack(PREFIX, checkstring[:cs_len])
4514hunk ./src/allmydata/mutable/layout.py 146
4515         raise UnknownVersionError("got mutable share version %d, but I only understand version 0" % version)
4516     return (seqnum, root_hash, IV)
4517 
4518-def pack_prefix(seqnum, root_hash, IV,
4519-                required_shares, total_shares,
4520-                segment_size, data_length):
4521-    prefix = struct.pack(SIGNED_PREFIX,
4522-                         0, # version,
4523-                         seqnum,
4524-                         root_hash,
4525-                         IV,
4526-
4527-                         required_shares,
4528-                         total_shares,
4529-                         segment_size,
4530-                         data_length,
4531-                         )
4532-    return prefix
4533 
4534 def pack_offsets(verification_key_length, signature_length,
4535                  share_hash_chain_length, block_hash_tree_length,
4536hunk ./src/allmydata/mutable/layout.py 192
4537                            encprivkey])
4538     return final_share
4539 
4540+def pack_prefix(seqnum, root_hash, IV,
4541+                required_shares, total_shares,
4542+                segment_size, data_length):
4543+    prefix = struct.pack(SIGNED_PREFIX,
4544+                         0, # version,
4545+                         seqnum,
4546+                         root_hash,
4547+                         IV,
4548+                         required_shares,
4549+                         total_shares,
4550+                         segment_size,
4551+                         data_length,
4552+                         )
4553+    return prefix
4554+
4555+
4556+class SDMFSlotWriteProxy:
4557+    implements(IMutableSlotWriter)
4558+    """
4559+    I represent a remote write slot for an SDMF mutable file. I build a
4560+    share in memory, and then write it in one piece to the remote
4561+    server. This mimics how SDMF shares were built before MDMF (and the
4562+    new MDMF uploader), but provides that functionality in a way that
4563+    allows the MDMF uploader to be built without much special-casing for
4564+    file format, which makes the uploader code more readable.
4565+    """
4566+    def __init__(self,
4567+                 shnum,
4568+                 rref, # a remote reference to a storage server
4569+                 storage_index,
4570+                 secrets, # (write_enabler, renew_secret, cancel_secret)
4571+                 seqnum, # the sequence number of the mutable file
4572+                 required_shares,
4573+                 total_shares,
4574+                 segment_size,
4575+                 data_length): # the length of the original file
4576+        self.shnum = shnum
4577+        self._rref = rref
4578+        self._storage_index = storage_index
4579+        self._secrets = secrets
4580+        self._seqnum = seqnum
4581+        self._required_shares = required_shares
4582+        self._total_shares = total_shares
4583+        self._segment_size = segment_size
4584+        self._data_length = data_length
4585+
4586+        # This is an SDMF file, so it should have only one segment, so,
4587+        # modulo padding of the data length, the segment size and the
4588+        # data length should be the same.
4589+        expected_segment_size = mathutil.next_multiple(data_length,
4590+                                                       self._required_shares)
4591+        assert expected_segment_size == segment_size
4592+
4593+        self._block_size = self._segment_size / self._required_shares
4594+
4595+        # This is meant to mimic how SDMF files were built before MDMF
4596+        # entered the picture: we generate each share in its entirety,
4597+        # then push it off to the storage server in one write. When
4598+        # callers call set_*, they are just populating this dict.
4599+        # finish_publishing will stitch these pieces together into a
4600+        # coherent share, and then write the coherent share to the
4601+        # storage server.
4602+        self._share_pieces = {}
4603+
4604+        # This tells the write logic what checkstring to use when
4605+        # writing remote shares.
4606+        self._testvs = []
4607+
4608+        self._readvs = [(0, struct.calcsize(PREFIX))]
4609+
4610+
4611+    def set_checkstring(self, checkstring_or_seqnum,
4612+                              root_hash=None,
4613+                              salt=None):
4614+        """
4615+        Set the checkstring that I will pass to the remote server when
4616+        writing.
4617+
4618+            @param checkstring_or_seqnum: A packed checkstring to use,
4619+                   or a sequence number. I will treat this as a checkstr
4620+
4621+        Note that implementations can differ in which semantics they
4622+        wish to support for set_checkstring -- they can, for example,
4623+        build the checkstring themselves from its constituents, or
4624+        some other thing.
4625+        """
4626+        if root_hash and salt:
4627+            checkstring = struct.pack(PREFIX,
4628+                                      0,
4629+                                      checkstring_or_seqnum,
4630+                                      root_hash,
4631+                                      salt)
4632+        else:
4633+            checkstring = checkstring_or_seqnum
4634+        self._testvs = [(0, len(checkstring), "eq", checkstring)]
4635+
4636+
4637+    def get_checkstring(self):
4638+        """
4639+        Get the checkstring that I think currently exists on the remote
4640+        server.
4641+        """
4642+        if self._testvs:
4643+            return self._testvs[0][3]
4644+        return ""
4645+
4646+
4647+    def put_block(self, data, segnum, salt):
4648+        """
4649+        Add a block and salt to the share.
4650+        """
4651+        # SDMF files have only one segment
4652+        assert segnum == 0
4653+        assert len(data) == self._block_size
4654+        assert len(salt) == SALT_SIZE
4655+
4656+        self._share_pieces['sharedata'] = data
4657+        self._share_pieces['salt'] = salt
4658+
4659+        # TODO: Figure out something intelligent to return.
4660+        return defer.succeed(None)
4661+
4662+
4663+    def put_encprivkey(self, encprivkey):
4664+        """
4665+        Add the encrypted private key to the share.
4666+        """
4667+        self._share_pieces['encprivkey'] = encprivkey
4668+
4669+        return defer.succeed(None)
4670+
4671+
4672+    def put_blockhashes(self, blockhashes):
4673+        """
4674+        Add the block hash tree to the share.
4675+        """
4676+        assert isinstance(blockhashes, list)
4677+        for h in blockhashes:
4678+            assert len(h) == HASH_SIZE
4679+
4680+        # serialize the blockhashes, then set them.
4681+        blockhashes_s = "".join(blockhashes)
4682+        self._share_pieces['block_hash_tree'] = blockhashes_s
4683+
4684+        return defer.succeed(None)
4685+
4686+
4687+    def put_sharehashes(self, sharehashes):
4688+        """
4689+        Add the share hash chain to the share.
4690+        """
4691+        assert isinstance(sharehashes, dict)
4692+        for h in sharehashes.itervalues():
4693+            assert len(h) == HASH_SIZE
4694+
4695+        # serialize the sharehashes, then set them.
4696+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
4697+                                 for i in sorted(sharehashes.keys())])
4698+        self._share_pieces['share_hash_chain'] = sharehashes_s
4699+
4700+        return defer.succeed(None)
4701+
4702+
4703+    def put_root_hash(self, root_hash):
4704+        """
4705+        Add the root hash to the share.
4706+        """
4707+        assert len(root_hash) == HASH_SIZE
4708+
4709+        self._share_pieces['root_hash'] = root_hash
4710+
4711+        return defer.succeed(None)
4712+
4713+
4714+    def put_salt(self, salt):
4715+        """
4716+        Add a salt to an empty SDMF file.
4717+        """
4718+        assert len(salt) == SALT_SIZE
4719+
4720+        self._share_pieces['salt'] = salt
4721+        self._share_pieces['sharedata'] = ""
4722+
4723+
4724+    def get_signable(self):
4725+        """
4726+        Return the part of the share that needs to be signed.
4727+
4728+        SDMF writers need to sign the packed representation of the
4729+        first eight fields of the remote share, that is:
4730+            - version number (0)
4731+            - sequence number
4732+            - root of the share hash tree
4733+            - salt
4734+            - k
4735+            - n
4736+            - segsize
4737+            - datalen
4738+
4739+        This method is responsible for returning that to callers.
4740+        """
4741+        return struct.pack(SIGNED_PREFIX,
4742+                           0,
4743+                           self._seqnum,
4744+                           self._share_pieces['root_hash'],
4745+                           self._share_pieces['salt'],
4746+                           self._required_shares,
4747+                           self._total_shares,
4748+                           self._segment_size,
4749+                           self._data_length)
4750+
4751+
4752+    def put_signature(self, signature):
4753+        """
4754+        Add the signature to the share.
4755+        """
4756+        self._share_pieces['signature'] = signature
4757+
4758+        return defer.succeed(None)
4759+
4760+
4761+    def put_verification_key(self, verification_key):
4762+        """
4763+        Add the verification key to the share.
4764+        """
4765+        self._share_pieces['verification_key'] = verification_key
4766+
4767+        return defer.succeed(None)
4768+
4769+
4770+    def get_verinfo(self):
4771+        """
4772+        I return my verinfo tuple. This is used by the ServermapUpdater
4773+        to keep track of versions of mutable files.
4774+
4775+        The verinfo tuple for MDMF files contains:
4776+            - seqnum
4777+            - root hash
4778+            - a blank (nothing)
4779+            - segsize
4780+            - datalen
4781+            - k
4782+            - n
4783+            - prefix (the thing that you sign)
4784+            - a tuple of offsets
4785+
4786+        We include the nonce in MDMF to simplify processing of version
4787+        information tuples.
4788+
4789+        The verinfo tuple for SDMF files is the same, but contains a
4790+        16-byte IV instead of a hash of salts.
4791+        """
4792+        return (self._seqnum,
4793+                self._share_pieces['root_hash'],
4794+                self._share_pieces['salt'],
4795+                self._segment_size,
4796+                self._data_length,
4797+                self._required_shares,
4798+                self._total_shares,
4799+                self.get_signable(),
4800+                self._get_offsets_tuple())
4801+
4802+    def _get_offsets_dict(self):
4803+        post_offset = HEADER_LENGTH
4804+        offsets = {}
4805+
4806+        verification_key_length = len(self._share_pieces['verification_key'])
4807+        o1 = offsets['signature'] = post_offset + verification_key_length
4808+
4809+        signature_length = len(self._share_pieces['signature'])
4810+        o2 = offsets['share_hash_chain'] = o1 + signature_length
4811+
4812+        share_hash_chain_length = len(self._share_pieces['share_hash_chain'])
4813+        o3 = offsets['block_hash_tree'] = o2 + share_hash_chain_length
4814+
4815+        block_hash_tree_length = len(self._share_pieces['block_hash_tree'])
4816+        o4 = offsets['share_data'] = o3 + block_hash_tree_length
4817+
4818+        share_data_length = len(self._share_pieces['sharedata'])
4819+        o5 = offsets['enc_privkey'] = o4 + share_data_length
4820+
4821+        encprivkey_length = len(self._share_pieces['encprivkey'])
4822+        offsets['EOF'] = o5 + encprivkey_length
4823+        return offsets
4824+
4825+
4826+    def _get_offsets_tuple(self):
4827+        offsets = self._get_offsets_dict()
4828+        return tuple([(key, value) for key, value in offsets.items()])
4829+
4830+
4831+    def _pack_offsets(self):
4832+        offsets = self._get_offsets_dict()
4833+        return struct.pack(">LLLLQQ",
4834+                           offsets['signature'],
4835+                           offsets['share_hash_chain'],
4836+                           offsets['block_hash_tree'],
4837+                           offsets['share_data'],
4838+                           offsets['enc_privkey'],
4839+                           offsets['EOF'])
4840+
4841+
4842+    def finish_publishing(self):
4843+        """
4844+        Do anything necessary to finish writing the share to a remote
4845+        server. I require that no further publishing needs to take place
4846+        after this method has been called.
4847+        """
4848+        for k in ["sharedata", "encprivkey", "signature", "verification_key",
4849+                  "share_hash_chain", "block_hash_tree"]:
4850+            assert k in self._share_pieces
4851+        # This is the only method that actually writes something to the
4852+        # remote server.
4853+        # First, we need to pack the share into data that we can write
4854+        # to the remote server in one write.
4855+        offsets = self._pack_offsets()
4856+        prefix = self.get_signable()
4857+        final_share = "".join([prefix,
4858+                               offsets,
4859+                               self._share_pieces['verification_key'],
4860+                               self._share_pieces['signature'],
4861+                               self._share_pieces['share_hash_chain'],
4862+                               self._share_pieces['block_hash_tree'],
4863+                               self._share_pieces['sharedata'],
4864+                               self._share_pieces['encprivkey']])
4865+
4866+        # Our only data vector is going to be writing the final share,
4867+        # in its entirely.
4868+        datavs = [(0, final_share)]
4869+
4870+        if not self._testvs:
4871+            # Our caller has not provided us with another checkstring
4872+            # yet, so we assume that we are writing a new share, and set
4873+            # a test vector that will allow a new share to be written.
4874+            self._testvs = []
4875+            self._testvs.append(tuple([0, 1, "eq", ""]))
4876+
4877+        tw_vectors = {}
4878+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
4879+        return self._rref.callRemote("slot_testv_and_readv_and_writev",
4880+                                     self._storage_index,
4881+                                     self._secrets,
4882+                                     tw_vectors,
4883+                                     # TODO is it useful to read something?
4884+                                     self._readvs)
4885+
4886+
4887+MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
4888+MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
4889+MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
4890+MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
4891+MDMFCHECKSTRING = ">BQ32s"
4892+MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
4893+MDMFOFFSETS = ">QQQQQQ"
4894+MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
4895+
4896+class MDMFSlotWriteProxy:
4897+    implements(IMutableSlotWriter)
4898+
4899+    """
4900+    I represent a remote write slot for an MDMF mutable file.
4901+
4902+    I abstract away from my caller the details of block and salt
4903+    management, and the implementation of the on-disk format for MDMF
4904+    shares.
4905+    """
4906+    # Expected layout, MDMF:
4907+    # offset:     size:       name:
4908+    #-- signed part --
4909+    # 0           1           version number (01)
4910+    # 1           8           sequence number
4911+    # 9           32          share tree root hash
4912+    # 41          1           The "k" encoding parameter
4913+    # 42          1           The "N" encoding parameter
4914+    # 43          8           The segment size of the uploaded file
4915+    # 51          8           The data length of the original plaintext
4916+    #-- end signed part --
4917+    # 59          8           The offset of the encrypted private key
4918+    # 83          8           The offset of the signature
4919+    # 91          8           The offset of the verification key
4920+    # 67          8           The offset of the block hash tree
4921+    # 75          8           The offset of the share hash chain
4922+    # 99          8           The offset of the EOF
4923+    #
4924+    # followed by salts and share data, the encrypted private key, the
4925+    # block hash tree, the salt hash tree, the share hash chain, a
4926+    # signature over the first eight fields, and a verification key.
4927+    #
4928+    # The checkstring is the first three fields -- the version number,
4929+    # sequence number, root hash and root salt hash. This is consistent
4930+    # in meaning to what we have with SDMF files, except now instead of
4931+    # using the literal salt, we use a value derived from all of the
4932+    # salts -- the share hash root.
4933+    #
4934+    # The salt is stored before the block for each segment. The block
4935+    # hash tree is computed over the combination of block and salt for
4936+    # each segment. In this way, we get integrity checking for both
4937+    # block and salt with the current block hash tree arrangement.
4938+    #
4939+    # The ordering of the offsets is different to reflect the dependencies
4940+    # that we'll run into with an MDMF file. The expected write flow is
4941+    # something like this:
4942+    #
4943+    #   0: Initialize with the sequence number, encoding parameters and
4944+    #      data length. From this, we can deduce the number of segments,
4945+    #      and where they should go.. We can also figure out where the
4946+    #      encrypted private key should go, because we can figure out how
4947+    #      big the share data will be.
4948+    #
4949+    #   1: Encrypt, encode, and upload the file in chunks. Do something
4950+    #      like
4951+    #
4952+    #       put_block(data, segnum, salt)
4953+    #
4954+    #      to write a block and a salt to the disk. We can do both of
4955+    #      these operations now because we have enough of the offsets to
4956+    #      know where to put them.
4957+    #
4958+    #   2: Put the encrypted private key. Use:
4959+    #
4960+    #        put_encprivkey(encprivkey)
4961+    #
4962+    #      Now that we know the length of the private key, we can fill
4963+    #      in the offset for the block hash tree.
4964+    #
4965+    #   3: We're now in a position to upload the block hash tree for
4966+    #      a share. Put that using something like:
4967+    #       
4968+    #        put_blockhashes(block_hash_tree)
4969+    #
4970+    #      Note that block_hash_tree is a list of hashes -- we'll take
4971+    #      care of the details of serializing that appropriately. When
4972+    #      we get the block hash tree, we are also in a position to
4973+    #      calculate the offset for the share hash chain, and fill that
4974+    #      into the offsets table.
4975+    #
4976+    #   4: At the same time, we're in a position to upload the salt hash
4977+    #      tree. This is a Merkle tree over all of the salts. We use a
4978+    #      Merkle tree so that we can validate each block,salt pair as
4979+    #      we download them later. We do this using
4980+    #
4981+    #        put_salthashes(salt_hash_tree)
4982+    #
4983+    #      When you do this, I automatically put the root of the tree
4984+    #      (the hash at index 0 of the list) in its appropriate slot in
4985+    #      the signed prefix of the share.
4986+    #
4987+    #   5: We're now in a position to upload the share hash chain for
4988+    #      a share. Do that with something like:
4989+    #     
4990+    #        put_sharehashes(share_hash_chain)
4991+    #
4992+    #      share_hash_chain should be a dictionary mapping shnums to
4993+    #      32-byte hashes -- the wrapper handles serialization.
4994+    #      We'll know where to put the signature at this point, also.
4995+    #      The root of this tree will be put explicitly in the next
4996+    #      step.
4997+    #
4998+    #      TODO: Why? Why not just include it in the tree here?
4999+    #
5000+    #   6: Before putting the signature, we must first put the
5001+    #      root_hash. Do this with:
5002+    #
5003+    #        put_root_hash(root_hash).
5004+    #     
5005+    #      In terms of knowing where to put this value, it was always
5006+    #      possible to place it, but it makes sense semantically to
5007+    #      place it after the share hash tree, so that's why you do it
5008+    #      in this order.
5009+    #
5010+    #   6: With the root hash put, we can now sign the header. Use:
5011+    #
5012+    #        get_signable()
5013+    #
5014+    #      to get the part of the header that you want to sign, and use:
5015+    #       
5016+    #        put_signature(signature)
5017+    #
5018+    #      to write your signature to the remote server.
5019+    #
5020+    #   6: Add the verification key, and finish. Do:
5021+    #
5022+    #        put_verification_key(key)
5023+    #
5024+    #      and
5025+    #
5026+    #        finish_publish()
5027+    #
5028+    # Checkstring management:
5029+    #
5030+    # To write to a mutable slot, we have to provide test vectors to ensure
5031+    # that we are writing to the same data that we think we are. These
5032+    # vectors allow us to detect uncoordinated writes; that is, writes
5033+    # where both we and some other shareholder are writing to the
5034+    # mutable slot, and to report those back to the parts of the program
5035+    # doing the writing.
5036+    #
5037+    # With SDMF, this was easy -- all of the share data was written in
5038+    # one go, so it was easy to detect uncoordinated writes, and we only
5039+    # had to do it once. With MDMF, not all of the file is written at
5040+    # once.
5041+    #
5042+    # If a share is new, we write out as much of the header as we can
5043+    # before writing out anything else. This gives other writers a
5044+    # canary that they can use to detect uncoordinated writes, and, if
5045+    # they do the same thing, gives us the same canary. We them update
5046+    # the share. We won't be able to write out two fields of the header
5047+    # -- the share tree hash and the salt hash -- until we finish
5048+    # writing out the share. We only require the writer to provide the
5049+    # initial checkstring, and keep track of what it should be after
5050+    # updates ourselves.
5051+    #
5052+    # If we haven't written anything yet, then on the first write (which
5053+    # will probably be a block + salt of a share), we'll also write out
5054+    # the header. On subsequent passes, we'll expect to see the header.
5055+    # This changes in two places:
5056+    #
5057+    #   - When we write out the salt hash
5058+    #   - When we write out the root of the share hash tree
5059+    #
5060+    # since these values will change the header. It is possible that we
5061+    # can just make those be written in one operation to minimize
5062+    # disruption.
5063+    def __init__(self,
5064+                 shnum,
5065+                 rref, # a remote reference to a storage server
5066+                 storage_index,
5067+                 secrets, # (write_enabler, renew_secret, cancel_secret)
5068+                 seqnum, # the sequence number of the mutable file
5069+                 required_shares,
5070+                 total_shares,
5071+                 segment_size,
5072+                 data_length): # the length of the original file
5073+        self.shnum = shnum
5074+        self._rref = rref
5075+        self._storage_index = storage_index
5076+        self._seqnum = seqnum
5077+        self._required_shares = required_shares
5078+        assert self.shnum >= 0 and self.shnum < total_shares
5079+        self._total_shares = total_shares
5080+        # We build up the offset table as we write things. It is the
5081+        # last thing we write to the remote server.
5082+        self._offsets = {}
5083+        self._testvs = []
5084+        # This is a list of write vectors that will be sent to our
5085+        # remote server once we are directed to write things there.
5086+        self._writevs = []
5087+        self._secrets = secrets
5088+        # The segment size needs to be a multiple of the k parameter --
5089+        # any padding should have been carried out by the publisher
5090+        # already.
5091+        assert segment_size % required_shares == 0
5092+        self._segment_size = segment_size
5093+        self._data_length = data_length
5094+
5095+        # These are set later -- we define them here so that we can
5096+        # check for their existence easily
5097+
5098+        # This is the root of the share hash tree -- the Merkle tree
5099+        # over the roots of the block hash trees computed for shares in
5100+        # this upload.
5101+        self._root_hash = None
5102+
5103+        # We haven't yet written anything to the remote bucket. By
5104+        # setting this, we tell the _write method as much. The write
5105+        # method will then know that it also needs to add a write vector
5106+        # for the checkstring (or what we have of it) to the first write
5107+        # request. We'll then record that value for future use.  If
5108+        # we're expecting something to be there already, we need to call
5109+        # set_checkstring before we write anything to tell the first
5110+        # write about that.
5111+        self._written = False
5112+
5113+        # When writing data to the storage servers, we get a read vector
5114+        # for free. We'll read the checkstring, which will help us
5115+        # figure out what's gone wrong if a write fails.
5116+        self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))]
5117+
5118+        # We calculate the number of segments because it tells us
5119+        # where the salt part of the file ends/share segment begins,
5120+        # and also because it provides a useful amount of bounds checking.
5121+        self._num_segments = mathutil.div_ceil(self._data_length,
5122+                                               self._segment_size)
5123+        self._block_size = self._segment_size / self._required_shares
5124+        # We also calculate the share size, to help us with block
5125+        # constraints later.
5126+        tail_size = self._data_length % self._segment_size
5127+        if not tail_size:
5128+            self._tail_block_size = self._block_size
5129+        else:
5130+            self._tail_block_size = mathutil.next_multiple(tail_size,
5131+                                                           self._required_shares)
5132+            self._tail_block_size /= self._required_shares
5133+
5134+        # We already know where the sharedata starts; right after the end
5135+        # of the header (which is defined as the signable part + the offsets)
5136+        # We can also calculate where the encrypted private key begins
5137+        # from what we know know.
5138+        self._actual_block_size = self._block_size + SALT_SIZE
5139+        data_size = self._actual_block_size * (self._num_segments - 1)
5140+        data_size += self._tail_block_size
5141+        data_size += SALT_SIZE
5142+        self._offsets['enc_privkey'] = MDMFHEADERSIZE
5143+        self._offsets['enc_privkey'] += data_size
5144+        # We'll wait for the rest. Callers can now call my "put_block" and
5145+        # "set_checkstring" methods.
5146+
5147+
5148+    def set_checkstring(self,
5149+                        seqnum_or_checkstring,
5150+                        root_hash=None,
5151+                        salt=None):
5152+        """
5153+        Set checkstring checkstring for the given shnum.
5154+
5155+        This can be invoked in one of two ways.
5156+
5157+        With one argument, I assume that you are giving me a literal
5158+        checkstring -- e.g., the output of get_checkstring. I will then
5159+        set that checkstring as it is. This form is used by unit tests.
5160+
5161+        With two arguments, I assume that you are giving me a sequence
5162+        number and root hash to make a checkstring from. In that case, I
5163+        will build a checkstring and set it for you. This form is used
5164+        by the publisher.
5165+
5166+        By default, I assume that I am writing new shares to the grid.
5167+        If you don't explcitly set your own checkstring, I will use
5168+        one that requires that the remote share not exist. You will want
5169+        to use this method if you are updating a share in-place;
5170+        otherwise, writes will fail.
5171+        """
5172+        # You're allowed to overwrite checkstrings with this method;
5173+        # I assume that users know what they are doing when they call
5174+        # it.
5175+        if root_hash:
5176+            checkstring = struct.pack(MDMFCHECKSTRING,
5177+                                      1,
5178+                                      seqnum_or_checkstring,
5179+                                      root_hash)
5180+        else:
5181+            checkstring = seqnum_or_checkstring
5182+
5183+        if checkstring == "":
5184+            # We special-case this, since len("") = 0, but we need
5185+            # length of 1 for the case of an empty share to work on the
5186+            # storage server, which is what a checkstring that is the
5187+            # empty string means.
5188+            self._testvs = []
5189+        else:
5190+            self._testvs = []
5191+            self._testvs.append((0, len(checkstring), "eq", checkstring))
5192+
5193+
5194+    def __repr__(self):
5195+        return "MDMFSlotWriteProxy for share %d" % self.shnum
5196+
5197+
5198+    def get_checkstring(self):
5199+        """
5200+        Given a share number, I return a representation of what the
5201+        checkstring for that share on the server will look like.
5202+
5203+        I am mostly used for tests.
5204+        """
5205+        if self._root_hash:
5206+            roothash = self._root_hash
5207+        else:
5208+            roothash = "\x00" * 32
5209+        return struct.pack(MDMFCHECKSTRING,
5210+                           1,
5211+                           self._seqnum,
5212+                           roothash)
5213+
5214+
5215+    def put_block(self, data, segnum, salt):
5216+        """
5217+        I queue a write vector for the data, salt, and segment number
5218+        provided to me. I return None, as I do not actually cause
5219+        anything to be written yet.
5220+        """
5221+        if segnum >= self._num_segments:
5222+            raise LayoutInvalid("I won't overwrite the private key")
5223+        if len(salt) != SALT_SIZE:
5224+            raise LayoutInvalid("I was given a salt of size %d, but "
5225+                                "I wanted a salt of size %d")
5226+        if segnum + 1 == self._num_segments:
5227+            if len(data) != self._tail_block_size:
5228+                raise LayoutInvalid("I was given the wrong size block to write")
5229+        elif len(data) != self._block_size:
5230+            raise LayoutInvalid("I was given the wrong size block to write")
5231+
5232+        # We want to write at len(MDMFHEADER) + segnum * block_size.
5233+
5234+        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
5235+        data = salt + data
5236+
5237+        self._writevs.append(tuple([offset, data]))
5238+
5239+
5240+    def put_encprivkey(self, encprivkey):
5241+        """
5242+        I queue a write vector for the encrypted private key provided to
5243+        me.
5244+        """
5245+        assert self._offsets
5246+        assert self._offsets['enc_privkey']
5247+        # You shouldn't re-write the encprivkey after the block hash
5248+        # tree is written, since that could cause the private key to run
5249+        # into the block hash tree. Before it writes the block hash
5250+        # tree, the block hash tree writing method writes the offset of
5251+        # the salt hash tree. So that's a good indicator of whether or
5252+        # not the block hash tree has been written.
5253+        if "share_hash_chain" in self._offsets:
5254+            raise LayoutInvalid("You must write this before the block hash tree")
5255+
5256+        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
5257+            len(encprivkey)
5258+        self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
5259+
5260+
5261+    def put_blockhashes(self, blockhashes):
5262+        """
5263+        I queue a write vector to put the block hash tree in blockhashes
5264+        onto the remote server.
5265+
5266+        The encrypted private key must be queued before the block hash
5267+        tree, since we need to know how large it is to know where the
5268+        block hash tree should go. The block hash tree must be put
5269+        before the salt hash tree, since its size determines the
5270+        offset of the share hash chain.
5271+        """
5272+        assert self._offsets
5273+        assert isinstance(blockhashes, list)
5274+        if "block_hash_tree" not in self._offsets:
5275+            raise LayoutInvalid("You must put the encrypted private key "
5276+                                "before you put the block hash tree")
5277+        # If written, the share hash chain causes the signature offset
5278+        # to be defined.
5279+        if "signature" in self._offsets:
5280+            raise LayoutInvalid("You must put the block hash tree before "
5281+                                "you put the share hash chain")
5282+        blockhashes_s = "".join(blockhashes)
5283+        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
5284+
5285+        self._writevs.append(tuple([self._offsets['block_hash_tree'],
5286+                                  blockhashes_s]))
5287+
5288+
5289+    def put_sharehashes(self, sharehashes):
5290+        """
5291+        I queue a write vector to put the share hash chain in my
5292+        argument onto the remote server.
5293+
5294+        The salt hash tree must be queued before the share hash chain,
5295+        since we need to know where the salt hash tree ends before we
5296+        can know where the share hash chain starts. The share hash chain
5297+        must be put before the signature, since the length of the packed
5298+        share hash chain determines the offset of the signature. Also,
5299+        semantically, you must know what the root of the salt hash tree
5300+        is before you can generate a valid signature.
5301+        """
5302+        assert isinstance(sharehashes, dict)
5303+        if "share_hash_chain" not in self._offsets:
5304+            raise LayoutInvalid("You need to put the salt hash tree before "
5305+                                "you can put the share hash chain")
5306+        # The signature comes after the share hash chain. If the
5307+        # signature has already been written, we must not write another
5308+        # share hash chain. The signature writes the verification key
5309+        # offset when it gets sent to the remote server, so we look for
5310+        # that.
5311+        if "verification_key" in self._offsets:
5312+            raise LayoutInvalid("You must write the share hash chain "
5313+                                "before you write the signature")
5314+        sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
5315+                                  for i in sorted(sharehashes.keys())])
5316+        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
5317+        self._writevs.append(tuple([self._offsets['share_hash_chain'],
5318+                            sharehashes_s]))
5319+
5320+
5321+    def put_root_hash(self, roothash):
5322+        """
5323+        Put the root hash (the root of the share hash tree) in the
5324+        remote slot.
5325+        """
5326+        # It does not make sense to be able to put the root
5327+        # hash without first putting the share hashes, since you need
5328+        # the share hashes to generate the root hash.
5329+        #
5330+        # Signature is defined by the routine that places the share hash
5331+        # chain, so it's a good thing to look for in finding out whether
5332+        # or not the share hash chain exists on the remote server.
5333+        if "signature" not in self._offsets:
5334+            raise LayoutInvalid("You need to put the share hash chain "
5335+                                "before you can put the root share hash")
5336+        if len(roothash) != HASH_SIZE:
5337+            raise LayoutInvalid("hashes and salts must be exactly %d bytes"
5338+                                 % HASH_SIZE)
5339+        self._root_hash = roothash
5340+        # To write both of these values, we update the checkstring on
5341+        # the remote server, which includes them
5342+        checkstring = self.get_checkstring()
5343+        self._writevs.append(tuple([0, checkstring]))
5344+        # This write, if successful, changes the checkstring, so we need
5345+        # to update our internal checkstring to be consistent with the
5346+        # one on the server.
5347+
5348+
5349+    def get_signable(self):
5350+        """
5351+        Get the first seven fields of the mutable file; the parts that
5352+        are signed.
5353+        """
5354+        if not self._root_hash:
5355+            raise LayoutInvalid("You need to set the root hash "
5356+                                "before getting something to "
5357+                                "sign")
5358+        return struct.pack(MDMFSIGNABLEHEADER,
5359+                           1,
5360+                           self._seqnum,
5361+                           self._root_hash,
5362+                           self._required_shares,
5363+                           self._total_shares,
5364+                           self._segment_size,
5365+                           self._data_length)
5366+
5367+
5368+    def put_signature(self, signature):
5369+        """
5370+        I queue a write vector for the signature of the MDMF share.
5371+
5372+        I require that the root hash and share hash chain have been put
5373+        to the grid before I will write the signature to the grid.
5374+        """
5375+        if "signature" not in self._offsets:
5376+            raise LayoutInvalid("You must put the share hash chain "
5377+        # It does not make sense to put a signature without first
5378+        # putting the root hash and the salt hash (since otherwise
5379+        # the signature would be incomplete), so we don't allow that.
5380+                       "before putting the signature")
5381+        if not self._root_hash:
5382+            raise LayoutInvalid("You must complete the signed prefix "
5383+                                "before computing a signature")
5384+        # If we put the signature after we put the verification key, we
5385+        # could end up running into the verification key, and will
5386+        # probably screw up the offsets as well. So we don't allow that.
5387+        # The method that writes the verification key defines the EOF
5388+        # offset before writing the verification key, so look for that.
5389+        if "EOF" in self._offsets:
5390+            raise LayoutInvalid("You must write the signature before the verification key")
5391+
5392+        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
5393+        self._writevs.append(tuple([self._offsets['signature'], signature]))
5394+
5395+
5396+    def put_verification_key(self, verification_key):
5397+        """
5398+        I queue a write vector for the verification key.
5399+
5400+        I require that the signature have been written to the storage
5401+        server before I allow the verification key to be written to the
5402+        remote server.
5403+        """
5404+        if "verification_key" not in self._offsets:
5405+            raise LayoutInvalid("You must put the signature before you "
5406+                                "can put the verification key")
5407+        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
5408+        self._writevs.append(tuple([self._offsets['verification_key'],
5409+                            verification_key]))
5410+
5411+
5412+    def _get_offsets_tuple(self):
5413+        return tuple([(key, value) for key, value in self._offsets.items()])
5414+
5415+
5416+    def get_verinfo(self):
5417+        return (self._seqnum,
5418+                self._root_hash,
5419+                self._required_shares,
5420+                self._total_shares,
5421+                self._segment_size,
5422+                self._data_length,
5423+                self.get_signable(),
5424+                self._get_offsets_tuple())
5425+
5426+
5427+    def finish_publishing(self):
5428+        """
5429+        I add a write vector for the offsets table, and then cause all
5430+        of the write vectors that I've dealt with so far to be published
5431+        to the remote server, ending the write process.
5432+        """
5433+        if "EOF" not in self._offsets:
5434+            raise LayoutInvalid("You must put the verification key before "
5435+                                "you can publish the offsets")
5436+        offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
5437+        offsets = struct.pack(MDMFOFFSETS,
5438+                              self._offsets['enc_privkey'],
5439+                              self._offsets['block_hash_tree'],
5440+                              self._offsets['share_hash_chain'],
5441+                              self._offsets['signature'],
5442+                              self._offsets['verification_key'],
5443+                              self._offsets['EOF'])
5444+        self._writevs.append(tuple([offsets_offset, offsets]))
5445+        encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
5446+        params = struct.pack(">BBQQ",
5447+                             self._required_shares,
5448+                             self._total_shares,
5449+                             self._segment_size,
5450+                             self._data_length)
5451+        self._writevs.append(tuple([encoding_parameters_offset, params]))
5452+        return self._write(self._writevs)
5453+
5454+
5455+    def _write(self, datavs, on_failure=None, on_success=None):
5456+        """I write the data vectors in datavs to the remote slot."""
5457+        tw_vectors = {}
5458+        if not self._testvs:
5459+            self._testvs = []
5460+            self._testvs.append(tuple([0, 1, "eq", ""]))
5461+        if not self._written:
5462+            # Write a new checkstring to the share when we write it, so
5463+            # that we have something to check later.
5464+            new_checkstring = self.get_checkstring()
5465+            datavs.append((0, new_checkstring))
5466+            def _first_write():
5467+                self._written = True
5468+                self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)]
5469+            on_success = _first_write
5470+        tw_vectors[self.shnum] = (self._testvs, datavs, None)
5471+        d = self._rref.callRemote("slot_testv_and_readv_and_writev",
5472+                                  self._storage_index,
5473+                                  self._secrets,
5474+                                  tw_vectors,
5475+                                  self._readv)
5476+        def _result(results):
5477+            if isinstance(results, failure.Failure) or not results[0]:
5478+                # Do nothing; the write was unsuccessful.
5479+                if on_failure: on_failure()
5480+            else:
5481+                if on_success: on_success()
5482+            return results
5483+        d.addCallback(_result)
5484+        return d
5485+
5486+
5487+class MDMFSlotReadProxy:
5488+    """
5489+    I read from a mutable slot filled with data written in the MDMF data
5490+    format (which is described above).
5491+
5492+    I can be initialized with some amount of data, which I will use (if
5493+    it is valid) to eliminate some of the need to fetch it from servers.
5494+    """
5495+    def __init__(self,
5496+                 rref,
5497+                 storage_index,
5498+                 shnum,
5499+                 data=""):
5500+        # Start the initialization process.
5501+        self._rref = rref
5502+        self._storage_index = storage_index
5503+        self.shnum = shnum
5504+
5505+        # Before doing anything, the reader is probably going to want to
5506+        # verify that the signature is correct. To do that, they'll need
5507+        # the verification key, and the signature. To get those, we'll
5508+        # need the offset table. So fetch the offset table on the
5509+        # assumption that that will be the first thing that a reader is
5510+        # going to do.
5511+
5512+        # The fact that these encoding parameters are None tells us
5513+        # that we haven't yet fetched them from the remote share, so we
5514+        # should. We could just not set them, but the checks will be
5515+        # easier to read if we don't have to use hasattr.
5516+        self._version_number = None
5517+        self._sequence_number = None
5518+        self._root_hash = None
5519+        # Filled in if we're dealing with an SDMF file. Unused
5520+        # otherwise.
5521+        self._salt = None
5522+        self._required_shares = None
5523+        self._total_shares = None
5524+        self._segment_size = None
5525+        self._data_length = None
5526+        self._offsets = None
5527+
5528+        # If the user has chosen to initialize us with some data, we'll
5529+        # try to satisfy subsequent data requests with that data before
5530+        # asking the storage server for it. If
5531+        self._data = data
5532+        # The way callers interact with cache in the filenode returns
5533+        # None if there isn't any cached data, but the way we index the
5534+        # cached data requires a string, so convert None to "".
5535+        if self._data == None:
5536+            self._data = ""
5537+
5538+        self._queue_observers = observer.ObserverList()
5539+        self._queue_errbacks = observer.ObserverList()
5540+        self._readvs = []
5541+
5542+
5543+    def _maybe_fetch_offsets_and_header(self, force_remote=False):
5544+        """
5545+        I fetch the offset table and the header from the remote slot if
5546+        I don't already have them. If I do have them, I do nothing and
5547+        return an empty Deferred.
5548+        """
5549+        if self._offsets:
5550+            return defer.succeed(None)
5551+        # At this point, we may be either SDMF or MDMF. Fetching 107
5552+        # bytes will be enough to get header and offsets for both SDMF and
5553+        # MDMF, though we'll be left with 4 more bytes than we
5554+        # need if this ends up being MDMF. This is probably less
5555+        # expensive than the cost of a second roundtrip.
5556+        readvs = [(0, 107)]
5557+        d = self._read(readvs, force_remote)
5558+        d.addCallback(self._process_encoding_parameters)
5559+        d.addCallback(self._process_offsets)
5560+        return d
5561+
5562+
5563+    def _process_encoding_parameters(self, encoding_parameters):
5564+        assert self.shnum in encoding_parameters
5565+        encoding_parameters = encoding_parameters[self.shnum][0]
5566+        # The first byte is the version number. It will tell us what
5567+        # to do next.
5568+        (verno,) = struct.unpack(">B", encoding_parameters[:1])
5569+        if verno == MDMF_VERSION:
5570+            read_size = MDMFHEADERWITHOUTOFFSETSSIZE
5571+            (verno,
5572+             seqnum,
5573+             root_hash,
5574+             k,
5575+             n,
5576+             segsize,
5577+             datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS,
5578+                                      encoding_parameters[:read_size])
5579+            if segsize == 0 and datalen == 0:
5580+                # Empty file, no segments.
5581+                self._num_segments = 0
5582+            else:
5583+                self._num_segments = mathutil.div_ceil(datalen, segsize)
5584+
5585+        elif verno == SDMF_VERSION:
5586+            read_size = SIGNED_PREFIX_LENGTH
5587+            (verno,
5588+             seqnum,
5589+             root_hash,
5590+             salt,
5591+             k,
5592+             n,
5593+             segsize,
5594+             datalen) = struct.unpack(">BQ32s16s BBQQ",
5595+                                encoding_parameters[:SIGNED_PREFIX_LENGTH])
5596+            self._salt = salt
5597+            if segsize == 0 and datalen == 0:
5598+                # empty file
5599+                self._num_segments = 0
5600+            else:
5601+                # non-empty SDMF files have one segment.
5602+                self._num_segments = 1
5603+        else:
5604+            raise UnknownVersionError("You asked me to read mutable file "
5605+                                      "version %d, but I only understand "
5606+                                      "%d and %d" % (verno, SDMF_VERSION,
5607+                                                     MDMF_VERSION))
5608+
5609+        self._version_number = verno
5610+        self._sequence_number = seqnum
5611+        self._root_hash = root_hash
5612+        self._required_shares = k
5613+        self._total_shares = n
5614+        self._segment_size = segsize
5615+        self._data_length = datalen
5616+
5617+        self._block_size = self._segment_size / self._required_shares
5618+        # We can upload empty files, and need to account for this fact
5619+        # so as to avoid zero-division and zero-modulo errors.
5620+        if datalen > 0:
5621+            tail_size = self._data_length % self._segment_size
5622+        else:
5623+            tail_size = 0
5624+        if not tail_size:
5625+            self._tail_block_size = self._block_size
5626+        else:
5627+            self._tail_block_size = mathutil.next_multiple(tail_size,
5628+                                                    self._required_shares)
5629+            self._tail_block_size /= self._required_shares
5630+
5631+        return encoding_parameters
5632+
5633+
5634+    def _process_offsets(self, offsets):
5635+        if self._version_number == 0:
5636+            read_size = OFFSETS_LENGTH
5637+            read_offset = SIGNED_PREFIX_LENGTH
5638+            end = read_size + read_offset
5639+            (signature,
5640+             share_hash_chain,
5641+             block_hash_tree,
5642+             share_data,
5643+             enc_privkey,
5644+             EOF) = struct.unpack(">LLLLQQ",
5645+                                  offsets[read_offset:end])
5646+            self._offsets = {}
5647+            self._offsets['signature'] = signature
5648+            self._offsets['share_data'] = share_data
5649+            self._offsets['block_hash_tree'] = block_hash_tree
5650+            self._offsets['share_hash_chain'] = share_hash_chain
5651+            self._offsets['enc_privkey'] = enc_privkey
5652+            self._offsets['EOF'] = EOF
5653+
5654+        elif self._version_number == 1:
5655+            read_offset = MDMFHEADERWITHOUTOFFSETSSIZE
5656+            read_length = MDMFOFFSETS_LENGTH
5657+            end = read_offset + read_length
5658+            (encprivkey,
5659+             blockhashes,
5660+             sharehashes,
5661+             signature,
5662+             verification_key,
5663+             eof) = struct.unpack(MDMFOFFSETS,
5664+                                  offsets[read_offset:end])
5665+            self._offsets = {}
5666+            self._offsets['enc_privkey'] = encprivkey
5667+            self._offsets['block_hash_tree'] = blockhashes
5668+            self._offsets['share_hash_chain'] = sharehashes
5669+            self._offsets['signature'] = signature
5670+            self._offsets['verification_key'] = verification_key
5671+            self._offsets['EOF'] = eof
5672+
5673+
5674+    def get_block_and_salt(self, segnum, queue=False):
5675+        """
5676+        I return (block, salt), where block is the block data and
5677+        salt is the salt used to encrypt that segment.
5678+        """
5679+        d = self._maybe_fetch_offsets_and_header()
5680+        def _then(ignored):
5681+            if self._version_number == 1:
5682+                base_share_offset = MDMFHEADERSIZE
5683+            else:
5684+                base_share_offset = self._offsets['share_data']
5685+
5686+            if segnum + 1 > self._num_segments:
5687+                raise LayoutInvalid("Not a valid segment number")
5688+
5689+            if self._version_number == 0:
5690+                share_offset = base_share_offset + self._block_size * segnum
5691+            else:
5692+                share_offset = base_share_offset + (self._block_size + \
5693+                                                    SALT_SIZE) * segnum
5694+            if segnum + 1 == self._num_segments:
5695+                data = self._tail_block_size
5696+            else:
5697+                data = self._block_size
5698+
5699+            if self._version_number == 1:
5700+                data += SALT_SIZE
5701+
5702+            readvs = [(share_offset, data)]
5703+            return readvs
5704+        d.addCallback(_then)
5705+        d.addCallback(lambda readvs:
5706+            self._read(readvs, queue=queue))
5707+        def _process_results(results):
5708+            assert self.shnum in results
5709+            if self._version_number == 0:
5710+                # We only read the share data, but we know the salt from
5711+                # when we fetched the header
5712+                data = results[self.shnum]
5713+                if not data:
5714+                    data = ""
5715+                else:
5716+                    assert len(data) == 1
5717+                    data = data[0]
5718+                salt = self._salt
5719+            else:
5720+                data = results[self.shnum]
5721+                if not data:
5722+                    salt = data = ""
5723+                else:
5724+                    salt_and_data = results[self.shnum][0]
5725+                    salt = salt_and_data[:SALT_SIZE]
5726+                    data = salt_and_data[SALT_SIZE:]
5727+            return data, salt
5728+        d.addCallback(_process_results)
5729+        return d
5730+
5731+
5732+    def get_blockhashes(self, needed=None, queue=False, force_remote=False):
5733+        """
5734+        I return the block hash tree
5735+
5736+        I take an optional argument, needed, which is a set of indices
5737+        correspond to hashes that I should fetch. If this argument is
5738+        missing, I will fetch the entire block hash tree; otherwise, I
5739+        may attempt to fetch fewer hashes, based on what needed says
5740+        that I should do. Note that I may fetch as many hashes as I
5741+        want, so long as the set of hashes that I do fetch is a superset
5742+        of the ones that I am asked for, so callers should be prepared
5743+        to tolerate additional hashes.
5744+        """
5745+        # TODO: Return only the parts of the block hash tree necessary
5746+        # to validate the blocknum provided?
5747+        # This is a good idea, but it is hard to implement correctly. It
5748+        # is bad to fetch any one block hash more than once, so we
5749+        # probably just want to fetch the whole thing at once and then
5750+        # serve it.
5751+        if needed == set([]):
5752+            return defer.succeed([])
5753+        d = self._maybe_fetch_offsets_and_header()
5754+        def _then(ignored):
5755+            blockhashes_offset = self._offsets['block_hash_tree']
5756+            if self._version_number == 1:
5757+                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
5758+            else:
5759+                blockhashes_length = self._offsets['share_data'] - blockhashes_offset
5760+            readvs = [(blockhashes_offset, blockhashes_length)]
5761+            return readvs
5762+        d.addCallback(_then)
5763+        d.addCallback(lambda readvs:
5764+            self._read(readvs, queue=queue, force_remote=force_remote))
5765+        def _build_block_hash_tree(results):
5766+            assert self.shnum in results
5767+
5768+            rawhashes = results[self.shnum][0]
5769+            results = [rawhashes[i:i+HASH_SIZE]
5770+                       for i in range(0, len(rawhashes), HASH_SIZE)]
5771+            return results
5772+        d.addCallback(_build_block_hash_tree)
5773+        return d
5774+
5775+
5776+    def get_sharehashes(self, needed=None, queue=False, force_remote=False):
5777+        """
5778+        I return the part of the share hash chain placed to validate
5779+        this share.
5780+
5781+        I take an optional argument, needed. Needed is a set of indices
5782+        that correspond to the hashes that I should fetch. If needed is
5783+        not present, I will fetch and return the entire share hash
5784+        chain. Otherwise, I may fetch and return any part of the share
5785+        hash chain that is a superset of the part that I am asked to
5786+        fetch. Callers should be prepared to deal with more hashes than
5787+        they've asked for.
5788+        """
5789+        if needed == set([]):
5790+            return defer.succeed([])
5791+        d = self._maybe_fetch_offsets_and_header()
5792+
5793+        def _make_readvs(ignored):
5794+            sharehashes_offset = self._offsets['share_hash_chain']
5795+            if self._version_number == 0:
5796+                sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset
5797+            else:
5798+                sharehashes_length = self._offsets['signature'] - sharehashes_offset
5799+            readvs = [(sharehashes_offset, sharehashes_length)]
5800+            return readvs
5801+        d.addCallback(_make_readvs)
5802+        d.addCallback(lambda readvs:
5803+            self._read(readvs, queue=queue, force_remote=force_remote))
5804+        def _build_share_hash_chain(results):
5805+            assert self.shnum in results
5806+
5807+            sharehashes = results[self.shnum][0]
5808+            results = [sharehashes[i:i+(HASH_SIZE + 2)]
5809+                       for i in range(0, len(sharehashes), HASH_SIZE + 2)]
5810+            results = dict([struct.unpack(">H32s", data)
5811+                            for data in results])
5812+            return results
5813+        d.addCallback(_build_share_hash_chain)
5814+        return d
5815+
5816+
5817+    def get_encprivkey(self, queue=False):
5818+        """
5819+        I return the encrypted private key.
5820+        """
5821+        d = self._maybe_fetch_offsets_and_header()
5822+
5823+        def _make_readvs(ignored):
5824+            privkey_offset = self._offsets['enc_privkey']
5825+            if self._version_number == 0:
5826+                privkey_length = self._offsets['EOF'] - privkey_offset
5827+            else:
5828+                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
5829+            readvs = [(privkey_offset, privkey_length)]
5830+            return readvs
5831+        d.addCallback(_make_readvs)
5832+        d.addCallback(lambda readvs:
5833+            self._read(readvs, queue=queue))
5834+        def _process_results(results):
5835+            assert self.shnum in results
5836+            privkey = results[self.shnum][0]
5837+            return privkey
5838+        d.addCallback(_process_results)
5839+        return d
5840+
5841+
5842+    def get_signature(self, queue=False):
5843+        """
5844+        I return the signature of my share.
5845+        """
5846+        d = self._maybe_fetch_offsets_and_header()
5847+
5848+        def _make_readvs(ignored):
5849+            signature_offset = self._offsets['signature']
5850+            if self._version_number == 1:
5851+                signature_length = self._offsets['verification_key'] - signature_offset
5852+            else:
5853+                signature_length = self._offsets['share_hash_chain'] - signature_offset
5854+            readvs = [(signature_offset, signature_length)]
5855+            return readvs
5856+        d.addCallback(_make_readvs)
5857+        d.addCallback(lambda readvs:
5858+            self._read(readvs, queue=queue))
5859+        def _process_results(results):
5860+            assert self.shnum in results
5861+            signature = results[self.shnum][0]
5862+            return signature
5863+        d.addCallback(_process_results)
5864+        return d
5865+
5866+
5867+    def get_verification_key(self, queue=False):
5868+        """
5869+        I return the verification key.
5870+        """
5871+        d = self._maybe_fetch_offsets_and_header()
5872+
5873+        def _make_readvs(ignored):
5874+            if self._version_number == 1:
5875+                vk_offset = self._offsets['verification_key']
5876+                vk_length = self._offsets['EOF'] - vk_offset
5877+            else:
5878+                vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
5879+                vk_length = self._offsets['signature'] - vk_offset
5880+            readvs = [(vk_offset, vk_length)]
5881+            return readvs
5882+        d.addCallback(_make_readvs)
5883+        d.addCallback(lambda readvs:
5884+            self._read(readvs, queue=queue))
5885+        def _process_results(results):
5886+            assert self.shnum in results
5887+            verification_key = results[self.shnum][0]
5888+            return verification_key
5889+        d.addCallback(_process_results)
5890+        return d
5891+
5892+
5893+    def get_encoding_parameters(self):
5894+        """
5895+        I return (k, n, segsize, datalen)
5896+        """
5897+        d = self._maybe_fetch_offsets_and_header()
5898+        d.addCallback(lambda ignored:
5899+            (self._required_shares,
5900+             self._total_shares,
5901+             self._segment_size,
5902+             self._data_length))
5903+        return d
5904+
5905+
5906+    def get_seqnum(self):
5907+        """
5908+        I return the sequence number for this share.
5909+        """
5910+        d = self._maybe_fetch_offsets_and_header()
5911+        d.addCallback(lambda ignored:
5912+            self._sequence_number)
5913+        return d
5914+
5915+
5916+    def get_root_hash(self):
5917+        """
5918+        I return the root of the block hash tree
5919+        """
5920+        d = self._maybe_fetch_offsets_and_header()
5921+        d.addCallback(lambda ignored: self._root_hash)
5922+        return d
5923+
5924+
5925+    def get_checkstring(self):
5926+        """
5927+        I return the packed representation of the following:
5928+
5929+            - version number
5930+            - sequence number
5931+            - root hash
5932+            - salt hash
5933+
5934+        which my users use as a checkstring to detect other writers.
5935+        """
5936+        d = self._maybe_fetch_offsets_and_header()
5937+        def _build_checkstring(ignored):
5938+            if self._salt:
5939+                checkstring = struct.pack(PREFIX,
5940+                                          self._version_number,
5941+                                          self._sequence_number,
5942+                                          self._root_hash,
5943+                                          self._salt)
5944+            else:
5945+                checkstring = struct.pack(MDMFCHECKSTRING,
5946+                                          self._version_number,
5947+                                          self._sequence_number,
5948+                                          self._root_hash)
5949+
5950+            return checkstring
5951+        d.addCallback(_build_checkstring)
5952+        return d
5953+
5954+
5955+    def get_prefix(self, force_remote):
5956+        d = self._maybe_fetch_offsets_and_header(force_remote)
5957+        d.addCallback(lambda ignored:
5958+            self._build_prefix())
5959+        return d
5960+
5961+
5962+    def _build_prefix(self):
5963+        # The prefix is another name for the part of the remote share
5964+        # that gets signed. It consists of everything up to and
5965+        # including the datalength, packed by struct.
5966+        if self._version_number == SDMF_VERSION:
5967+            return struct.pack(SIGNED_PREFIX,
5968+                           self._version_number,
5969+                           self._sequence_number,
5970+                           self._root_hash,
5971+                           self._salt,
5972+                           self._required_shares,
5973+                           self._total_shares,
5974+                           self._segment_size,
5975+                           self._data_length)
5976+
5977+        else:
5978+            return struct.pack(MDMFSIGNABLEHEADER,
5979+                           self._version_number,
5980+                           self._sequence_number,
5981+                           self._root_hash,
5982+                           self._required_shares,
5983+                           self._total_shares,
5984+                           self._segment_size,
5985+                           self._data_length)
5986+
5987+
5988+    def _get_offsets_tuple(self):
5989+        # The offsets tuple is another component of the version
5990+        # information tuple. It is basically our offsets dictionary,
5991+        # itemized and in a tuple.
5992+        return self._offsets.copy()
5993+
5994+
5995+    def get_verinfo(self):
5996+        """
5997+        I return my verinfo tuple. This is used by the ServermapUpdater
5998+        to keep track of versions of mutable files.
5999+
6000+        The verinfo tuple for MDMF files contains:
6001+            - seqnum
6002+            - root hash
6003+            - a blank (nothing)
6004+            - segsize
6005+            - datalen
6006+            - k
6007+            - n
6008+            - prefix (the thing that you sign)
6009+            - a tuple of offsets
6010+
6011+        We include the nonce in MDMF to simplify processing of version
6012+        information tuples.
6013+
6014+        The verinfo tuple for SDMF files is the same, but contains a
6015+        16-byte IV instead of a hash of salts.
6016+        """
6017+        d = self._maybe_fetch_offsets_and_header()
6018+        def _build_verinfo(ignored):
6019+            if self._version_number == SDMF_VERSION:
6020+                salt_to_use = self._salt
6021+            else:
6022+                salt_to_use = None
6023+            return (self._sequence_number,
6024+                    self._root_hash,
6025+                    salt_to_use,
6026+                    self._segment_size,
6027+                    self._data_length,
6028+                    self._required_shares,
6029+                    self._total_shares,
6030+                    self._build_prefix(),
6031+                    self._get_offsets_tuple())
6032+        d.addCallback(_build_verinfo)
6033+        return d
6034+
6035+
6036+    def flush(self):
6037+        """
6038+        I flush my queue of read vectors.
6039+        """
6040+        d = self._read(self._readvs)
6041+        def _then(results):
6042+            self._readvs = []
6043+            if isinstance(results, failure.Failure):
6044+                self._queue_errbacks.notify(results)
6045+            else:
6046+                self._queue_observers.notify(results)
6047+            self._queue_observers = observer.ObserverList()
6048+            self._queue_errbacks = observer.ObserverList()
6049+        d.addBoth(_then)
6050+
6051+
6052+    def _read(self, readvs, force_remote=False, queue=False):
6053+        unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs)
6054+        # TODO: It's entirely possible to tweak this so that it just
6055+        # fulfills the requests that it can, and not demand that all
6056+        # requests are satisfiable before running it.
6057+        if not unsatisfiable and not force_remote:
6058+            results = [self._data[offset:offset+length]
6059+                       for (offset, length) in readvs]
6060+            results = {self.shnum: results}
6061+            return defer.succeed(results)
6062+        else:
6063+            if queue:
6064+                start = len(self._readvs)
6065+                self._readvs += readvs
6066+                end = len(self._readvs)
6067+                def _get_results(results, start, end):
6068+                    if not self.shnum in results:
6069+                        return {self._shnum: [""]}
6070+                    return {self.shnum: results[self.shnum][start:end]}
6071+                d = defer.Deferred()
6072+                d.addCallback(_get_results, start, end)
6073+                self._queue_observers.subscribe(d.callback)
6074+                self._queue_errbacks.subscribe(d.errback)
6075+                return d
6076+            return self._rref.callRemote("slot_readv",
6077+                                         self._storage_index,
6078+                                         [self.shnum],
6079+                                         readvs)
6080+
6081+
6082+    def is_sdmf(self):
6083+        """I tell my caller whether or not my remote file is SDMF or MDMF
6084+        """
6085+        d = self._maybe_fetch_offsets_and_header()
6086+        d.addCallback(lambda ignored:
6087+            self._version_number == 0)
6088+        return d
6089+
6090+
6091+class LayoutInvalid(Exception):
6092+    """
6093+    This isn't a valid MDMF mutable file
6094+    """
6095merger 0.0 (
6096hunk ./src/allmydata/test/test_storage.py 3
6097-from allmydata.util import log
6098-
6099merger 0.0 (
6100hunk ./src/allmydata/test/test_storage.py 3
6101-import time, os.path, stat, re, simplejson, struct
6102+from allmydata.util import log
6103+
6104+import mock
6105hunk ./src/allmydata/test/test_storage.py 3
6106-import time, os.path, stat, re, simplejson, struct
6107+import time, os.path, stat, re, simplejson, struct, shutil
6108)
6109)
6110hunk ./src/allmydata/test/test_storage.py 23
6111 from allmydata.storage.expirer import LeaseCheckingCrawler
6112 from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \
6113      ReadBucketProxy
6114-from allmydata.interfaces import BadWriteEnablerError
6115-from allmydata.test.common import LoggingServiceParent
6116+from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
6117+                                     LayoutInvalid, MDMFSIGNABLEHEADER, \
6118+                                     SIGNED_PREFIX, MDMFHEADER, \
6119+                                     MDMFOFFSETS, SDMFSlotWriteProxy
6120+from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
6121+                                 SDMF_VERSION
6122+from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
6123 from allmydata.test.common_web import WebRenderingMixin
6124 from allmydata.web.storage import StorageStatus, remove_prefix
6125 
6126hunk ./src/allmydata/test/test_storage.py 107
6127 
6128 class RemoteBucket:
6129 
6130+    def __init__(self):
6131+        self.read_count = 0
6132+        self.write_count = 0
6133+
6134     def callRemote(self, methname, *args, **kwargs):
6135         def _call():
6136             meth = getattr(self.target, "remote_" + methname)
6137hunk ./src/allmydata/test/test_storage.py 115
6138             return meth(*args, **kwargs)
6139+
6140+        if methname == "slot_readv":
6141+            self.read_count += 1
6142+        if "writev" in methname:
6143+            self.write_count += 1
6144+
6145         return defer.maybeDeferred(_call)
6146 
6147hunk ./src/allmydata/test/test_storage.py 123
6148+
6149 class BucketProxy(unittest.TestCase):
6150     def make_bucket(self, name, size):
6151         basedir = os.path.join("storage", "BucketProxy", name)
6152hunk ./src/allmydata/test/test_storage.py 1306
6153         self.failUnless(os.path.exists(prefixdir), prefixdir)
6154         self.failIf(os.path.exists(bucketdir), bucketdir)
6155 
6156+
6157+class MDMFProxies(unittest.TestCase, ShouldFailMixin):
6158+    def setUp(self):
6159+        self.sparent = LoggingServiceParent()
6160+        self._lease_secret = itertools.count()
6161+        self.ss = self.create("MDMFProxies storage test server")
6162+        self.rref = RemoteBucket()
6163+        self.rref.target = self.ss
6164+        self.secrets = (self.write_enabler("we_secret"),
6165+                        self.renew_secret("renew_secret"),
6166+                        self.cancel_secret("cancel_secret"))
6167+        self.segment = "aaaaaa"
6168+        self.block = "aa"
6169+        self.salt = "a" * 16
6170+        self.block_hash = "a" * 32
6171+        self.block_hash_tree = [self.block_hash for i in xrange(6)]
6172+        self.share_hash = self.block_hash
6173+        self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)])
6174+        self.signature = "foobarbaz"
6175+        self.verification_key = "vvvvvv"
6176+        self.encprivkey = "private"
6177+        self.root_hash = self.block_hash
6178+        self.salt_hash = self.root_hash
6179+        self.salt_hash_tree = [self.salt_hash for i in xrange(6)]
6180+        self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree)
6181+        self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain)
6182+        # blockhashes and salt hashes are serialized in the same way,
6183+        # only we lop off the first element and store that in the
6184+        # header.
6185+        self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:])
6186+
6187+
6188+    def tearDown(self):
6189+        self.sparent.stopService()
6190+        shutil.rmtree(self.workdir("MDMFProxies storage test server"))
6191+
6192+
6193+    def write_enabler(self, we_tag):
6194+        return hashutil.tagged_hash("we_blah", we_tag)
6195+
6196+
6197+    def renew_secret(self, tag):
6198+        return hashutil.tagged_hash("renew_blah", str(tag))
6199+
6200+
6201+    def cancel_secret(self, tag):
6202+        return hashutil.tagged_hash("cancel_blah", str(tag))
6203+
6204+
6205+    def workdir(self, name):
6206+        basedir = os.path.join("storage", "MutableServer", name)
6207+        return basedir
6208+
6209+
6210+    def create(self, name):
6211+        workdir = self.workdir(name)
6212+        ss = StorageServer(workdir, "\x00" * 20)
6213+        ss.setServiceParent(self.sparent)
6214+        return ss
6215+
6216+
6217+    def build_test_mdmf_share(self, tail_segment=False, empty=False):
6218+        # Start with the checkstring
6219+        data = struct.pack(">BQ32s",
6220+                           1,
6221+                           0,
6222+                           self.root_hash)
6223+        self.checkstring = data
6224+        # Next, the encoding parameters
6225+        if tail_segment:
6226+            data += struct.pack(">BBQQ",
6227+                                3,
6228+                                10,
6229+                                6,
6230+                                33)
6231+        elif empty:
6232+            data += struct.pack(">BBQQ",
6233+                                3,
6234+                                10,
6235+                                0,
6236+                                0)
6237+        else:
6238+            data += struct.pack(">BBQQ",
6239+                                3,
6240+                                10,
6241+                                6,
6242+                                36)
6243+        # Now we'll build the offsets.
6244+        sharedata = ""
6245+        if not tail_segment and not empty:
6246+            for i in xrange(6):
6247+                sharedata += self.salt + self.block
6248+        elif tail_segment:
6249+            for i in xrange(5):
6250+                sharedata += self.salt + self.block
6251+            sharedata += self.salt + "a"
6252+
6253+        # The encrypted private key comes after the shares + salts
6254+        offset_size = struct.calcsize(MDMFOFFSETS)
6255+        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
6256+        # The blockhashes come after the private key
6257+        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
6258+        # The sharehashes come after the salt hashes
6259+        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
6260+        # The signature comes after the share hash chain
6261+        signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
6262+        # The verification key comes after the signature
6263+        verification_offset = signature_offset + len(self.signature)
6264+        # The EOF comes after the verification key
6265+        eof_offset = verification_offset + len(self.verification_key)
6266+        data += struct.pack(MDMFOFFSETS,
6267+                            encrypted_private_key_offset,
6268+                            blockhashes_offset,
6269+                            sharehashes_offset,
6270+                            signature_offset,
6271+                            verification_offset,
6272+                            eof_offset)
6273+        self.offsets = {}
6274+        self.offsets['enc_privkey'] = encrypted_private_key_offset
6275+        self.offsets['block_hash_tree'] = blockhashes_offset
6276+        self.offsets['share_hash_chain'] = sharehashes_offset
6277+        self.offsets['signature'] = signature_offset
6278+        self.offsets['verification_key'] = verification_offset
6279+        self.offsets['EOF'] = eof_offset
6280+        # Next, we'll add in the salts and share data,
6281+        data += sharedata
6282+        # the private key,
6283+        data += self.encprivkey
6284+        # the block hash tree,
6285+        data += self.block_hash_tree_s
6286+        # the share hash chain,
6287+        data += self.share_hash_chain_s
6288+        # the signature,
6289+        data += self.signature
6290+        # and the verification key
6291+        data += self.verification_key
6292+        return data
6293+
6294+
6295+    def write_test_share_to_server(self,
6296+                                   storage_index,
6297+                                   tail_segment=False,
6298+                                   empty=False):
6299+        """
6300+        I write some data for the read tests to read to self.ss
6301+
6302+        If tail_segment=True, then I will write a share that has a
6303+        smaller tail segment than other segments.
6304+        """
6305+        write = self.ss.remote_slot_testv_and_readv_and_writev
6306+        data = self.build_test_mdmf_share(tail_segment, empty)
6307+        # Finally, we write the whole thing to the storage server in one
6308+        # pass.
6309+        testvs = [(0, 1, "eq", "")]
6310+        tws = {}
6311+        tws[0] = (testvs, [(0, data)], None)
6312+        readv = [(0, 1)]
6313+        results = write(storage_index, self.secrets, tws, readv)
6314+        self.failUnless(results[0])
6315+
6316+
6317+    def build_test_sdmf_share(self, empty=False):
6318+        if empty:
6319+            sharedata = ""
6320+        else:
6321+            sharedata = self.segment * 6
6322+        self.sharedata = sharedata
6323+        blocksize = len(sharedata) / 3
6324+        block = sharedata[:blocksize]
6325+        self.blockdata = block
6326+        prefix = struct.pack(">BQ32s16s BBQQ",
6327+                             0, # version,
6328+                             0,
6329+                             self.root_hash,
6330+                             self.salt,
6331+                             3,
6332+                             10,
6333+                             len(sharedata),
6334+                             len(sharedata),
6335+                            )
6336+        post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
6337+        signature_offset = post_offset + len(self.verification_key)
6338+        sharehashes_offset = signature_offset + len(self.signature)
6339+        blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s)
6340+        sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s)
6341+        encprivkey_offset = sharedata_offset + len(block)
6342+        eof_offset = encprivkey_offset + len(self.encprivkey)
6343+        offsets = struct.pack(">LLLLQQ",
6344+                              signature_offset,
6345+                              sharehashes_offset,
6346+                              blockhashes_offset,
6347+                              sharedata_offset,
6348+                              encprivkey_offset,
6349+                              eof_offset)
6350+        final_share = "".join([prefix,
6351+                           offsets,
6352+                           self.verification_key,
6353+                           self.signature,
6354+                           self.share_hash_chain_s,
6355+                           self.block_hash_tree_s,
6356+                           block,
6357+                           self.encprivkey])
6358+        self.offsets = {}
6359+        self.offsets['signature'] = signature_offset
6360+        self.offsets['share_hash_chain'] = sharehashes_offset
6361+        self.offsets['block_hash_tree'] = blockhashes_offset
6362+        self.offsets['share_data'] = sharedata_offset
6363+        self.offsets['enc_privkey'] = encprivkey_offset
6364+        self.offsets['EOF'] = eof_offset
6365+        return final_share
6366+
6367+
6368+    def write_sdmf_share_to_server(self,
6369+                                   storage_index,
6370+                                   empty=False):
6371+        # Some tests need SDMF shares to verify that we can still
6372+        # read them. This method writes one, which resembles but is not
6373+        assert self.rref
6374+        write = self.ss.remote_slot_testv_and_readv_and_writev
6375+        share = self.build_test_sdmf_share(empty)
6376+        testvs = [(0, 1, "eq", "")]
6377+        tws = {}
6378+        tws[0] = (testvs, [(0, share)], None)
6379+        readv = []
6380+        results = write(storage_index, self.secrets, tws, readv)
6381+        self.failUnless(results[0])
6382+
6383+
6384+    def test_read(self):
6385+        self.write_test_share_to_server("si1")
6386+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6387+        # Check that every method equals what we expect it to.
6388+        d = defer.succeed(None)
6389+        def _check_block_and_salt((block, salt)):
6390+            self.failUnlessEqual(block, self.block)
6391+            self.failUnlessEqual(salt, self.salt)
6392+
6393+        for i in xrange(6):
6394+            d.addCallback(lambda ignored, i=i:
6395+                mr.get_block_and_salt(i))
6396+            d.addCallback(_check_block_and_salt)
6397+
6398+        d.addCallback(lambda ignored:
6399+            mr.get_encprivkey())
6400+        d.addCallback(lambda encprivkey:
6401+            self.failUnlessEqual(self.encprivkey, encprivkey))
6402+
6403+        d.addCallback(lambda ignored:
6404+            mr.get_blockhashes())
6405+        d.addCallback(lambda blockhashes:
6406+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
6407+
6408+        d.addCallback(lambda ignored:
6409+            mr.get_sharehashes())
6410+        d.addCallback(lambda sharehashes:
6411+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
6412+
6413+        d.addCallback(lambda ignored:
6414+            mr.get_signature())
6415+        d.addCallback(lambda signature:
6416+            self.failUnlessEqual(signature, self.signature))
6417+
6418+        d.addCallback(lambda ignored:
6419+            mr.get_verification_key())
6420+        d.addCallback(lambda verification_key:
6421+            self.failUnlessEqual(verification_key, self.verification_key))
6422+
6423+        d.addCallback(lambda ignored:
6424+            mr.get_seqnum())
6425+        d.addCallback(lambda seqnum:
6426+            self.failUnlessEqual(seqnum, 0))
6427+
6428+        d.addCallback(lambda ignored:
6429+            mr.get_root_hash())
6430+        d.addCallback(lambda root_hash:
6431+            self.failUnlessEqual(self.root_hash, root_hash))
6432+
6433+        d.addCallback(lambda ignored:
6434+            mr.get_seqnum())
6435+        d.addCallback(lambda seqnum:
6436+            self.failUnlessEqual(0, seqnum))
6437+
6438+        d.addCallback(lambda ignored:
6439+            mr.get_encoding_parameters())
6440+        def _check_encoding_parameters((k, n, segsize, datalen)):
6441+            self.failUnlessEqual(k, 3)
6442+            self.failUnlessEqual(n, 10)
6443+            self.failUnlessEqual(segsize, 6)
6444+            self.failUnlessEqual(datalen, 36)
6445+        d.addCallback(_check_encoding_parameters)
6446+
6447+        d.addCallback(lambda ignored:
6448+            mr.get_checkstring())
6449+        d.addCallback(lambda checkstring:
6450+            self.failUnlessEqual(checkstring, checkstring))
6451+        return d
6452+
6453+
6454+    def test_read_with_different_tail_segment_size(self):
6455+        self.write_test_share_to_server("si1", tail_segment=True)
6456+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6457+        d = mr.get_block_and_salt(5)
6458+        def _check_tail_segment(results):
6459+            block, salt = results
6460+            self.failUnlessEqual(len(block), 1)
6461+            self.failUnlessEqual(block, "a")
6462+        d.addCallback(_check_tail_segment)
6463+        return d
6464+
6465+
6466+    def test_get_block_with_invalid_segnum(self):
6467+        self.write_test_share_to_server("si1")
6468+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6469+        d = defer.succeed(None)
6470+        d.addCallback(lambda ignored:
6471+            self.shouldFail(LayoutInvalid, "test invalid segnum",
6472+                            None,
6473+                            mr.get_block_and_salt, 7))
6474+        return d
6475+
6476+
6477+    def test_get_encoding_parameters_first(self):
6478+        self.write_test_share_to_server("si1")
6479+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6480+        d = mr.get_encoding_parameters()
6481+        def _check_encoding_parameters((k, n, segment_size, datalen)):
6482+            self.failUnlessEqual(k, 3)
6483+            self.failUnlessEqual(n, 10)
6484+            self.failUnlessEqual(segment_size, 6)
6485+            self.failUnlessEqual(datalen, 36)
6486+        d.addCallback(_check_encoding_parameters)
6487+        return d
6488+
6489+
6490+    def test_get_seqnum_first(self):
6491+        self.write_test_share_to_server("si1")
6492+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6493+        d = mr.get_seqnum()
6494+        d.addCallback(lambda seqnum:
6495+            self.failUnlessEqual(seqnum, 0))
6496+        return d
6497+
6498+
6499+    def test_get_root_hash_first(self):
6500+        self.write_test_share_to_server("si1")
6501+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6502+        d = mr.get_root_hash()
6503+        d.addCallback(lambda root_hash:
6504+            self.failUnlessEqual(root_hash, self.root_hash))
6505+        return d
6506+
6507+
6508+    def test_get_checkstring_first(self):
6509+        self.write_test_share_to_server("si1")
6510+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
6511+        d = mr.get_checkstring()
6512+        d.addCallback(lambda checkstring:
6513+            self.failUnlessEqual(checkstring, self.checkstring))
6514+        return d
6515+
6516+
6517+    def test_write_read_vectors(self):
6518+        # When writing for us, the storage server will return to us a
6519+        # read vector, along with its result. If a write fails because
6520+        # the test vectors failed, this read vector can help us to
6521+        # diagnose the problem. This test ensures that the read vector
6522+        # is working appropriately.
6523+        mw = self._make_new_mw("si1", 0)
6524+
6525+        for i in xrange(6):
6526+            mw.put_block(self.block, i, self.salt)
6527+        mw.put_encprivkey(self.encprivkey)
6528+        mw.put_blockhashes(self.block_hash_tree)
6529+        mw.put_sharehashes(self.share_hash_chain)
6530+        mw.put_root_hash(self.root_hash)
6531+        mw.put_signature(self.signature)
6532+        mw.put_verification_key(self.verification_key)
6533+        d = mw.finish_publishing()
6534+        def _then(results):
6535+            self.failUnless(len(results), 2)
6536+            result, readv = results
6537+            self.failUnless(result)
6538+            self.failIf(readv)
6539+            self.old_checkstring = mw.get_checkstring()
6540+            mw.set_checkstring("")
6541+        d.addCallback(_then)
6542+        d.addCallback(lambda ignored:
6543+            mw.finish_publishing())
6544+        def _then_again(results):
6545+            self.failUnlessEqual(len(results), 2)
6546+            result, readvs = results
6547+            self.failIf(result)
6548+            self.failUnlessIn(0, readvs)
6549+            readv = readvs[0][0]
6550+            self.failUnlessEqual(readv, self.old_checkstring)
6551+        d.addCallback(_then_again)
6552+        # The checkstring remains the same for the rest of the process.
6553+        return d
6554+
6555+
6556+    def test_blockhashes_after_share_hash_chain(self):
6557+        mw = self._make_new_mw("si1", 0)
6558+        d = defer.succeed(None)
6559+        # Put everything up to and including the share hash chain
6560+        for i in xrange(6):
6561+            d.addCallback(lambda ignored, i=i:
6562+                mw.put_block(self.block, i, self.salt))
6563+        d.addCallback(lambda ignored:
6564+            mw.put_encprivkey(self.encprivkey))
6565+        d.addCallback(lambda ignored:
6566+            mw.put_blockhashes(self.block_hash_tree))
6567+        d.addCallback(lambda ignored:
6568+            mw.put_sharehashes(self.share_hash_chain))
6569+
6570+        # Now try to put the block hash tree again.
6571+        d.addCallback(lambda ignored:
6572+            self.shouldFail(LayoutInvalid, "test repeat salthashes",
6573+                            None,
6574+                            mw.put_blockhashes, self.block_hash_tree))
6575+        return d
6576+
6577+
6578+    def test_encprivkey_after_blockhashes(self):
6579+        mw = self._make_new_mw("si1", 0)
6580+        d = defer.succeed(None)
6581+        # Put everything up to and including the block hash tree
6582+        for i in xrange(6):
6583+            d.addCallback(lambda ignored, i=i:
6584+                mw.put_block(self.block, i, self.salt))
6585+        d.addCallback(lambda ignored:
6586+            mw.put_encprivkey(self.encprivkey))
6587+        d.addCallback(lambda ignored:
6588+            mw.put_blockhashes(self.block_hash_tree))
6589+        d.addCallback(lambda ignored:
6590+            self.shouldFail(LayoutInvalid, "out of order private key",
6591+                            None,
6592+                            mw.put_encprivkey, self.encprivkey))
6593+        return d
6594+
6595+
6596+    def test_share_hash_chain_after_signature(self):
6597+        mw = self._make_new_mw("si1", 0)
6598+        d = defer.succeed(None)
6599+        # Put everything up to and including the signature
6600+        for i in xrange(6):
6601+            d.addCallback(lambda ignored, i=i:
6602+                mw.put_block(self.block, i, self.salt))
6603+        d.addCallback(lambda ignored:
6604+            mw.put_encprivkey(self.encprivkey))
6605+        d.addCallback(lambda ignored:
6606+            mw.put_blockhashes(self.block_hash_tree))
6607+        d.addCallback(lambda ignored:
6608+            mw.put_sharehashes(self.share_hash_chain))
6609+        d.addCallback(lambda ignored:
6610+            mw.put_root_hash(self.root_hash))
6611+        d.addCallback(lambda ignored:
6612+            mw.put_signature(self.signature))
6613+        # Now try to put the share hash chain again. This should fail
6614+        d.addCallback(lambda ignored:
6615+            self.shouldFail(LayoutInvalid, "out of order share hash chain",
6616+                            None,
6617+                            mw.put_sharehashes, self.share_hash_chain))
6618+        return d
6619+
6620+
6621+    def test_signature_after_verification_key(self):
6622+        mw = self._make_new_mw("si1", 0)
6623+        d = defer.succeed(None)
6624+        # Put everything up to and including the verification key.
6625+        for i in xrange(6):
6626+            d.addCallback(lambda ignored, i=i:
6627+                mw.put_block(self.block, i, self.salt))
6628+        d.addCallback(lambda ignored:
6629+            mw.put_encprivkey(self.encprivkey))
6630+        d.addCallback(lambda ignored:
6631+            mw.put_blockhashes(self.block_hash_tree))
6632+        d.addCallback(lambda ignored:
6633+            mw.put_sharehashes(self.share_hash_chain))
6634+        d.addCallback(lambda ignored:
6635+            mw.put_root_hash(self.root_hash))
6636+        d.addCallback(lambda ignored:
6637+            mw.put_signature(self.signature))
6638+        d.addCallback(lambda ignored:
6639+            mw.put_verification_key(self.verification_key))
6640+        # Now try to put the signature again. This should fail
6641+        d.addCallback(lambda ignored:
6642+            self.shouldFail(LayoutInvalid, "signature after verification",
6643+                            None,
6644+                            mw.put_signature, self.signature))
6645+        return d
6646+
6647+
6648+    def test_uncoordinated_write(self):
6649+        # Make two mutable writers, both pointing to the same storage
6650+        # server, both at the same storage index, and try writing to the
6651+        # same share.
6652+        mw1 = self._make_new_mw("si1", 0)
6653+        mw2 = self._make_new_mw("si1", 0)
6654+
6655+        def _check_success(results):
6656+            result, readvs = results
6657+            self.failUnless(result)
6658+
6659+        def _check_failure(results):
6660+            result, readvs = results
6661+            self.failIf(result)
6662+
6663+        def _write_share(mw):
6664+            for i in xrange(6):
6665+                mw.put_block(self.block, i, self.salt)
6666+            mw.put_encprivkey(self.encprivkey)
6667+            mw.put_blockhashes(self.block_hash_tree)
6668+            mw.put_sharehashes(self.share_hash_chain)
6669+            mw.put_root_hash(self.root_hash)
6670+            mw.put_signature(self.signature)
6671+            mw.put_verification_key(self.verification_key)
6672+            return mw.finish_publishing()
6673+        d = _write_share(mw1)
6674+        d.addCallback(_check_success)
6675+        d.addCallback(lambda ignored:
6676+            _write_share(mw2))
6677+        d.addCallback(_check_failure)
6678+        return d
6679+
6680+
6681+    def test_invalid_salt_size(self):
6682+        # Salts need to be 16 bytes in size. Writes that attempt to
6683+        # write more or less than this should be rejected.
6684+        mw = self._make_new_mw("si1", 0)
6685+        invalid_salt = "a" * 17 # 17 bytes
6686+        another_invalid_salt = "b" * 15 # 15 bytes
6687+        d = defer.succeed(None)
6688+        d.addCallback(lambda ignored:
6689+            self.shouldFail(LayoutInvalid, "salt too big",
6690+                            None,
6691+                            mw.put_block, self.block, 0, invalid_salt))
6692+        d.addCallback(lambda ignored:
6693+            self.shouldFail(LayoutInvalid, "salt too small",
6694+                            None,
6695+                            mw.put_block, self.block, 0,
6696+                            another_invalid_salt))
6697+        return d
6698+
6699+
6700+    def test_write_test_vectors(self):
6701+        # If we give the write proxy a bogus test vector at
6702+        # any point during the process, it should fail to write when we
6703+        # tell it to write.
6704+        def _check_failure(results):
6705+            self.failUnlessEqual(len(results), 2)
6706+            res, d = results
6707+            self.failIf(res)
6708+
6709+        def _check_success(results):
6710+            self.failUnlessEqual(len(results), 2)
6711+            res, d = results
6712+            self.failUnless(results)
6713+
6714+        mw = self._make_new_mw("si1", 0)
6715+        mw.set_checkstring("this is a lie")
6716+        for i in xrange(6):
6717+            mw.put_block(self.block, i, self.salt)
6718+        mw.put_encprivkey(self.encprivkey)
6719+        mw.put_blockhashes(self.block_hash_tree)
6720+        mw.put_sharehashes(self.share_hash_chain)
6721+        mw.put_root_hash(self.root_hash)
6722+        mw.put_signature(self.signature)
6723+        mw.put_verification_key(self.verification_key)
6724+        d = mw.finish_publishing()
6725+        d.addCallback(_check_failure)
6726+        d.addCallback(lambda ignored:
6727+            mw.set_checkstring(""))
6728+        d.addCallback(lambda ignored:
6729+            mw.finish_publishing())
6730+        d.addCallback(_check_success)
6731+        return d
6732+
6733+
6734+    def serialize_blockhashes(self, blockhashes):
6735+        return "".join(blockhashes)
6736+
6737+
6738+    def serialize_sharehashes(self, sharehashes):
6739+        ret = "".join([struct.pack(">H32s", i, sharehashes[i])
6740+                        for i in sorted(sharehashes.keys())])
6741+        return ret
6742+
6743+
6744+    def test_write(self):
6745+        # This translates to a file with 6 6-byte segments, and with 2-byte
6746+        # blocks.
6747+        mw = self._make_new_mw("si1", 0)
6748+        # Test writing some blocks.
6749+        read = self.ss.remote_slot_readv
6750+        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
6751+        written_block_size = 2 + len(self.salt)
6752+        written_block = self.block + self.salt
6753+        for i in xrange(6):
6754+            mw.put_block(self.block, i, self.salt)
6755+
6756+        mw.put_encprivkey(self.encprivkey)
6757+        mw.put_blockhashes(self.block_hash_tree)
6758+        mw.put_sharehashes(self.share_hash_chain)
6759+        mw.put_root_hash(self.root_hash)
6760+        mw.put_signature(self.signature)
6761+        mw.put_verification_key(self.verification_key)
6762+        d = mw.finish_publishing()
6763+        def _check_publish(results):
6764+            self.failUnlessEqual(len(results), 2)
6765+            result, ign = results
6766+            self.failUnless(result, "publish failed")
6767+            for i in xrange(6):
6768+                self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
6769+                                {0: [written_block]})
6770+
6771+            expected_private_key_offset = expected_sharedata_offset + \
6772+                                      len(written_block) * 6
6773+            self.failUnlessEqual(len(self.encprivkey), 7)
6774+            self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
6775+                                 {0: [self.encprivkey]})
6776+
6777+            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
6778+            self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
6779+            self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
6780+                                 {0: [self.block_hash_tree_s]})
6781+
6782+            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
6783+            self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
6784+                                 {0: [self.share_hash_chain_s]})
6785+
6786+            self.failUnlessEqual(read("si1", [0], [(9, 32)]),
6787+                                 {0: [self.root_hash]})
6788+            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
6789+            self.failUnlessEqual(len(self.signature), 9)
6790+            self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
6791+                                 {0: [self.signature]})
6792+
6793+            expected_verification_key_offset = expected_signature_offset + len(self.signature)
6794+            self.failUnlessEqual(len(self.verification_key), 6)
6795+            self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]),
6796+                                 {0: [self.verification_key]})
6797+
6798+            signable = mw.get_signable()
6799+            verno, seq, roothash, k, n, segsize, datalen = \
6800+                                            struct.unpack(">BQ32sBBQQ",
6801+                                                          signable)
6802+            self.failUnlessEqual(verno, 1)
6803+            self.failUnlessEqual(seq, 0)
6804+            self.failUnlessEqual(roothash, self.root_hash)
6805+            self.failUnlessEqual(k, 3)
6806+            self.failUnlessEqual(n, 10)
6807+            self.failUnlessEqual(segsize, 6)
6808+            self.failUnlessEqual(datalen, 36)
6809+            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
6810+
6811+            # Check the version number to make sure that it is correct.
6812+            expected_version_number = struct.pack(">B", 1)
6813+            self.failUnlessEqual(read("si1", [0], [(0, 1)]),
6814+                                 {0: [expected_version_number]})
6815+            # Check the sequence number to make sure that it is correct
6816+            expected_sequence_number = struct.pack(">Q", 0)
6817+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
6818+                                 {0: [expected_sequence_number]})
6819+            # Check that the encoding parameters (k, N, segement size, data
6820+            # length) are what they should be. These are  3, 10, 6, 36
6821+            expected_k = struct.pack(">B", 3)
6822+            self.failUnlessEqual(read("si1", [0], [(41, 1)]),
6823+                                 {0: [expected_k]})
6824+            expected_n = struct.pack(">B", 10)
6825+            self.failUnlessEqual(read("si1", [0], [(42, 1)]),
6826+                                 {0: [expected_n]})
6827+            expected_segment_size = struct.pack(">Q", 6)
6828+            self.failUnlessEqual(read("si1", [0], [(43, 8)]),
6829+                                 {0: [expected_segment_size]})
6830+            expected_data_length = struct.pack(">Q", 36)
6831+            self.failUnlessEqual(read("si1", [0], [(51, 8)]),
6832+                                 {0: [expected_data_length]})
6833+            expected_offset = struct.pack(">Q", expected_private_key_offset)
6834+            self.failUnlessEqual(read("si1", [0], [(59, 8)]),
6835+                                 {0: [expected_offset]})
6836+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
6837+            self.failUnlessEqual(read("si1", [0], [(67, 8)]),
6838+                                 {0: [expected_offset]})
6839+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
6840+            self.failUnlessEqual(read("si1", [0], [(75, 8)]),
6841+                                 {0: [expected_offset]})
6842+            expected_offset = struct.pack(">Q", expected_signature_offset)
6843+            self.failUnlessEqual(read("si1", [0], [(83, 8)]),
6844+                                 {0: [expected_offset]})
6845+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
6846+            self.failUnlessEqual(read("si1", [0], [(91, 8)]),
6847+                                 {0: [expected_offset]})
6848+            expected_offset = struct.pack(">Q", expected_eof_offset)
6849+            self.failUnlessEqual(read("si1", [0], [(99, 8)]),
6850+                                 {0: [expected_offset]})
6851+        d.addCallback(_check_publish)
6852+        return d
6853+
6854+    def _make_new_mw(self, si, share, datalength=36):
6855+        # This is a file of size 36 bytes. Since it has a segment
6856+        # size of 6, we know that it has 6 byte segments, which will
6857+        # be split into blocks of 2 bytes because our FEC k
6858+        # parameter is 3.
6859+        mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10,
6860+                                6, datalength)
6861+        return mw
6862+
6863+
6864+    def test_write_rejected_with_too_many_blocks(self):
6865+        mw = self._make_new_mw("si0", 0)
6866+
6867+        # Try writing too many blocks. We should not be able to write
6868+        # more than 6
6869+        # blocks into each share.
6870+        d = defer.succeed(None)
6871+        for i in xrange(6):
6872+            d.addCallback(lambda ignored, i=i:
6873+                mw.put_block(self.block, i, self.salt))
6874+        d.addCallback(lambda ignored:
6875+            self.shouldFail(LayoutInvalid, "too many blocks",
6876+                            None,
6877+                            mw.put_block, self.block, 7, self.salt))
6878+        return d
6879+
6880+
6881+    def test_write_rejected_with_invalid_salt(self):
6882+        # Try writing an invalid salt. Salts are 16 bytes -- any more or
6883+        # less should cause an error.
6884+        mw = self._make_new_mw("si1", 0)
6885+        bad_salt = "a" * 17 # 17 bytes
6886+        d = defer.succeed(None)
6887+        d.addCallback(lambda ignored:
6888+            self.shouldFail(LayoutInvalid, "test_invalid_salt",
6889+                            None, mw.put_block, self.block, 7, bad_salt))
6890+        return d
6891+
6892+
6893+    def test_write_rejected_with_invalid_root_hash(self):
6894+        # Try writing an invalid root hash. This should be SHA256d, and
6895+        # 32 bytes long as a result.
6896+        mw = self._make_new_mw("si2", 0)
6897+        # 17 bytes != 32 bytes
6898+        invalid_root_hash = "a" * 17
6899+        d = defer.succeed(None)
6900+        # Before this test can work, we need to put some blocks + salts,
6901+        # a block hash tree, and a share hash tree. Otherwise, we'll see
6902+        # failures that match what we are looking for, but are caused by
6903+        # the constraints imposed on operation ordering.
6904+        for i in xrange(6):
6905+            d.addCallback(lambda ignored, i=i:
6906+                mw.put_block(self.block, i, self.salt))
6907+        d.addCallback(lambda ignored:
6908+            mw.put_encprivkey(self.encprivkey))
6909+        d.addCallback(lambda ignored:
6910+            mw.put_blockhashes(self.block_hash_tree))
6911+        d.addCallback(lambda ignored:
6912+            mw.put_sharehashes(self.share_hash_chain))
6913+        d.addCallback(lambda ignored:
6914+            self.shouldFail(LayoutInvalid, "invalid root hash",
6915+                            None, mw.put_root_hash, invalid_root_hash))
6916+        return d
6917+
6918+
6919+    def test_write_rejected_with_invalid_blocksize(self):
6920+        # The blocksize implied by the writer that we get from
6921+        # _make_new_mw is 2bytes -- any more or any less than this
6922+        # should be cause for failure, unless it is the tail segment, in
6923+        # which case it may not be failure.
6924+        invalid_block = "a"
6925+        mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with
6926+                                             # one byte blocks
6927+        # 1 bytes != 2 bytes
6928+        d = defer.succeed(None)
6929+        d.addCallback(lambda ignored, invalid_block=invalid_block:
6930+            self.shouldFail(LayoutInvalid, "test blocksize too small",
6931+                            None, mw.put_block, invalid_block, 0,
6932+                            self.salt))
6933+        invalid_block = invalid_block * 3
6934+        # 3 bytes != 2 bytes
6935+        d.addCallback(lambda ignored:
6936+            self.shouldFail(LayoutInvalid, "test blocksize too large",
6937+                            None,
6938+                            mw.put_block, invalid_block, 0, self.salt))
6939+        for i in xrange(5):
6940+            d.addCallback(lambda ignored, i=i:
6941+                mw.put_block(self.block, i, self.salt))
6942+        # Try to put an invalid tail segment
6943+        d.addCallback(lambda ignored:
6944+            self.shouldFail(LayoutInvalid, "test invalid tail segment",
6945+                            None,
6946+                            mw.put_block, self.block, 5, self.salt))
6947+        valid_block = "a"
6948+        d.addCallback(lambda ignored:
6949+            mw.put_block(valid_block, 5, self.salt))
6950+        return d
6951+
6952+
6953+    def test_write_enforces_order_constraints(self):
6954+        # We require that the MDMFSlotWriteProxy be interacted with in a
6955+        # specific way.
6956+        # That way is:
6957+        # 0: __init__
6958+        # 1: write blocks and salts
6959+        # 2: Write the encrypted private key
6960+        # 3: Write the block hashes
6961+        # 4: Write the share hashes
6962+        # 5: Write the root hash and salt hash
6963+        # 6: Write the signature and verification key
6964+        # 7: Write the file.
6965+        #
6966+        # Some of these can be performed out-of-order, and some can't.
6967+        # The dependencies that I want to test here are:
6968+        #  - Private key before block hashes
6969+        #  - share hashes and block hashes before root hash
6970+        #  - root hash before signature
6971+        #  - signature before verification key
6972+        mw0 = self._make_new_mw("si0", 0)
6973+        # Write some shares
6974+        d = defer.succeed(None)
6975+        for i in xrange(6):
6976+            d.addCallback(lambda ignored, i=i:
6977+                mw0.put_block(self.block, i, self.salt))
6978+        # Try to write the block hashes before writing the encrypted
6979+        # private key
6980+        d.addCallback(lambda ignored:
6981+            self.shouldFail(LayoutInvalid, "block hashes before key",
6982+                            None, mw0.put_blockhashes,
6983+                            self.block_hash_tree))
6984+
6985+        # Write the private key.
6986+        d.addCallback(lambda ignored:
6987+            mw0.put_encprivkey(self.encprivkey))
6988+
6989+
6990+        # Try to write the share hash chain without writing the block
6991+        # hash tree
6992+        d.addCallback(lambda ignored:
6993+            self.shouldFail(LayoutInvalid, "share hash chain before "
6994+                                           "salt hash tree",
6995+                            None,
6996+                            mw0.put_sharehashes, self.share_hash_chain))
6997+
6998+        # Try to write the root hash and without writing either the
6999+        # block hashes or the or the share hashes
7000+        d.addCallback(lambda ignored:
7001+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
7002+                            None,
7003+                            mw0.put_root_hash, self.root_hash))
7004+
7005+        # Now write the block hashes and try again
7006+        d.addCallback(lambda ignored:
7007+            mw0.put_blockhashes(self.block_hash_tree))
7008+
7009+        d.addCallback(lambda ignored:
7010+            self.shouldFail(LayoutInvalid, "root hash before share hashes",
7011+                            None, mw0.put_root_hash, self.root_hash))
7012+
7013+        # We haven't yet put the root hash on the share, so we shouldn't
7014+        # be able to sign it.
7015+        d.addCallback(lambda ignored:
7016+            self.shouldFail(LayoutInvalid, "signature before root hash",
7017+                            None, mw0.put_signature, self.signature))
7018+
7019+        d.addCallback(lambda ignored:
7020+            self.failUnlessRaises(LayoutInvalid, mw0.get_signable))
7021+
7022+        # ..and, since that fails, we also shouldn't be able to put the
7023+        # verification key.
7024+        d.addCallback(lambda ignored:
7025+            self.shouldFail(LayoutInvalid, "key before signature",
7026+                            None, mw0.put_verification_key,
7027+                            self.verification_key))
7028+
7029+        # Now write the share hashes.
7030+        d.addCallback(lambda ignored:
7031+            mw0.put_sharehashes(self.share_hash_chain))
7032+        # We should be able to write the root hash now too
7033+        d.addCallback(lambda ignored:
7034+            mw0.put_root_hash(self.root_hash))
7035+
7036+        # We should still be unable to put the verification key
7037+        d.addCallback(lambda ignored:
7038+            self.shouldFail(LayoutInvalid, "key before signature",
7039+                            None, mw0.put_verification_key,
7040+                            self.verification_key))
7041+
7042+        d.addCallback(lambda ignored:
7043+            mw0.put_signature(self.signature))
7044+
7045+        # We shouldn't be able to write the offsets to the remote server
7046+        # until the offset table is finished; IOW, until we have written
7047+        # the verification key.
7048+        d.addCallback(lambda ignored:
7049+            self.shouldFail(LayoutInvalid, "offsets before verification key",
7050+                            None,
7051+                            mw0.finish_publishing))
7052+
7053+        d.addCallback(lambda ignored:
7054+            mw0.put_verification_key(self.verification_key))
7055+        return d
7056+
7057+
7058+    def test_end_to_end(self):
7059+        mw = self._make_new_mw("si1", 0)
7060+        # Write a share using the mutable writer, and make sure that the
7061+        # reader knows how to read everything back to us.
7062+        d = defer.succeed(None)
7063+        for i in xrange(6):
7064+            d.addCallback(lambda ignored, i=i:
7065+                mw.put_block(self.block, i, self.salt))
7066+        d.addCallback(lambda ignored:
7067+            mw.put_encprivkey(self.encprivkey))
7068+        d.addCallback(lambda ignored:
7069+            mw.put_blockhashes(self.block_hash_tree))
7070+        d.addCallback(lambda ignored:
7071+            mw.put_sharehashes(self.share_hash_chain))
7072+        d.addCallback(lambda ignored:
7073+            mw.put_root_hash(self.root_hash))
7074+        d.addCallback(lambda ignored:
7075+            mw.put_signature(self.signature))
7076+        d.addCallback(lambda ignored:
7077+            mw.put_verification_key(self.verification_key))
7078+        d.addCallback(lambda ignored:
7079+            mw.finish_publishing())
7080+
7081+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7082+        def _check_block_and_salt((block, salt)):
7083+            self.failUnlessEqual(block, self.block)
7084+            self.failUnlessEqual(salt, self.salt)
7085+
7086+        for i in xrange(6):
7087+            d.addCallback(lambda ignored, i=i:
7088+                mr.get_block_and_salt(i))
7089+            d.addCallback(_check_block_and_salt)
7090+
7091+        d.addCallback(lambda ignored:
7092+            mr.get_encprivkey())
7093+        d.addCallback(lambda encprivkey:
7094+            self.failUnlessEqual(self.encprivkey, encprivkey))
7095+
7096+        d.addCallback(lambda ignored:
7097+            mr.get_blockhashes())
7098+        d.addCallback(lambda blockhashes:
7099+            self.failUnlessEqual(self.block_hash_tree, blockhashes))
7100+
7101+        d.addCallback(lambda ignored:
7102+            mr.get_sharehashes())
7103+        d.addCallback(lambda sharehashes:
7104+            self.failUnlessEqual(self.share_hash_chain, sharehashes))
7105+
7106+        d.addCallback(lambda ignored:
7107+            mr.get_signature())
7108+        d.addCallback(lambda signature:
7109+            self.failUnlessEqual(signature, self.signature))
7110+
7111+        d.addCallback(lambda ignored:
7112+            mr.get_verification_key())
7113+        d.addCallback(lambda verification_key:
7114+            self.failUnlessEqual(verification_key, self.verification_key))
7115+
7116+        d.addCallback(lambda ignored:
7117+            mr.get_seqnum())
7118+        d.addCallback(lambda seqnum:
7119+            self.failUnlessEqual(seqnum, 0))
7120+
7121+        d.addCallback(lambda ignored:
7122+            mr.get_root_hash())
7123+        d.addCallback(lambda root_hash:
7124+            self.failUnlessEqual(self.root_hash, root_hash))
7125+
7126+        d.addCallback(lambda ignored:
7127+            mr.get_encoding_parameters())
7128+        def _check_encoding_parameters((k, n, segsize, datalen)):
7129+            self.failUnlessEqual(k, 3)
7130+            self.failUnlessEqual(n, 10)
7131+            self.failUnlessEqual(segsize, 6)
7132+            self.failUnlessEqual(datalen, 36)
7133+        d.addCallback(_check_encoding_parameters)
7134+
7135+        d.addCallback(lambda ignored:
7136+            mr.get_checkstring())
7137+        d.addCallback(lambda checkstring:
7138+            self.failUnlessEqual(checkstring, mw.get_checkstring()))
7139+        return d
7140+
7141+
7142+    def test_is_sdmf(self):
7143+        # The MDMFSlotReadProxy should also know how to read SDMF files,
7144+        # since it will encounter them on the grid. Callers use the
7145+        # is_sdmf method to test this.
7146+        self.write_sdmf_share_to_server("si1")
7147+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7148+        d = mr.is_sdmf()
7149+        d.addCallback(lambda issdmf:
7150+            self.failUnless(issdmf))
7151+        return d
7152+
7153+
7154+    def test_reads_sdmf(self):
7155+        # The slot read proxy should, naturally, know how to tell us
7156+        # about data in the SDMF format
7157+        self.write_sdmf_share_to_server("si1")
7158+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7159+        d = defer.succeed(None)
7160+        d.addCallback(lambda ignored:
7161+            mr.is_sdmf())
7162+        d.addCallback(lambda issdmf:
7163+            self.failUnless(issdmf))
7164+
7165+        # What do we need to read?
7166+        #  - The sharedata
7167+        #  - The salt
7168+        d.addCallback(lambda ignored:
7169+            mr.get_block_and_salt(0))
7170+        def _check_block_and_salt(results):
7171+            block, salt = results
7172+            # Our original file is 36 bytes long. Then each share is 12
7173+            # bytes in size. The share is composed entirely of the
7174+            # letter a. self.block contains 2 as, so 6 * self.block is
7175+            # what we are looking for.
7176+            self.failUnlessEqual(block, self.block * 6)
7177+            self.failUnlessEqual(salt, self.salt)
7178+        d.addCallback(_check_block_and_salt)
7179+
7180+        #  - The blockhashes
7181+        d.addCallback(lambda ignored:
7182+            mr.get_blockhashes())
7183+        d.addCallback(lambda blockhashes:
7184+            self.failUnlessEqual(self.block_hash_tree,
7185+                                 blockhashes,
7186+                                 blockhashes))
7187+        #  - The sharehashes
7188+        d.addCallback(lambda ignored:
7189+            mr.get_sharehashes())
7190+        d.addCallback(lambda sharehashes:
7191+            self.failUnlessEqual(self.share_hash_chain,
7192+                                 sharehashes))
7193+        #  - The keys
7194+        d.addCallback(lambda ignored:
7195+            mr.get_encprivkey())
7196+        d.addCallback(lambda encprivkey:
7197+            self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey))
7198+        d.addCallback(lambda ignored:
7199+            mr.get_verification_key())
7200+        d.addCallback(lambda verification_key:
7201+            self.failUnlessEqual(verification_key,
7202+                                 self.verification_key,
7203+                                 verification_key))
7204+        #  - The signature
7205+        d.addCallback(lambda ignored:
7206+            mr.get_signature())
7207+        d.addCallback(lambda signature:
7208+            self.failUnlessEqual(signature, self.signature, signature))
7209+
7210+        #  - The sequence number
7211+        d.addCallback(lambda ignored:
7212+            mr.get_seqnum())
7213+        d.addCallback(lambda seqnum:
7214+            self.failUnlessEqual(seqnum, 0, seqnum))
7215+
7216+        #  - The root hash
7217+        d.addCallback(lambda ignored:
7218+            mr.get_root_hash())
7219+        d.addCallback(lambda root_hash:
7220+            self.failUnlessEqual(root_hash, self.root_hash, root_hash))
7221+        return d
7222+
7223+
7224+    def test_only_reads_one_segment_sdmf(self):
7225+        # SDMF shares have only one segment, so it doesn't make sense to
7226+        # read more segments than that. The reader should know this and
7227+        # complain if we try to do that.
7228+        self.write_sdmf_share_to_server("si1")
7229+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7230+        d = defer.succeed(None)
7231+        d.addCallback(lambda ignored:
7232+            mr.is_sdmf())
7233+        d.addCallback(lambda issdmf:
7234+            self.failUnless(issdmf))
7235+        d.addCallback(lambda ignored:
7236+            self.shouldFail(LayoutInvalid, "test bad segment",
7237+                            None,
7238+                            mr.get_block_and_salt, 1))
7239+        return d
7240+
7241+
7242+    def test_read_with_prefetched_mdmf_data(self):
7243+        # The MDMFSlotReadProxy will prefill certain fields if you pass
7244+        # it data that you have already fetched. This is useful for
7245+        # cases like the Servermap, which prefetches ~2kb of data while
7246+        # finding out which shares are on the remote peer so that it
7247+        # doesn't waste round trips.
7248+        mdmf_data = self.build_test_mdmf_share()
7249+        self.write_test_share_to_server("si1")
7250+        def _make_mr(ignored, length):
7251+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length])
7252+            return mr
7253+
7254+        d = defer.succeed(None)
7255+        # This should be enough to fill in both the encoding parameters
7256+        # and the table of offsets, which will complete the version
7257+        # information tuple.
7258+        d.addCallback(_make_mr, 107)
7259+        d.addCallback(lambda mr:
7260+            mr.get_verinfo())
7261+        def _check_verinfo(verinfo):
7262+            self.failUnless(verinfo)
7263+            self.failUnlessEqual(len(verinfo), 9)
7264+            (seqnum,
7265+             root_hash,
7266+             salt_hash,
7267+             segsize,
7268+             datalen,
7269+             k,
7270+             n,
7271+             prefix,
7272+             offsets) = verinfo
7273+            self.failUnlessEqual(seqnum, 0)
7274+            self.failUnlessEqual(root_hash, self.root_hash)
7275+            self.failUnlessEqual(segsize, 6)
7276+            self.failUnlessEqual(datalen, 36)
7277+            self.failUnlessEqual(k, 3)
7278+            self.failUnlessEqual(n, 10)
7279+            expected_prefix = struct.pack(MDMFSIGNABLEHEADER,
7280+                                          1,
7281+                                          seqnum,
7282+                                          root_hash,
7283+                                          k,
7284+                                          n,
7285+                                          segsize,
7286+                                          datalen)
7287+            self.failUnlessEqual(expected_prefix, prefix)
7288+            self.failUnlessEqual(self.rref.read_count, 0)
7289+        d.addCallback(_check_verinfo)
7290+        # This is not enough data to read a block and a share, so the
7291+        # wrapper should attempt to read this from the remote server.
7292+        d.addCallback(_make_mr, 107)
7293+        d.addCallback(lambda mr:
7294+            mr.get_block_and_salt(0))
7295+        def _check_block_and_salt((block, salt)):
7296+            self.failUnlessEqual(block, self.block)
7297+            self.failUnlessEqual(salt, self.salt)
7298+            self.failUnlessEqual(self.rref.read_count, 1)
7299+        # This should be enough data to read one block.
7300+        d.addCallback(_make_mr, 249)
7301+        d.addCallback(lambda mr:
7302+            mr.get_block_and_salt(0))
7303+        d.addCallback(_check_block_and_salt)
7304+        return d
7305+
7306+
7307+    def test_read_with_prefetched_sdmf_data(self):
7308+        sdmf_data = self.build_test_sdmf_share()
7309+        self.write_sdmf_share_to_server("si1")
7310+        def _make_mr(ignored, length):
7311+            mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length])
7312+            return mr
7313+
7314+        d = defer.succeed(None)
7315+        # This should be enough to get us the encoding parameters,
7316+        # offset table, and everything else we need to build a verinfo
7317+        # string.
7318+        d.addCallback(_make_mr, 107)
7319+        d.addCallback(lambda mr:
7320+            mr.get_verinfo())
7321+        def _check_verinfo(verinfo):
7322+            self.failUnless(verinfo)
7323+            self.failUnlessEqual(len(verinfo), 9)
7324+            (seqnum,
7325+             root_hash,
7326+             salt,
7327+             segsize,
7328+             datalen,
7329+             k,
7330+             n,
7331+             prefix,
7332+             offsets) = verinfo
7333+            self.failUnlessEqual(seqnum, 0)
7334+            self.failUnlessEqual(root_hash, self.root_hash)
7335+            self.failUnlessEqual(salt, self.salt)
7336+            self.failUnlessEqual(segsize, 36)
7337+            self.failUnlessEqual(datalen, 36)
7338+            self.failUnlessEqual(k, 3)
7339+            self.failUnlessEqual(n, 10)
7340+            expected_prefix = struct.pack(SIGNED_PREFIX,
7341+                                          0,
7342+                                          seqnum,
7343+                                          root_hash,
7344+                                          salt,
7345+                                          k,
7346+                                          n,
7347+                                          segsize,
7348+                                          datalen)
7349+            self.failUnlessEqual(expected_prefix, prefix)
7350+            self.failUnlessEqual(self.rref.read_count, 0)
7351+        d.addCallback(_check_verinfo)
7352+        # This shouldn't be enough to read any share data.
7353+        d.addCallback(_make_mr, 107)
7354+        d.addCallback(lambda mr:
7355+            mr.get_block_and_salt(0))
7356+        def _check_block_and_salt((block, salt)):
7357+            self.failUnlessEqual(block, self.block * 6)
7358+            self.failUnlessEqual(salt, self.salt)
7359+            # TODO: Fix the read routine so that it reads only the data
7360+            #       that it has cached if it can't read all of it.
7361+            self.failUnlessEqual(self.rref.read_count, 2)
7362+
7363+        # This should be enough to read share data.
7364+        d.addCallback(_make_mr, self.offsets['share_data'])
7365+        d.addCallback(lambda mr:
7366+            mr.get_block_and_salt(0))
7367+        d.addCallback(_check_block_and_salt)
7368+        return d
7369+
7370+
7371+    def test_read_with_empty_mdmf_file(self):
7372+        # Some tests upload a file with no contents to test things
7373+        # unrelated to the actual handling of the content of the file.
7374+        # The reader should behave intelligently in these cases.
7375+        self.write_test_share_to_server("si1", empty=True)
7376+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7377+        # We should be able to get the encoding parameters, and they
7378+        # should be correct.
7379+        d = defer.succeed(None)
7380+        d.addCallback(lambda ignored:
7381+            mr.get_encoding_parameters())
7382+        def _check_encoding_parameters(params):
7383+            self.failUnlessEqual(len(params), 4)
7384+            k, n, segsize, datalen = params
7385+            self.failUnlessEqual(k, 3)
7386+            self.failUnlessEqual(n, 10)
7387+            self.failUnlessEqual(segsize, 0)
7388+            self.failUnlessEqual(datalen, 0)
7389+        d.addCallback(_check_encoding_parameters)
7390+
7391+        # We should not be able to fetch a block, since there are no
7392+        # blocks to fetch
7393+        d.addCallback(lambda ignored:
7394+            self.shouldFail(LayoutInvalid, "get block on empty file",
7395+                            None,
7396+                            mr.get_block_and_salt, 0))
7397+        return d
7398+
7399+
7400+    def test_read_with_empty_sdmf_file(self):
7401+        self.write_sdmf_share_to_server("si1", empty=True)
7402+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7403+        # We should be able to get the encoding parameters, and they
7404+        # should be correct
7405+        d = defer.succeed(None)
7406+        d.addCallback(lambda ignored:
7407+            mr.get_encoding_parameters())
7408+        def _check_encoding_parameters(params):
7409+            self.failUnlessEqual(len(params), 4)
7410+            k, n, segsize, datalen = params
7411+            self.failUnlessEqual(k, 3)
7412+            self.failUnlessEqual(n, 10)
7413+            self.failUnlessEqual(segsize, 0)
7414+            self.failUnlessEqual(datalen, 0)
7415+        d.addCallback(_check_encoding_parameters)
7416+
7417+        # It does not make sense to get a block in this format, so we
7418+        # should not be able to.
7419+        d.addCallback(lambda ignored:
7420+            self.shouldFail(LayoutInvalid, "get block on an empty file",
7421+                            None,
7422+                            mr.get_block_and_salt, 0))
7423+        return d
7424+
7425+
7426+    def test_verinfo_with_sdmf_file(self):
7427+        self.write_sdmf_share_to_server("si1")
7428+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7429+        # We should be able to get the version information.
7430+        d = defer.succeed(None)
7431+        d.addCallback(lambda ignored:
7432+            mr.get_verinfo())
7433+        def _check_verinfo(verinfo):
7434+            self.failUnless(verinfo)
7435+            self.failUnlessEqual(len(verinfo), 9)
7436+            (seqnum,
7437+             root_hash,
7438+             salt,
7439+             segsize,
7440+             datalen,
7441+             k,
7442+             n,
7443+             prefix,
7444+             offsets) = verinfo
7445+            self.failUnlessEqual(seqnum, 0)
7446+            self.failUnlessEqual(root_hash, self.root_hash)
7447+            self.failUnlessEqual(salt, self.salt)
7448+            self.failUnlessEqual(segsize, 36)
7449+            self.failUnlessEqual(datalen, 36)
7450+            self.failUnlessEqual(k, 3)
7451+            self.failUnlessEqual(n, 10)
7452+            expected_prefix = struct.pack(">BQ32s16s BBQQ",
7453+                                          0,
7454+                                          seqnum,
7455+                                          root_hash,
7456+                                          salt,
7457+                                          k,
7458+                                          n,
7459+                                          segsize,
7460+                                          datalen)
7461+            self.failUnlessEqual(prefix, expected_prefix)
7462+            self.failUnlessEqual(offsets, self.offsets)
7463+        d.addCallback(_check_verinfo)
7464+        return d
7465+
7466+
7467+    def test_verinfo_with_mdmf_file(self):
7468+        self.write_test_share_to_server("si1")
7469+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7470+        d = defer.succeed(None)
7471+        d.addCallback(lambda ignored:
7472+            mr.get_verinfo())
7473+        def _check_verinfo(verinfo):
7474+            self.failUnless(verinfo)
7475+            self.failUnlessEqual(len(verinfo), 9)
7476+            (seqnum,
7477+             root_hash,
7478+             IV,
7479+             segsize,
7480+             datalen,
7481+             k,
7482+             n,
7483+             prefix,
7484+             offsets) = verinfo
7485+            self.failUnlessEqual(seqnum, 0)
7486+            self.failUnlessEqual(root_hash, self.root_hash)
7487+            self.failIf(IV)
7488+            self.failUnlessEqual(segsize, 6)
7489+            self.failUnlessEqual(datalen, 36)
7490+            self.failUnlessEqual(k, 3)
7491+            self.failUnlessEqual(n, 10)
7492+            expected_prefix = struct.pack(">BQ32s BBQQ",
7493+                                          1,
7494+                                          seqnum,
7495+                                          root_hash,
7496+                                          k,
7497+                                          n,
7498+                                          segsize,
7499+                                          datalen)
7500+            self.failUnlessEqual(prefix, expected_prefix)
7501+            self.failUnlessEqual(offsets, self.offsets)
7502+        d.addCallback(_check_verinfo)
7503+        return d
7504+
7505+
7506+    def test_reader_queue(self):
7507+        self.write_test_share_to_server('si1')
7508+        mr = MDMFSlotReadProxy(self.rref, "si1", 0)
7509+        d1 = mr.get_block_and_salt(0, queue=True)
7510+        d2 = mr.get_blockhashes(queue=True)
7511+        d3 = mr.get_sharehashes(queue=True)
7512+        d4 = mr.get_signature(queue=True)
7513+        d5 = mr.get_verification_key(queue=True)
7514+        dl = defer.DeferredList([d1, d2, d3, d4, d5])
7515+        mr.flush()
7516+        def _print(results):
7517+            self.failUnlessEqual(len(results), 5)
7518+            # We have one read for version information and offsets, and
7519+            # one for everything else.
7520+            self.failUnlessEqual(self.rref.read_count, 2)
7521+            block, salt = results[0][1] # results[0] is a boolean that says
7522+                                           # whether or not the operation
7523+                                           # worked.
7524+            self.failUnlessEqual(self.block, block)
7525+            self.failUnlessEqual(self.salt, salt)
7526+
7527+            blockhashes = results[1][1]
7528+            self.failUnlessEqual(self.block_hash_tree, blockhashes)
7529+
7530+            sharehashes = results[2][1]
7531+            self.failUnlessEqual(self.share_hash_chain, sharehashes)
7532+
7533+            signature = results[3][1]
7534+            self.failUnlessEqual(self.signature, signature)
7535+
7536+            verification_key = results[4][1]
7537+            self.failUnlessEqual(self.verification_key, verification_key)
7538+        dl.addCallback(_print)
7539+        return dl
7540+
7541+
7542+    def test_sdmf_writer(self):
7543+        # Go through the motions of writing an SDMF share to the storage
7544+        # server. Then read the storage server to see that the share got
7545+        # written in the way that we think it should have.
7546+
7547+        # We do this first so that the necessary instance variables get
7548+        # set the way we want them for the tests below.
7549+        data = self.build_test_sdmf_share()
7550+        sdmfr = SDMFSlotWriteProxy(0,
7551+                                   self.rref,
7552+                                   "si1",
7553+                                   self.secrets,
7554+                                   0, 3, 10, 36, 36)
7555+        # Put the block and salt.
7556+        sdmfr.put_block(self.blockdata, 0, self.salt)
7557+
7558+        # Put the encprivkey
7559+        sdmfr.put_encprivkey(self.encprivkey)
7560+
7561+        # Put the block and share hash chains
7562+        sdmfr.put_blockhashes(self.block_hash_tree)
7563+        sdmfr.put_sharehashes(self.share_hash_chain)
7564+        sdmfr.put_root_hash(self.root_hash)
7565+
7566+        # Put the signature
7567+        sdmfr.put_signature(self.signature)
7568+
7569+        # Put the verification key
7570+        sdmfr.put_verification_key(self.verification_key)
7571+
7572+        # Now check to make sure that nothing has been written yet.
7573+        self.failUnlessEqual(self.rref.write_count, 0)
7574+
7575+        # Now finish publishing
7576+        d = sdmfr.finish_publishing()
7577+        def _then(ignored):
7578+            self.failUnlessEqual(self.rref.write_count, 1)
7579+            read = self.ss.remote_slot_readv
7580+            self.failUnlessEqual(read("si1", [0], [(0, len(data))]),
7581+                                 {0: [data]})
7582+        d.addCallback(_then)
7583+        return d
7584+
7585+
7586+    def test_sdmf_writer_preexisting_share(self):
7587+        data = self.build_test_sdmf_share()
7588+        self.write_sdmf_share_to_server("si1")
7589+
7590+        # Now there is a share on the storage server. To successfully
7591+        # write, we need to set the checkstring correctly. When we
7592+        # don't, no write should occur.
7593+        sdmfw = SDMFSlotWriteProxy(0,
7594+                                   self.rref,
7595+                                   "si1",
7596+                                   self.secrets,
7597+                                   1, 3, 10, 36, 36)
7598+        sdmfw.put_block(self.blockdata, 0, self.salt)
7599+
7600+        # Put the encprivkey
7601+        sdmfw.put_encprivkey(self.encprivkey)
7602+
7603+        # Put the block and share hash chains
7604+        sdmfw.put_blockhashes(self.block_hash_tree)
7605+        sdmfw.put_sharehashes(self.share_hash_chain)
7606+
7607+        # Put the root hash
7608+        sdmfw.put_root_hash(self.root_hash)
7609+
7610+        # Put the signature
7611+        sdmfw.put_signature(self.signature)
7612+
7613+        # Put the verification key
7614+        sdmfw.put_verification_key(self.verification_key)
7615+
7616+        # We shouldn't have a checkstring yet
7617+        self.failUnlessEqual(sdmfw.get_checkstring(), "")
7618+
7619+        d = sdmfw.finish_publishing()
7620+        def _then(results):
7621+            self.failIf(results[0])
7622+            # this is the correct checkstring
7623+            self._expected_checkstring = results[1][0][0]
7624+            return self._expected_checkstring
7625+
7626+        d.addCallback(_then)
7627+        d.addCallback(sdmfw.set_checkstring)
7628+        d.addCallback(lambda ignored:
7629+            sdmfw.get_checkstring())
7630+        d.addCallback(lambda checkstring:
7631+            self.failUnlessEqual(checkstring, self._expected_checkstring))
7632+        d.addCallback(lambda ignored:
7633+            sdmfw.finish_publishing())
7634+        def _then_again(results):
7635+            self.failUnless(results[0])
7636+            read = self.ss.remote_slot_readv
7637+            self.failUnlessEqual(read("si1", [0], [(1, 8)]),
7638+                                 {0: [struct.pack(">Q", 1)]})
7639+            self.failUnlessEqual(read("si1", [0], [(9, len(data) - 9)]),
7640+                                 {0: [data[9:]]})
7641+        d.addCallback(_then_again)
7642+        return d
7643+
7644+
7645 class Stats(unittest.TestCase):
7646 
7647     def setUp(self):
7648}
7649[mutable/retrieve.py: Modify the retrieval process to support MDMF
7650Kevan Carstensen <kevan@isnotajoke.com>**20100819003409
7651 Ignore-this: c03f4e41aaa0366a9bf44847f2caf9db
7652 
7653 The logic behind a mutable file download had to be adapted to work with
7654 segmented mutable files; this patch performs those adaptations. It also
7655 exposes some decoding and decrypting functionality to make partial-file
7656 updates a little easier, and supports efficient random-access downloads
7657 of parts of an MDMF file.
7658] {
7659hunk ./src/allmydata/mutable/retrieve.py 2
7660 
7661-import struct, time
7662+import time
7663 from itertools import count
7664 from zope.interface import implements
7665 from twisted.internet import defer
7666merger 0.0 (
7667hunk ./src/allmydata/mutable/retrieve.py 10
7668+from allmydata.util.dictutil import DictOfSets
7669hunk ./src/allmydata/mutable/retrieve.py 7
7670-from foolscap.api import DeadReferenceError, eventually, fireEventually
7671-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
7672-from allmydata.util import hashutil, idlib, log
7673+from twisted.internet.interfaces import IPushProducer, IConsumer
7674+from foolscap.api import eventually, fireEventually
7675+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
7676+                                 MDMF_VERSION, SDMF_VERSION
7677+from allmydata.util import hashutil, log, mathutil
7678)
7679hunk ./src/allmydata/mutable/retrieve.py 16
7680 from pycryptopp.publickey import rsa
7681 
7682 from allmydata.mutable.common import CorruptShareError, UncoordinatedWriteError
7683-from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data
7684+from allmydata.mutable.layout import MDMFSlotReadProxy
7685 
7686 class RetrieveStatus:
7687     implements(IRetrieveStatus)
7688hunk ./src/allmydata/mutable/retrieve.py 83
7689     # times, and each will have a separate response chain. However the
7690     # Retrieve object will remain tied to a specific version of the file, and
7691     # will use a single ServerMap instance.
7692+    implements(IPushProducer)
7693 
7694hunk ./src/allmydata/mutable/retrieve.py 85
7695-    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False):
7696+    def __init__(self, filenode, servermap, verinfo, fetch_privkey=False,
7697+                 verify=False):
7698         self._node = filenode
7699         assert self._node.get_pubkey()
7700         self._storage_index = filenode.get_storage_index()
7701hunk ./src/allmydata/mutable/retrieve.py 104
7702         self.verinfo = verinfo
7703         # during repair, we may be called upon to grab the private key, since
7704         # it wasn't picked up during a verify=False checker run, and we'll
7705-        # need it for repair to generate the a new version.
7706-        self._need_privkey = fetch_privkey
7707-        if self._node.get_privkey():
7708+        # need it for repair to generate a new version.
7709+        self._need_privkey = fetch_privkey or verify
7710+        if self._node.get_privkey() and not verify:
7711             self._need_privkey = False
7712 
7713hunk ./src/allmydata/mutable/retrieve.py 109
7714+        if self._need_privkey:
7715+            # TODO: Evaluate the need for this. We'll use it if we want
7716+            # to limit how many queries are on the wire for the privkey
7717+            # at once.
7718+            self._privkey_query_markers = [] # one Marker for each time we've
7719+                                             # tried to get the privkey.
7720+
7721+        # verify means that we are using the downloader logic to verify all
7722+        # of our shares. This tells the downloader a few things.
7723+        #
7724+        # 1. We need to download all of the shares.
7725+        # 2. We don't need to decode or decrypt the shares, since our
7726+        #    caller doesn't care about the plaintext, only the
7727+        #    information about which shares are or are not valid.
7728+        # 3. When we are validating readers, we need to validate the
7729+        #    signature on the prefix. Do we? We already do this in the
7730+        #    servermap update?
7731+        self._verify = False
7732+        if verify:
7733+            self._verify = True
7734+
7735         self._status = RetrieveStatus()
7736         self._status.set_storage_index(self._storage_index)
7737         self._status.set_helper(False)
7738hunk ./src/allmydata/mutable/retrieve.py 139
7739          offsets_tuple) = self.verinfo
7740         self._status.set_size(datalength)
7741         self._status.set_encoding(k, N)
7742+        self.readers = {}
7743+        self._paused = False
7744+        self._paused_deferred = None
7745+        self._offset = None
7746+        self._read_length = None
7747+        self.log("got seqnum %d" % self.verinfo[0])
7748+
7749 
7750     def get_status(self):
7751         return self._status
7752hunk ./src/allmydata/mutable/retrieve.py 157
7753             kwargs["facility"] = "tahoe.mutable.retrieve"
7754         return log.msg(*args, **kwargs)
7755 
7756-    def download(self):
7757+
7758+    ###################
7759+    # IPushProducer
7760+
7761+    def pauseProducing(self):
7762+        """
7763+        I am called by my download target if we have produced too much
7764+        data for it to handle. I make the downloader stop producing new
7765+        data until my resumeProducing method is called.
7766+        """
7767+        if self._paused:
7768+            return
7769+
7770+        # fired when the download is unpaused.
7771+        self._old_status = self._status.get_status()
7772+        self._status.set_status("Paused")
7773+
7774+        self._pause_deferred = defer.Deferred()
7775+        self._paused = True
7776+
7777+
7778+    def resumeProducing(self):
7779+        """
7780+        I am called by my download target once it is ready to begin
7781+        receiving data again.
7782+        """
7783+        if not self._paused:
7784+            return
7785+
7786+        self._paused = False
7787+        p = self._pause_deferred
7788+        self._pause_deferred = None
7789+        self._status.set_status(self._old_status)
7790+
7791+        eventually(p.callback, None)
7792+
7793+
7794+    def _check_for_paused(self, res):
7795+        """
7796+        I am called just before a write to the consumer. I return a
7797+        Deferred that eventually fires with the data that is to be
7798+        written to the consumer. If the download has not been paused,
7799+        the Deferred fires immediately. Otherwise, the Deferred fires
7800+        when the downloader is unpaused.
7801+        """
7802+        if self._paused:
7803+            d = defer.Deferred()
7804+            self._pause_defered.addCallback(lambda ignored: d.callback(res))
7805+            return d
7806+        return defer.succeed(res)
7807+
7808+
7809+    def download(self, consumer=None, offset=0, size=None):
7810+        assert IConsumer.providedBy(consumer) or self._verify
7811+
7812+        if consumer:
7813+            self._consumer = consumer
7814+            # we provide IPushProducer, so streaming=True, per
7815+            # IConsumer.
7816+            self._consumer.registerProducer(self, streaming=True)
7817+
7818         self._done_deferred = defer.Deferred()
7819         self._started = time.time()
7820         self._status.set_status("Retrieving Shares")
7821hunk ./src/allmydata/mutable/retrieve.py 222
7822 
7823+        self._offset = offset
7824+        self._read_length = size
7825+
7826         # first, which servers can we use?
7827         versionmap = self.servermap.make_versionmap()
7828         shares = versionmap[self.verinfo]
7829hunk ./src/allmydata/mutable/retrieve.py 232
7830         self.remaining_sharemap = DictOfSets()
7831         for (shnum, peerid, timestamp) in shares:
7832             self.remaining_sharemap.add(shnum, peerid)
7833+            # If the servermap update fetched anything, it fetched at least 1
7834+            # KiB, so we ask for that much.
7835+            # TODO: Change the cache methods to allow us to fetch all of the
7836+            # data that they have, then change this method to do that.
7837+            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
7838+                                                               shnum,
7839+                                                               0,
7840+                                                               1000)
7841+            ss = self.servermap.connections[peerid]
7842+            reader = MDMFSlotReadProxy(ss,
7843+                                       self._storage_index,
7844+                                       shnum,
7845+                                       any_cache)
7846+            reader.peerid = peerid
7847+            self.readers[shnum] = reader
7848+
7849 
7850         self.shares = {} # maps shnum to validated blocks
7851hunk ./src/allmydata/mutable/retrieve.py 250
7852+        self._active_readers = [] # list of active readers for this dl.
7853+        self._validated_readers = set() # set of readers that we have
7854+                                        # validated the prefix of
7855+        self._block_hash_trees = {} # shnum => hashtree
7856 
7857         # how many shares do we need?
7858hunk ./src/allmydata/mutable/retrieve.py 256
7859-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
7860+        (seqnum,
7861+         root_hash,
7862+         IV,
7863+         segsize,
7864+         datalength,
7865+         k,
7866+         N,
7867+         prefix,
7868          offsets_tuple) = self.verinfo
7869hunk ./src/allmydata/mutable/retrieve.py 265
7870-        assert len(self.remaining_sharemap) >= k
7871-        # we start with the lowest shnums we have available, since FEC is
7872-        # faster if we're using "primary shares"
7873-        self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k])
7874-        for shnum in self.active_shnums:
7875-            # we use an arbitrary peer who has the share. If shares are
7876-            # doubled up (more than one share per peer), we could make this
7877-            # run faster by spreading the load among multiple peers. But the
7878-            # algorithm to do that is more complicated than I want to write
7879-            # right now, and a well-provisioned grid shouldn't have multiple
7880-            # shares per peer.
7881-            peerid = list(self.remaining_sharemap[shnum])[0]
7882-            self.get_data(shnum, peerid)
7883 
7884hunk ./src/allmydata/mutable/retrieve.py 266
7885-        # control flow beyond this point: state machine. Receiving responses
7886-        # from queries is the input. We might send out more queries, or we
7887-        # might produce a result.
7888 
7889hunk ./src/allmydata/mutable/retrieve.py 267
7890+        # We need one share hash tree for the entire file; its leaves
7891+        # are the roots of the block hash trees for the shares that
7892+        # comprise it, and its root is in the verinfo.
7893+        self.share_hash_tree = hashtree.IncompleteHashTree(N)
7894+        self.share_hash_tree.set_hashes({0: root_hash})
7895+
7896+        # This will set up both the segment decoder and the tail segment
7897+        # decoder, as well as a variety of other instance variables that
7898+        # the download process will use.
7899+        self._setup_encoding_parameters()
7900+        assert len(self.remaining_sharemap) >= k
7901+
7902+        self.log("starting download")
7903+        self._paused = False
7904+        self._started_fetching = time.time()
7905+
7906+        self._add_active_peers()
7907+        # The download process beyond this is a state machine.
7908+        # _add_active_peers will select the peers that we want to use
7909+        # for the download, and then attempt to start downloading. After
7910+        # each segment, it will check for doneness, reacting to broken
7911+        # peers and corrupt shares as necessary. If it runs out of good
7912+        # peers before downloading all of the segments, _done_deferred
7913+        # will errback.  Otherwise, it will eventually callback with the
7914+        # contents of the mutable file.
7915         return self._done_deferred
7916 
7917hunk ./src/allmydata/mutable/retrieve.py 294
7918-    def get_data(self, shnum, peerid):
7919-        self.log(format="sending sh#%(shnum)d request to [%(peerid)s]",
7920-                 shnum=shnum,
7921-                 peerid=idlib.shortnodeid_b2a(peerid),
7922-                 level=log.NOISY)
7923-        ss = self.servermap.connections[peerid]
7924-        started = time.time()
7925-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
7926+
7927+    def decode(self, blocks_and_salts, segnum):
7928+        """
7929+        I am a helper method that the mutable file update process uses
7930+        as a shortcut to decode and decrypt the segments that it needs
7931+        to fetch in order to perform a file update. I take in a
7932+        collection of blocks and salts, and pick some of those to make a
7933+        segment with. I return the plaintext associated with that
7934+        segment.
7935+        """
7936+        # shnum => block hash tree. Unusued, but setup_encoding_parameters will
7937+        # want to set this.
7938+        # XXX: Make it so that it won't set this if we're just decoding.
7939+        self._block_hash_trees = {}
7940+        self._setup_encoding_parameters()
7941+        # This is the form expected by decode.
7942+        blocks_and_salts = blocks_and_salts.items()
7943+        blocks_and_salts = [(True, [d]) for d in blocks_and_salts]
7944+
7945+        d = self._decode_blocks(blocks_and_salts, segnum)
7946+        d.addCallback(self._decrypt_segment)
7947+        return d
7948+
7949+
7950+    def _setup_encoding_parameters(self):
7951+        """
7952+        I set up the encoding parameters, including k, n, the number
7953+        of segments associated with this file, and the segment decoder.
7954+        """
7955+        (seqnum,
7956+         root_hash,
7957+         IV,
7958+         segsize,
7959+         datalength,
7960+         k,
7961+         n,
7962+         known_prefix,
7963          offsets_tuple) = self.verinfo
7964hunk ./src/allmydata/mutable/retrieve.py 332
7965-        offsets = dict(offsets_tuple)
7966+        self._required_shares = k
7967+        self._total_shares = n
7968+        self._segment_size = segsize
7969+        self._data_length = datalength
7970 
7971hunk ./src/allmydata/mutable/retrieve.py 337
7972-        # we read the checkstring, to make sure that the data we grab is from
7973-        # the right version.
7974-        readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ]
7975+        if not IV:
7976+            self._version = MDMF_VERSION
7977+        else:
7978+            self._version = SDMF_VERSION
7979 
7980hunk ./src/allmydata/mutable/retrieve.py 342
7981-        # We also read the data, and the hashes necessary to validate them
7982-        # (share_hash_chain, block_hash_tree, share_data). We don't read the
7983-        # signature or the pubkey, since that was handled during the
7984-        # servermap phase, and we'll be comparing the share hash chain
7985-        # against the roothash that was validated back then.
7986+        if datalength and segsize:
7987+            self._num_segments = mathutil.div_ceil(datalength, segsize)
7988+            self._tail_data_size = datalength % segsize
7989+        else:
7990+            self._num_segments = 0
7991+            self._tail_data_size = 0
7992 
7993hunk ./src/allmydata/mutable/retrieve.py 349
7994-        readv.append( (offsets['share_hash_chain'],
7995-                       offsets['enc_privkey'] - offsets['share_hash_chain'] ) )
7996+        self._segment_decoder = codec.CRSDecoder()
7997+        self._segment_decoder.set_params(segsize, k, n)
7998 
7999hunk ./src/allmydata/mutable/retrieve.py 352
8000-        # if we need the private key (for repair), we also fetch that
8001-        if self._need_privkey:
8002-            readv.append( (offsets['enc_privkey'],
8003-                           offsets['EOF'] - offsets['enc_privkey']) )
8004+        if  not self._tail_data_size:
8005+            self._tail_data_size = segsize
8006+
8007+        self._tail_segment_size = mathutil.next_multiple(self._tail_data_size,
8008+                                                         self._required_shares)
8009+        if self._tail_segment_size == self._segment_size:
8010+            self._tail_decoder = self._segment_decoder
8011+        else:
8012+            self._tail_decoder = codec.CRSDecoder()
8013+            self._tail_decoder.set_params(self._tail_segment_size,
8014+                                          self._required_shares,
8015+                                          self._total_shares)
8016 
8017hunk ./src/allmydata/mutable/retrieve.py 365
8018-        m = Marker()
8019-        self._outstanding_queries[m] = (peerid, shnum, started)
8020+        self.log("got encoding parameters: "
8021+                 "k: %d "
8022+                 "n: %d "
8023+                 "%d segments of %d bytes each (%d byte tail segment)" % \
8024+                 (k, n, self._num_segments, self._segment_size,
8025+                  self._tail_segment_size))
8026 
8027         # ask the cache first
8028         got_from_cache = False
8029merger 0.0 (
8030hunk ./src/allmydata/mutable/retrieve.py 376
8031-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
8032-                                                            offset, length)
8033+            data = self._node._read_from_cache(self.verinfo, shnum, offset, length)
8034hunk ./src/allmydata/mutable/retrieve.py 372
8035-        # ask the cache first
8036-        got_from_cache = False
8037-        datavs = []
8038-        for (offset, length) in readv:
8039-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
8040-                                                            offset, length)
8041-            if data is not None:
8042-                datavs.append(data)
8043-        if len(datavs) == len(readv):
8044-            self.log("got data from cache")
8045-            got_from_cache = True
8046-            d = fireEventually({shnum: datavs})
8047-            # datavs is a dict mapping shnum to a pair of strings
8048+        for i in xrange(self._total_shares):
8049+            # So we don't have to do this later.
8050+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
8051+
8052+        # Our last task is to tell the downloader where to start and
8053+        # where to stop. We use three parameters for that:
8054+        #   - self._start_segment: the segment that we need to start
8055+        #     downloading from.
8056+        #   - self._current_segment: the next segment that we need to
8057+        #     download.
8058+        #   - self._last_segment: The last segment that we were asked to
8059+        #     download.
8060+        #
8061+        #  We say that the download is complete when
8062+        #  self._current_segment > self._last_segment. We use
8063+        #  self._start_segment and self._last_segment to know when to
8064+        #  strip things off of segments, and how much to strip.
8065+        if self._offset:
8066+            self.log("got offset: %d" % self._offset)
8067+            # our start segment is the first segment containing the
8068+            # offset we were given.
8069+            start = mathutil.div_ceil(self._offset,
8070+                                      self._segment_size)
8071+            # this gets us the first segment after self._offset. Then
8072+            # our start segment is the one before it.
8073+            start -= 1
8074+
8075+            assert start < self._num_segments
8076+            self._start_segment = start
8077+            self.log("got start segment: %d" % self._start_segment)
8078)
8079hunk ./src/allmydata/mutable/retrieve.py 386
8080             d = fireEventually({shnum: datavs})
8081             # datavs is a dict mapping shnum to a pair of strings
8082         else:
8083-            d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
8084-        self.remaining_sharemap.discard(shnum, peerid)
8085+            self._start_segment = 0
8086 
8087hunk ./src/allmydata/mutable/retrieve.py 388
8088-        d.addCallback(self._got_results, m, peerid, started, got_from_cache)
8089-        d.addErrback(self._query_failed, m, peerid)
8090-        # errors that aren't handled by _query_failed (and errors caused by
8091-        # _query_failed) get logged, but we still want to check for doneness.
8092-        def _oops(f):
8093-            self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s",
8094-                     shnum=shnum,
8095-                     peerid=idlib.shortnodeid_b2a(peerid),
8096-                     failure=f,
8097-                     level=log.WEIRD, umid="W0xnQA")
8098-        d.addErrback(_oops)
8099-        d.addBoth(self._check_for_done)
8100-        # any error during _check_for_done means the download fails. If the
8101-        # download is successful, _check_for_done will fire _done by itself.
8102-        d.addErrback(self._done)
8103-        d.addErrback(log.err)
8104-        return d # purely for testing convenience
8105 
8106hunk ./src/allmydata/mutable/retrieve.py 389
8107-    def _do_read(self, ss, peerid, storage_index, shnums, readv):
8108-        # isolate the callRemote to a separate method, so tests can subclass
8109-        # Publish and override it
8110-        d = ss.callRemote("slot_readv", storage_index, shnums, readv)
8111-        return d
8112+        if self._read_length:
8113+            # our end segment is the last segment containing part of the
8114+            # segment that we were asked to read.
8115+            self.log("got read length %d" % self._read_length)
8116+            end_data = self._offset + self._read_length
8117+            end = mathutil.div_ceil(end_data,
8118+                                    self._segment_size)
8119+            end -= 1
8120+            assert end < self._num_segments
8121+            self._last_segment = end
8122+            self.log("got end segment: %d" % self._last_segment)
8123+        else:
8124+            self._last_segment = self._num_segments - 1
8125 
8126hunk ./src/allmydata/mutable/retrieve.py 403
8127-    def remove_peer(self, peerid):
8128-        for shnum in list(self.remaining_sharemap.keys()):
8129-            self.remaining_sharemap.discard(shnum, peerid)
8130+        self._current_segment = self._start_segment
8131 
8132hunk ./src/allmydata/mutable/retrieve.py 405
8133-    def _got_results(self, datavs, marker, peerid, started, got_from_cache):
8134-        now = time.time()
8135-        elapsed = now - started
8136-        if not got_from_cache:
8137-            self._status.add_fetch_timing(peerid, elapsed)
8138-        self.log(format="got results (%(shares)d shares) from [%(peerid)s]",
8139-                 shares=len(datavs),
8140-                 peerid=idlib.shortnodeid_b2a(peerid),
8141-                 level=log.NOISY)
8142-        self._outstanding_queries.pop(marker, None)
8143-        if not self._running:
8144-            return
8145+    def _add_active_peers(self):
8146+        """
8147+        I populate self._active_readers with enough active readers to
8148+        retrieve the contents of this mutable file. I am called before
8149+        downloading starts, and (eventually) after each validation
8150+        error, connection error, or other problem in the download.
8151+        """
8152+        # TODO: It would be cool to investigate other heuristics for
8153+        # reader selection. For instance, the cost (in time the user
8154+        # spends waiting for their file) of selecting a really slow peer
8155+        # that happens to have a primary share is probably more than
8156+        # selecting a really fast peer that doesn't have a primary
8157+        # share. Maybe the servermap could be extended to provide this
8158+        # information; it could keep track of latency information while
8159+        # it gathers more important data, and then this routine could
8160+        # use that to select active readers.
8161+        #
8162+        # (these and other questions would be easier to answer with a
8163+        #  robust, configurable tahoe-lafs simulator, which modeled node
8164+        #  failures, differences in node speed, and other characteristics
8165+        #  that we expect storage servers to have.  You could have
8166+        #  presets for really stable grids (like allmydata.com),
8167+        #  friendnets, make it easy to configure your own settings, and
8168+        #  then simulate the effect of big changes on these use cases
8169+        #  instead of just reasoning about what the effect might be. Out
8170+        #  of scope for MDMF, though.)
8171 
8172hunk ./src/allmydata/mutable/retrieve.py 432
8173-        # note that we only ask for a single share per query, so we only
8174-        # expect a single share back. On the other hand, we use the extra
8175-        # shares if we get them.. seems better than an assert().
8176+        # We need at least self._required_shares readers to download a
8177+        # segment.
8178+        if self._verify:
8179+            needed = self._total_shares
8180+        else:
8181+            needed = self._required_shares - len(self._active_readers)
8182+        # XXX: Why don't format= log messages work here?
8183+        self.log("adding %d peers to the active peers list" % needed)
8184 
8185hunk ./src/allmydata/mutable/retrieve.py 441
8186-        for shnum,datav in datavs.items():
8187-            (prefix, hash_and_data) = datav[:2]
8188-            try:
8189-                self._got_results_one_share(shnum, peerid,
8190-                                            prefix, hash_and_data)
8191-            except CorruptShareError, e:
8192-                # log it and give the other shares a chance to be processed
8193-                f = failure.Failure()
8194-                self.log(format="bad share: %(f_value)s",
8195-                         f_value=str(f.value), failure=f,
8196-                         level=log.WEIRD, umid="7fzWZw")
8197-                self.notify_server_corruption(peerid, shnum, str(e))
8198-                self.remove_peer(peerid)
8199-                self.servermap.mark_bad_share(peerid, shnum, prefix)
8200-                self._bad_shares.add( (peerid, shnum) )
8201-                self._status.problems[peerid] = f
8202-                self._last_failure = f
8203-                pass
8204-            if self._need_privkey and len(datav) > 2:
8205-                lp = None
8206-                self._try_to_validate_privkey(datav[2], peerid, shnum, lp)
8207-        # all done!
8208+        # We favor lower numbered shares, since FEC is faster with
8209+        # primary shares than with other shares, and lower-numbered
8210+        # shares are more likely to be primary than higher numbered
8211+        # shares.
8212+        active_shnums = set(sorted(self.remaining_sharemap.keys()))
8213+        # We shouldn't consider adding shares that we already have; this
8214+        # will cause problems later.
8215+        active_shnums -= set([reader.shnum for reader in self._active_readers])
8216+        active_shnums = list(active_shnums)[:needed]
8217+        if len(active_shnums) < needed and not self._verify:
8218+            # We don't have enough readers to retrieve the file; fail.
8219+            return self._failed()
8220 
8221hunk ./src/allmydata/mutable/retrieve.py 454
8222-    def notify_server_corruption(self, peerid, shnum, reason):
8223-        ss = self.servermap.connections[peerid]
8224-        ss.callRemoteOnly("advise_corrupt_share",
8225-                          "mutable", self._storage_index, shnum, reason)
8226+        for shnum in active_shnums:
8227+            self._active_readers.append(self.readers[shnum])
8228+            self.log("added reader for share %d" % shnum)
8229+        assert len(self._active_readers) >= self._required_shares
8230+        # Conceptually, this is part of the _add_active_peers step. It
8231+        # validates the prefixes of newly added readers to make sure
8232+        # that they match what we are expecting for self.verinfo. If
8233+        # validation is successful, _validate_active_prefixes will call
8234+        # _download_current_segment for us. If validation is
8235+        # unsuccessful, then _validate_prefixes will remove the peer and
8236+        # call _add_active_peers again, where we will attempt to rectify
8237+        # the problem by choosing another peer.
8238+        return self._validate_active_prefixes()
8239 
8240hunk ./src/allmydata/mutable/retrieve.py 468
8241-    def _got_results_one_share(self, shnum, peerid,
8242-                               got_prefix, got_hash_and_data):
8243-        self.log("_got_results: got shnum #%d from peerid %s"
8244-                 % (shnum, idlib.shortnodeid_b2a(peerid)))
8245-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
8246-         offsets_tuple) = self.verinfo
8247-        assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix))
8248-        if got_prefix != prefix:
8249-            msg = "someone wrote to the data since we read the servermap: prefix changed"
8250-            raise UncoordinatedWriteError(msg)
8251-        (share_hash_chain, block_hash_tree,
8252-         share_data) = unpack_share_data(self.verinfo, got_hash_and_data)
8253 
8254hunk ./src/allmydata/mutable/retrieve.py 469
8255-        assert isinstance(share_data, str)
8256-        # build the block hash tree. SDMF has only one leaf.
8257-        leaves = [hashutil.block_hash(share_data)]
8258-        t = hashtree.HashTree(leaves)
8259-        if list(t) != block_hash_tree:
8260-            raise CorruptShareError(peerid, shnum, "block hash tree failure")
8261-        share_hash_leaf = t[0]
8262-        t2 = hashtree.IncompleteHashTree(N)
8263-        # root_hash was checked by the signature
8264-        t2.set_hashes({0: root_hash})
8265-        try:
8266-            t2.set_hashes(hashes=share_hash_chain,
8267-                          leaves={shnum: share_hash_leaf})
8268-        except (hashtree.BadHashError, hashtree.NotEnoughHashesError,
8269-                IndexError), e:
8270-            msg = "corrupt hashes: %s" % (e,)
8271-            raise CorruptShareError(peerid, shnum, msg)
8272-        self.log(" data valid! len=%d" % len(share_data))
8273-        # each query comes down to this: placing validated share data into
8274-        # self.shares
8275-        self.shares[shnum] = share_data
8276+    def _validate_active_prefixes(self):
8277+        """
8278+        I check to make sure that the prefixes on the peers that I am
8279+        currently reading from match the prefix that we want to see, as
8280+        said in self.verinfo.
8281 
8282hunk ./src/allmydata/mutable/retrieve.py 475
8283-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
8284+        If I find that all of the active peers have acceptable prefixes,
8285+        I pass control to _download_current_segment, which will use
8286+        those peers to do cool things. If I find that some of the active
8287+        peers have unacceptable prefixes, I will remove them from active
8288+        peers (and from further consideration) and call
8289+        _add_active_peers to attempt to rectify the situation. I keep
8290+        track of which peers I have already validated so that I don't
8291+        need to do so again.
8292+        """
8293+        assert self._active_readers, "No more active readers"
8294 
8295hunk ./src/allmydata/mutable/retrieve.py 486
8296-        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
8297-        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
8298-        if alleged_writekey != self._node.get_writekey():
8299-            self.log("invalid privkey from %s shnum %d" %
8300-                     (idlib.nodeid_b2a(peerid)[:8], shnum),
8301-                     parent=lp, level=log.WEIRD, umid="YIw4tA")
8302-            return
8303+        ds = []
8304+        new_readers = set(self._active_readers) - self._validated_readers
8305+        self.log('validating %d newly-added active readers' % len(new_readers))
8306 
8307hunk ./src/allmydata/mutable/retrieve.py 490
8308-        # it's good
8309-        self.log("got valid privkey from shnum %d on peerid %s" %
8310-                 (shnum, idlib.shortnodeid_b2a(peerid)),
8311-                 parent=lp)
8312-        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
8313-        self._node._populate_encprivkey(enc_privkey)
8314-        self._node._populate_privkey(privkey)
8315-        self._need_privkey = False
8316+        for reader in new_readers:
8317+            # We force a remote read here -- otherwise, we are relying
8318+            # on cached data that we already verified as valid, and we
8319+            # won't detect an uncoordinated write that has occurred
8320+            # since the last servermap update.
8321+            d = reader.get_prefix(force_remote=True)
8322+            d.addCallback(self._try_to_validate_prefix, reader)
8323+            ds.append(d)
8324+        dl = defer.DeferredList(ds, consumeErrors=True)
8325+        def _check_results(results):
8326+            # Each result in results will be of the form (success, msg).
8327+            # We don't care about msg, but success will tell us whether
8328+            # or not the checkstring validated. If it didn't, we need to
8329+            # remove the offending (peer,share) from our active readers,
8330+            # and ensure that active readers is again populated.
8331+            bad_readers = []
8332+            for i, result in enumerate(results):
8333+                if not result[0]:
8334+                    reader = self._active_readers[i]
8335+                    f = result[1]
8336+                    assert isinstance(f, failure.Failure)
8337 
8338hunk ./src/allmydata/mutable/retrieve.py 512
8339-    def _query_failed(self, f, marker, peerid):
8340-        self.log(format="query to [%(peerid)s] failed",
8341-                 peerid=idlib.shortnodeid_b2a(peerid),
8342-                 level=log.NOISY)
8343-        self._status.problems[peerid] = f
8344-        self._outstanding_queries.pop(marker, None)
8345-        if not self._running:
8346-            return
8347-        self._last_failure = f
8348-        self.remove_peer(peerid)
8349-        level = log.WEIRD
8350-        if f.check(DeadReferenceError):
8351-            level = log.UNUSUAL
8352-        self.log(format="error during query: %(f_value)s",
8353-                 f_value=str(f.value), failure=f, level=level, umid="gOJB5g")
8354+                    self.log("The reader %s failed to "
8355+                             "properly validate: %s" % \
8356+                             (reader, str(f.value)))
8357+                    bad_readers.append((reader, f))
8358+                else:
8359+                    reader = self._active_readers[i]
8360+                    self.log("the reader %s checks out, so we'll use it" % \
8361+                             reader)
8362+                    self._validated_readers.add(reader)
8363+                    # Each time we validate a reader, we check to see if
8364+                    # we need the private key. If we do, we politely ask
8365+                    # for it and then continue computing. If we find
8366+                    # that we haven't gotten it at the end of
8367+                    # segment decoding, then we'll take more drastic
8368+                    # measures.
8369+                    if self._need_privkey and not self._node.is_readonly():
8370+                        d = reader.get_encprivkey()
8371+                        d.addCallback(self._try_to_validate_privkey, reader)
8372+            if bad_readers:
8373+                # We do them all at once, or else we screw up list indexing.
8374+                for (reader, f) in bad_readers:
8375+                    self._mark_bad_share(reader, f)
8376+                if self._verify:
8377+                    if len(self._active_readers) >= self._required_shares:
8378+                        return self._download_current_segment()
8379+                    else:
8380+                        return self._failed()
8381+                else:
8382+                    return self._add_active_peers()
8383+            else:
8384+                return self._download_current_segment()
8385+            # The next step will assert that it has enough active
8386+            # readers to fetch shares; we just need to remove it.
8387+        dl.addCallback(_check_results)
8388+        return dl
8389 
8390hunk ./src/allmydata/mutable/retrieve.py 548
8391-    def _check_for_done(self, res):
8392-        # exit paths:
8393-        #  return : keep waiting, no new queries
8394-        #  return self._send_more_queries(outstanding) : send some more queries
8395-        #  fire self._done(plaintext) : download successful
8396-        #  raise exception : download fails
8397 
8398hunk ./src/allmydata/mutable/retrieve.py 549
8399-        self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s",
8400-                 running=self._running, decoding=self._decoding,
8401-                 level=log.NOISY)
8402-        if not self._running:
8403-            return
8404-        if self._decoding:
8405-            return
8406-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
8407+    def _try_to_validate_prefix(self, prefix, reader):
8408+        """
8409+        I check that the prefix returned by a candidate server for
8410+        retrieval matches the prefix that the servermap knows about
8411+        (and, hence, the prefix that was validated earlier). If it does,
8412+        I return True, which means that I approve of the use of the
8413+        candidate server for segment retrieval. If it doesn't, I return
8414+        False, which means that another server must be chosen.
8415+        """
8416+        (seqnum,
8417+         root_hash,
8418+         IV,
8419+         segsize,
8420+         datalength,
8421+         k,
8422+         N,
8423+         known_prefix,
8424          offsets_tuple) = self.verinfo
8425hunk ./src/allmydata/mutable/retrieve.py 567
8426+        if known_prefix != prefix:
8427+            self.log("prefix from share %d doesn't match" % reader.shnum)
8428+            raise UncoordinatedWriteError("Mismatched prefix -- this could "
8429+                                          "indicate an uncoordinated write")
8430+        # Otherwise, we're okay -- no issues.
8431 
8432hunk ./src/allmydata/mutable/retrieve.py 573
8433-        if len(self.shares) < k:
8434-            # we don't have enough shares yet
8435-            return self._maybe_send_more_queries(k)
8436-        if self._need_privkey:
8437-            # we got k shares, but none of them had a valid privkey. TODO:
8438-            # look further. Adding code to do this is a bit complicated, and
8439-            # I want to avoid that complication, and this should be pretty
8440-            # rare (k shares with bitflips in the enc_privkey but not in the
8441-            # data blocks). If we actually do get here, the subsequent repair
8442-            # will fail for lack of a privkey.
8443-            self.log("got k shares but still need_privkey, bummer",
8444-                     level=log.WEIRD, umid="MdRHPA")
8445 
8446hunk ./src/allmydata/mutable/retrieve.py 574
8447-        # we have enough to finish. All the shares have had their hashes
8448-        # checked, so if something fails at this point, we don't know how
8449-        # to fix it, so the download will fail.
8450+    def _remove_reader(self, reader):
8451+        """
8452+        At various points, we will wish to remove a peer from
8453+        consideration and/or use. These include, but are not necessarily
8454+        limited to:
8455 
8456hunk ./src/allmydata/mutable/retrieve.py 580
8457-        self._decoding = True # avoid reentrancy
8458-        self._status.set_status("decoding")
8459-        now = time.time()
8460-        elapsed = now - self._started
8461-        self._status.timings["fetch"] = elapsed
8462+            - A connection error.
8463+            - A mismatched prefix (that is, a prefix that does not match
8464+              our conception of the version information string).
8465+            - A failing block hash, salt hash, or share hash, which can
8466+              indicate disk failure/bit flips, or network trouble.
8467 
8468hunk ./src/allmydata/mutable/retrieve.py 586
8469-        d = defer.maybeDeferred(self._decode)
8470-        d.addCallback(self._decrypt, IV, self._node.get_readkey())
8471-        d.addBoth(self._done)
8472-        return d # purely for test convenience
8473+        This method will do that. I will make sure that the
8474+        (shnum,reader) combination represented by my reader argument is
8475+        not used for anything else during this download. I will not
8476+        advise the reader of any corruption, something that my callers
8477+        may wish to do on their own.
8478+        """
8479+        # TODO: When you're done writing this, see if this is ever
8480+        # actually used for something that _mark_bad_share isn't. I have
8481+        # a feeling that they will be used for very similar things, and
8482+        # that having them both here is just going to be an epic amount
8483+        # of code duplication.
8484+        #
8485+        # (well, okay, not epic, but meaningful)
8486+        self.log("removing reader %s" % reader)
8487+        # Remove the reader from _active_readers
8488+        self._active_readers.remove(reader)
8489+        # TODO: self.readers.remove(reader)?
8490+        for shnum in list(self.remaining_sharemap.keys()):
8491+            self.remaining_sharemap.discard(shnum, reader.peerid)
8492 
8493hunk ./src/allmydata/mutable/retrieve.py 606
8494-    def _maybe_send_more_queries(self, k):
8495-        # we don't have enough shares yet. Should we send out more queries?
8496-        # There are some number of queries outstanding, each for a single
8497-        # share. If we can generate 'needed_shares' additional queries, we do
8498-        # so. If we can't, then we know this file is a goner, and we raise
8499-        # NotEnoughSharesError.
8500-        self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, "
8501-                         "outstanding=%(outstanding)d"),
8502-                 have=len(self.shares), k=k,
8503-                 outstanding=len(self._outstanding_queries),
8504-                 level=log.NOISY)
8505 
8506hunk ./src/allmydata/mutable/retrieve.py 607
8507-        remaining_shares = k - len(self.shares)
8508-        needed = remaining_shares - len(self._outstanding_queries)
8509-        if not needed:
8510-            # we have enough queries in flight already
8511+    def _mark_bad_share(self, reader, f):
8512+        """
8513+        I mark the (peerid, shnum) encapsulated by my reader argument as
8514+        a bad share, which means that it will not be used anywhere else.
8515 
8516hunk ./src/allmydata/mutable/retrieve.py 612
8517-            # TODO: but if they've been in flight for a long time, and we
8518-            # have reason to believe that new queries might respond faster
8519-            # (i.e. we've seen other queries come back faster, then consider
8520-            # sending out new queries. This could help with peers which have
8521-            # silently gone away since the servermap was updated, for which
8522-            # we're still waiting for the 15-minute TCP disconnect to happen.
8523-            self.log("enough queries are in flight, no more are needed",
8524-                     level=log.NOISY)
8525-            return
8526+        There are several reasons to want to mark something as a bad
8527+        share. These include:
8528+
8529+            - A connection error to the peer.
8530+            - A mismatched prefix (that is, a prefix that does not match
8531+              our local conception of the version information string).
8532+            - A failing block hash, salt hash, share hash, or other
8533+              integrity check.
8534 
8535hunk ./src/allmydata/mutable/retrieve.py 621
8536-        outstanding_shnums = set([shnum
8537-                                  for (peerid, shnum, started)
8538-                                  in self._outstanding_queries.values()])
8539-        # prefer low-numbered shares, they are more likely to be primary
8540-        available_shnums = sorted(self.remaining_sharemap.keys())
8541-        for shnum in available_shnums:
8542-            if shnum in outstanding_shnums:
8543-                # skip ones that are already in transit
8544-                continue
8545-            if shnum not in self.remaining_sharemap:
8546-                # no servers for that shnum. note that DictOfSets removes
8547-                # empty sets from the dict for us.
8548-                continue
8549-            peerid = list(self.remaining_sharemap[shnum])[0]
8550-            # get_data will remove that peerid from the sharemap, and add the
8551-            # query to self._outstanding_queries
8552-            self._status.set_status("Retrieving More Shares")
8553-            self.get_data(shnum, peerid)
8554-            needed -= 1
8555-            if not needed:
8556+        This method will ensure that readers that we wish to mark bad
8557+        (for these reasons or other reasons) are not used for the rest
8558+        of the download. Additionally, it will attempt to tell the
8559+        remote peer (with no guarantee of success) that its share is
8560+        corrupt.
8561+        """
8562+        self.log("marking share %d on server %s as bad" % \
8563+                 (reader.shnum, reader))
8564+        prefix = self.verinfo[-2]
8565+        self.servermap.mark_bad_share(reader.peerid,
8566+                                      reader.shnum,
8567+                                      prefix)
8568+        self._remove_reader(reader)
8569+        self._bad_shares.add((reader.peerid, reader.shnum, f))
8570+        self._status.problems[reader.peerid] = f
8571+        self._last_failure = f
8572+        self.notify_server_corruption(reader.peerid, reader.shnum,
8573+                                      str(f.value))
8574+
8575+
8576+    def _download_current_segment(self):
8577+        """
8578+        I download, validate, decode, decrypt, and assemble the segment
8579+        that this Retrieve is currently responsible for downloading.
8580+        """
8581+        assert len(self._active_readers) >= self._required_shares
8582+        if self._current_segment <= self._last_segment:
8583+            d = self._process_segment(self._current_segment)
8584+        else:
8585+            d = defer.succeed(None)
8586+        d.addBoth(self._turn_barrier)
8587+        d.addCallback(self._check_for_done)
8588+        return d
8589+
8590+
8591+    def _turn_barrier(self, result):
8592+        """
8593+        I help the download process avoid the recursion limit issues
8594+        discussed in #237.
8595+        """
8596+        return fireEventually(result)
8597+
8598+
8599+    def _process_segment(self, segnum):
8600+        """
8601+        I download, validate, decode, and decrypt one segment of the
8602+        file that this Retrieve is retrieving. This means coordinating
8603+        the process of getting k blocks of that file, validating them,
8604+        assembling them into one segment with the decoder, and then
8605+        decrypting them.
8606+        """
8607+        self.log("processing segment %d" % segnum)
8608+
8609+        # TODO: The old code uses a marker. Should this code do that
8610+        # too? What did the Marker do?
8611+        assert len(self._active_readers) >= self._required_shares
8612+
8613+        # We need to ask each of our active readers for its block and
8614+        # salt. We will then validate those. If validation is
8615+        # successful, we will assemble the results into plaintext.
8616+        ds = []
8617+        for reader in self._active_readers:
8618+            started = time.time()
8619+            d = reader.get_block_and_salt(segnum, queue=True)
8620+            d2 = self._get_needed_hashes(reader, segnum)
8621+            dl = defer.DeferredList([d, d2], consumeErrors=True)
8622+            dl.addCallback(self._validate_block, segnum, reader, started)
8623+            dl.addErrback(self._validation_or_decoding_failed, [reader])
8624+            ds.append(dl)
8625+            reader.flush()
8626+        dl = defer.DeferredList(ds)
8627+        if self._verify:
8628+            dl.addCallback(lambda ignored: "")
8629+            dl.addCallback(self._set_segment)
8630+        else:
8631+            dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum)
8632+        return dl
8633+
8634+
8635+    def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum):
8636+        """
8637+        I take the results of fetching and validating the blocks from a
8638+        callback chain in another method. If the results are such that
8639+        they tell me that validation and fetching succeeded without
8640+        incident, I will proceed with decoding and decryption.
8641+        Otherwise, I will do nothing.
8642+        """
8643+        self.log("trying to decode and decrypt segment %d" % segnum)
8644+        failures = False
8645+        for block_and_salt in blocks_and_salts:
8646+            if not block_and_salt[0] or block_and_salt[1] == None:
8647+                self.log("some validation operations failed; not proceeding")
8648+                failures = True
8649                 break
8650hunk ./src/allmydata/mutable/retrieve.py 715
8651+        if not failures:
8652+            self.log("everything looks ok, building segment %d" % segnum)
8653+            d = self._decode_blocks(blocks_and_salts, segnum)
8654+            d.addCallback(self._decrypt_segment)
8655+            d.addErrback(self._validation_or_decoding_failed,
8656+                         self._active_readers)
8657+            # check to see whether we've been paused before writing
8658+            # anything.
8659+            d.addCallback(self._check_for_paused)
8660+            d.addCallback(self._set_segment)
8661+            return d
8662+        else:
8663+            return defer.succeed(None)
8664+
8665+
8666+    def _set_segment(self, segment):
8667+        """
8668+        Given a plaintext segment, I register that segment with the
8669+        target that is handling the file download.
8670+        """
8671+        self.log("got plaintext for segment %d" % self._current_segment)
8672+        if self._current_segment == self._start_segment:
8673+            # We're on the first segment. It's possible that we want
8674+            # only some part of the end of this segment, and that we
8675+            # just downloaded the whole thing to get that part. If so,
8676+            # we need to account for that and give the reader just the
8677+            # data that they want.
8678+            n = self._offset % self._segment_size
8679+            self.log("stripping %d bytes off of the first segment" % n)
8680+            self.log("original segment length: %d" % len(segment))
8681+            segment = segment[n:]
8682+            self.log("new segment length: %d" % len(segment))
8683+
8684+        if self._current_segment == self._last_segment and self._read_length is not None:
8685+            # We're on the last segment. It's possible that we only want
8686+            # part of the beginning of this segment, and that we
8687+            # downloaded the whole thing anyway. Make sure to give the
8688+            # caller only the portion of the segment that they want to
8689+            # receive.
8690+            extra = self._read_length
8691+            if self._start_segment != self._last_segment:
8692+                extra -= self._segment_size - \
8693+                            (self._offset % self._segment_size)
8694+            extra %= self._segment_size
8695+            self.log("original segment length: %d" % len(segment))
8696+            segment = segment[:extra]
8697+            self.log("new segment length: %d" % len(segment))
8698+            self.log("only taking %d bytes of the last segment" % extra)
8699+
8700+        if not self._verify:
8701+            self._consumer.write(segment)
8702+        else:
8703+            # we don't care about the plaintext if we are doing a verify.
8704+            segment = None
8705+        self._current_segment += 1
8706 
8707hunk ./src/allmydata/mutable/retrieve.py 771
8708-        # at this point, we have as many outstanding queries as we can. If
8709-        # needed!=0 then we might not have enough to recover the file.
8710-        if needed:
8711-            format = ("ran out of peers: "
8712-                      "have %(have)d shares (k=%(k)d), "
8713-                      "%(outstanding)d queries in flight, "
8714-                      "need %(need)d more, "
8715-                      "found %(bad)d bad shares")
8716-            args = {"have": len(self.shares),
8717-                    "k": k,
8718-                    "outstanding": len(self._outstanding_queries),
8719-                    "need": needed,
8720-                    "bad": len(self._bad_shares),
8721-                    }
8722-            self.log(format=format,
8723-                     level=log.WEIRD, umid="ezTfjw", **args)
8724-            err = NotEnoughSharesError("%s, last failure: %s" %
8725-                                      (format % args, self._last_failure))
8726-            if self._bad_shares:
8727-                self.log("We found some bad shares this pass. You should "
8728-                         "update the servermap and try again to check "
8729-                         "more peers",
8730-                         level=log.WEIRD, umid="EFkOlA")
8731-                err.servermap = self.servermap
8732-            raise err
8733 
8734hunk ./src/allmydata/mutable/retrieve.py 772
8735+    def _validation_or_decoding_failed(self, f, readers):
8736+        """
8737+        I am called when a block or a salt fails to correctly validate, or when
8738+        the decryption or decoding operation fails for some reason.  I react to
8739+        this failure by notifying the remote server of corruption, and then
8740+        removing the remote peer from further activity.
8741+        """
8742+        assert isinstance(readers, list)
8743+        bad_shnums = [reader.shnum for reader in readers]
8744+
8745+        self.log("validation or decoding failed on share(s) %s, peer(s) %s "
8746+                 ", segment %d: %s" % \
8747+                 (bad_shnums, readers, self._current_segment, str(f)))
8748+        for reader in readers:
8749+            self._mark_bad_share(reader, f)
8750         return
8751 
8752hunk ./src/allmydata/mutable/retrieve.py 789
8753-    def _decode(self):
8754-        started = time.time()
8755-        (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
8756-         offsets_tuple) = self.verinfo
8757 
8758hunk ./src/allmydata/mutable/retrieve.py 790
8759-        # shares_dict is a dict mapping shnum to share data, but the codec
8760-        # wants two lists.
8761-        shareids = []; shares = []
8762-        for shareid, share in self.shares.items():
8763+    def _validate_block(self, results, segnum, reader, started):
8764+        """
8765+        I validate a block from one share on a remote server.
8766+        """
8767+        # Grab the part of the block hash tree that is necessary to
8768+        # validate this block, then generate the block hash root.
8769+        self.log("validating share %d for segment %d" % (reader.shnum,
8770+                                                             segnum))
8771+        self._status.add_fetch_timing(reader.peerid, started)
8772+        self._status.set_status("Valdiating blocks for segment %d" % segnum)
8773+        # Did we fail to fetch either of the things that we were
8774+        # supposed to? Fail if so.
8775+        if not results[0][0] and results[1][0]:
8776+            # handled by the errback handler.
8777+
8778+            # These all get batched into one query, so the resulting
8779+            # failure should be the same for all of them, so we can just
8780+            # use the first one.
8781+            assert isinstance(results[0][1], failure.Failure)
8782+
8783+            f = results[0][1]
8784+            raise CorruptShareError(reader.peerid,
8785+                                    reader.shnum,
8786+                                    "Connection error: %s" % str(f))
8787+
8788+        block_and_salt, block_and_sharehashes = results
8789+        block, salt = block_and_salt[1]
8790+        blockhashes, sharehashes = block_and_sharehashes[1]
8791+
8792+        blockhashes = dict(enumerate(blockhashes[1]))
8793+        self.log("the reader gave me the following blockhashes: %s" % \
8794+                 blockhashes.keys())
8795+        self.log("the reader gave me the following sharehashes: %s" % \
8796+                 sharehashes[1].keys())
8797+        bht = self._block_hash_trees[reader.shnum]
8798+
8799+        if bht.needed_hashes(segnum, include_leaf=True):
8800+            try:
8801+                bht.set_hashes(blockhashes)
8802+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
8803+                    IndexError), e:
8804+                raise CorruptShareError(reader.peerid,
8805+                                        reader.shnum,
8806+                                        "block hash tree failure: %s" % e)
8807+
8808+        if self._version == MDMF_VERSION:
8809+            blockhash = hashutil.block_hash(salt + block)
8810+        else:
8811+            blockhash = hashutil.block_hash(block)
8812+        # If this works without an error, then validation is
8813+        # successful.
8814+        try:
8815+           bht.set_hashes(leaves={segnum: blockhash})
8816+        except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
8817+                IndexError), e:
8818+            raise CorruptShareError(reader.peerid,
8819+                                    reader.shnum,
8820+                                    "block hash tree failure: %s" % e)
8821+
8822+        # Reaching this point means that we know that this segment
8823+        # is correct. Now we need to check to see whether the share
8824+        # hash chain is also correct.
8825+        # SDMF wrote share hash chains that didn't contain the
8826+        # leaves, which would be produced from the block hash tree.
8827+        # So we need to validate the block hash tree first. If
8828+        # successful, then bht[0] will contain the root for the
8829+        # shnum, which will be a leaf in the share hash tree, which
8830+        # will allow us to validate the rest of the tree.
8831+        if self.share_hash_tree.needed_hashes(reader.shnum,
8832+                                              include_leaf=True) or \
8833+                                              self._verify:
8834+            try:
8835+                self.share_hash_tree.set_hashes(hashes=sharehashes[1],
8836+                                            leaves={reader.shnum: bht[0]})
8837+            except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \
8838+                    IndexError), e:
8839+                raise CorruptShareError(reader.peerid,
8840+                                        reader.shnum,
8841+                                        "corrupt hashes: %s" % e)
8842+
8843+        self.log('share %d is valid for segment %d' % (reader.shnum,
8844+                                                       segnum))
8845+        return {reader.shnum: (block, salt)}
8846+
8847+
8848+    def _get_needed_hashes(self, reader, segnum):
8849+        """
8850+        I get the hashes needed to validate segnum from the reader, then return
8851+        to my caller when this is done.
8852+        """
8853+        bht = self._block_hash_trees[reader.shnum]
8854+        needed = bht.needed_hashes(segnum, include_leaf=True)
8855+        # The root of the block hash tree is also a leaf in the share
8856+        # hash tree. So we don't need to fetch it from the remote
8857+        # server. In the case of files with one segment, this means that
8858+        # we won't fetch any block hash tree from the remote server,
8859+        # since the hash of each share of the file is the entire block
8860+        # hash tree, and is a leaf in the share hash tree. This is fine,
8861+        # since any share corruption will be detected in the share hash
8862+        # tree.
8863+        #needed.discard(0)
8864+        self.log("getting blockhashes for segment %d, share %d: %s" % \
8865+                 (segnum, reader.shnum, str(needed)))
8866+        d1 = reader.get_blockhashes(needed, queue=True, force_remote=True)
8867+        if self.share_hash_tree.needed_hashes(reader.shnum):
8868+            need = self.share_hash_tree.needed_hashes(reader.shnum)
8869+            self.log("also need sharehashes for share %d: %s" % (reader.shnum,
8870+                                                                 str(need)))
8871+            d2 = reader.get_sharehashes(need, queue=True, force_remote=True)
8872+        else:
8873+            d2 = defer.succeed({}) # the logic in the next method
8874+                                   # expects a dict
8875+        dl = defer.DeferredList([d1, d2], consumeErrors=True)
8876+        return dl
8877+
8878+
8879+    def _decode_blocks(self, blocks_and_salts, segnum):
8880+        """
8881+        I take a list of k blocks and salts, and decode that into a
8882+        single encrypted segment.
8883+        """
8884+        d = {}
8885+        # We want to merge our dictionaries to the form
8886+        # {shnum: blocks_and_salts}
8887+        #
8888+        # The dictionaries come from validate block that way, so we just
8889+        # need to merge them.
8890+        for block_and_salt in blocks_and_salts:
8891+            d.update(block_and_salt[1])
8892+
8893+        # All of these blocks should have the same salt; in SDMF, it is
8894+        # the file-wide IV, while in MDMF it is the per-segment salt. In
8895+        # either case, we just need to get one of them and use it.
8896+        #
8897+        # d.items()[0] is like (shnum, (block, salt))
8898+        # d.items()[0][1] is like (block, salt)
8899+        # d.items()[0][1][1] is the salt.
8900+        salt = d.items()[0][1][1]
8901+        # Next, extract just the blocks from the dict. We'll use the
8902+        # salt in the next step.
8903+        share_and_shareids = [(k, v[0]) for k, v in d.items()]
8904+        d2 = dict(share_and_shareids)
8905+        shareids = []
8906+        shares = []
8907+        for shareid, share in d2.items():
8908             shareids.append(shareid)
8909             shares.append(share)
8910 
8911hunk ./src/allmydata/mutable/retrieve.py 938
8912-        assert len(shareids) >= k, len(shareids)
8913+        self._status.set_status("Decoding")
8914+        started = time.time()
8915+        assert len(shareids) >= self._required_shares, len(shareids)
8916         # zfec really doesn't want extra shares
8917hunk ./src/allmydata/mutable/retrieve.py 942
8918-        shareids = shareids[:k]
8919-        shares = shares[:k]
8920-
8921-        fec = codec.CRSDecoder()
8922-        fec.set_params(segsize, k, N)
8923-
8924-        self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares)))
8925-        self.log("about to decode, shareids=%s" % (shareids,))
8926-        d = defer.maybeDeferred(fec.decode, shares, shareids)
8927-        def _done(buffers):
8928-            self._status.timings["decode"] = time.time() - started
8929-            self.log(" decode done, %d buffers" % len(buffers))
8930+        shareids = shareids[:self._required_shares]
8931+        shares = shares[:self._required_shares]
8932+        self.log("decoding segment %d" % segnum)
8933+        if segnum == self._num_segments - 1:
8934+            d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids)
8935+        else:
8936+            d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids)
8937+        def _process(buffers):
8938             segment = "".join(buffers)
8939hunk ./src/allmydata/mutable/retrieve.py 951
8940+            self.log(format="now decoding segment %(segnum)s of %(numsegs)s",
8941+                     segnum=segnum,
8942+                     numsegs=self._num_segments,
8943+                     level=log.NOISY)
8944             self.log(" joined length %d, datalength %d" %
8945hunk ./src/allmydata/mutable/retrieve.py 956
8946-                     (len(segment), datalength))
8947-            segment = segment[:datalength]
8948+                     (len(segment), self._data_length))
8949+            if segnum == self._num_segments - 1:
8950+                size_to_use = self._tail_data_size
8951+            else:
8952+                size_to_use = self._segment_size
8953+            segment = segment[:size_to_use]
8954             self.log(" segment len=%d" % len(segment))
8955hunk ./src/allmydata/mutable/retrieve.py 963
8956-            return segment
8957-        def _err(f):
8958-            self.log(" decode failed: %s" % f)
8959-            return f
8960-        d.addCallback(_done)
8961-        d.addErrback(_err)
8962+            self._status.timings.setdefault("decode", 0)
8963+            self._status.timings['decode'] = time.time() - started
8964+            return segment, salt
8965+        d.addCallback(_process)
8966         return d
8967 
8968hunk ./src/allmydata/mutable/retrieve.py 969
8969-    def _decrypt(self, crypttext, IV, readkey):
8970+
8971+    def _decrypt_segment(self, segment_and_salt):
8972+        """
8973+        I take a single segment and its salt, and decrypt it. I return
8974+        the plaintext of the segment that is in my argument.
8975+        """
8976+        segment, salt = segment_and_salt
8977         self._status.set_status("decrypting")
8978hunk ./src/allmydata/mutable/retrieve.py 977
8979+        self.log("decrypting segment %d" % self._current_segment)
8980         started = time.time()
8981hunk ./src/allmydata/mutable/retrieve.py 979
8982-        key = hashutil.ssk_readkey_data_hash(IV, readkey)
8983+        key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey())
8984         decryptor = AES(key)
8985hunk ./src/allmydata/mutable/retrieve.py 981
8986-        plaintext = decryptor.process(crypttext)
8987-        self._status.timings["decrypt"] = time.time() - started
8988+        plaintext = decryptor.process(segment)
8989+        self._status.timings.setdefault("decrypt", 0)
8990+        self._status.timings['decrypt'] = time.time() - started
8991         return plaintext
8992 
8993hunk ./src/allmydata/mutable/retrieve.py 986
8994-    def _done(self, res):
8995-        if not self._running:
8996+
8997+    def notify_server_corruption(self, peerid, shnum, reason):
8998+        ss = self.servermap.connections[peerid]
8999+        ss.callRemoteOnly("advise_corrupt_share",
9000+                          "mutable", self._storage_index, shnum, reason)
9001+
9002+
9003+    def _try_to_validate_privkey(self, enc_privkey, reader):
9004+        alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
9005+        alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
9006+        if alleged_writekey != self._node.get_writekey():
9007+            self.log("invalid privkey from %s shnum %d" %
9008+                     (reader, reader.shnum),
9009+                     level=log.WEIRD, umid="YIw4tA")
9010+            if self._verify:
9011+                self.servermap.mark_bad_share(reader.peerid, reader.shnum,
9012+                                              self.verinfo[-2])
9013+                e = CorruptShareError(reader.peerid,
9014+                                      reader.shnum,
9015+                                      "invalid privkey")
9016+                f = failure.Failure(e)
9017+                self._bad_shares.add((reader.peerid, reader.shnum, f))
9018             return
9019hunk ./src/allmydata/mutable/retrieve.py 1009
9020+
9021+        # it's good
9022+        self.log("got valid privkey from shnum %d on reader %s" %
9023+                 (reader.shnum, reader))
9024+        privkey = rsa.create_signing_key_from_string(alleged_privkey_s)
9025+        self._node._populate_encprivkey(enc_privkey)
9026+        self._node._populate_privkey(privkey)
9027+        self._need_privkey = False
9028+
9029+
9030+    def _check_for_done(self, res):
9031+        """
9032+        I check to see if this Retrieve object has successfully finished
9033+        its work.
9034+
9035+        I can exit in the following ways:
9036+            - If there are no more segments to download, then I exit by
9037+              causing self._done_deferred to fire with the plaintext
9038+              content requested by the caller.
9039+            - If there are still segments to be downloaded, and there
9040+              are enough active readers (readers which have not broken
9041+              and have not given us corrupt data) to continue
9042+              downloading, I send control back to
9043+              _download_current_segment.
9044+            - If there are still segments to be downloaded but there are
9045+              not enough active peers to download them, I ask
9046+              _add_active_peers to add more peers. If it is successful,
9047+              it will call _download_current_segment. If there are not
9048+              enough peers to retrieve the file, then that will cause
9049+              _done_deferred to errback.
9050+        """
9051+        self.log("checking for doneness")
9052+        if self._current_segment > self._last_segment:
9053+            # No more segments to download, we're done.
9054+            self.log("got plaintext, done")
9055+            return self._done()
9056+
9057+        if len(self._active_readers) >= self._required_shares:
9058+            # More segments to download, but we have enough good peers
9059+            # in self._active_readers that we can do that without issue,
9060+            # so go nab the next segment.
9061+            self.log("not done yet: on segment %d of %d" % \
9062+                     (self._current_segment + 1, self._num_segments))
9063+            return self._download_current_segment()
9064+
9065+        self.log("not done yet: on segment %d of %d, need to add peers" % \
9066+                 (self._current_segment + 1, self._num_segments))
9067+        return self._add_active_peers()
9068+
9069+
9070+    def _done(self):
9071+        """
9072+        I am called by _check_for_done when the download process has
9073+        finished successfully. After making some useful logging
9074+        statements, I return the decrypted contents to the owner of this
9075+        Retrieve object through self._done_deferred.
9076+        """
9077         self._running = False
9078         self._status.set_active(False)
9079hunk ./src/allmydata/mutable/retrieve.py 1068
9080-        self._status.timings["total"] = time.time() - self._started
9081-        # res is either the new contents, or a Failure
9082-        if isinstance(res, failure.Failure):
9083-            self.log("Retrieve done, with failure", failure=res,
9084-                     level=log.UNUSUAL)
9085-            self._status.set_status("Failed")
9086+        now = time.time()
9087+        self._status.timings['total'] = now - self._started
9088+        self._status.timings['fetch'] = now - self._started_fetching
9089+
9090+        if self._verify:
9091+            ret = list(self._bad_shares)
9092+            self.log("done verifying, found %d bad shares" % len(ret))
9093         else:
9094hunk ./src/allmydata/mutable/retrieve.py 1076
9095-            self.log("Retrieve done, success!")
9096-            self._status.set_status("Finished")
9097-            self._status.set_progress(1.0)
9098-            # remember the encoding parameters, use them again next time
9099-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9100-             offsets_tuple) = self.verinfo
9101-            self._node._populate_required_shares(k)
9102-            self._node._populate_total_shares(N)
9103-        eventually(self._done_deferred.callback, res)
9104+            # TODO: upload status here?
9105+            ret = self._consumer
9106+            self._consumer.unregisterProducer()
9107+        eventually(self._done_deferred.callback, ret)
9108+
9109 
9110hunk ./src/allmydata/mutable/retrieve.py 1082
9111+    def _failed(self):
9112+        """
9113+        I am called by _add_active_peers when there are not enough
9114+        active peers left to complete the download. After making some
9115+        useful logging statements, I return an exception to that effect
9116+        to the caller of this Retrieve object through
9117+        self._done_deferred.
9118+        """
9119+        self._running = False
9120+        self._status.set_active(False)
9121+        now = time.time()
9122+        self._status.timings['total'] = now - self._started
9123+        self._status.timings['fetch'] = now - self._started_fetching
9124+
9125+        if self._verify:
9126+            ret = list(self._bad_shares)
9127+        else:
9128+            format = ("ran out of peers: "
9129+                      "have %(have)d of %(total)d segments "
9130+                      "found %(bad)d bad shares "
9131+                      "encoding %(k)d-of-%(n)d")
9132+            args = {"have": self._current_segment,
9133+                    "total": self._num_segments,
9134+                    "need": self._last_segment,
9135+                    "k": self._required_shares,
9136+                    "n": self._total_shares,
9137+                    "bad": len(self._bad_shares)}
9138+            e = NotEnoughSharesError("%s, last failure: %s" % \
9139+                                     (format % args, str(self._last_failure)))
9140+            f = failure.Failure(e)
9141+            ret = f
9142+        eventually(self._done_deferred.callback, ret)
9143}
9144[mutable/servermap.py: Alter the servermap updater to work with MDMF files
9145Kevan Carstensen <kevan@isnotajoke.com>**20100819003439
9146 Ignore-this: 7e408303194834bd59a2f27efab3bdb
9147 
9148 These modifications were basically all to the end of having the
9149 servermap updater use the unified MDMF + SDMF read interface whenever
9150 possible -- this reduces the complexity of the code, making it easier to
9151 read and maintain. To do this, I needed to modify the process of
9152 updating the servermap a little bit.
9153 
9154 To support partial-file updates, I also modified the servermap updater
9155 to fetch the block hash trees and certain segments of files while it
9156 performed a servermap update (this can be done without adding any new
9157 roundtrips because of batch-read functionality that the read proxy has).
9158 
9159] {
9160hunk ./src/allmydata/mutable/servermap.py 2
9161 
9162-import sys, time
9163+import sys, time, struct
9164 from zope.interface import implements
9165 from itertools import count
9166 from twisted.internet import defer
9167merger 0.0 (
9168hunk ./src/allmydata/mutable/servermap.py 9
9169+from allmydata.util.dictutil import DictOfSets
9170hunk ./src/allmydata/mutable/servermap.py 7
9171-from foolscap.api import DeadReferenceError, RemoteException, eventually
9172-from allmydata.util import base32, hashutil, idlib, log
9173+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
9174+                         fireEventually
9175+from allmydata.util import base32, hashutil, idlib, log, deferredutil
9176)
9177merger 0.0 (
9178hunk ./src/allmydata/mutable/servermap.py 14
9179-     DictOfSets, CorruptShareError, NeedMoreDataError
9180+     CorruptShareError, NeedMoreDataError
9181hunk ./src/allmydata/mutable/servermap.py 14
9182-     DictOfSets, CorruptShareError, NeedMoreDataError
9183-from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
9184-     SIGNED_PREFIX_LENGTH
9185+     DictOfSets, CorruptShareError
9186+from allmydata.mutable.layout import SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
9187)
9188hunk ./src/allmydata/mutable/servermap.py 123
9189         self.bad_shares = {} # maps (peerid,shnum) to old checkstring
9190         self.last_update_mode = None
9191         self.last_update_time = 0
9192+        self.update_data = {} # (verinfo,shnum) => data
9193 
9194     def copy(self):
9195         s = ServerMap()
9196hunk ./src/allmydata/mutable/servermap.py 254
9197         """Return a set of versionids, one for each version that is currently
9198         recoverable."""
9199         versionmap = self.make_versionmap()
9200-
9201         recoverable_versions = set()
9202         for (verinfo, shares) in versionmap.items():
9203             (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9204hunk ./src/allmydata/mutable/servermap.py 339
9205         return False
9206 
9207 
9208+    def get_update_data_for_share_and_verinfo(self, shnum, verinfo):
9209+        """
9210+        I return the update data for the given shnum
9211+        """
9212+        update_data = self.update_data[shnum]
9213+        update_datum = [i[1] for i in update_data if i[0] == verinfo][0]
9214+        return update_datum
9215+
9216+
9217+    def set_update_data_for_share_and_verinfo(self, shnum, verinfo, data):
9218+        """
9219+        I record the block hash tree for the given shnum.
9220+        """
9221+        self.update_data.setdefault(shnum , []).append((verinfo, data))
9222+
9223+
9224 class ServermapUpdater:
9225     def __init__(self, filenode, storage_broker, monitor, servermap,
9226hunk ./src/allmydata/mutable/servermap.py 357
9227-                 mode=MODE_READ, add_lease=False):
9228+                 mode=MODE_READ, add_lease=False, update_range=None):
9229         """I update a servermap, locating a sufficient number of useful
9230         shares and remembering where they are located.
9231 
9232hunk ./src/allmydata/mutable/servermap.py 382
9233         self._servers_responded = set()
9234 
9235         # how much data should we read?
9236+        # SDMF:
9237         #  * if we only need the checkstring, then [0:75]
9238         #  * if we need to validate the checkstring sig, then [543ish:799ish]
9239         #  * if we need the verification key, then [107:436ish]
9240merger 0.0 (
9241hunk ./src/allmydata/mutable/servermap.py 392
9242-        # read 2000 bytes, which also happens to read enough actual data to
9243-        # pre-fetch a 9-entry dirnode.
9244+        # read 4000 bytes, which also happens to read enough actual data to
9245+        # pre-fetch an 18-entry dirnode.
9246hunk ./src/allmydata/mutable/servermap.py 390
9247-        # A future version of the SMDF slot format should consider using
9248-        # fixed-size slots so we can retrieve less data. For now, we'll just
9249-        # read 2000 bytes, which also happens to read enough actual data to
9250-        # pre-fetch a 9-entry dirnode.
9251+        # MDMF:
9252+        #  * Checkstring? [0:72]
9253+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
9254+        #    the offset table will tell us for sure.
9255+        #  * If we need the verification key, we have to consult the offset
9256+        #    table as well.
9257+        # At this point, we don't know which we are. Our filenode can
9258+        # tell us, but it might be lying -- in some cases, we're
9259+        # responsible for telling it which kind of file it is.
9260)
9261hunk ./src/allmydata/mutable/servermap.py 399
9262             # we use unpack_prefix_and_signature, so we need 1k
9263             self._read_size = 1000
9264         self._need_privkey = False
9265+
9266         if mode == MODE_WRITE and not self._node.get_privkey():
9267             self._need_privkey = True
9268         # check+repair: repair requires the privkey, so if we didn't happen
9269hunk ./src/allmydata/mutable/servermap.py 406
9270         # to ask for it during the check, we'll have problems doing the
9271         # publish.
9272 
9273+        self.fetch_update_data = False
9274+        if mode == MODE_WRITE and update_range:
9275+            # We're updating the servermap in preparation for an
9276+            # in-place file update, so we need to fetch some additional
9277+            # data from each share that we find.
9278+            assert len(update_range) == 2
9279+
9280+            self.start_segment = update_range[0]
9281+            self.end_segment = update_range[1]
9282+            self.fetch_update_data = True
9283+
9284         prefix = si_b2a(self._storage_index)[:5]
9285         self._log_number = log.msg(format="SharemapUpdater(%(si)s): starting (%(mode)s)",
9286                                    si=prefix, mode=mode)
9287merger 0.0 (
9288hunk ./src/allmydata/mutable/servermap.py 455
9289-        full_peerlist = sb.get_servers_for_index(self._storage_index)
9290+        full_peerlist = [(s.get_serverid(), s.get_rref())
9291+                         for s in sb.get_servers_for_psi(self._storage_index)]
9292hunk ./src/allmydata/mutable/servermap.py 455
9293+        # All of the peers, permuted by the storage index, as usual.
9294)
9295hunk ./src/allmydata/mutable/servermap.py 461
9296         self._good_peers = set() # peers who had some shares
9297         self._empty_peers = set() # peers who don't have any shares
9298         self._bad_peers = set() # peers to whom our queries failed
9299+        self._readers = {} # peerid -> dict(sharewriters), filled in
9300+                           # after responses come in.
9301 
9302         k = self._node.get_required_shares()
9303hunk ./src/allmydata/mutable/servermap.py 465
9304+        # For what cases can these conditions work?
9305         if k is None:
9306             # make a guess
9307             k = 3
9308hunk ./src/allmydata/mutable/servermap.py 478
9309         self.num_peers_to_query = k + self.EPSILON
9310 
9311         if self.mode == MODE_CHECK:
9312+            # We want to query all of the peers.
9313             initial_peers_to_query = dict(full_peerlist)
9314             must_query = set(initial_peers_to_query.keys())
9315             self.extra_peers = []
9316hunk ./src/allmydata/mutable/servermap.py 486
9317             # we're planning to replace all the shares, so we want a good
9318             # chance of finding them all. We will keep searching until we've
9319             # seen epsilon that don't have a share.
9320+            # We don't query all of the peers because that could take a while.
9321             self.num_peers_to_query = N + self.EPSILON
9322             initial_peers_to_query, must_query = self._build_initial_querylist()
9323             self.required_num_empty_peers = self.EPSILON
9324hunk ./src/allmydata/mutable/servermap.py 496
9325             # might also avoid the round trip required to read the encrypted
9326             # private key.
9327 
9328-        else:
9329+        else: # MODE_READ, MODE_ANYTHING
9330+            # 2k peers is good enough.
9331             initial_peers_to_query, must_query = self._build_initial_querylist()
9332 
9333         # this is a set of peers that we are required to get responses from:
9334hunk ./src/allmydata/mutable/servermap.py 512
9335         # before we can consider ourselves finished, and self.extra_peers
9336         # contains the overflow (peers that we should tap if we don't get
9337         # enough responses)
9338+        # I guess that self._must_query is a subset of
9339+        # initial_peers_to_query?
9340+        assert set(must_query).issubset(set(initial_peers_to_query))
9341 
9342         self._send_initial_requests(initial_peers_to_query)
9343         self._status.timings["initial_queries"] = time.time() - self._started
9344hunk ./src/allmydata/mutable/servermap.py 571
9345         # errors that aren't handled by _query_failed (and errors caused by
9346         # _query_failed) get logged, but we still want to check for doneness.
9347         d.addErrback(log.err)
9348-        d.addBoth(self._check_for_done)
9349         d.addErrback(self._fatal_error)
9350hunk ./src/allmydata/mutable/servermap.py 572
9351+        d.addCallback(self._check_for_done)
9352         return d
9353 
9354     def _do_read(self, ss, peerid, storage_index, shnums, readv):
9355hunk ./src/allmydata/mutable/servermap.py 591
9356         d = ss.callRemote("slot_readv", storage_index, shnums, readv)
9357         return d
9358 
9359+
9360+    def _got_corrupt_share(self, e, shnum, peerid, data, lp):
9361+        """
9362+        I am called when a remote server returns a corrupt share in
9363+        response to one of our queries. By corrupt, I mean a share
9364+        without a valid signature. I then record the failure, notify the
9365+        server of the corruption, and record the share as bad.
9366+        """
9367+        f = failure.Failure(e)
9368+        self.log(format="bad share: %(f_value)s", f_value=str(f),
9369+                 failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
9370+        # Notify the server that its share is corrupt.
9371+        self.notify_server_corruption(peerid, shnum, str(e))
9372+        # By flagging this as a bad peer, we won't count any of
9373+        # the other shares on that peer as valid, though if we
9374+        # happen to find a valid version string amongst those
9375+        # shares, we'll keep track of it so that we don't need
9376+        # to validate the signature on those again.
9377+        self._bad_peers.add(peerid)
9378+        self._last_failure = f
9379+        # XXX: Use the reader for this?
9380+        checkstring = data[:SIGNED_PREFIX_LENGTH]
9381+        self._servermap.mark_bad_share(peerid, shnum, checkstring)
9382+        self._servermap.problems.append(f)
9383+
9384+
9385+    def _cache_good_sharedata(self, verinfo, shnum, now, data):
9386+        """
9387+        If one of my queries returns successfully (which means that we
9388+        were able to and successfully did validate the signature), I
9389+        cache the data that we initially fetched from the storage
9390+        server. This will help reduce the number of roundtrips that need
9391+        to occur when the file is downloaded, or when the file is
9392+        updated.
9393+        """
9394+        if verinfo:
9395+            self._node._add_to_cache(verinfo, shnum, 0, data, now)
9396+
9397+
9398     def _got_results(self, datavs, peerid, readsize, stuff, started):
9399         lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares",
9400                       peerid=idlib.shortnodeid_b2a(peerid),
9401hunk ./src/allmydata/mutable/servermap.py 633
9402-                      numshares=len(datavs),
9403-                      level=log.NOISY)
9404+                      numshares=len(datavs))
9405         now = time.time()
9406         elapsed = now - started
9407hunk ./src/allmydata/mutable/servermap.py 636
9408-        self._queries_outstanding.discard(peerid)
9409-        self._servermap.reachable_peers.add(peerid)
9410-        self._must_query.discard(peerid)
9411-        self._queries_completed += 1
9412+        def _done_processing(ignored=None):
9413+            self._queries_outstanding.discard(peerid)
9414+            self._servermap.reachable_peers.add(peerid)
9415+            self._must_query.discard(peerid)
9416+            self._queries_completed += 1
9417         if not self._running:
9418hunk ./src/allmydata/mutable/servermap.py 642
9419-            self.log("but we're not running, so we'll ignore it", parent=lp,
9420-                     level=log.NOISY)
9421+            self.log("but we're not running, so we'll ignore it", parent=lp)
9422+            _done_processing()
9423             self._status.add_per_server_time(peerid, "late", started, elapsed)
9424             return
9425         self._status.add_per_server_time(peerid, "query", started, elapsed)
9426hunk ./src/allmydata/mutable/servermap.py 653
9427         else:
9428             self._empty_peers.add(peerid)
9429 
9430-        last_verinfo = None
9431-        last_shnum = None
9432+        ss, storage_index = stuff
9433+        ds = []
9434+
9435         for shnum,datav in datavs.items():
9436             data = datav[0]
9437             try:
9438merger 0.0 (
9439hunk ./src/allmydata/mutable/servermap.py 662
9440-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
9441+                self._node._add_to_cache(verinfo, shnum, 0, data)
9442hunk ./src/allmydata/mutable/servermap.py 658
9443-            try:
9444-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
9445-                last_verinfo = verinfo
9446-                last_shnum = shnum
9447-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
9448-            except CorruptShareError, e:
9449-                # log it and give the other shares a chance to be processed
9450-                f = failure.Failure()
9451-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
9452-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
9453-                self.notify_server_corruption(peerid, shnum, str(e))
9454-                self._bad_peers.add(peerid)
9455-                self._last_failure = f
9456-                checkstring = data[:SIGNED_PREFIX_LENGTH]
9457-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
9458-                self._servermap.problems.append(f)
9459-                pass
9460+            reader = MDMFSlotReadProxy(ss,
9461+                                       storage_index,
9462+                                       shnum,
9463+                                       data)
9464+            self._readers.setdefault(peerid, dict())[shnum] = reader
9465+            # our goal, with each response, is to validate the version
9466+            # information and share data as best we can at this point --
9467+            # we do this by validating the signature. To do this, we
9468+            # need to do the following:
9469+            #   - If we don't already have the public key, fetch the
9470+            #     public key. We use this to validate the signature.
9471+            if not self._node.get_pubkey():
9472+                # fetch and set the public key.
9473+                d = reader.get_verification_key(queue=True)
9474+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
9475+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
9476+                # XXX: Make self._pubkey_query_failed?
9477+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
9478+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
9479+            else:
9480+                # we already have the public key.
9481+                d = defer.succeed(None)
9482)
9483hunk ./src/allmydata/mutable/servermap.py 676
9484                 self._servermap.problems.append(f)
9485                 pass
9486 
9487-        self._status.timings["cumulative_verify"] += (time.time() - now)
9488+            # Neither of these two branches return anything of
9489+            # consequence, so the first entry in our deferredlist will
9490+            # be None.
9491 
9492hunk ./src/allmydata/mutable/servermap.py 680
9493-        if self._need_privkey and last_verinfo:
9494-            # send them a request for the privkey. We send one request per
9495-            # server.
9496-            lp2 = self.log("sending privkey request",
9497-                           parent=lp, level=log.NOISY)
9498-            (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9499-             offsets_tuple) = last_verinfo
9500-            o = dict(offsets_tuple)
9501+            # - Next, we need the version information. We almost
9502+            #   certainly got this by reading the first thousand or so
9503+            #   bytes of the share on the storage server, so we
9504+            #   shouldn't need to fetch anything at this step.
9505+            d2 = reader.get_verinfo()
9506+            d2.addErrback(lambda error, shnum=shnum, peerid=peerid:
9507+                self._got_corrupt_share(error, shnum, peerid, data, lp))
9508+            # - Next, we need the signature. For an SDMF share, it is
9509+            #   likely that we fetched this when doing our initial fetch
9510+            #   to get the version information. In MDMF, this lives at
9511+            #   the end of the share, so unless the file is quite small,
9512+            #   we'll need to do a remote fetch to get it.
9513+            d3 = reader.get_signature(queue=True)
9514+            d3.addErrback(lambda error, shnum=shnum, peerid=peerid:
9515+                self._got_corrupt_share(error, shnum, peerid, data, lp))
9516+            #  Once we have all three of these responses, we can move on
9517+            #  to validating the signature
9518 
9519hunk ./src/allmydata/mutable/servermap.py 698
9520-            self._queries_outstanding.add(peerid)
9521-            readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ]
9522-            ss = self._servermap.connections[peerid]
9523-            privkey_started = time.time()
9524-            d = self._do_read(ss, peerid, self._storage_index,
9525-                              [last_shnum], readv)
9526-            d.addCallback(self._got_privkey_results, peerid, last_shnum,
9527-                          privkey_started, lp2)
9528-            d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2)
9529-            d.addErrback(log.err)
9530-            d.addCallback(self._check_for_done)
9531-            d.addErrback(self._fatal_error)
9532+            # Does the node already have a privkey? If not, we'll try to
9533+            # fetch it here.
9534+            if self._need_privkey:
9535+                d4 = reader.get_encprivkey(queue=True)
9536+                d4.addCallback(lambda results, shnum=shnum, peerid=peerid:
9537+                    self._try_to_validate_privkey(results, peerid, shnum, lp))
9538+                d4.addErrback(lambda error, shnum=shnum, peerid=peerid:
9539+                    self._privkey_query_failed(error, shnum, data, lp))
9540+            else:
9541+                d4 = defer.succeed(None)
9542+
9543+
9544+            if self.fetch_update_data:
9545+                # fetch the block hash tree and first + last segment, as
9546+                # configured earlier.
9547+                # Then set them in wherever we happen to want to set
9548+                # them.
9549+                ds = []
9550+                # XXX: We do this above, too. Is there a good way to
9551+                # make the two routines share the value without
9552+                # introducing more roundtrips?
9553+                ds.append(reader.get_verinfo())
9554+                ds.append(reader.get_blockhashes(queue=True))
9555+                ds.append(reader.get_block_and_salt(self.start_segment,
9556+                                                    queue=True))
9557+                ds.append(reader.get_block_and_salt(self.end_segment,
9558+                                                    queue=True))
9559+                d5 = deferredutil.gatherResults(ds)
9560+                d5.addCallback(self._got_update_results_one_share, shnum)
9561+            else:
9562+                d5 = defer.succeed(None)
9563 
9564hunk ./src/allmydata/mutable/servermap.py 730
9565+            dl = defer.DeferredList([d, d2, d3, d4, d5])
9566+            dl.addBoth(self._turn_barrier)
9567+            reader.flush()
9568+            dl.addCallback(lambda results, shnum=shnum, peerid=peerid:
9569+                self._got_signature_one_share(results, shnum, peerid, lp))
9570+            dl.addErrback(lambda error, shnum=shnum, data=data:
9571+               self._got_corrupt_share(error, shnum, peerid, data, lp))
9572+            dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data:
9573+                self._cache_good_sharedata(verinfo, shnum, now, data))
9574+            ds.append(dl)
9575+        # dl is a deferred list that will fire when all of the shares
9576+        # that we found on this peer are done processing. When dl fires,
9577+        # we know that processing is done, so we can decrement the
9578+        # semaphore-like thing that we incremented earlier.
9579+        dl = defer.DeferredList(ds, fireOnOneErrback=True)
9580+        # Are we done? Done means that there are no more queries to
9581+        # send, that there are no outstanding queries, and that we
9582+        # haven't received any queries that are still processing. If we
9583+        # are done, self._check_for_done will cause the done deferred
9584+        # that we returned to our caller to fire, which tells them that
9585+        # they have a complete servermap, and that we won't be touching
9586+        # the servermap anymore.
9587+        dl.addCallback(_done_processing)
9588+        dl.addCallback(self._check_for_done)
9589+        dl.addErrback(self._fatal_error)
9590         # all done!
9591         self.log("_got_results done", parent=lp, level=log.NOISY)
9592hunk ./src/allmydata/mutable/servermap.py 757
9593+        return dl
9594+
9595+
9596+    def _turn_barrier(self, result):
9597+        """
9598+        I help the servermap updater avoid the recursion limit issues
9599+        discussed in #237.
9600+        """
9601+        return fireEventually(result)
9602+
9603+
9604+    def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp):
9605+        if self._node.get_pubkey():
9606+            return # don't go through this again if we don't have to
9607+        fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
9608+        assert len(fingerprint) == 32
9609+        if fingerprint != self._node.get_fingerprint():
9610+            raise CorruptShareError(peerid, shnum,
9611+                                "pubkey doesn't match fingerprint")
9612+        self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
9613+        assert self._node.get_pubkey()
9614+
9615 
9616     def notify_server_corruption(self, peerid, shnum, reason):
9617         ss = self._servermap.connections[peerid]
9618hunk ./src/allmydata/mutable/servermap.py 785
9619         ss.callRemoteOnly("advise_corrupt_share",
9620                           "mutable", self._storage_index, shnum, reason)
9621 
9622-    def _got_results_one_share(self, shnum, data, peerid, lp):
9623+
9624+    def _got_signature_one_share(self, results, shnum, peerid, lp):
9625+        # It is our job to give versioninfo to our caller. We need to
9626+        # raise CorruptShareError if the share is corrupt for any
9627+        # reason, something that our caller will handle.
9628         self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s",
9629                  shnum=shnum,
9630                  peerid=idlib.shortnodeid_b2a(peerid),
9631hunk ./src/allmydata/mutable/servermap.py 795
9632                  level=log.NOISY,
9633                  parent=lp)
9634+        if not self._running:
9635+            # We can't process the results, since we can't touch the
9636+            # servermap anymore.
9637+            self.log("but we're not running anymore.")
9638+            return None
9639 
9640hunk ./src/allmydata/mutable/servermap.py 801
9641-        # this might raise NeedMoreDataError, if the pubkey and signature
9642-        # live at some weird offset. That shouldn't happen, so I'm going to
9643-        # treat it as a bad share.
9644-        (seqnum, root_hash, IV, k, N, segsize, datalength,
9645-         pubkey_s, signature, prefix) = unpack_prefix_and_signature(data)
9646-
9647-        if not self._node.get_pubkey():
9648-            fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
9649-            assert len(fingerprint) == 32
9650-            if fingerprint != self._node.get_fingerprint():
9651-                raise CorruptShareError(peerid, shnum,
9652-                                        "pubkey doesn't match fingerprint")
9653-            self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s))
9654-
9655-        if self._need_privkey:
9656-            self._try_to_extract_privkey(data, peerid, shnum, lp)
9657-
9658-        (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N,
9659-         ig_segsize, ig_datalen, offsets) = unpack_header(data)
9660+        _, verinfo, signature, __, ___ = results
9661+        (seqnum,
9662+         root_hash,
9663+         saltish,
9664+         segsize,
9665+         datalen,
9666+         k,
9667+         n,
9668+         prefix,
9669+         offsets) = verinfo[1]
9670         offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
9671 
9672hunk ./src/allmydata/mutable/servermap.py 813
9673-        verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
9674+        # XXX: This should be done for us in the method, so
9675+        # presumably you can go in there and fix it.
9676+        verinfo = (seqnum,
9677+                   root_hash,
9678+                   saltish,
9679+                   segsize,
9680+                   datalen,
9681+                   k,
9682+                   n,
9683+                   prefix,
9684                    offsets_tuple)
9685hunk ./src/allmydata/mutable/servermap.py 824
9686+        # This tuple uniquely identifies a share on the grid; we use it
9687+        # to keep track of the ones that we've already seen.
9688 
9689         if verinfo not in self._valid_versions:
9690hunk ./src/allmydata/mutable/servermap.py 828
9691-            # it's a new pair. Verify the signature.
9692-            valid = self._node.get_pubkey().verify(prefix, signature)
9693+            # This is a new version tuple, and we need to validate it
9694+            # against the public key before keeping track of it.
9695+            assert self._node.get_pubkey()
9696+            valid = self._node.get_pubkey().verify(prefix, signature[1])
9697             if not valid:
9698hunk ./src/allmydata/mutable/servermap.py 833
9699-                raise CorruptShareError(peerid, shnum, "signature is invalid")
9700+                raise CorruptShareError(peerid, shnum,
9701+                                        "signature is invalid")
9702 
9703hunk ./src/allmydata/mutable/servermap.py 836
9704-            # ok, it's a valid verinfo. Add it to the list of validated
9705-            # versions.
9706-            self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
9707-                     % (seqnum, base32.b2a(root_hash)[:4],
9708-                        idlib.shortnodeid_b2a(peerid), shnum,
9709-                        k, N, segsize, datalength),
9710-                     parent=lp)
9711-            self._valid_versions.add(verinfo)
9712-        # We now know that this is a valid candidate verinfo.
9713+        # ok, it's a valid verinfo. Add it to the list of validated
9714+        # versions.
9715+        self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d"
9716+                 % (seqnum, base32.b2a(root_hash)[:4],
9717+                    idlib.shortnodeid_b2a(peerid), shnum,
9718+                    k, n, segsize, datalen),
9719+                    parent=lp)
9720+        self._valid_versions.add(verinfo)
9721+        # We now know that this is a valid candidate verinfo. Whether or
9722+        # not this instance of it is valid is a matter for the next
9723+        # statement; at this point, we just know that if we see this
9724+        # version info again, that its signature checks out and that
9725+        # we're okay to skip the signature-checking step.
9726 
9727hunk ./src/allmydata/mutable/servermap.py 850
9728+        # (peerid, shnum) are bound in the method invocation.
9729         if (peerid, shnum) in self._servermap.bad_shares:
9730             # we've been told that the rest of the data in this share is
9731             # unusable, so don't add it to the servermap.
9732hunk ./src/allmydata/mutable/servermap.py 863
9733         self._servermap.add_new_share(peerid, shnum, verinfo, timestamp)
9734         # and the versionmap
9735         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
9736+
9737+        # It's our job to set the protocol version of our parent
9738+        # filenode if it isn't already set.
9739+        if not self._node.get_version():
9740+            # The first byte of the prefix is the version.
9741+            v = struct.unpack(">B", prefix[:1])[0]
9742+            self.log("got version %d" % v)
9743+            self._node.set_version(v)
9744+
9745         return verinfo
9746 
9747hunk ./src/allmydata/mutable/servermap.py 874
9748-    def _deserialize_pubkey(self, pubkey_s):
9749-        verifier = rsa.create_verifying_key_from_string(pubkey_s)
9750-        return verifier
9751 
9752hunk ./src/allmydata/mutable/servermap.py 875
9753-    def _try_to_extract_privkey(self, data, peerid, shnum, lp):
9754-        try:
9755-            r = unpack_share(data)
9756-        except NeedMoreDataError, e:
9757-            # this share won't help us. oh well.
9758-            offset = e.encprivkey_offset
9759-            length = e.encprivkey_length
9760-            self.log("shnum %d on peerid %s: share was too short (%dB) "
9761-                     "to get the encprivkey; [%d:%d] ought to hold it" %
9762-                     (shnum, idlib.shortnodeid_b2a(peerid), len(data),
9763-                      offset, offset+length),
9764-                     parent=lp)
9765-            # NOTE: if uncoordinated writes are taking place, someone might
9766-            # change the share (and most probably move the encprivkey) before
9767-            # we get a chance to do one of these reads and fetch it. This
9768-            # will cause us to see a NotEnoughSharesError(unable to fetch
9769-            # privkey) instead of an UncoordinatedWriteError . This is a
9770-            # nuisance, but it will go away when we move to DSA-based mutable
9771-            # files (since the privkey will be small enough to fit in the
9772-            # write cap).
9773+    def _got_update_results_one_share(self, results, share):
9774+        """
9775+        I record the update results in results.
9776+        """
9777+        assert len(results) == 4
9778+        verinfo, blockhashes, start, end = results
9779+        (seqnum,
9780+         root_hash,
9781+         saltish,
9782+         segsize,
9783+         datalen,
9784+         k,
9785+         n,
9786+         prefix,
9787+         offsets) = verinfo
9788+        offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] )
9789 
9790hunk ./src/allmydata/mutable/servermap.py 892
9791-            return
9792+        # XXX: This should be done for us in the method, so
9793+        # presumably you can go in there and fix it.
9794+        verinfo = (seqnum,
9795+                   root_hash,
9796+                   saltish,
9797+                   segsize,
9798+                   datalen,
9799+                   k,
9800+                   n,
9801+                   prefix,
9802+                   offsets_tuple)
9803 
9804hunk ./src/allmydata/mutable/servermap.py 904
9805-        (seqnum, root_hash, IV, k, N, segsize, datalen,
9806-         pubkey, signature, share_hash_chain, block_hash_tree,
9807-         share_data, enc_privkey) = r
9808+        update_data = (blockhashes, start, end)
9809+        self._servermap.set_update_data_for_share_and_verinfo(share,
9810+                                                              verinfo,
9811+                                                              update_data)
9812 
9813hunk ./src/allmydata/mutable/servermap.py 909
9814-        return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
9815+
9816+    def _deserialize_pubkey(self, pubkey_s):
9817+        verifier = rsa.create_verifying_key_from_string(pubkey_s)
9818+        return verifier
9819 
9820hunk ./src/allmydata/mutable/servermap.py 914
9821-    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
9822 
9823hunk ./src/allmydata/mutable/servermap.py 915
9824+    def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp):
9825+        """
9826+        Given a writekey from a remote server, I validate it against the
9827+        writekey stored in my node. If it is valid, then I set the
9828+        privkey and encprivkey properties of the node.
9829+        """
9830         alleged_privkey_s = self._node._decrypt_privkey(enc_privkey)
9831         alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s)
9832         if alleged_writekey != self._node.get_writekey():
9833hunk ./src/allmydata/mutable/servermap.py 993
9834         self._queries_completed += 1
9835         self._last_failure = f
9836 
9837-    def _got_privkey_results(self, datavs, peerid, shnum, started, lp):
9838-        now = time.time()
9839-        elapsed = now - started
9840-        self._status.add_per_server_time(peerid, "privkey", started, elapsed)
9841-        self._queries_outstanding.discard(peerid)
9842-        if not self._need_privkey:
9843-            return
9844-        if shnum not in datavs:
9845-            self.log("privkey wasn't there when we asked it",
9846-                     level=log.WEIRD, umid="VA9uDQ")
9847-            return
9848-        datav = datavs[shnum]
9849-        enc_privkey = datav[0]
9850-        self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp)
9851 
9852     def _privkey_query_failed(self, f, peerid, shnum, lp):
9853         self._queries_outstanding.discard(peerid)
9854hunk ./src/allmydata/mutable/servermap.py 1007
9855         self._servermap.problems.append(f)
9856         self._last_failure = f
9857 
9858+
9859     def _check_for_done(self, res):
9860         # exit paths:
9861         #  return self._send_more_queries(outstanding) : send some more queries
9862hunk ./src/allmydata/mutable/servermap.py 1013
9863         #  return self._done() : all done
9864         #  return : keep waiting, no new queries
9865-
9866         lp = self.log(format=("_check_for_done, mode is '%(mode)s', "
9867                               "%(outstanding)d queries outstanding, "
9868                               "%(extra)d extra peers available, "
9869hunk ./src/allmydata/mutable/servermap.py 1204
9870 
9871     def _done(self):
9872         if not self._running:
9873+            self.log("not running; we're already done")
9874             return
9875         self._running = False
9876         now = time.time()
9877hunk ./src/allmydata/mutable/servermap.py 1219
9878         self._servermap.last_update_time = self._started
9879         # the servermap will not be touched after this
9880         self.log("servermap: %s" % self._servermap.summarize_versions())
9881+
9882         eventually(self._done_deferred.callback, self._servermap)
9883 
9884     def _fatal_error(self, f):
9885}
9886[tests:
9887Kevan Carstensen <kevan@isnotajoke.com>**20100819003531
9888 Ignore-this: 314e8bbcce532ea4d5d2cecc9f31cca0
9889 
9890     - A lot of existing tests relied on aspects of the mutable file
9891       implementation that were changed. This patch updates those tests
9892       to work with the changes.
9893     - This patch also adds tests for new features.
9894] {
9895hunk ./src/allmydata/test/common.py 11
9896 from foolscap.api import flushEventualQueue, fireEventually
9897 from allmydata import uri, dirnode, client
9898 from allmydata.introducer.server import IntroducerNode
9899-from allmydata.interfaces import IMutableFileNode, IImmutableFileNode, \
9900-     FileTooLargeError, NotEnoughSharesError, ICheckable
9901+from allmydata.interfaces import IMutableFileNode, IImmutableFileNode,\
9902+                                 NotEnoughSharesError, ICheckable, \
9903+                                 IMutableUploadable, SDMF_VERSION, \
9904+                                 MDMF_VERSION
9905 from allmydata.check_results import CheckResults, CheckAndRepairResults, \
9906      DeepCheckResults, DeepCheckAndRepairResults
9907 from allmydata.mutable.common import CorruptShareError
9908hunk ./src/allmydata/test/common.py 19
9909 from allmydata.mutable.layout import unpack_header
9910+from allmydata.mutable.publish import MutableData
9911 from allmydata.storage.server import storage_index_to_dir
9912 from allmydata.storage.mutable import MutableShareFile
9913 from allmydata.util import hashutil, log, fileutil, pollmixin
9914hunk ./src/allmydata/test/common.py 153
9915         consumer.write(data[start:end])
9916         return consumer
9917 
9918+
9919+    def get_best_readable_version(self):
9920+        return defer.succeed(self)
9921+
9922+
9923+    download_best_version = download_to_data
9924+
9925+
9926+    def download_to_data(self):
9927+        return download_to_data(self)
9928+
9929+
9930+    def get_size_of_best_version(self):
9931+        return defer.succeed(self.get_size)
9932+
9933+
9934 def make_chk_file_cap(size):
9935     return uri.CHKFileURI(key=os.urandom(16),
9936                           uri_extension_hash=os.urandom(32),
9937hunk ./src/allmydata/test/common.py 193
9938     MUTABLE_SIZELIMIT = 10000
9939     all_contents = {}
9940     bad_shares = {}
9941+    file_types = {} # storage index => MDMF_VERSION or SDMF_VERSION
9942 
9943     def __init__(self, storage_broker, secret_holder,
9944                  default_encoding_parameters, history):
9945hunk ./src/allmydata/test/common.py 200
9946         self.init_from_cap(make_mutable_file_cap())
9947     def create(self, contents, key_generator=None, keysize=None):
9948         initial_contents = self._get_initial_contents(contents)
9949-        if len(initial_contents) > self.MUTABLE_SIZELIMIT:
9950-            raise FileTooLargeError("SDMF is limited to one segment, and "
9951-                                    "%d > %d" % (len(initial_contents),
9952-                                                 self.MUTABLE_SIZELIMIT))
9953-        self.all_contents[self.storage_index] = initial_contents
9954+        data = initial_contents.read(initial_contents.get_size())
9955+        data = "".join(data)
9956+        self.all_contents[self.storage_index] = data
9957         return defer.succeed(self)
9958     def _get_initial_contents(self, contents):
9959hunk ./src/allmydata/test/common.py 205
9960-        if isinstance(contents, str):
9961-            return contents
9962         if contents is None:
9963hunk ./src/allmydata/test/common.py 206
9964-            return ""
9965+            return MutableData("")
9966+
9967+        if IMutableUploadable.providedBy(contents):
9968+            return contents
9969+
9970         assert callable(contents), "%s should be callable, not %s" % \
9971                (contents, type(contents))
9972         return contents(self)
9973hunk ./src/allmydata/test/common.py 258
9974     def get_storage_index(self):
9975         return self.storage_index
9976 
9977+    def get_servermap(self, mode):
9978+        return defer.succeed(None)
9979+
9980+    def set_version(self, version):
9981+        assert version in (SDMF_VERSION, MDMF_VERSION)
9982+        self.file_types[self.storage_index] = version
9983+
9984+    def get_version(self):
9985+        assert self.storage_index in self.file_types
9986+        return self.file_types[self.storage_index]
9987+
9988     def check(self, monitor, verify=False, add_lease=False):
9989         r = CheckResults(self.my_uri, self.storage_index)
9990         is_bad = self.bad_shares.get(self.storage_index, None)
9991hunk ./src/allmydata/test/common.py 327
9992         return d
9993 
9994     def download_best_version(self):
9995+        return defer.succeed(self._download_best_version())
9996+
9997+
9998+    def _download_best_version(self, ignored=None):
9999         if isinstance(self.my_uri, uri.LiteralFileURI):
10000hunk ./src/allmydata/test/common.py 332
10001-            return defer.succeed(self.my_uri.data)
10002+            return self.my_uri.data
10003         if self.storage_index not in self.all_contents:
10004hunk ./src/allmydata/test/common.py 334
10005-            return defer.fail(NotEnoughSharesError(None, 0, 3))
10006-        return defer.succeed(self.all_contents[self.storage_index])
10007+            raise NotEnoughSharesError(None, 0, 3)
10008+        return self.all_contents[self.storage_index]
10009+
10010 
10011     def overwrite(self, new_contents):
10012hunk ./src/allmydata/test/common.py 339
10013-        if len(new_contents) > self.MUTABLE_SIZELIMIT:
10014-            raise FileTooLargeError("SDMF is limited to one segment, and "
10015-                                    "%d > %d" % (len(new_contents),
10016-                                                 self.MUTABLE_SIZELIMIT))
10017         assert not self.is_readonly()
10018hunk ./src/allmydata/test/common.py 340
10019-        self.all_contents[self.storage_index] = new_contents
10020+        new_data = new_contents.read(new_contents.get_size())
10021+        new_data = "".join(new_data)
10022+        self.all_contents[self.storage_index] = new_data
10023         return defer.succeed(None)
10024     def modify(self, modifier):
10025         # this does not implement FileTooLargeError, but the real one does
10026hunk ./src/allmydata/test/common.py 350
10027     def _modify(self, modifier):
10028         assert not self.is_readonly()
10029         old_contents = self.all_contents[self.storage_index]
10030-        self.all_contents[self.storage_index] = modifier(old_contents, None, True)
10031+        new_data = modifier(old_contents, None, True)
10032+        self.all_contents[self.storage_index] = new_data
10033         return None
10034 
10035hunk ./src/allmydata/test/common.py 354
10036+    # As actually implemented, MutableFilenode and MutableFileVersion
10037+    # are distinct. However, nothing in the webapi uses (yet) that
10038+    # distinction -- it just uses the unified download interface
10039+    # provided by get_best_readable_version and read. When we start
10040+    # doing cooler things like LDMF, we will want to revise this code to
10041+    # be less simplistic.
10042+    def get_best_readable_version(self):
10043+        return defer.succeed(self)
10044+
10045+
10046+    def get_best_mutable_version(self):
10047+        return defer.succeed(self)
10048+
10049+    # Ditto for this, which is an implementation of IWritable.
10050+    # XXX: Declare that the same is implemented.
10051+    def update(self, data, offset):
10052+        assert not self.is_readonly()
10053+        def modifier(old, servermap, first_time):
10054+            new = old[:offset] + "".join(data.read(data.get_size()))
10055+            new += old[len(new):]
10056+            return new
10057+        return self.modify(modifier)
10058+
10059+
10060+    def read(self, consumer, offset=0, size=None):
10061+        data = self._download_best_version()
10062+        if size:
10063+            data = data[offset:offset+size]
10064+        consumer.write(data)
10065+        return defer.succeed(consumer)
10066+
10067+
10068 def make_mutable_file_cap():
10069     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
10070                                    fingerprint=os.urandom(32))
10071hunk ./src/allmydata/test/test_checker.py 11
10072 from allmydata.test.no_network import GridTestMixin
10073 from allmydata.immutable.upload import Data
10074 from allmydata.test.common_web import WebRenderingMixin
10075+from allmydata.mutable.publish import MutableData
10076 
10077 class FakeClient:
10078     def get_storage_broker(self):
10079hunk ./src/allmydata/test/test_checker.py 291
10080         def _stash_immutable(ur):
10081             self.imm = c0.create_node_from_uri(ur.uri)
10082         d.addCallback(_stash_immutable)
10083-        d.addCallback(lambda ign: c0.create_mutable_file("contents"))
10084+        d.addCallback(lambda ign:
10085+            c0.create_mutable_file(MutableData("contents")))
10086         def _stash_mutable(node):
10087             self.mut = node
10088         d.addCallback(_stash_mutable)
10089hunk ./src/allmydata/test/test_cli.py 13
10090 from allmydata.util import fileutil, hashutil, base32
10091 from allmydata import uri
10092 from allmydata.immutable import upload
10093+from allmydata.mutable.publish import MutableData
10094 from allmydata.dirnode import normalize
10095 
10096 # Test that the scripts can be imported.
10097hunk ./src/allmydata/test/test_cli.py 662
10098 
10099         d = self.do_cli("create-alias", etudes_arg)
10100         def _check_create_unicode((rc, out, err)):
10101-            self.failUnlessReallyEqual(rc, 0)
10102+            #self.failUnlessReallyEqual(rc, 0)
10103             self.failUnlessReallyEqual(err, "")
10104             self.failUnlessIn("Alias %s created" % quote_output(u"\u00E9tudes"), out)
10105 
10106hunk ./src/allmydata/test/test_cli.py 967
10107         d.addCallback(lambda (rc,out,err): self.failUnlessReallyEqual(out, DATA2))
10108         return d
10109 
10110+    def test_mutable_type(self):
10111+        self.basedir = "cli/Put/mutable_type"
10112+        self.set_up_grid()
10113+        data = "data" * 100000
10114+        fn1 = os.path.join(self.basedir, "data")
10115+        fileutil.write(fn1, data)
10116+        d = self.do_cli("create-alias", "tahoe")
10117+        d.addCallback(lambda ignored:
10118+            self.do_cli("put", "--mutable", "--mutable-type=mdmf",
10119+                        fn1, "tahoe:uploaded.txt"))
10120+        d.addCallback(lambda ignored:
10121+            self.do_cli("ls", "--json", "tahoe:uploaded.txt"))
10122+        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
10123+        d.addCallback(lambda ignored:
10124+            self.do_cli("put", "--mutable", "--mutable-type=sdmf",
10125+                        fn1, "tahoe:uploaded2.txt"))
10126+        d.addCallback(lambda ignored:
10127+            self.do_cli("ls", "--json", "tahoe:uploaded2.txt"))
10128+        d.addCallback(lambda (rc, json, err):
10129+            self.failUnlessIn("sdmf", json))
10130+        return d
10131+
10132+    def test_mutable_type_unlinked(self):
10133+        self.basedir = "cli/Put/mutable_type_unlinked"
10134+        self.set_up_grid()
10135+        data = "data" * 100000
10136+        fn1 = os.path.join(self.basedir, "data")
10137+        fileutil.write(fn1, data)
10138+        d = self.do_cli("put", "--mutable", "--mutable-type=mdmf", fn1)
10139+        d.addCallback(lambda (rc, cap, err):
10140+            self.do_cli("ls", "--json", cap))
10141+        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
10142+        d.addCallback(lambda ignored:
10143+            self.do_cli("put", "--mutable", "--mutable-type=sdmf", fn1))
10144+        d.addCallback(lambda (rc, cap, err):
10145+            self.do_cli("ls", "--json", cap))
10146+        d.addCallback(lambda (rc, json, err):
10147+            self.failUnlessIn("sdmf", json))
10148+        return d
10149+
10150+    def test_mutable_type_invalid_format(self):
10151+        self.basedir = "cli/Put/mutable_type_invalid_format"
10152+        self.set_up_grid()
10153+        data = "data" * 100000
10154+        fn1 = os.path.join(self.basedir, "data")
10155+        fileutil.write(fn1, data)
10156+        d = self.do_cli("put", "--mutable", "--mutable-type=ldmf", fn1)
10157+        def _check_failure((rc, out, err)):
10158+            self.failIfEqual(rc, 0)
10159+            self.failUnlessIn("invalid", err)
10160+        d.addCallback(_check_failure)
10161+        return d
10162+
10163     def test_put_with_nonexistent_alias(self):
10164         # when invoked with an alias that doesn't exist, 'tahoe put'
10165         # should output a useful error message, not a stack trace
10166hunk ./src/allmydata/test/test_cli.py 2136
10167         self.set_up_grid()
10168         c0 = self.g.clients[0]
10169         DATA = "data" * 100
10170-        d = c0.create_mutable_file(DATA)
10171+        DATA_uploadable = MutableData(DATA)
10172+        d = c0.create_mutable_file(DATA_uploadable)
10173         def _stash_uri(n):
10174             self.uri = n.get_uri()
10175         d.addCallback(_stash_uri)
10176hunk ./src/allmydata/test/test_cli.py 2238
10177                                            upload.Data("literal",
10178                                                         convergence="")))
10179         d.addCallback(_stash_uri, "small")
10180-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"1"))
10181+        d.addCallback(lambda ign:
10182+            c0.create_mutable_file(MutableData(DATA+"1")))
10183         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
10184         d.addCallback(_stash_uri, "mutable")
10185 
10186hunk ./src/allmydata/test/test_cli.py 2257
10187         # root/small
10188         # root/mutable
10189 
10190+        # We haven't broken anything yet, so this should all be healthy.
10191         d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
10192                                               self.rooturi))
10193         def _check2((rc, out, err)):
10194hunk ./src/allmydata/test/test_cli.py 2272
10195                             in lines, out)
10196         d.addCallback(_check2)
10197 
10198+        # Similarly, all of these results should be as we expect them to
10199+        # be for a healthy file layout.
10200         d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
10201         def _check_stats((rc, out, err)):
10202             self.failUnlessReallyEqual(err, "")
10203hunk ./src/allmydata/test/test_cli.py 2289
10204             self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
10205         d.addCallback(_check_stats)
10206 
10207+        # Now we break things.
10208         def _clobber_shares(ignored):
10209             shares = self.find_uri_shares(self.uris[u"g\u00F6\u00F6d"])
10210             self.failUnlessReallyEqual(len(shares), 10)
10211hunk ./src/allmydata/test/test_cli.py 2314
10212 
10213         d.addCallback(lambda ign:
10214                       self.do_cli("deep-check", "--verbose", self.rooturi))
10215+        # This should reveal the missing share, but not the corrupt
10216+        # share, since we didn't tell the deep check operation to also
10217+        # verify.
10218         def _check3((rc, out, err)):
10219             self.failUnlessReallyEqual(err, "")
10220             self.failUnlessReallyEqual(rc, 0)
10221hunk ./src/allmydata/test/test_cli.py 2365
10222                                   "--verbose", "--verify", "--repair",
10223                                   self.rooturi))
10224         def _check6((rc, out, err)):
10225+            # We've just repaired the directory. There is no reason for
10226+            # that repair to be unsuccessful.
10227             self.failUnlessReallyEqual(err, "")
10228             self.failUnlessReallyEqual(rc, 0)
10229             lines = out.splitlines()
10230hunk ./src/allmydata/test/test_deepcheck.py 9
10231 from twisted.internet import threads # CLI tests use deferToThread
10232 from allmydata.immutable import upload
10233 from allmydata.mutable.common import UnrecoverableFileError
10234+from allmydata.mutable.publish import MutableData
10235 from allmydata.util import idlib
10236 from allmydata.util import base32
10237 from allmydata.scripts import runner
10238hunk ./src/allmydata/test/test_deepcheck.py 38
10239         self.basedir = "deepcheck/MutableChecker/good"
10240         self.set_up_grid()
10241         CONTENTS = "a little bit of data"
10242-        d = self.g.clients[0].create_mutable_file(CONTENTS)
10243+        CONTENTS_uploadable = MutableData(CONTENTS)
10244+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
10245         def _created(node):
10246             self.node = node
10247             self.fileurl = "uri/" + urllib.quote(node.get_uri())
10248hunk ./src/allmydata/test/test_deepcheck.py 61
10249         self.basedir = "deepcheck/MutableChecker/corrupt"
10250         self.set_up_grid()
10251         CONTENTS = "a little bit of data"
10252-        d = self.g.clients[0].create_mutable_file(CONTENTS)
10253+        CONTENTS_uploadable = MutableData(CONTENTS)
10254+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
10255         def _stash_and_corrupt(node):
10256             self.node = node
10257             self.fileurl = "uri/" + urllib.quote(node.get_uri())
10258hunk ./src/allmydata/test/test_deepcheck.py 99
10259         self.basedir = "deepcheck/MutableChecker/delete_share"
10260         self.set_up_grid()
10261         CONTENTS = "a little bit of data"
10262-        d = self.g.clients[0].create_mutable_file(CONTENTS)
10263+        CONTENTS_uploadable = MutableData(CONTENTS)
10264+        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
10265         def _stash_and_delete(node):
10266             self.node = node
10267             self.fileurl = "uri/" + urllib.quote(node.get_uri())
10268hunk ./src/allmydata/test/test_deepcheck.py 223
10269             self.root = n
10270             self.root_uri = n.get_uri()
10271         d.addCallback(_created_root)
10272-        d.addCallback(lambda ign: c0.create_mutable_file("mutable file contents"))
10273+        d.addCallback(lambda ign:
10274+            c0.create_mutable_file(MutableData("mutable file contents")))
10275         d.addCallback(lambda n: self.root.set_node(u"mutable", n))
10276         def _created_mutable(n):
10277             self.mutable = n
10278hunk ./src/allmydata/test/test_deepcheck.py 965
10279     def create_mangled(self, ignored, name):
10280         nodetype, mangletype = name.split("-", 1)
10281         if nodetype == "mutable":
10282-            d = self.g.clients[0].create_mutable_file("mutable file contents")
10283+            mutable_uploadable = MutableData("mutable file contents")
10284+            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
10285             d.addCallback(lambda n: self.root.set_node(unicode(name), n))
10286         elif nodetype == "large":
10287             large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
10288hunk ./src/allmydata/test/test_dirnode.py 1304
10289     implements(IMutableFileNode)
10290     counter = 0
10291     def __init__(self, initial_contents=""):
10292-        self.data = self._get_initial_contents(initial_contents)
10293+        data = self._get_initial_contents(initial_contents)
10294+        self.data = data.read(data.get_size())
10295+        self.data = "".join(self.data)
10296+
10297         counter = FakeMutableFile.counter
10298         FakeMutableFile.counter += 1
10299         writekey = hashutil.ssk_writekey_hash(str(counter))
10300hunk ./src/allmydata/test/test_dirnode.py 1354
10301         pass
10302 
10303     def modify(self, modifier):
10304-        self.data = modifier(self.data, None, True)
10305+        data = modifier(self.data, None, True)
10306+        self.data = data
10307         return defer.succeed(None)
10308 
10309 class FakeNodeMaker(NodeMaker):
10310hunk ./src/allmydata/test/test_dirnode.py 1359
10311-    def create_mutable_file(self, contents="", keysize=None):
10312+    def create_mutable_file(self, contents="", keysize=None, version=None):
10313         return defer.succeed(FakeMutableFile(contents))
10314 
10315 class FakeClient2(Client):
10316hunk ./src/allmydata/test/test_filenode.py 98
10317         def _check_segment(res):
10318             self.failUnlessEqual(res, DATA[1:1+5])
10319         d.addCallback(_check_segment)
10320+        d.addCallback(lambda ignored: fn1.get_best_readable_version())
10321+        d.addCallback(lambda fn2: self.failUnlessEqual(fn1, fn2))
10322+        d.addCallback(lambda ignored:
10323+            fn1.get_size_of_best_version())
10324+        d.addCallback(lambda size:
10325+            self.failUnlessEqual(size, len(DATA)))
10326+        d.addCallback(lambda ignored:
10327+            fn1.download_to_data())
10328+        d.addCallback(lambda data:
10329+            self.failUnlessEqual(data, DATA))
10330+        d.addCallback(lambda ignored:
10331+            fn1.download_best_version())
10332+        d.addCallback(lambda data:
10333+            self.failUnlessEqual(data, DATA))
10334 
10335         return d
10336 
10337hunk ./src/allmydata/test/test_hung_server.py 10
10338 from allmydata.util.consumer import download_to_data
10339 from allmydata.immutable import upload
10340 from allmydata.mutable.common import UnrecoverableFileError
10341+from allmydata.mutable.publish import MutableData
10342 from allmydata.storage.common import storage_index_to_dir
10343 from allmydata.test.no_network import GridTestMixin
10344 from allmydata.test.common import ShouldFailMixin
10345hunk ./src/allmydata/test/test_hung_server.py 110
10346         self.servers = self.servers[5:] + self.servers[:5]
10347 
10348         if mutable:
10349-            d = nm.create_mutable_file(mutable_plaintext)
10350+            uploadable = MutableData(mutable_plaintext)
10351+            d = nm.create_mutable_file(uploadable)
10352             def _uploaded_mutable(node):
10353                 self.uri = node.get_uri()
10354                 self.shares = self.find_uri_shares(self.uri)
10355hunk ./src/allmydata/test/test_immutable.py 267
10356         d.addCallback(_after_attempt)
10357         return d
10358 
10359+    def test_download_to_data(self):
10360+        d = self.n.download_to_data()
10361+        d.addCallback(lambda data:
10362+            self.failUnlessEqual(data, common.TEST_DATA))
10363+        return d
10364 
10365hunk ./src/allmydata/test/test_immutable.py 273
10366+
10367+    def test_download_best_version(self):
10368+        d = self.n.download_best_version()
10369+        d.addCallback(lambda data:
10370+            self.failUnlessEqual(data, common.TEST_DATA))
10371+        return d
10372+
10373+
10374+    def test_get_best_readable_version(self):
10375+        d = self.n.get_best_readable_version()
10376+        d.addCallback(lambda n2:
10377+            self.failUnlessEqual(n2, self.n))
10378+        return d
10379+
10380+    def test_get_size_of_best_version(self):
10381+        d = self.n.get_size_of_best_version()
10382+        d.addCallback(lambda size:
10383+            self.failUnlessEqual(size, len(common.TEST_DATA)))
10384+        return d
10385+
10386+
10387 # XXX extend these tests to show bad behavior of various kinds from servers:
10388 # raising exception from each remove_foo() method, for example
10389 
10390hunk ./src/allmydata/test/test_mutable.py 2
10391 
10392-import struct
10393+import os
10394 from cStringIO import StringIO
10395 from twisted.trial import unittest
10396 from twisted.internet import defer, reactor
10397hunk ./src/allmydata/test/test_mutable.py 8
10398 from allmydata import uri, client
10399 from allmydata.nodemaker import NodeMaker
10400-from allmydata.util import base32
10401+from allmydata.util import base32, consumer
10402 from allmydata.util.hashutil import tagged_hash, ssk_writekey_hash, \
10403      ssk_pubkey_fingerprint_hash
10404hunk ./src/allmydata/test/test_mutable.py 11
10405+from allmydata.util.deferredutil import gatherResults
10406 from allmydata.interfaces import IRepairResults, ICheckAndRepairResults, \
10407hunk ./src/allmydata/test/test_mutable.py 13
10408-     NotEnoughSharesError
10409+     NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
10410 from allmydata.monitor import Monitor
10411 from allmydata.test.common import ShouldFailMixin
10412 from allmydata.test.no_network import GridTestMixin
10413hunk ./src/allmydata/test/test_mutable.py 27
10414      NeedMoreDataError, UnrecoverableFileError, UncoordinatedWriteError, \
10415      NotEnoughServersError, CorruptShareError
10416 from allmydata.mutable.retrieve import Retrieve
10417-from allmydata.mutable.publish import Publish
10418+from allmydata.mutable.publish import Publish, MutableFileHandle, \
10419+                                      MutableData, \
10420+                                      DEFAULT_MAX_SEGMENT_SIZE
10421 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
10422hunk ./src/allmydata/test/test_mutable.py 31
10423-from allmydata.mutable.layout import unpack_header, unpack_share
10424+from allmydata.mutable.layout import unpack_header, MDMFSlotReadProxy
10425 from allmydata.mutable.repairer import MustForceRepairError
10426 
10427 import allmydata.test.common_util as testutil
10428hunk ./src/allmydata/test/test_mutable.py 100
10429         self.storage = storage
10430         self.queries = 0
10431     def callRemote(self, methname, *args, **kwargs):
10432+        self.queries += 1
10433         def _call():
10434             meth = getattr(self, methname)
10435             return meth(*args, **kwargs)
10436hunk ./src/allmydata/test/test_mutable.py 107
10437         d = fireEventually()
10438         d.addCallback(lambda res: _call())
10439         return d
10440+
10441     def callRemoteOnly(self, methname, *args, **kwargs):
10442hunk ./src/allmydata/test/test_mutable.py 109
10443+        self.queries += 1
10444         d = self.callRemote(methname, *args, **kwargs)
10445         d.addBoth(lambda ignore: None)
10446         pass
10447hunk ./src/allmydata/test/test_mutable.py 157
10448             chr(ord(original[byte_offset]) ^ 0x01) +
10449             original[byte_offset+1:])
10450 
10451+def add_two(original, byte_offset):
10452+    # It isn't enough to simply flip the bit for the version number,
10453+    # because 1 is a valid version number. So we add two instead.
10454+    return (original[:byte_offset] +
10455+            chr(ord(original[byte_offset]) ^ 0x02) +
10456+            original[byte_offset+1:])
10457+
10458 def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0):
10459     # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a
10460     # list of shnums to corrupt.
10461hunk ./src/allmydata/test/test_mutable.py 167
10462+    ds = []
10463     for peerid in s._peers:
10464         shares = s._peers[peerid]
10465         for shnum in shares:
10466hunk ./src/allmydata/test/test_mutable.py 175
10467                 and shnum not in shnums_to_corrupt):
10468                 continue
10469             data = shares[shnum]
10470-            (version,
10471-             seqnum,
10472-             root_hash,
10473-             IV,
10474-             k, N, segsize, datalen,
10475-             o) = unpack_header(data)
10476-            if isinstance(offset, tuple):
10477-                offset1, offset2 = offset
10478-            else:
10479-                offset1 = offset
10480-                offset2 = 0
10481-            if offset1 == "pubkey":
10482-                real_offset = 107
10483-            elif offset1 in o:
10484-                real_offset = o[offset1]
10485-            else:
10486-                real_offset = offset1
10487-            real_offset = int(real_offset) + offset2 + offset_offset
10488-            assert isinstance(real_offset, int), offset
10489-            shares[shnum] = flip_bit(data, real_offset)
10490-    return res
10491+            # We're feeding the reader all of the share data, so it
10492+            # won't need to use the rref that we didn't provide, nor the
10493+            # storage index that we didn't provide. We do this because
10494+            # the reader will work for both MDMF and SDMF.
10495+            reader = MDMFSlotReadProxy(None, None, shnum, data)
10496+            # We need to get the offsets for the next part.
10497+            d = reader.get_verinfo()
10498+            def _do_corruption(verinfo, data, shnum):
10499+                (seqnum,
10500+                 root_hash,
10501+                 IV,
10502+                 segsize,
10503+                 datalen,
10504+                 k, n, prefix, o) = verinfo
10505+                if isinstance(offset, tuple):
10506+                    offset1, offset2 = offset
10507+                else:
10508+                    offset1 = offset
10509+                    offset2 = 0
10510+                if offset1 == "pubkey" and IV:
10511+                    real_offset = 107
10512+                elif offset1 == "share_data" and not IV:
10513+                    real_offset = 107
10514+                elif offset1 in o:
10515+                    real_offset = o[offset1]
10516+                else:
10517+                    real_offset = offset1
10518+                real_offset = int(real_offset) + offset2 + offset_offset
10519+                assert isinstance(real_offset, int), offset
10520+                if offset1 == 0: # verbyte
10521+                    f = add_two
10522+                else:
10523+                    f = flip_bit
10524+                shares[shnum] = f(data, real_offset)
10525+            d.addCallback(_do_corruption, data, shnum)
10526+            ds.append(d)
10527+    dl = defer.DeferredList(ds)
10528+    dl.addCallback(lambda ignored: res)
10529+    return dl
10530 
10531 def make_storagebroker(s=None, num_peers=10):
10532     if not s:
10533hunk ./src/allmydata/test/test_mutable.py 256
10534             self.failUnlessEqual(len(shnums), 1)
10535         d.addCallback(_created)
10536         return d
10537+    test_create.timeout = 15
10538+
10539+
10540+    def test_create_mdmf(self):
10541+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
10542+        def _created(n):
10543+            self.failUnless(isinstance(n, MutableFileNode))
10544+            self.failUnlessEqual(n.get_storage_index(), n._storage_index)
10545+            sb = self.nodemaker.storage_broker
10546+            peer0 = sorted(sb.get_all_serverids())[0]
10547+            shnums = self._storage._peers[peer0].keys()
10548+            self.failUnlessEqual(len(shnums), 1)
10549+        d.addCallback(_created)
10550+        return d
10551+
10552 
10553     def test_serialize(self):
10554         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
10555hunk ./src/allmydata/test/test_mutable.py 301
10556             d.addCallback(lambda smap: smap.dump(StringIO()))
10557             d.addCallback(lambda sio:
10558                           self.failUnless("3-of-10" in sio.getvalue()))
10559-            d.addCallback(lambda res: n.overwrite("contents 1"))
10560+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
10561             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
10562             d.addCallback(lambda res: n.download_best_version())
10563             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
10564hunk ./src/allmydata/test/test_mutable.py 308
10565             d.addCallback(lambda res: n.get_size_of_best_version())
10566             d.addCallback(lambda size:
10567                           self.failUnlessEqual(size, len("contents 1")))
10568-            d.addCallback(lambda res: n.overwrite("contents 2"))
10569+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
10570             d.addCallback(lambda res: n.download_best_version())
10571             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
10572             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
10573hunk ./src/allmydata/test/test_mutable.py 312
10574-            d.addCallback(lambda smap: n.upload("contents 3", smap))
10575+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
10576             d.addCallback(lambda res: n.download_best_version())
10577             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
10578             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
10579hunk ./src/allmydata/test/test_mutable.py 324
10580             # mapupdate-to-retrieve data caching (i.e. make the shares larger
10581             # than the default readsize, which is 2000 bytes). A 15kB file
10582             # will have 5kB shares.
10583-            d.addCallback(lambda res: n.overwrite("large size file" * 1000))
10584+            d.addCallback(lambda res: n.overwrite(MutableData("large size file" * 1000)))
10585             d.addCallback(lambda res: n.download_best_version())
10586             d.addCallback(lambda res:
10587                           self.failUnlessEqual(res, "large size file" * 1000))
10588hunk ./src/allmydata/test/test_mutable.py 332
10589         d.addCallback(_created)
10590         return d
10591 
10592+
10593+    def test_upload_and_download_mdmf(self):
10594+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
10595+        def _created(n):
10596+            d = defer.succeed(None)
10597+            d.addCallback(lambda ignored:
10598+                n.get_servermap(MODE_READ))
10599+            def _then(servermap):
10600+                dumped = servermap.dump(StringIO())
10601+                self.failUnlessIn("3-of-10", dumped.getvalue())
10602+            d.addCallback(_then)
10603+            # Now overwrite the contents with some new contents. We want
10604+            # to make them big enough to force the file to be uploaded
10605+            # in more than one segment.
10606+            big_contents = "contents1" * 100000 # about 900 KiB
10607+            big_contents_uploadable = MutableData(big_contents)
10608+            d.addCallback(lambda ignored:
10609+                n.overwrite(big_contents_uploadable))
10610+            d.addCallback(lambda ignored:
10611+                n.download_best_version())
10612+            d.addCallback(lambda data:
10613+                self.failUnlessEqual(data, big_contents))
10614+            # Overwrite the contents again with some new contents. As
10615+            # before, they need to be big enough to force multiple
10616+            # segments, so that we make the downloader deal with
10617+            # multiple segments.
10618+            bigger_contents = "contents2" * 1000000 # about 9MiB
10619+            bigger_contents_uploadable = MutableData(bigger_contents)
10620+            d.addCallback(lambda ignored:
10621+                n.overwrite(bigger_contents_uploadable))
10622+            d.addCallback(lambda ignored:
10623+                n.download_best_version())
10624+            d.addCallback(lambda data:
10625+                self.failUnlessEqual(data, bigger_contents))
10626+            return d
10627+        d.addCallback(_created)
10628+        return d
10629+
10630+
10631+    def test_mdmf_write_count(self):
10632+        # Publishing an MDMF file should only cause one write for each
10633+        # share that is to be published. Otherwise, we introduce
10634+        # undesirable semantics that are a regression from SDMF
10635+        upload = MutableData("MDMF" * 100000) # about 400 KiB
10636+        d = self.nodemaker.create_mutable_file(upload,
10637+                                               version=MDMF_VERSION)
10638+        def _check_server_write_counts(ignored):
10639+            sb = self.nodemaker.storage_broker
10640+            peers = sb.test_servers.values()
10641+            for peer in peers:
10642+                self.failUnlessEqual(peer.queries, 1)
10643+        d.addCallback(_check_server_write_counts)
10644+        return d
10645+
10646+
10647     def test_create_with_initial_contents(self):
10648hunk ./src/allmydata/test/test_mutable.py 388
10649-        d = self.nodemaker.create_mutable_file("contents 1")
10650+        upload1 = MutableData("contents 1")
10651+        d = self.nodemaker.create_mutable_file(upload1)
10652         def _created(n):
10653             d = n.download_best_version()
10654             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
10655hunk ./src/allmydata/test/test_mutable.py 393
10656-            d.addCallback(lambda res: n.overwrite("contents 2"))
10657+            upload2 = MutableData("contents 2")
10658+            d.addCallback(lambda res: n.overwrite(upload2))
10659             d.addCallback(lambda res: n.download_best_version())
10660             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
10661             return d
10662hunk ./src/allmydata/test/test_mutable.py 400
10663         d.addCallback(_created)
10664         return d
10665+    test_create_with_initial_contents.timeout = 15
10666+
10667+
10668+    def test_create_mdmf_with_initial_contents(self):
10669+        initial_contents = "foobarbaz" * 131072 # 900KiB
10670+        initial_contents_uploadable = MutableData(initial_contents)
10671+        d = self.nodemaker.create_mutable_file(initial_contents_uploadable,
10672+                                               version=MDMF_VERSION)
10673+        def _created(n):
10674+            d = n.download_best_version()
10675+            d.addCallback(lambda data:
10676+                self.failUnlessEqual(data, initial_contents))
10677+            uploadable2 = MutableData(initial_contents + "foobarbaz")
10678+            d.addCallback(lambda ignored:
10679+                n.overwrite(uploadable2))
10680+            d.addCallback(lambda ignored:
10681+                n.download_best_version())
10682+            d.addCallback(lambda data:
10683+                self.failUnlessEqual(data, initial_contents +
10684+                                           "foobarbaz"))
10685+            return d
10686+        d.addCallback(_created)
10687+        return d
10688+    test_create_mdmf_with_initial_contents.timeout = 20
10689+
10690 
10691     def test_response_cache_memory_leak(self):
10692         d = self.nodemaker.create_mutable_file("contents")
10693hunk ./src/allmydata/test/test_mutable.py 451
10694             key = n.get_writekey()
10695             self.failUnless(isinstance(key, str), key)
10696             self.failUnlessEqual(len(key), 16) # AES key size
10697-            return data
10698+            return MutableData(data)
10699         d = self.nodemaker.create_mutable_file(_make_contents)
10700         def _created(n):
10701             return n.download_best_version()
10702hunk ./src/allmydata/test/test_mutable.py 459
10703         d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
10704         return d
10705 
10706+
10707+    def test_create_mdmf_with_initial_contents_function(self):
10708+        data = "initial contents" * 100000
10709+        def _make_contents(n):
10710+            self.failUnless(isinstance(n, MutableFileNode))
10711+            key = n.get_writekey()
10712+            self.failUnless(isinstance(key, str), key)
10713+            self.failUnlessEqual(len(key), 16)
10714+            return MutableData(data)
10715+        d = self.nodemaker.create_mutable_file(_make_contents,
10716+                                               version=MDMF_VERSION)
10717+        d.addCallback(lambda n:
10718+            n.download_best_version())
10719+        d.addCallback(lambda data2:
10720+            self.failUnlessEqual(data2, data))
10721+        return d
10722+
10723+
10724     def test_create_with_too_large_contents(self):
10725         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
10726hunk ./src/allmydata/test/test_mutable.py 479
10727-        d = self.nodemaker.create_mutable_file(BIG)
10728+        BIG_uploadable = MutableData(BIG)
10729+        d = self.nodemaker.create_mutable_file(BIG_uploadable)
10730         def _created(n):
10731hunk ./src/allmydata/test/test_mutable.py 482
10732-            d = n.overwrite(BIG)
10733+            other_BIG_uploadable = MutableData(BIG)
10734+            d = n.overwrite(other_BIG_uploadable)
10735             return d
10736         d.addCallback(_created)
10737         return d
10738hunk ./src/allmydata/test/test_mutable.py 497
10739 
10740     def test_modify(self):
10741         def _modifier(old_contents, servermap, first_time):
10742-            return old_contents + "line2"
10743+            new_contents = old_contents + "line2"
10744+            return new_contents
10745         def _non_modifier(old_contents, servermap, first_time):
10746             return old_contents
10747         def _none_modifier(old_contents, servermap, first_time):
10748hunk ./src/allmydata/test/test_mutable.py 506
10749         def _error_modifier(old_contents, servermap, first_time):
10750             raise ValueError("oops")
10751         def _toobig_modifier(old_contents, servermap, first_time):
10752-            return "b" * (self.OLD_MAX_SEGMENT_SIZE+1)
10753+            new_content = "b" * (self.OLD_MAX_SEGMENT_SIZE + 1)
10754+            return new_content
10755         calls = []
10756         def _ucw_error_modifier(old_contents, servermap, first_time):
10757             # simulate an UncoordinatedWriteError once
10758hunk ./src/allmydata/test/test_mutable.py 514
10759             calls.append(1)
10760             if len(calls) <= 1:
10761                 raise UncoordinatedWriteError("simulated")
10762-            return old_contents + "line3"
10763+            new_contents = old_contents + "line3"
10764+            return new_contents
10765         def _ucw_error_non_modifier(old_contents, servermap, first_time):
10766             # simulate an UncoordinatedWriteError once, and don't actually
10767             # modify the contents on subsequent invocations
10768hunk ./src/allmydata/test/test_mutable.py 524
10769                 raise UncoordinatedWriteError("simulated")
10770             return old_contents
10771 
10772-        d = self.nodemaker.create_mutable_file("line1")
10773+        initial_contents = "line1"
10774+        d = self.nodemaker.create_mutable_file(MutableData(initial_contents))
10775         def _created(n):
10776             d = n.modify(_modifier)
10777             d.addCallback(lambda res: n.download_best_version())
10778hunk ./src/allmydata/test/test_mutable.py 582
10779             return d
10780         d.addCallback(_created)
10781         return d
10782+    test_modify.timeout = 15
10783+
10784 
10785     def test_modify_backoffer(self):
10786         def _modifier(old_contents, servermap, first_time):
10787hunk ./src/allmydata/test/test_mutable.py 609
10788         giveuper._delay = 0.1
10789         giveuper.factor = 1
10790 
10791-        d = self.nodemaker.create_mutable_file("line1")
10792+        d = self.nodemaker.create_mutable_file(MutableData("line1"))
10793         def _created(n):
10794             d = n.modify(_modifier)
10795             d.addCallback(lambda res: n.download_best_version())
10796hunk ./src/allmydata/test/test_mutable.py 659
10797             d.addCallback(lambda smap: smap.dump(StringIO()))
10798             d.addCallback(lambda sio:
10799                           self.failUnless("3-of-10" in sio.getvalue()))
10800-            d.addCallback(lambda res: n.overwrite("contents 1"))
10801+            d.addCallback(lambda res: n.overwrite(MutableData("contents 1")))
10802             d.addCallback(lambda res: self.failUnlessIdentical(res, None))
10803             d.addCallback(lambda res: n.download_best_version())
10804             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
10805hunk ./src/allmydata/test/test_mutable.py 663
10806-            d.addCallback(lambda res: n.overwrite("contents 2"))
10807+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
10808             d.addCallback(lambda res: n.download_best_version())
10809             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
10810             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
10811hunk ./src/allmydata/test/test_mutable.py 667
10812-            d.addCallback(lambda smap: n.upload("contents 3", smap))
10813+            d.addCallback(lambda smap: n.upload(MutableData("contents 3"), smap))
10814             d.addCallback(lambda res: n.download_best_version())
10815             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 3"))
10816             d.addCallback(lambda res: n.get_servermap(MODE_ANYTHING))
10817hunk ./src/allmydata/test/test_mutable.py 680
10818         return d
10819 
10820 
10821-class MakeShares(unittest.TestCase):
10822-    def test_encrypt(self):
10823-        nm = make_nodemaker()
10824-        CONTENTS = "some initial contents"
10825-        d = nm.create_mutable_file(CONTENTS)
10826-        def _created(fn):
10827-            p = Publish(fn, nm.storage_broker, None)
10828-            p.salt = "SALT" * 4
10829-            p.readkey = "\x00" * 16
10830-            p.newdata = CONTENTS
10831-            p.required_shares = 3
10832-            p.total_shares = 10
10833-            p.setup_encoding_parameters()
10834-            return p._encrypt_and_encode()
10835+    def test_size_after_servermap_update(self):
10836+        # a mutable file node should have something to say about how big
10837+        # it is after a servermap update is performed, since this tells
10838+        # us how large the best version of that mutable file is.
10839+        d = self.nodemaker.create_mutable_file()
10840+        def _created(n):
10841+            self.n = n
10842+            return n.get_servermap(MODE_READ)
10843+        d.addCallback(_created)
10844+        d.addCallback(lambda ignored:
10845+            self.failUnlessEqual(self.n.get_size(), 0))
10846+        d.addCallback(lambda ignored:
10847+            self.n.overwrite(MutableData("foobarbaz")))
10848+        d.addCallback(lambda ignored:
10849+            self.failUnlessEqual(self.n.get_size(), 9))
10850+        d.addCallback(lambda ignored:
10851+            self.nodemaker.create_mutable_file(MutableData("foobarbaz")))
10852+        d.addCallback(_created)
10853+        d.addCallback(lambda ignored:
10854+            self.failUnlessEqual(self.n.get_size(), 9))
10855+        return d
10856+
10857+
10858+class PublishMixin:
10859+    def publish_one(self):
10860+        # publish a file and create shares, which can then be manipulated
10861+        # later.
10862+        self.CONTENTS = "New contents go here" * 1000
10863+        self.uploadable = MutableData(self.CONTENTS)
10864+        self._storage = FakeStorage()
10865+        self._nodemaker = make_nodemaker(self._storage)
10866+        self._storage_broker = self._nodemaker.storage_broker
10867+        d = self._nodemaker.create_mutable_file(self.uploadable)
10868+        def _created(node):
10869+            self._fn = node
10870+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
10871         d.addCallback(_created)
10872hunk ./src/allmydata/test/test_mutable.py 717
10873-        def _done(shares_and_shareids):
10874-            (shares, share_ids) = shares_and_shareids
10875-            self.failUnlessEqual(len(shares), 10)
10876-            for sh in shares:
10877-                self.failUnless(isinstance(sh, str))
10878-                self.failUnlessEqual(len(sh), 7)
10879-            self.failUnlessEqual(len(share_ids), 10)
10880-        d.addCallback(_done)
10881         return d
10882 
10883hunk ./src/allmydata/test/test_mutable.py 719
10884-    def test_generate(self):
10885-        nm = make_nodemaker()
10886-        CONTENTS = "some initial contents"
10887-        d = nm.create_mutable_file(CONTENTS)
10888-        def _created(fn):
10889-            self._fn = fn
10890-            p = Publish(fn, nm.storage_broker, None)
10891-            self._p = p
10892-            p.newdata = CONTENTS
10893-            p.required_shares = 3
10894-            p.total_shares = 10
10895-            p.setup_encoding_parameters()
10896-            p._new_seqnum = 3
10897-            p.salt = "SALT" * 4
10898-            # make some fake shares
10899-            shares_and_ids = ( ["%07d" % i for i in range(10)], range(10) )
10900-            p._privkey = fn.get_privkey()
10901-            p._encprivkey = fn.get_encprivkey()
10902-            p._pubkey = fn.get_pubkey()
10903-            return p._generate_shares(shares_and_ids)
10904+    def publish_mdmf(self):
10905+        # like publish_one, except that the result is guaranteed to be
10906+        # an MDMF file.
10907+        # self.CONTENTS should have more than one segment.
10908+        self.CONTENTS = "This is an MDMF file" * 100000
10909+        self.uploadable = MutableData(self.CONTENTS)
10910+        self._storage = FakeStorage()
10911+        self._nodemaker = make_nodemaker(self._storage)
10912+        self._storage_broker = self._nodemaker.storage_broker
10913+        d = self._nodemaker.create_mutable_file(self.uploadable, version=MDMF_VERSION)
10914+        def _created(node):
10915+            self._fn = node
10916+            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
10917         d.addCallback(_created)
10918hunk ./src/allmydata/test/test_mutable.py 733
10919-        def _generated(res):
10920-            p = self._p
10921-            final_shares = p.shares
10922-            root_hash = p.root_hash
10923-            self.failUnlessEqual(len(root_hash), 32)
10924-            self.failUnless(isinstance(final_shares, dict))
10925-            self.failUnlessEqual(len(final_shares), 10)
10926-            self.failUnlessEqual(sorted(final_shares.keys()), range(10))
10927-            for i,sh in final_shares.items():
10928-                self.failUnless(isinstance(sh, str))
10929-                # feed the share through the unpacker as a sanity-check
10930-                pieces = unpack_share(sh)
10931-                (u_seqnum, u_root_hash, IV, k, N, segsize, datalen,
10932-                 pubkey, signature, share_hash_chain, block_hash_tree,
10933-                 share_data, enc_privkey) = pieces
10934-                self.failUnlessEqual(u_seqnum, 3)
10935-                self.failUnlessEqual(u_root_hash, root_hash)
10936-                self.failUnlessEqual(k, 3)
10937-                self.failUnlessEqual(N, 10)
10938-                self.failUnlessEqual(segsize, 21)
10939-                self.failUnlessEqual(datalen, len(CONTENTS))
10940-                self.failUnlessEqual(pubkey, p._pubkey.serialize())
10941-                sig_material = struct.pack(">BQ32s16s BBQQ",
10942-                                           0, p._new_seqnum, root_hash, IV,
10943-                                           k, N, segsize, datalen)
10944-                self.failUnless(p._pubkey.verify(sig_material, signature))
10945-                #self.failUnlessEqual(signature, p._privkey.sign(sig_material))
10946-                self.failUnless(isinstance(share_hash_chain, dict))
10947-                self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++
10948-                for shnum,share_hash in share_hash_chain.items():
10949-                    self.failUnless(isinstance(shnum, int))
10950-                    self.failUnless(isinstance(share_hash, str))
10951-                    self.failUnlessEqual(len(share_hash), 32)
10952-                self.failUnless(isinstance(block_hash_tree, list))
10953-                self.failUnlessEqual(len(block_hash_tree), 1) # very small tree
10954-                self.failUnlessEqual(IV, "SALT"*4)
10955-                self.failUnlessEqual(len(share_data), len("%07d" % 1))
10956-                self.failUnlessEqual(enc_privkey, self._fn.get_encprivkey())
10957-        d.addCallback(_generated)
10958         return d
10959 
10960hunk ./src/allmydata/test/test_mutable.py 735
10961-    # TODO: when we publish to 20 peers, we should get one share per peer on 10
10962-    # when we publish to 3 peers, we should get either 3 or 4 shares per peer
10963-    # when we publish to zero peers, we should get a NotEnoughSharesError
10964 
10965hunk ./src/allmydata/test/test_mutable.py 736
10966-class PublishMixin:
10967-    def publish_one(self):
10968-        # publish a file and create shares, which can then be manipulated
10969-        # later.
10970-        self.CONTENTS = "New contents go here" * 1000
10971+    def publish_sdmf(self):
10972+        # like publish_one, except that the result is guaranteed to be
10973+        # an SDMF file
10974+        self.CONTENTS = "This is an SDMF file" * 1000
10975+        self.uploadable = MutableData(self.CONTENTS)
10976         self._storage = FakeStorage()
10977         self._nodemaker = make_nodemaker(self._storage)
10978         self._storage_broker = self._nodemaker.storage_broker
10979hunk ./src/allmydata/test/test_mutable.py 744
10980-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
10981+        d = self._nodemaker.create_mutable_file(self.uploadable, version=SDMF_VERSION)
10982         def _created(node):
10983             self._fn = node
10984             self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
10985hunk ./src/allmydata/test/test_mutable.py 751
10986         d.addCallback(_created)
10987         return d
10988 
10989-    def publish_multiple(self):
10990+
10991+    def publish_multiple(self, version=0):
10992         self.CONTENTS = ["Contents 0",
10993                          "Contents 1",
10994                          "Contents 2",
10995hunk ./src/allmydata/test/test_mutable.py 758
10996                          "Contents 3a",
10997                          "Contents 3b"]
10998+        self.uploadables = [MutableData(d) for d in self.CONTENTS]
10999         self._copied_shares = {}
11000         self._storage = FakeStorage()
11001         self._nodemaker = make_nodemaker(self._storage)
11002hunk ./src/allmydata/test/test_mutable.py 762
11003-        d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1
11004+        d = self._nodemaker.create_mutable_file(self.uploadables[0], version=version) # seqnum=1
11005         def _created(node):
11006             self._fn = node
11007             # now create multiple versions of the same file, and accumulate
11008hunk ./src/allmydata/test/test_mutable.py 769
11009             # their shares, so we can mix and match them later.
11010             d = defer.succeed(None)
11011             d.addCallback(self._copy_shares, 0)
11012-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[1])) #s2
11013+            d.addCallback(lambda res: node.overwrite(self.uploadables[1])) #s2
11014             d.addCallback(self._copy_shares, 1)
11015hunk ./src/allmydata/test/test_mutable.py 771
11016-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[2])) #s3
11017+            d.addCallback(lambda res: node.overwrite(self.uploadables[2])) #s3
11018             d.addCallback(self._copy_shares, 2)
11019hunk ./src/allmydata/test/test_mutable.py 773
11020-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[3])) #s4a
11021+            d.addCallback(lambda res: node.overwrite(self.uploadables[3])) #s4a
11022             d.addCallback(self._copy_shares, 3)
11023             # now we replace all the shares with version s3, and upload a new
11024             # version to get s4b.
11025hunk ./src/allmydata/test/test_mutable.py 779
11026             rollback = dict([(i,2) for i in range(10)])
11027             d.addCallback(lambda res: self._set_versions(rollback))
11028-            d.addCallback(lambda res: node.overwrite(self.CONTENTS[4])) #s4b
11029+            d.addCallback(lambda res: node.overwrite(self.uploadables[4])) #s4b
11030             d.addCallback(self._copy_shares, 4)
11031             # we leave the storage in state 4
11032             return d
11033hunk ./src/allmydata/test/test_mutable.py 786
11034         d.addCallback(_created)
11035         return d
11036 
11037+
11038     def _copy_shares(self, ignored, index):
11039         shares = self._storage._peers
11040         # we need a deep copy
11041hunk ./src/allmydata/test/test_mutable.py 810
11042                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
11043 
11044 
11045+
11046+
11047 class Servermap(unittest.TestCase, PublishMixin):
11048     def setUp(self):
11049         return self.publish_one()
11050hunk ./src/allmydata/test/test_mutable.py 816
11051 
11052-    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None):
11053+    def make_servermap(self, mode=MODE_CHECK, fn=None, sb=None,
11054+                       update_range=None):
11055         if fn is None:
11056             fn = self._fn
11057         if sb is None:
11058hunk ./src/allmydata/test/test_mutable.py 823
11059             sb = self._storage_broker
11060         smu = ServermapUpdater(fn, sb, Monitor(),
11061-                               ServerMap(), mode)
11062+                               ServerMap(), mode, update_range=update_range)
11063         d = smu.update()
11064         return d
11065 
11066hunk ./src/allmydata/test/test_mutable.py 889
11067         # create a new file, which is large enough to knock the privkey out
11068         # of the early part of the file
11069         LARGE = "These are Larger contents" * 200 # about 5KB
11070-        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE))
11071+        LARGE_uploadable = MutableData(LARGE)
11072+        d.addCallback(lambda res: self._nodemaker.create_mutable_file(LARGE_uploadable))
11073         def _created(large_fn):
11074             large_fn2 = self._nodemaker.create_from_cap(large_fn.get_uri())
11075             return self.make_servermap(MODE_WRITE, large_fn2)
11076hunk ./src/allmydata/test/test_mutable.py 898
11077         d.addCallback(lambda sm: self.failUnlessOneRecoverable(sm, 10))
11078         return d
11079 
11080+
11081     def test_mark_bad(self):
11082         d = defer.succeed(None)
11083         ms = self.make_servermap
11084hunk ./src/allmydata/test/test_mutable.py 944
11085         self._storage._peers = {} # delete all shares
11086         ms = self.make_servermap
11087         d = defer.succeed(None)
11088-
11089+#
11090         d.addCallback(lambda res: ms(mode=MODE_CHECK))
11091         d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm))
11092 
11093hunk ./src/allmydata/test/test_mutable.py 996
11094         return d
11095 
11096 
11097+    def test_servermapupdater_finds_mdmf_files(self):
11098+        # setUp already published an MDMF file for us. We just need to
11099+        # make sure that when we run the ServermapUpdater, the file is
11100+        # reported to have one recoverable version.
11101+        d = defer.succeed(None)
11102+        d.addCallback(lambda ignored:
11103+            self.publish_mdmf())
11104+        d.addCallback(lambda ignored:
11105+            self.make_servermap(mode=MODE_CHECK))
11106+        # Calling make_servermap also updates the servermap in the mode
11107+        # that we specify, so we just need to see what it says.
11108+        def _check_servermap(sm):
11109+            self.failUnlessEqual(len(sm.recoverable_versions()), 1)
11110+        d.addCallback(_check_servermap)
11111+        return d
11112+
11113+
11114+    def test_fetch_update(self):
11115+        d = defer.succeed(None)
11116+        d.addCallback(lambda ignored:
11117+            self.publish_mdmf())
11118+        d.addCallback(lambda ignored:
11119+            self.make_servermap(mode=MODE_WRITE, update_range=(1, 2)))
11120+        def _check_servermap(sm):
11121+            # 10 shares
11122+            self.failUnlessEqual(len(sm.update_data), 10)
11123+            # one version
11124+            for data in sm.update_data.itervalues():
11125+                self.failUnlessEqual(len(data), 1)
11126+        d.addCallback(_check_servermap)
11127+        return d
11128+
11129+
11130+    def test_servermapupdater_finds_sdmf_files(self):
11131+        d = defer.succeed(None)
11132+        d.addCallback(lambda ignored:
11133+            self.publish_sdmf())
11134+        d.addCallback(lambda ignored:
11135+            self.make_servermap(mode=MODE_CHECK))
11136+        d.addCallback(lambda servermap:
11137+            self.failUnlessEqual(len(servermap.recoverable_versions()), 1))
11138+        return d
11139+
11140 
11141 class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin):
11142     def setUp(self):
11143hunk ./src/allmydata/test/test_mutable.py 1079
11144         if version is None:
11145             version = servermap.best_recoverable_version()
11146         r = Retrieve(self._fn, servermap, version)
11147-        return r.download()
11148+        c = consumer.MemoryConsumer()
11149+        d = r.download(consumer=c)
11150+        d.addCallback(lambda mc: "".join(mc.chunks))
11151+        return d
11152+
11153 
11154     def test_basic(self):
11155         d = self.make_servermap()
11156hunk ./src/allmydata/test/test_mutable.py 1160
11157         return d
11158     test_no_servers_download.timeout = 15
11159 
11160+
11161     def _test_corrupt_all(self, offset, substring,
11162hunk ./src/allmydata/test/test_mutable.py 1162
11163-                          should_succeed=False, corrupt_early=True,
11164-                          failure_checker=None):
11165+                          should_succeed=False,
11166+                          corrupt_early=True,
11167+                          failure_checker=None,
11168+                          fetch_privkey=False):
11169         d = defer.succeed(None)
11170         if corrupt_early:
11171             d.addCallback(corrupt, self._storage, offset)
11172hunk ./src/allmydata/test/test_mutable.py 1182
11173                     self.failUnlessIn(substring, "".join(allproblems))
11174                 return servermap
11175             if should_succeed:
11176-                d1 = self._fn.download_version(servermap, ver)
11177+                d1 = self._fn.download_version(servermap, ver,
11178+                                               fetch_privkey)
11179                 d1.addCallback(lambda new_contents:
11180                                self.failUnlessEqual(new_contents, self.CONTENTS))
11181             else:
11182hunk ./src/allmydata/test/test_mutable.py 1190
11183                 d1 = self.shouldFail(NotEnoughSharesError,
11184                                      "_corrupt_all(offset=%s)" % (offset,),
11185                                      substring,
11186-                                     self._fn.download_version, servermap, ver)
11187+                                     self._fn.download_version, servermap,
11188+                                                                ver,
11189+                                                                fetch_privkey)
11190             if failure_checker:
11191                 d1.addCallback(failure_checker)
11192             d1.addCallback(lambda res: servermap)
11193hunk ./src/allmydata/test/test_mutable.py 1201
11194         return d
11195 
11196     def test_corrupt_all_verbyte(self):
11197-        # when the version byte is not 0, we hit an UnknownVersionError error
11198-        # in unpack_share().
11199+        # when the version byte is not 0 or 1, we hit an UnknownVersionError
11200+        # error in unpack_share().
11201         d = self._test_corrupt_all(0, "UnknownVersionError")
11202         def _check_servermap(servermap):
11203             # and the dump should mention the problems
11204hunk ./src/allmydata/test/test_mutable.py 1208
11205             s = StringIO()
11206             dump = servermap.dump(s).getvalue()
11207-            self.failUnless("10 PROBLEMS" in dump, dump)
11208+            self.failUnless("30 PROBLEMS" in dump, dump)
11209         d.addCallback(_check_servermap)
11210         return d
11211 
11212hunk ./src/allmydata/test/test_mutable.py 1278
11213         return self._test_corrupt_all("enc_privkey", None, should_succeed=True)
11214 
11215 
11216+    def test_corrupt_all_encprivkey_late(self):
11217+        # this should work for the same reason as above, but we corrupt
11218+        # after the servermap update to exercise the error handling
11219+        # code.
11220+        # We need to remove the privkey from the node, or the retrieve
11221+        # process won't know to update it.
11222+        self._fn._privkey = None
11223+        return self._test_corrupt_all("enc_privkey",
11224+                                      None, # this shouldn't fail
11225+                                      should_succeed=True,
11226+                                      corrupt_early=False,
11227+                                      fetch_privkey=True)
11228+
11229+
11230     def test_corrupt_all_seqnum_late(self):
11231         # corrupting the seqnum between mapupdate and retrieve should result
11232         # in NotEnoughSharesError, since each share will look invalid
11233hunk ./src/allmydata/test/test_mutable.py 1298
11234         def _check(res):
11235             f = res[0]
11236             self.failUnless(f.check(NotEnoughSharesError))
11237-            self.failUnless("someone wrote to the data since we read the servermap" in str(f))
11238+            self.failUnless("uncoordinated write" in str(f))
11239         return self._test_corrupt_all(1, "ran out of peers",
11240                                       corrupt_early=False,
11241                                       failure_checker=_check)
11242hunk ./src/allmydata/test/test_mutable.py 1342
11243                             in str(servermap.problems[0]))
11244             ver = servermap.best_recoverable_version()
11245             r = Retrieve(self._fn, servermap, ver)
11246-            return r.download()
11247+            c = consumer.MemoryConsumer()
11248+            return r.download(c)
11249         d.addCallback(_do_retrieve)
11250hunk ./src/allmydata/test/test_mutable.py 1345
11251+        d.addCallback(lambda mc: "".join(mc.chunks))
11252         d.addCallback(lambda new_contents:
11253                       self.failUnlessEqual(new_contents, self.CONTENTS))
11254         return d
11255hunk ./src/allmydata/test/test_mutable.py 1350
11256 
11257-    def test_corrupt_some(self):
11258-        # corrupt the data of first five shares (so the servermap thinks
11259-        # they're good but retrieve marks them as bad), so that the
11260-        # MODE_READ set of 6 will be insufficient, forcing node.download to
11261-        # retry with more servers.
11262-        corrupt(None, self._storage, "share_data", range(5))
11263-        d = self.make_servermap()
11264+
11265+    def _test_corrupt_some(self, offset, mdmf=False):
11266+        if mdmf:
11267+            d = self.publish_mdmf()
11268+        else:
11269+            d = defer.succeed(None)
11270+        d.addCallback(lambda ignored:
11271+            corrupt(None, self._storage, offset, range(5)))
11272+        d.addCallback(lambda ignored:
11273+            self.make_servermap())
11274         def _do_retrieve(servermap):
11275             ver = servermap.best_recoverable_version()
11276             self.failUnless(ver)
11277hunk ./src/allmydata/test/test_mutable.py 1366
11278             return self._fn.download_best_version()
11279         d.addCallback(_do_retrieve)
11280         d.addCallback(lambda new_contents:
11281-                      self.failUnlessEqual(new_contents, self.CONTENTS))
11282+            self.failUnlessEqual(new_contents, self.CONTENTS))
11283         return d
11284 
11285hunk ./src/allmydata/test/test_mutable.py 1369
11286+
11287+    def test_corrupt_some(self):
11288+        # corrupt the data of first five shares (so the servermap thinks
11289+        # they're good but retrieve marks them as bad), so that the
11290+        # MODE_READ set of 6 will be insufficient, forcing node.download to
11291+        # retry with more servers.
11292+        return self._test_corrupt_some("share_data")
11293+
11294+
11295     def test_download_fails(self):
11296hunk ./src/allmydata/test/test_mutable.py 1379
11297-        corrupt(None, self._storage, "signature")
11298-        d = self.shouldFail(UnrecoverableFileError, "test_download_anyway",
11299+        d = corrupt(None, self._storage, "signature")
11300+        d.addCallback(lambda ignored:
11301+            self.shouldFail(UnrecoverableFileError, "test_download_anyway",
11302                             "no recoverable versions",
11303hunk ./src/allmydata/test/test_mutable.py 1383
11304-                            self._fn.download_best_version)
11305+                            self._fn.download_best_version))
11306         return d
11307 
11308 
11309hunk ./src/allmydata/test/test_mutable.py 1387
11310+
11311+    def test_corrupt_mdmf_block_hash_tree(self):
11312+        d = self.publish_mdmf()
11313+        d.addCallback(lambda ignored:
11314+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
11315+                                   "block hash tree failure",
11316+                                   corrupt_early=False,
11317+                                   should_succeed=False))
11318+        return d
11319+
11320+
11321+    def test_corrupt_mdmf_block_hash_tree_late(self):
11322+        d = self.publish_mdmf()
11323+        d.addCallback(lambda ignored:
11324+            self._test_corrupt_all(("block_hash_tree", 12 * 32),
11325+                                   "block hash tree failure",
11326+                                   corrupt_early=True,
11327+                                   should_succeed=False))
11328+        return d
11329+
11330+
11331+    def test_corrupt_mdmf_share_data(self):
11332+        d = self.publish_mdmf()
11333+        d.addCallback(lambda ignored:
11334+            # TODO: Find out what the block size is and corrupt a
11335+            # specific block, rather than just guessing.
11336+            self._test_corrupt_all(("share_data", 12 * 40),
11337+                                    "block hash tree failure",
11338+                                    corrupt_early=True,
11339+                                    should_succeed=False))
11340+        return d
11341+
11342+
11343+    def test_corrupt_some_mdmf(self):
11344+        return self._test_corrupt_some(("share_data", 12 * 40),
11345+                                       mdmf=True)
11346+
11347+
11348 class CheckerMixin:
11349     def check_good(self, r, where):
11350         self.failUnless(r.is_healthy(), where)
11351hunk ./src/allmydata/test/test_mutable.py 1455
11352         d.addCallback(self.check_good, "test_check_good")
11353         return d
11354 
11355+    def test_check_mdmf_good(self):
11356+        d = self.publish_mdmf()
11357+        d.addCallback(lambda ignored:
11358+            self._fn.check(Monitor()))
11359+        d.addCallback(self.check_good, "test_check_mdmf_good")
11360+        return d
11361+
11362     def test_check_no_shares(self):
11363         for shares in self._storage._peers.values():
11364             shares.clear()
11365hunk ./src/allmydata/test/test_mutable.py 1469
11366         d.addCallback(self.check_bad, "test_check_no_shares")
11367         return d
11368 
11369+    def test_check_mdmf_no_shares(self):
11370+        d = self.publish_mdmf()
11371+        def _then(ignored):
11372+            for share in self._storage._peers.values():
11373+                share.clear()
11374+        d.addCallback(_then)
11375+        d.addCallback(lambda ignored:
11376+            self._fn.check(Monitor()))
11377+        d.addCallback(self.check_bad, "test_check_mdmf_no_shares")
11378+        return d
11379+
11380     def test_check_not_enough_shares(self):
11381         for shares in self._storage._peers.values():
11382             for shnum in shares.keys():
11383hunk ./src/allmydata/test/test_mutable.py 1489
11384         d.addCallback(self.check_bad, "test_check_not_enough_shares")
11385         return d
11386 
11387+    def test_check_mdmf_not_enough_shares(self):
11388+        d = self.publish_mdmf()
11389+        def _then(ignored):
11390+            for shares in self._storage._peers.values():
11391+                for shnum in shares.keys():
11392+                    if shnum > 0:
11393+                        del shares[shnum]
11394+        d.addCallback(_then)
11395+        d.addCallback(lambda ignored:
11396+            self._fn.check(Monitor()))
11397+        d.addCallback(self.check_bad, "test_check_mdmf_not_enougH_shares")
11398+        return d
11399+
11400+
11401     def test_check_all_bad_sig(self):
11402hunk ./src/allmydata/test/test_mutable.py 1504
11403-        corrupt(None, self._storage, 1) # bad sig
11404-        d = self._fn.check(Monitor())
11405+        d = corrupt(None, self._storage, 1) # bad sig
11406+        d.addCallback(lambda ignored:
11407+            self._fn.check(Monitor()))
11408         d.addCallback(self.check_bad, "test_check_all_bad_sig")
11409         return d
11410 
11411hunk ./src/allmydata/test/test_mutable.py 1510
11412+    def test_check_mdmf_all_bad_sig(self):
11413+        d = self.publish_mdmf()
11414+        d.addCallback(lambda ignored:
11415+            corrupt(None, self._storage, 1))
11416+        d.addCallback(lambda ignored:
11417+            self._fn.check(Monitor()))
11418+        d.addCallback(self.check_bad, "test_check_mdmf_all_bad_sig")
11419+        return d
11420+
11421     def test_check_all_bad_blocks(self):
11422hunk ./src/allmydata/test/test_mutable.py 1520
11423-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
11424+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
11425         # the Checker won't notice this.. it doesn't look at actual data
11426hunk ./src/allmydata/test/test_mutable.py 1522
11427-        d = self._fn.check(Monitor())
11428+        d.addCallback(lambda ignored:
11429+            self._fn.check(Monitor()))
11430         d.addCallback(self.check_good, "test_check_all_bad_blocks")
11431         return d
11432 
11433hunk ./src/allmydata/test/test_mutable.py 1527
11434+
11435+    def test_check_mdmf_all_bad_blocks(self):
11436+        d = self.publish_mdmf()
11437+        d.addCallback(lambda ignored:
11438+            corrupt(None, self._storage, "share_data"))
11439+        d.addCallback(lambda ignored:
11440+            self._fn.check(Monitor()))
11441+        d.addCallback(self.check_good, "test_check_mdmf_all_bad_blocks")
11442+        return d
11443+
11444     def test_verify_good(self):
11445         d = self._fn.check(Monitor(), verify=True)
11446         d.addCallback(self.check_good, "test_verify_good")
11447hunk ./src/allmydata/test/test_mutable.py 1541
11448         return d
11449+    test_verify_good.timeout = 15
11450 
11451     def test_verify_all_bad_sig(self):
11452hunk ./src/allmydata/test/test_mutable.py 1544
11453-        corrupt(None, self._storage, 1) # bad sig
11454-        d = self._fn.check(Monitor(), verify=True)
11455+        d = corrupt(None, self._storage, 1) # bad sig
11456+        d.addCallback(lambda ignored:
11457+            self._fn.check(Monitor(), verify=True))
11458         d.addCallback(self.check_bad, "test_verify_all_bad_sig")
11459         return d
11460 
11461hunk ./src/allmydata/test/test_mutable.py 1551
11462     def test_verify_one_bad_sig(self):
11463-        corrupt(None, self._storage, 1, [9]) # bad sig
11464-        d = self._fn.check(Monitor(), verify=True)
11465+        d = corrupt(None, self._storage, 1, [9]) # bad sig
11466+        d.addCallback(lambda ignored:
11467+            self._fn.check(Monitor(), verify=True))
11468         d.addCallback(self.check_bad, "test_verify_one_bad_sig")
11469         return d
11470 
11471hunk ./src/allmydata/test/test_mutable.py 1558
11472     def test_verify_one_bad_block(self):
11473-        corrupt(None, self._storage, "share_data", [9]) # bad blocks
11474+        d = corrupt(None, self._storage, "share_data", [9]) # bad blocks
11475         # the Verifier *will* notice this, since it examines every byte
11476hunk ./src/allmydata/test/test_mutable.py 1560
11477-        d = self._fn.check(Monitor(), verify=True)
11478+        d.addCallback(lambda ignored:
11479+            self._fn.check(Monitor(), verify=True))
11480         d.addCallback(self.check_bad, "test_verify_one_bad_block")
11481         d.addCallback(self.check_expected_failure,
11482                       CorruptShareError, "block hash tree failure",
11483hunk ./src/allmydata/test/test_mutable.py 1569
11484         return d
11485 
11486     def test_verify_one_bad_sharehash(self):
11487-        corrupt(None, self._storage, "share_hash_chain", [9], 5)
11488-        d = self._fn.check(Monitor(), verify=True)
11489+        d = corrupt(None, self._storage, "share_hash_chain", [9], 5)
11490+        d.addCallback(lambda ignored:
11491+            self._fn.check(Monitor(), verify=True))
11492         d.addCallback(self.check_bad, "test_verify_one_bad_sharehash")
11493         d.addCallback(self.check_expected_failure,
11494                       CorruptShareError, "corrupt hashes",
11495hunk ./src/allmydata/test/test_mutable.py 1579
11496         return d
11497 
11498     def test_verify_one_bad_encprivkey(self):
11499-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11500-        d = self._fn.check(Monitor(), verify=True)
11501+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11502+        d.addCallback(lambda ignored:
11503+            self._fn.check(Monitor(), verify=True))
11504         d.addCallback(self.check_bad, "test_verify_one_bad_encprivkey")
11505         d.addCallback(self.check_expected_failure,
11506                       CorruptShareError, "invalid privkey",
11507hunk ./src/allmydata/test/test_mutable.py 1589
11508         return d
11509 
11510     def test_verify_one_bad_encprivkey_uncheckable(self):
11511-        corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11512+        d = corrupt(None, self._storage, "enc_privkey", [9]) # bad privkey
11513         readonly_fn = self._fn.get_readonly()
11514         # a read-only node has no way to validate the privkey
11515hunk ./src/allmydata/test/test_mutable.py 1592
11516-        d = readonly_fn.check(Monitor(), verify=True)
11517+        d.addCallback(lambda ignored:
11518+            readonly_fn.check(Monitor(), verify=True))
11519         d.addCallback(self.check_good,
11520                       "test_verify_one_bad_encprivkey_uncheckable")
11521         return d
11522hunk ./src/allmydata/test/test_mutable.py 1598
11523 
11524+
11525+    def test_verify_mdmf_good(self):
11526+        d = self.publish_mdmf()
11527+        d.addCallback(lambda ignored:
11528+            self._fn.check(Monitor(), verify=True))
11529+        d.addCallback(self.check_good, "test_verify_mdmf_good")
11530+        return d
11531+
11532+
11533+    def test_verify_mdmf_one_bad_block(self):
11534+        d = self.publish_mdmf()
11535+        d.addCallback(lambda ignored:
11536+            corrupt(None, self._storage, "share_data", [1]))
11537+        d.addCallback(lambda ignored:
11538+            self._fn.check(Monitor(), verify=True))
11539+        # We should find one bad block here
11540+        d.addCallback(self.check_bad, "test_verify_mdmf_one_bad_block")
11541+        d.addCallback(self.check_expected_failure,
11542+                      CorruptShareError, "block hash tree failure",
11543+                      "test_verify_mdmf_one_bad_block")
11544+        return d
11545+
11546+
11547+    def test_verify_mdmf_bad_encprivkey(self):
11548+        d = self.publish_mdmf()
11549+        d.addCallback(lambda ignored:
11550+            corrupt(None, self._storage, "enc_privkey", [1]))
11551+        d.addCallback(lambda ignored:
11552+            self._fn.check(Monitor(), verify=True))
11553+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
11554+        d.addCallback(self.check_expected_failure,
11555+                      CorruptShareError, "privkey",
11556+                      "test_verify_mdmf_bad_encprivkey")
11557+        return d
11558+
11559+
11560+    def test_verify_mdmf_bad_sig(self):
11561+        d = self.publish_mdmf()
11562+        d.addCallback(lambda ignored:
11563+            corrupt(None, self._storage, 1, [1]))
11564+        d.addCallback(lambda ignored:
11565+            self._fn.check(Monitor(), verify=True))
11566+        d.addCallback(self.check_bad, "test_verify_mdmf_bad_sig")
11567+        return d
11568+
11569+
11570+    def test_verify_mdmf_bad_encprivkey_uncheckable(self):
11571+        d = self.publish_mdmf()
11572+        d.addCallback(lambda ignored:
11573+            corrupt(None, self._storage, "enc_privkey", [1]))
11574+        d.addCallback(lambda ignored:
11575+            self._fn.get_readonly())
11576+        d.addCallback(lambda fn:
11577+            fn.check(Monitor(), verify=True))
11578+        d.addCallback(self.check_good,
11579+                      "test_verify_mdmf_bad_encprivkey_uncheckable")
11580+        return d
11581+
11582+
11583 class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
11584 
11585     def get_shares(self, s):
11586hunk ./src/allmydata/test/test_mutable.py 1722
11587         current_shares = self.old_shares[-1]
11588         self.failUnlessEqual(old_shares, current_shares)
11589 
11590+
11591     def test_unrepairable_0shares(self):
11592         d = self.publish_one()
11593         def _delete_all_shares(ign):
11594hunk ./src/allmydata/test/test_mutable.py 1737
11595         d.addCallback(_check)
11596         return d
11597 
11598+    def test_mdmf_unrepairable_0shares(self):
11599+        d = self.publish_mdmf()
11600+        def _delete_all_shares(ign):
11601+            shares = self._storage._peers
11602+            for peerid in shares:
11603+                shares[peerid] = {}
11604+        d.addCallback(_delete_all_shares)
11605+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11606+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11607+        d.addCallback(lambda crr: self.failIf(crr.get_successful()))
11608+        return d
11609+
11610+
11611     def test_unrepairable_1share(self):
11612         d = self.publish_one()
11613         def _delete_all_shares(ign):
11614hunk ./src/allmydata/test/test_mutable.py 1766
11615         d.addCallback(_check)
11616         return d
11617 
11618+    def test_mdmf_unrepairable_1share(self):
11619+        d = self.publish_mdmf()
11620+        def _delete_all_shares(ign):
11621+            shares = self._storage._peers
11622+            for peerid in shares:
11623+                for shnum in list(shares[peerid]):
11624+                    if shnum > 0:
11625+                        del shares[peerid][shnum]
11626+        d.addCallback(_delete_all_shares)
11627+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11628+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11629+        def _check(crr):
11630+            self.failUnlessEqual(crr.get_successful(), False)
11631+        d.addCallback(_check)
11632+        return d
11633+
11634+    def test_repairable_5shares(self):
11635+        d = self.publish_mdmf()
11636+        def _delete_all_shares(ign):
11637+            shares = self._storage._peers
11638+            for peerid in shares:
11639+                for shnum in list(shares[peerid]):
11640+                    if shnum > 4:
11641+                        del shares[peerid][shnum]
11642+        d.addCallback(_delete_all_shares)
11643+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11644+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11645+        def _check(crr):
11646+            self.failUnlessEqual(crr.get_successful(), True)
11647+        d.addCallback(_check)
11648+        return d
11649+
11650+    def test_mdmf_repairable_5shares(self):
11651+        d = self.publish_mdmf()
11652+        def _delete_some_shares(ign):
11653+            shares = self._storage._peers
11654+            for peerid in shares:
11655+                for shnum in list(shares[peerid]):
11656+                    if shnum > 5:
11657+                        del shares[peerid][shnum]
11658+        d.addCallback(_delete_some_shares)
11659+        d.addCallback(lambda ign: self._fn.check(Monitor()))
11660+        def _check(cr):
11661+            self.failIf(cr.is_healthy())
11662+            self.failUnless(cr.is_recoverable())
11663+            return cr
11664+        d.addCallback(_check)
11665+        d.addCallback(lambda check_results: self._fn.repair(check_results))
11666+        def _check1(crr):
11667+            self.failUnlessEqual(crr.get_successful(), True)
11668+        d.addCallback(_check1)
11669+        return d
11670+
11671+
11672     def test_merge(self):
11673         self.old_shares = []
11674         d = self.publish_multiple()
11675hunk ./src/allmydata/test/test_mutable.py 1934
11676 class MultipleEncodings(unittest.TestCase):
11677     def setUp(self):
11678         self.CONTENTS = "New contents go here"
11679+        self.uploadable = MutableData(self.CONTENTS)
11680         self._storage = FakeStorage()
11681         self._nodemaker = make_nodemaker(self._storage, num_peers=20)
11682         self._storage_broker = self._nodemaker.storage_broker
11683hunk ./src/allmydata/test/test_mutable.py 1938
11684-        d = self._nodemaker.create_mutable_file(self.CONTENTS)
11685+        d = self._nodemaker.create_mutable_file(self.uploadable)
11686         def _created(node):
11687             self._fn = node
11688         d.addCallback(_created)
11689hunk ./src/allmydata/test/test_mutable.py 1944
11690         return d
11691 
11692-    def _encode(self, k, n, data):
11693+    def _encode(self, k, n, data, version=SDMF_VERSION):
11694         # encode 'data' into a peerid->shares dict.
11695 
11696         fn = self._fn
11697hunk ./src/allmydata/test/test_mutable.py 1960
11698         # and set the encoding parameters to something completely different
11699         fn2._required_shares = k
11700         fn2._total_shares = n
11701+        # Normally a servermap update would occur before a publish.
11702+        # Here, it doesn't, so we have to do it ourselves.
11703+        fn2.set_version(version)
11704 
11705         s = self._storage
11706         s._peers = {} # clear existing storage
11707hunk ./src/allmydata/test/test_mutable.py 1967
11708         p2 = Publish(fn2, self._storage_broker, None)
11709-        d = p2.publish(data)
11710+        uploadable = MutableData(data)
11711+        d = p2.publish(uploadable)
11712         def _published(res):
11713             shares = s._peers
11714             s._peers = {}
11715hunk ./src/allmydata/test/test_mutable.py 2235
11716         self.basedir = "mutable/Problems/test_publish_surprise"
11717         self.set_up_grid()
11718         nm = self.g.clients[0].nodemaker
11719-        d = nm.create_mutable_file("contents 1")
11720+        d = nm.create_mutable_file(MutableData("contents 1"))
11721         def _created(n):
11722             d = defer.succeed(None)
11723             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
11724hunk ./src/allmydata/test/test_mutable.py 2245
11725             d.addCallback(_got_smap1)
11726             # then modify the file, leaving the old map untouched
11727             d.addCallback(lambda res: log.msg("starting winning write"))
11728-            d.addCallback(lambda res: n.overwrite("contents 2"))
11729+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11730             # now attempt to modify the file with the old servermap. This
11731             # will look just like an uncoordinated write, in which every
11732             # single share got updated between our mapupdate and our publish
11733hunk ./src/allmydata/test/test_mutable.py 2254
11734                           self.shouldFail(UncoordinatedWriteError,
11735                                           "test_publish_surprise", None,
11736                                           n.upload,
11737-                                          "contents 2a", self.old_map))
11738+                                          MutableData("contents 2a"), self.old_map))
11739             return d
11740         d.addCallback(_created)
11741         return d
11742hunk ./src/allmydata/test/test_mutable.py 2263
11743         self.basedir = "mutable/Problems/test_retrieve_surprise"
11744         self.set_up_grid()
11745         nm = self.g.clients[0].nodemaker
11746-        d = nm.create_mutable_file("contents 1")
11747+        d = nm.create_mutable_file(MutableData("contents 1"))
11748         def _created(n):
11749             d = defer.succeed(None)
11750             d.addCallback(lambda res: n.get_servermap(MODE_READ))
11751hunk ./src/allmydata/test/test_mutable.py 2273
11752             d.addCallback(_got_smap1)
11753             # then modify the file, leaving the old map untouched
11754             d.addCallback(lambda res: log.msg("starting winning write"))
11755-            d.addCallback(lambda res: n.overwrite("contents 2"))
11756+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11757             # now attempt to retrieve the old version with the old servermap.
11758             # This will look like someone has changed the file since we
11759             # updated the servermap.
11760hunk ./src/allmydata/test/test_mutable.py 2282
11761             d.addCallback(lambda res:
11762                           self.shouldFail(NotEnoughSharesError,
11763                                           "test_retrieve_surprise",
11764-                                          "ran out of peers: have 0 shares (k=3)",
11765+                                          "ran out of peers: have 0 of 1",
11766                                           n.download_version,
11767                                           self.old_map,
11768                                           self.old_map.best_recoverable_version(),
11769hunk ./src/allmydata/test/test_mutable.py 2291
11770         d.addCallback(_created)
11771         return d
11772 
11773+
11774     def test_unexpected_shares(self):
11775         # upload the file, take a servermap, shut down one of the servers,
11776         # upload it again (causing shares to appear on a new server), then
11777hunk ./src/allmydata/test/test_mutable.py 2301
11778         self.basedir = "mutable/Problems/test_unexpected_shares"
11779         self.set_up_grid()
11780         nm = self.g.clients[0].nodemaker
11781-        d = nm.create_mutable_file("contents 1")
11782+        d = nm.create_mutable_file(MutableData("contents 1"))
11783         def _created(n):
11784             d = defer.succeed(None)
11785             d.addCallback(lambda res: n.get_servermap(MODE_WRITE))
11786hunk ./src/allmydata/test/test_mutable.py 2313
11787                 self.g.remove_server(peer0)
11788                 # then modify the file, leaving the old map untouched
11789                 log.msg("starting winning write")
11790-                return n.overwrite("contents 2")
11791+                return n.overwrite(MutableData("contents 2"))
11792             d.addCallback(_got_smap1)
11793             # now attempt to modify the file with the old servermap. This
11794             # will look just like an uncoordinated write, in which every
11795hunk ./src/allmydata/test/test_mutable.py 2323
11796                           self.shouldFail(UncoordinatedWriteError,
11797                                           "test_surprise", None,
11798                                           n.upload,
11799-                                          "contents 2a", self.old_map))
11800+                                          MutableData("contents 2a"), self.old_map))
11801             return d
11802         d.addCallback(_created)
11803         return d
11804hunk ./src/allmydata/test/test_mutable.py 2327
11805+    test_unexpected_shares.timeout = 15
11806 
11807     def test_bad_server(self):
11808         # Break one server, then create the file: the initial publish should
11809hunk ./src/allmydata/test/test_mutable.py 2361
11810         d.addCallback(_break_peer0)
11811         # now "create" the file, using the pre-established key, and let the
11812         # initial publish finally happen
11813-        d.addCallback(lambda res: nm.create_mutable_file("contents 1"))
11814+        d.addCallback(lambda res: nm.create_mutable_file(MutableData("contents 1")))
11815         # that ought to work
11816         def _got_node(n):
11817             d = n.download_best_version()
11818hunk ./src/allmydata/test/test_mutable.py 2370
11819             def _break_peer1(res):
11820                 self.g.break_server(self.server1.get_serverid())
11821             d.addCallback(_break_peer1)
11822-            d.addCallback(lambda res: n.overwrite("contents 2"))
11823+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11824             # that ought to work too
11825             d.addCallback(lambda res: n.download_best_version())
11826             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
11827hunk ./src/allmydata/test/test_mutable.py 2402
11828         peerids = [s.get_serverid() for s in sb.get_connected_servers()]
11829         self.g.break_server(peerids[0])
11830 
11831-        d = nm.create_mutable_file("contents 1")
11832+        d = nm.create_mutable_file(MutableData("contents 1"))
11833         def _created(n):
11834             d = n.download_best_version()
11835             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 1"))
11836hunk ./src/allmydata/test/test_mutable.py 2410
11837             def _break_second_server(res):
11838                 self.g.break_server(peerids[1])
11839             d.addCallback(_break_second_server)
11840-            d.addCallback(lambda res: n.overwrite("contents 2"))
11841+            d.addCallback(lambda res: n.overwrite(MutableData("contents 2")))
11842             # that ought to work too
11843             d.addCallback(lambda res: n.download_best_version())
11844             d.addCallback(lambda res: self.failUnlessEqual(res, "contents 2"))
11845hunk ./src/allmydata/test/test_mutable.py 2429
11846         d = self.shouldFail(NotEnoughServersError,
11847                             "test_publish_all_servers_bad",
11848                             "Ran out of non-bad servers",
11849-                            nm.create_mutable_file, "contents")
11850+                            nm.create_mutable_file, MutableData("contents"))
11851         return d
11852 
11853     def test_publish_no_servers(self):
11854hunk ./src/allmydata/test/test_mutable.py 2441
11855         d = self.shouldFail(NotEnoughServersError,
11856                             "test_publish_no_servers",
11857                             "Ran out of non-bad servers",
11858-                            nm.create_mutable_file, "contents")
11859+                            nm.create_mutable_file, MutableData("contents"))
11860         return d
11861     test_publish_no_servers.timeout = 30
11862 
11863hunk ./src/allmydata/test/test_mutable.py 2459
11864         # we need some contents that are large enough to push the privkey out
11865         # of the early part of the file
11866         LARGE = "These are Larger contents" * 2000 # about 50KB
11867-        d = nm.create_mutable_file(LARGE)
11868+        LARGE_uploadable = MutableData(LARGE)
11869+        d = nm.create_mutable_file(LARGE_uploadable)
11870         def _created(n):
11871             self.uri = n.get_uri()
11872             self.n2 = nm.create_from_cap(self.uri)
11873hunk ./src/allmydata/test/test_mutable.py 2495
11874         self.basedir = "mutable/Problems/test_privkey_query_missing"
11875         self.set_up_grid(num_servers=20)
11876         nm = self.g.clients[0].nodemaker
11877-        LARGE = "These are Larger contents" * 2000 # about 50KB
11878+        LARGE = "These are Larger contents" * 2000 # about 50KiB
11879+        LARGE_uploadable = MutableData(LARGE)
11880         nm._node_cache = DevNullDictionary() # disable the nodecache
11881 
11882hunk ./src/allmydata/test/test_mutable.py 2499
11883-        d = nm.create_mutable_file(LARGE)
11884+        d = nm.create_mutable_file(LARGE_uploadable)
11885         def _created(n):
11886             self.uri = n.get_uri()
11887             self.n2 = nm.create_from_cap(self.uri)
11888hunk ./src/allmydata/test/test_mutable.py 2509
11889         d.addCallback(_created)
11890         d.addCallback(lambda res: self.n2.get_servermap(MODE_WRITE))
11891         return d
11892+
11893+
11894+    def test_block_and_hash_query_error(self):
11895+        # This tests for what happens when a query to a remote server
11896+        # fails in either the hash validation step or the block getting
11897+        # step (because of batching, this is the same actual query).
11898+        # We need to have the storage server persist up until the point
11899+        # that its prefix is validated, then suddenly die. This
11900+        # exercises some exception handling code in Retrieve.
11901+        self.basedir = "mutable/Problems/test_block_and_hash_query_error"
11902+        self.set_up_grid(num_servers=20)
11903+        nm = self.g.clients[0].nodemaker
11904+        CONTENTS = "contents" * 2000
11905+        CONTENTS_uploadable = MutableData(CONTENTS)
11906+        d = nm.create_mutable_file(CONTENTS_uploadable)
11907+        def _created(node):
11908+            self._node = node
11909+        d.addCallback(_created)
11910+        d.addCallback(lambda ignored:
11911+            self._node.get_servermap(MODE_READ))
11912+        def _then(servermap):
11913+            # we have our servermap. Now we set up the servers like the
11914+            # tests above -- the first one that gets a read call should
11915+            # start throwing errors, but only after returning its prefix
11916+            # for validation. Since we'll download without fetching the
11917+            # private key, the next query to the remote server will be
11918+            # for either a block and salt or for hashes, either of which
11919+            # will exercise the error handling code.
11920+            killer = FirstServerGetsKilled()
11921+            for (serverid, ss) in nm.storage_broker.get_all_servers():
11922+                ss.post_call_notifier = killer.notify
11923+            ver = servermap.best_recoverable_version()
11924+            assert ver
11925+            return self._node.download_version(servermap, ver)
11926+        d.addCallback(_then)
11927+        d.addCallback(lambda data:
11928+            self.failUnlessEqual(data, CONTENTS))
11929+        return d
11930+
11931+
11932+class FileHandle(unittest.TestCase):
11933+    def setUp(self):
11934+        self.test_data = "Test Data" * 50000
11935+        self.sio = StringIO(self.test_data)
11936+        self.uploadable = MutableFileHandle(self.sio)
11937+
11938+
11939+    def test_filehandle_read(self):
11940+        self.basedir = "mutable/FileHandle/test_filehandle_read"
11941+        chunk_size = 10
11942+        for i in xrange(0, len(self.test_data), chunk_size):
11943+            data = self.uploadable.read(chunk_size)
11944+            data = "".join(data)
11945+            start = i
11946+            end = i + chunk_size
11947+            self.failUnlessEqual(data, self.test_data[start:end])
11948+
11949+
11950+    def test_filehandle_get_size(self):
11951+        self.basedir = "mutable/FileHandle/test_filehandle_get_size"
11952+        actual_size = len(self.test_data)
11953+        size = self.uploadable.get_size()
11954+        self.failUnlessEqual(size, actual_size)
11955+
11956+
11957+    def test_filehandle_get_size_out_of_order(self):
11958+        # We should be able to call get_size whenever we want without
11959+        # disturbing the location of the seek pointer.
11960+        chunk_size = 100
11961+        data = self.uploadable.read(chunk_size)
11962+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
11963+
11964+        # Now get the size.
11965+        size = self.uploadable.get_size()
11966+        self.failUnlessEqual(size, len(self.test_data))
11967+
11968+        # Now get more data. We should be right where we left off.
11969+        more_data = self.uploadable.read(chunk_size)
11970+        start = chunk_size
11971+        end = chunk_size * 2
11972+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
11973+
11974+
11975+    def test_filehandle_file(self):
11976+        # Make sure that the MutableFileHandle works on a file as well
11977+        # as a StringIO object, since in some cases it will be asked to
11978+        # deal with files.
11979+        self.basedir = self.mktemp()
11980+        # necessary? What am I doing wrong here?
11981+        os.mkdir(self.basedir)
11982+        f_path = os.path.join(self.basedir, "test_file")
11983+        f = open(f_path, "w")
11984+        f.write(self.test_data)
11985+        f.close()
11986+        f = open(f_path, "r")
11987+
11988+        uploadable = MutableFileHandle(f)
11989+
11990+        data = uploadable.read(len(self.test_data))
11991+        self.failUnlessEqual("".join(data), self.test_data)
11992+        size = uploadable.get_size()
11993+        self.failUnlessEqual(size, len(self.test_data))
11994+
11995+
11996+    def test_close(self):
11997+        # Make sure that the MutableFileHandle closes its handle when
11998+        # told to do so.
11999+        self.uploadable.close()
12000+        self.failUnless(self.sio.closed)
12001+
12002+
12003+class DataHandle(unittest.TestCase):
12004+    def setUp(self):
12005+        self.test_data = "Test Data" * 50000
12006+        self.uploadable = MutableData(self.test_data)
12007+
12008+
12009+    def test_datahandle_read(self):
12010+        chunk_size = 10
12011+        for i in xrange(0, len(self.test_data), chunk_size):
12012+            data = self.uploadable.read(chunk_size)
12013+            data = "".join(data)
12014+            start = i
12015+            end = i + chunk_size
12016+            self.failUnlessEqual(data, self.test_data[start:end])
12017+
12018+
12019+    def test_datahandle_get_size(self):
12020+        actual_size = len(self.test_data)
12021+        size = self.uploadable.get_size()
12022+        self.failUnlessEqual(size, actual_size)
12023+
12024+
12025+    def test_datahandle_get_size_out_of_order(self):
12026+        # We should be able to call get_size whenever we want without
12027+        # disturbing the location of the seek pointer.
12028+        chunk_size = 100
12029+        data = self.uploadable.read(chunk_size)
12030+        self.failUnlessEqual("".join(data), self.test_data[:chunk_size])
12031+
12032+        # Now get the size.
12033+        size = self.uploadable.get_size()
12034+        self.failUnlessEqual(size, len(self.test_data))
12035+
12036+        # Now get more data. We should be right where we left off.
12037+        more_data = self.uploadable.read(chunk_size)
12038+        start = chunk_size
12039+        end = chunk_size * 2
12040+        self.failUnlessEqual("".join(more_data), self.test_data[start:end])
12041+
12042+
12043+class Version(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin, \
12044+              PublishMixin):
12045+    def setUp(self):
12046+        GridTestMixin.setUp(self)
12047+        self.basedir = self.mktemp()
12048+        self.set_up_grid()
12049+        self.c = self.g.clients[0]
12050+        self.nm = self.c.nodemaker
12051+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12052+        self.small_data = "test data" * 10 # about 90 B; SDMF
12053+        return self.do_upload()
12054+
12055+
12056+    def do_upload(self):
12057+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12058+                                         version=MDMF_VERSION)
12059+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12060+        dl = gatherResults([d1, d2])
12061+        def _then((n1, n2)):
12062+            assert isinstance(n1, MutableFileNode)
12063+            assert isinstance(n2, MutableFileNode)
12064+
12065+            self.mdmf_node = n1
12066+            self.sdmf_node = n2
12067+        dl.addCallback(_then)
12068+        return dl
12069+
12070+
12071+    def test_get_readonly_mutable_version(self):
12072+        # Attempting to get a mutable version of a mutable file from a
12073+        # filenode initialized with a readcap should return a readonly
12074+        # version of that same node.
12075+        ro = self.mdmf_node.get_readonly()
12076+        d = ro.get_best_mutable_version()
12077+        d.addCallback(lambda version:
12078+            self.failUnless(version.is_readonly()))
12079+        d.addCallback(lambda ignored:
12080+            self.sdmf_node.get_readonly())
12081+        d.addCallback(lambda version:
12082+            self.failUnless(version.is_readonly()))
12083+        return d
12084+
12085+
12086+    def test_get_sequence_number(self):
12087+        d = self.mdmf_node.get_best_readable_version()
12088+        d.addCallback(lambda bv:
12089+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12090+        d.addCallback(lambda ignored:
12091+            self.sdmf_node.get_best_readable_version())
12092+        d.addCallback(lambda bv:
12093+            self.failUnlessEqual(bv.get_sequence_number(), 1))
12094+        # Now update. The sequence number in both cases should be 1 in
12095+        # both cases.
12096+        def _do_update(ignored):
12097+            new_data = MutableData("foo bar baz" * 100000)
12098+            new_small_data = MutableData("foo bar baz" * 10)
12099+            d1 = self.mdmf_node.overwrite(new_data)
12100+            d2 = self.sdmf_node.overwrite(new_small_data)
12101+            dl = gatherResults([d1, d2])
12102+            return dl
12103+        d.addCallback(_do_update)
12104+        d.addCallback(lambda ignored:
12105+            self.mdmf_node.get_best_readable_version())
12106+        d.addCallback(lambda bv:
12107+            self.failUnlessEqual(bv.get_sequence_number(), 2))
12108+        d.addCallback(lambda ignored:
12109+            self.sdmf_node.get_best_readable_version())
12110+        d.addCallback(lambda bv:
12111+            self.failUnlessEqual(bv.get_sequence_number(), 2))
12112+        return d
12113+
12114+
12115+    def test_get_writekey(self):
12116+        d = self.mdmf_node.get_best_mutable_version()
12117+        d.addCallback(lambda bv:
12118+            self.failUnlessEqual(bv.get_writekey(),
12119+                                 self.mdmf_node.get_writekey()))
12120+        d.addCallback(lambda ignored:
12121+            self.sdmf_node.get_best_mutable_version())
12122+        d.addCallback(lambda bv:
12123+            self.failUnlessEqual(bv.get_writekey(),
12124+                                 self.sdmf_node.get_writekey()))
12125+        return d
12126+
12127+
12128+    def test_get_storage_index(self):
12129+        d = self.mdmf_node.get_best_mutable_version()
12130+        d.addCallback(lambda bv:
12131+            self.failUnlessEqual(bv.get_storage_index(),
12132+                                 self.mdmf_node.get_storage_index()))
12133+        d.addCallback(lambda ignored:
12134+            self.sdmf_node.get_best_mutable_version())
12135+        d.addCallback(lambda bv:
12136+            self.failUnlessEqual(bv.get_storage_index(),
12137+                                 self.sdmf_node.get_storage_index()))
12138+        return d
12139+
12140+
12141+    def test_get_readonly_version(self):
12142+        d = self.mdmf_node.get_best_readable_version()
12143+        d.addCallback(lambda bv:
12144+            self.failUnless(bv.is_readonly()))
12145+        d.addCallback(lambda ignored:
12146+            self.sdmf_node.get_best_readable_version())
12147+        d.addCallback(lambda bv:
12148+            self.failUnless(bv.is_readonly()))
12149+        return d
12150+
12151+
12152+    def test_get_mutable_version(self):
12153+        d = self.mdmf_node.get_best_mutable_version()
12154+        d.addCallback(lambda bv:
12155+            self.failIf(bv.is_readonly()))
12156+        d.addCallback(lambda ignored:
12157+            self.sdmf_node.get_best_mutable_version())
12158+        d.addCallback(lambda bv:
12159+            self.failIf(bv.is_readonly()))
12160+        return d
12161+
12162+
12163+    def test_toplevel_overwrite(self):
12164+        new_data = MutableData("foo bar baz" * 100000)
12165+        new_small_data = MutableData("foo bar baz" * 10)
12166+        d = self.mdmf_node.overwrite(new_data)
12167+        d.addCallback(lambda ignored:
12168+            self.mdmf_node.download_best_version())
12169+        d.addCallback(lambda data:
12170+            self.failUnlessEqual(data, "foo bar baz" * 100000))
12171+        d.addCallback(lambda ignored:
12172+            self.sdmf_node.overwrite(new_small_data))
12173+        d.addCallback(lambda ignored:
12174+            self.sdmf_node.download_best_version())
12175+        d.addCallback(lambda data:
12176+            self.failUnlessEqual(data, "foo bar baz" * 10))
12177+        return d
12178+
12179+
12180+    def test_toplevel_modify(self):
12181+        def modifier(old_contents, servermap, first_time):
12182+            return old_contents + "modified"
12183+        d = self.mdmf_node.modify(modifier)
12184+        d.addCallback(lambda ignored:
12185+            self.mdmf_node.download_best_version())
12186+        d.addCallback(lambda data:
12187+            self.failUnlessIn("modified", data))
12188+        d.addCallback(lambda ignored:
12189+            self.sdmf_node.modify(modifier))
12190+        d.addCallback(lambda ignored:
12191+            self.sdmf_node.download_best_version())
12192+        d.addCallback(lambda data:
12193+            self.failUnlessIn("modified", data))
12194+        return d
12195+
12196+
12197+    def test_version_modify(self):
12198+        # TODO: When we can publish multiple versions, alter this test
12199+        # to modify a version other than the best usable version, then
12200+        # test to see that the best recoverable version is that.
12201+        def modifier(old_contents, servermap, first_time):
12202+            return old_contents + "modified"
12203+        d = self.mdmf_node.modify(modifier)
12204+        d.addCallback(lambda ignored:
12205+            self.mdmf_node.download_best_version())
12206+        d.addCallback(lambda data:
12207+            self.failUnlessIn("modified", data))
12208+        d.addCallback(lambda ignored:
12209+            self.sdmf_node.modify(modifier))
12210+        d.addCallback(lambda ignored:
12211+            self.sdmf_node.download_best_version())
12212+        d.addCallback(lambda data:
12213+            self.failUnlessIn("modified", data))
12214+        return d
12215+
12216+
12217+    def test_download_version(self):
12218+        d = self.publish_multiple()
12219+        # We want to have two recoverable versions on the grid.
12220+        d.addCallback(lambda res:
12221+                      self._set_versions({0:0,2:0,4:0,6:0,8:0,
12222+                                          1:1,3:1,5:1,7:1,9:1}))
12223+        # Now try to download each version. We should get the plaintext
12224+        # associated with that version.
12225+        d.addCallback(lambda ignored:
12226+            self._fn.get_servermap(mode=MODE_READ))
12227+        def _got_servermap(smap):
12228+            versions = smap.recoverable_versions()
12229+            assert len(versions) == 2
12230+
12231+            self.servermap = smap
12232+            self.version1, self.version2 = versions
12233+            assert self.version1 != self.version2
12234+
12235+            self.version1_seqnum = self.version1[0]
12236+            self.version2_seqnum = self.version2[0]
12237+            self.version1_index = self.version1_seqnum - 1
12238+            self.version2_index = self.version2_seqnum - 1
12239+
12240+        d.addCallback(_got_servermap)
12241+        d.addCallback(lambda ignored:
12242+            self._fn.download_version(self.servermap, self.version1))
12243+        d.addCallback(lambda results:
12244+            self.failUnlessEqual(self.CONTENTS[self.version1_index],
12245+                                 results))
12246+        d.addCallback(lambda ignored:
12247+            self._fn.download_version(self.servermap, self.version2))
12248+        d.addCallback(lambda results:
12249+            self.failUnlessEqual(self.CONTENTS[self.version2_index],
12250+                                 results))
12251+        return d
12252+
12253+
12254+    def test_download_nonexistent_version(self):
12255+        d = self.mdmf_node.get_servermap(mode=MODE_WRITE)
12256+        def _set_servermap(servermap):
12257+            self.servermap = servermap
12258+        d.addCallback(_set_servermap)
12259+        d.addCallback(lambda ignored:
12260+           self.shouldFail(UnrecoverableFileError, "nonexistent version",
12261+                           None,
12262+                           self.mdmf_node.download_version, self.servermap,
12263+                           "not a version"))
12264+        return d
12265+
12266+
12267+    def test_partial_read(self):
12268+        # read only a few bytes at a time, and see that the results are
12269+        # what we expect.
12270+        d = self.mdmf_node.get_best_readable_version()
12271+        def _read_data(version):
12272+            c = consumer.MemoryConsumer()
12273+            d2 = defer.succeed(None)
12274+            for i in xrange(0, len(self.data), 10000):
12275+                d2.addCallback(lambda ignored, i=i: version.read(c, i, 10000))
12276+            d2.addCallback(lambda ignored:
12277+                self.failUnlessEqual(self.data, "".join(c.chunks)))
12278+            return d2
12279+        d.addCallback(_read_data)
12280+        return d
12281+
12282+
12283+    def test_read(self):
12284+        d = self.mdmf_node.get_best_readable_version()
12285+        def _read_data(version):
12286+            c = consumer.MemoryConsumer()
12287+            d2 = defer.succeed(None)
12288+            d2.addCallback(lambda ignored: version.read(c))
12289+            d2.addCallback(lambda ignored:
12290+                self.failUnlessEqual("".join(c.chunks), self.data))
12291+            return d2
12292+        d.addCallback(_read_data)
12293+        return d
12294+
12295+
12296+    def test_download_best_version(self):
12297+        d = self.mdmf_node.download_best_version()
12298+        d.addCallback(lambda data:
12299+            self.failUnlessEqual(data, self.data))
12300+        d.addCallback(lambda ignored:
12301+            self.sdmf_node.download_best_version())
12302+        d.addCallback(lambda data:
12303+            self.failUnlessEqual(data, self.small_data))
12304+        return d
12305+
12306+
12307+class Update(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
12308+    def setUp(self):
12309+        GridTestMixin.setUp(self)
12310+        self.basedir = self.mktemp()
12311+        self.set_up_grid()
12312+        self.c = self.g.clients[0]
12313+        self.nm = self.c.nodemaker
12314+        self.data = "test data" * 100000 # about 900 KiB; MDMF
12315+        self.small_data = "test data" * 10 # about 90 B; SDMF
12316+        return self.do_upload()
12317+
12318+
12319+    def do_upload(self):
12320+        d1 = self.nm.create_mutable_file(MutableData(self.data),
12321+                                         version=MDMF_VERSION)
12322+        d2 = self.nm.create_mutable_file(MutableData(self.small_data))
12323+        dl = gatherResults([d1, d2])
12324+        def _then((n1, n2)):
12325+            assert isinstance(n1, MutableFileNode)
12326+            assert isinstance(n2, MutableFileNode)
12327+
12328+            self.mdmf_node = n1
12329+            self.sdmf_node = n2
12330+        dl.addCallback(_then)
12331+        return dl
12332+
12333+
12334+    def test_append(self):
12335+        # We should be able to append data to the middle of a mutable
12336+        # file and get what we expect.
12337+        new_data = self.data + "appended"
12338+        d = self.mdmf_node.get_best_mutable_version()
12339+        d.addCallback(lambda mv:
12340+            mv.update(MutableData("appended"), len(self.data)))
12341+        d.addCallback(lambda ignored:
12342+            self.mdmf_node.download_best_version())
12343+        d.addCallback(lambda results:
12344+            self.failUnlessEqual(results, new_data))
12345+        return d
12346+    test_append.timeout = 15
12347+
12348+
12349+    def test_replace(self):
12350+        # We should be able to replace data in the middle of a mutable
12351+        # file and get what we expect back.
12352+        new_data = self.data[:100]
12353+        new_data += "appended"
12354+        new_data += self.data[108:]
12355+        d = self.mdmf_node.get_best_mutable_version()
12356+        d.addCallback(lambda mv:
12357+            mv.update(MutableData("appended"), 100))
12358+        d.addCallback(lambda ignored:
12359+            self.mdmf_node.download_best_version())
12360+        d.addCallback(lambda results:
12361+            self.failUnlessEqual(results, new_data))
12362+        return d
12363+
12364+
12365+    def test_replace_and_extend(self):
12366+        # We should be able to replace data in the middle of a mutable
12367+        # file and extend that mutable file and get what we expect.
12368+        new_data = self.data[:100]
12369+        new_data += "modified " * 100000
12370+        d = self.mdmf_node.get_best_mutable_version()
12371+        d.addCallback(lambda mv:
12372+            mv.update(MutableData("modified " * 100000), 100))
12373+        d.addCallback(lambda ignored:
12374+            self.mdmf_node.download_best_version())
12375+        d.addCallback(lambda results:
12376+            self.failUnlessEqual(results, new_data))
12377+        return d
12378+
12379+
12380+    def test_append_power_of_two(self):
12381+        # If we attempt to extend a mutable file so that its segment
12382+        # count crosses a power-of-two boundary, the update operation
12383+        # should know how to reencode the file.
12384+
12385+        # Note that the data populating self.mdmf_node is about 900 KiB
12386+        # long -- this is 7 segments in the default segment size. So we
12387+        # need to add 2 segments worth of data to push it over a
12388+        # power-of-two boundary.
12389+        segment = "a" * DEFAULT_MAX_SEGMENT_SIZE
12390+        new_data = self.data + (segment * 2)
12391+        d = self.mdmf_node.get_best_mutable_version()
12392+        d.addCallback(lambda mv:
12393+            mv.update(MutableData(segment * 2), len(self.data)))
12394+        d.addCallback(lambda ignored:
12395+            self.mdmf_node.download_best_version())
12396+        d.addCallback(lambda results:
12397+            self.failUnlessEqual(results, new_data))
12398+        return d
12399+    test_append_power_of_two.timeout = 15
12400+
12401+
12402+    def test_update_sdmf(self):
12403+        # Running update on a single-segment file should still work.
12404+        new_data = self.small_data + "appended"
12405+        d = self.sdmf_node.get_best_mutable_version()
12406+        d.addCallback(lambda mv:
12407+            mv.update(MutableData("appended"), len(self.small_data)))
12408+        d.addCallback(lambda ignored:
12409+            self.sdmf_node.download_best_version())
12410+        d.addCallback(lambda results:
12411+            self.failUnlessEqual(results, new_data))
12412+        return d
12413+
12414+    def test_replace_in_last_segment(self):
12415+        # The wrapper should know how to handle the tail segment
12416+        # appropriately.
12417+        replace_offset = len(self.data) - 100
12418+        new_data = self.data[:replace_offset] + "replaced"
12419+        rest_offset = replace_offset + len("replaced")
12420+        new_data += self.data[rest_offset:]
12421+        d = self.mdmf_node.get_best_mutable_version()
12422+        d.addCallback(lambda mv:
12423+            mv.update(MutableData("replaced"), replace_offset))
12424+        d.addCallback(lambda ignored:
12425+            self.mdmf_node.download_best_version())
12426+        d.addCallback(lambda results:
12427+            self.failUnlessEqual(results, new_data))
12428+        return d
12429+
12430+
12431+    def test_multiple_segment_replace(self):
12432+        replace_offset = 2 * DEFAULT_MAX_SEGMENT_SIZE
12433+        new_data = self.data[:replace_offset]
12434+        new_segment = "a" * DEFAULT_MAX_SEGMENT_SIZE
12435+        new_data += 2 * new_segment
12436+        new_data += "replaced"
12437+        rest_offset = len(new_data)
12438+        new_data += self.data[rest_offset:]
12439+        d = self.mdmf_node.get_best_mutable_version()
12440+        d.addCallback(lambda mv:
12441+            mv.update(MutableData((2 * new_segment) + "replaced"),
12442+                      replace_offset))
12443+        d.addCallback(lambda ignored:
12444+            self.mdmf_node.download_best_version())
12445+        d.addCallback(lambda results:
12446+            self.failUnlessEqual(results, new_data))
12447+        return d
12448hunk ./src/allmydata/test/test_sftp.py 32
12449 
12450 from allmydata.util.consumer import download_to_data
12451 from allmydata.immutable import upload
12452+from allmydata.mutable import publish
12453 from allmydata.test.no_network import GridTestMixin
12454 from allmydata.test.common import ShouldFailMixin
12455 from allmydata.test.common_util import ReallyEqualMixin
12456hunk ./src/allmydata/test/test_sftp.py 84
12457         return d
12458 
12459     def _set_up_tree(self):
12460-        d = self.client.create_mutable_file("mutable file contents")
12461+        u = publish.MutableData("mutable file contents")
12462+        d = self.client.create_mutable_file(u)
12463         d.addCallback(lambda node: self.root.set_node(u"mutable", node))
12464         def _created_mutable(n):
12465             self.mutable = n
12466hunk ./src/allmydata/test/test_sftp.py 1334
12467         d.addCallback(lambda ign: self.failUnlessEqual(sftpd.all_heisenfiles, {}))
12468         d.addCallback(lambda ign: self.failUnlessEqual(self.handler._heisenfiles, {}))
12469         return d
12470+    test_makeDirectory.timeout = 15
12471 
12472     def test_execCommand_and_openShell(self):
12473         class FakeProtocol:
12474hunk ./src/allmydata/test/test_storage.py 27
12475                                      LayoutInvalid, MDMFSIGNABLEHEADER, \
12476                                      SIGNED_PREFIX, MDMFHEADER, \
12477                                      MDMFOFFSETS, SDMFSlotWriteProxy
12478-from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \
12479-                                 SDMF_VERSION
12480+from allmydata.interfaces import BadWriteEnablerError
12481 from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
12482 from allmydata.test.common_web import WebRenderingMixin
12483 from allmydata.web.storage import StorageStatus, remove_prefix
12484hunk ./src/allmydata/test/test_system.py 26
12485 from allmydata.monitor import Monitor
12486 from allmydata.mutable.common import NotWriteableError
12487 from allmydata.mutable import layout as mutable_layout
12488+from allmydata.mutable.publish import MutableData
12489 from foolscap.api import DeadReferenceError
12490 from twisted.python.failure import Failure
12491 from twisted.web.client import getPage
12492hunk ./src/allmydata/test/test_system.py 467
12493     def test_mutable(self):
12494         self.basedir = "system/SystemTest/test_mutable"
12495         DATA = "initial contents go here."  # 25 bytes % 3 != 0
12496+        DATA_uploadable = MutableData(DATA)
12497         NEWDATA = "new contents yay"
12498hunk ./src/allmydata/test/test_system.py 469
12499+        NEWDATA_uploadable = MutableData(NEWDATA)
12500         NEWERDATA = "this is getting old"
12501hunk ./src/allmydata/test/test_system.py 471
12502+        NEWERDATA_uploadable = MutableData(NEWERDATA)
12503 
12504         d = self.set_up_nodes(use_key_generator=True)
12505 
12506hunk ./src/allmydata/test/test_system.py 478
12507         def _create_mutable(res):
12508             c = self.clients[0]
12509             log.msg("starting create_mutable_file")
12510-            d1 = c.create_mutable_file(DATA)
12511+            d1 = c.create_mutable_file(DATA_uploadable)
12512             def _done(res):
12513                 log.msg("DONE: %s" % (res,))
12514                 self._mutable_node_1 = res
12515hunk ./src/allmydata/test/test_system.py 565
12516             self.failUnlessEqual(res, DATA)
12517             # replace the data
12518             log.msg("starting replace1")
12519-            d1 = newnode.overwrite(NEWDATA)
12520+            d1 = newnode.overwrite(NEWDATA_uploadable)
12521             d1.addCallback(lambda res: newnode.download_best_version())
12522             return d1
12523         d.addCallback(_check_download_3)
12524hunk ./src/allmydata/test/test_system.py 579
12525             newnode2 = self.clients[3].create_node_from_uri(uri)
12526             self._newnode3 = self.clients[3].create_node_from_uri(uri)
12527             log.msg("starting replace2")
12528-            d1 = newnode1.overwrite(NEWERDATA)
12529+            d1 = newnode1.overwrite(NEWERDATA_uploadable)
12530             d1.addCallback(lambda res: newnode2.download_best_version())
12531             return d1
12532         d.addCallback(_check_download_4)
12533hunk ./src/allmydata/test/test_system.py 649
12534         def _check_empty_file(res):
12535             # make sure we can create empty files, this usually screws up the
12536             # segsize math
12537-            d1 = self.clients[2].create_mutable_file("")
12538+            d1 = self.clients[2].create_mutable_file(MutableData(""))
12539             d1.addCallback(lambda newnode: newnode.download_best_version())
12540             d1.addCallback(lambda res: self.failUnlessEqual("", res))
12541             return d1
12542hunk ./src/allmydata/test/test_system.py 680
12543                                  self.key_generator_svc.key_generator.pool_size + size_delta)
12544 
12545         d.addCallback(check_kg_poolsize, 0)
12546-        d.addCallback(lambda junk: self.clients[3].create_mutable_file('hello, world'))
12547+        d.addCallback(lambda junk:
12548+            self.clients[3].create_mutable_file(MutableData('hello, world')))
12549         d.addCallback(check_kg_poolsize, -1)
12550         d.addCallback(lambda junk: self.clients[3].create_dirnode())
12551         d.addCallback(check_kg_poolsize, -2)
12552hunk ./src/allmydata/test/test_web.py 28
12553 from allmydata.util.encodingutil import to_str
12554 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
12555      create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
12556-from allmydata.interfaces import IMutableFileNode
12557+from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
12558 from allmydata.mutable import servermap, publish, retrieve
12559 import allmydata.test.common_util as testutil
12560 from allmydata.test.no_network import GridTestMixin
12561hunk ./src/allmydata/test/test_web.py 57
12562         return FakeCHKFileNode(cap)
12563     def _create_mutable(self, cap):
12564         return FakeMutableFileNode(None, None, None, None).init_from_cap(cap)
12565-    def create_mutable_file(self, contents="", keysize=None):
12566+    def create_mutable_file(self, contents="", keysize=None,
12567+                            version=SDMF_VERSION):
12568         n = FakeMutableFileNode(None, None, None, None)
12569hunk ./src/allmydata/test/test_web.py 60
12570+        n.set_version(version)
12571         return n.create(contents)
12572 
12573 class FakeUploader(service.Service):
12574hunk ./src/allmydata/test/test_web.py 157
12575         self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
12576                                        self.uploader, None,
12577                                        None, None)
12578+        self.mutable_file_default = SDMF_VERSION
12579 
12580     def startService(self):
12581         return service.MultiService.startService(self)
12582hunk ./src/allmydata/test/test_web.py 762
12583                              self.PUT, base + "/@@name=/blah.txt", "")
12584         return d
12585 
12586+
12587     def test_GET_DIRURL_named_bad(self):
12588         base = "/file/%s" % urllib.quote(self._foo_uri)
12589         d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
12590hunk ./src/allmydata/test/test_web.py 878
12591                                                       self.NEWFILE_CONTENTS))
12592         return d
12593 
12594+    def test_PUT_NEWFILEURL_unlinked_mdmf(self):
12595+        # this should get us a few segments of an MDMF mutable file,
12596+        # which we can then test for.
12597+        contents = self.NEWFILE_CONTENTS * 300000
12598+        d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
12599+                     contents)
12600+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12601+        d.addCallback(lambda json: self.failUnlessIn("mdmf", json))
12602+        return d
12603+
12604+    def test_PUT_NEWFILEURL_unlinked_sdmf(self):
12605+        contents = self.NEWFILE_CONTENTS * 300000
12606+        d = self.PUT("/uri?mutable=true&mutable-type=sdmf",
12607+                     contents)
12608+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12609+        d.addCallback(lambda json: self.failUnlessIn("sdmf", json))
12610+        return d
12611+
12612     def test_PUT_NEWFILEURL_range_bad(self):
12613         headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
12614         target = self.public_url + "/foo/new.txt"
12615hunk ./src/allmydata/test/test_web.py 928
12616         return d
12617 
12618     def test_PUT_NEWFILEURL_mutable_toobig(self):
12619-        d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
12620-                             "413 Request Entity Too Large",
12621-                             "SDMF is limited to one segment, and 10001 > 10000",
12622-                             self.PUT,
12623-                             self.public_url + "/foo/new.txt?mutable=true",
12624-                             "b" * (self.s.MUTABLE_SIZELIMIT+1))
12625+        # It is okay to upload large mutable files, so we should be able
12626+        # to do that.
12627+        d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
12628+                     "b" * (self.s.MUTABLE_SIZELIMIT + 1))
12629         return d
12630 
12631     def test_PUT_NEWFILEURL_replace(self):
12632hunk ./src/allmydata/test/test_web.py 1026
12633         d.addCallback(_check1)
12634         return d
12635 
12636+    def test_GET_FILEURL_json_mutable_type(self):
12637+        # The JSON should include mutable-type, which says whether the
12638+        # file is SDMF or MDMF
12639+        d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
12640+                     self.NEWFILE_CONTENTS * 300000)
12641+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12642+        def _got_json(json, version):
12643+            data = simplejson.loads(json)
12644+            assert "filenode" == data[0]
12645+            data = data[1]
12646+            assert isinstance(data, dict)
12647+
12648+            self.failUnlessIn("mutable-type", data)
12649+            self.failUnlessEqual(data['mutable-type'], version)
12650+
12651+        d.addCallback(_got_json, "mdmf")
12652+        # Now make an SDMF file and check that it is reported correctly.
12653+        d.addCallback(lambda ignored:
12654+            self.PUT("/uri?mutable=true&mutable-type=sdmf",
12655+                      self.NEWFILE_CONTENTS * 300000))
12656+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12657+        d.addCallback(_got_json, "sdmf")
12658+        return d
12659+
12660     def test_GET_FILEURL_json_missing(self):
12661         d = self.GET(self.public_url + "/foo/missing?json")
12662         d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
12663hunk ./src/allmydata/test/test_web.py 1088
12664         d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
12665         return d
12666 
12667-    def test_GET_DIRECTORY_html_banner(self):
12668+    def test_GET_DIRECTORY_html(self):
12669         d = self.GET(self.public_url + "/foo", followRedirect=True)
12670         def _check(res):
12671             self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
12672hunk ./src/allmydata/test/test_web.py 1092
12673+            self.failUnlessIn("mutable-type-mdmf", res)
12674+            self.failUnlessIn("mutable-type-sdmf", res)
12675         d.addCallback(_check)
12676         return d
12677 
12678hunk ./src/allmydata/test/test_web.py 1097
12679+    def test_GET_root_html(self):
12680+        # make sure that we have the option to upload an unlinked
12681+        # mutable file in SDMF and MDMF formats.
12682+        d = self.GET("/")
12683+        def _got_html(html):
12684+            # These are radio buttons that allow the user to toggle
12685+            # whether a particular mutable file is MDMF or SDMF.
12686+            self.failUnlessIn("mutable-type-mdmf", html)
12687+            self.failUnlessIn("mutable-type-sdmf", html)
12688+        d.addCallback(_got_html)
12689+        return d
12690+
12691+    def test_mutable_type_defaults(self):
12692+        # The checked="checked" attribute of the inputs corresponding to
12693+        # the mutable-type parameter should change as expected with the
12694+        # value configured in tahoe.cfg.
12695+        #
12696+        # By default, the value configured with the client is
12697+        # SDMF_VERSION, so that should be checked.
12698+        assert self.s.mutable_file_default == SDMF_VERSION
12699+
12700+        d = self.GET("/")
12701+        def _got_html(html, value):
12702+            i = 'input checked="checked" type="radio" id="mutable-type-%s"'
12703+            self.failUnlessIn(i % value, html)
12704+        d.addCallback(_got_html, "sdmf")
12705+        d.addCallback(lambda ignored:
12706+            self.GET(self.public_url + "/foo", followRedirect=True))
12707+        d.addCallback(_got_html, "sdmf")
12708+        # Now switch the configuration value to MDMF. The MDMF radio
12709+        # buttons should now be checked on these pages.
12710+        def _swap_values(ignored):
12711+            self.s.mutable_file_default = MDMF_VERSION
12712+        d.addCallback(_swap_values)
12713+        d.addCallback(lambda ignored: self.GET("/"))
12714+        d.addCallback(_got_html, "mdmf")
12715+        d.addCallback(lambda ignored:
12716+            self.GET(self.public_url + "/foo", followRedirect=True))
12717+        d.addCallback(_got_html, "mdmf")
12718+        return d
12719+
12720     def test_GET_DIRURL(self):
12721         # the addSlash means we get a redirect here
12722         # from /uri/$URI/foo/ , we need ../../../ to get back to the root
12723hunk ./src/allmydata/test/test_web.py 1227
12724         d.addCallback(self.failUnlessIsFooJSON)
12725         return d
12726 
12727+    def test_GET_DIRURL_json_mutable_type(self):
12728+        d = self.PUT(self.public_url + \
12729+                     "/foo/sdmf.txt?mutable=true&mutable-type=sdmf",
12730+                     self.NEWFILE_CONTENTS * 300000)
12731+        d.addCallback(lambda ignored:
12732+            self.PUT(self.public_url + \
12733+                     "/foo/mdmf.txt?mutable=true&mutable-type=mdmf",
12734+                     self.NEWFILE_CONTENTS * 300000))
12735+        # Now we have an MDMF and SDMF file in the directory. If we GET
12736+        # its JSON, we should see their encodings.
12737+        d.addCallback(lambda ignored:
12738+            self.GET(self.public_url + "/foo?t=json"))
12739+        def _got_json(json):
12740+            data = simplejson.loads(json)
12741+            assert data[0] == "dirnode"
12742+
12743+            data = data[1]
12744+            kids = data['children']
12745+
12746+            mdmf_data = kids['mdmf.txt'][1]
12747+            self.failUnlessIn("mutable-type", mdmf_data)
12748+            self.failUnlessEqual(mdmf_data['mutable-type'], "mdmf")
12749+
12750+            sdmf_data = kids['sdmf.txt'][1]
12751+            self.failUnlessIn("mutable-type", sdmf_data)
12752+            self.failUnlessEqual(sdmf_data['mutable-type'], "sdmf")
12753+        d.addCallback(_got_json)
12754+        return d
12755+
12756 
12757     def test_POST_DIRURL_manifest_no_ophandle(self):
12758         d = self.shouldFail2(error.Error,
12759hunk ./src/allmydata/test/test_web.py 1810
12760         return d
12761 
12762     def test_POST_upload_no_link_mutable_toobig(self):
12763-        d = self.shouldFail2(error.Error,
12764-                             "test_POST_upload_no_link_mutable_toobig",
12765-                             "413 Request Entity Too Large",
12766-                             "SDMF is limited to one segment, and 10001 > 10000",
12767-                             self.POST,
12768-                             "/uri", t="upload", mutable="true",
12769-                             file=("new.txt",
12770-                                   "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
12771+        # The SDMF size limit is no longer in place, so we should be
12772+        # able to upload mutable files that are as large as we want them
12773+        # to be.
12774+        d = self.POST("/uri", t="upload", mutable="true",
12775+                      file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
12776         return d
12777 
12778hunk ./src/allmydata/test/test_web.py 1817
12779+
12780+    def test_POST_upload_mutable_type_unlinked(self):
12781+        d = self.POST("/uri?t=upload&mutable=true&mutable-type=sdmf",
12782+                      file=("sdmf.txt", self.NEWFILE_CONTENTS * 300000))
12783+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12784+        def _got_json(json, version):
12785+            data = simplejson.loads(json)
12786+            data = data[1]
12787+
12788+            self.failUnlessIn("mutable-type", data)
12789+            self.failUnlessEqual(data['mutable-type'], version)
12790+        d.addCallback(_got_json, "sdmf")
12791+        d.addCallback(lambda ignored:
12792+            self.POST("/uri?t=upload&mutable=true&mutable-type=mdmf",
12793+                      file=('mdmf.txt', self.NEWFILE_CONTENTS * 300000)))
12794+        d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
12795+        d.addCallback(_got_json, "mdmf")
12796+        return d
12797+
12798+    def test_POST_upload_mutable_type(self):
12799+        d = self.POST(self.public_url + \
12800+                      "/foo?t=upload&mutable=true&mutable-type=sdmf",
12801+                      file=("sdmf.txt", self.NEWFILE_CONTENTS * 300000))
12802+        fn = self._foo_node
12803+        def _got_cap(filecap, filename):
12804+            filenameu = unicode(filename)
12805+            self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
12806+            return self.GET(self.public_url + "/foo/%s?t=json" % filename)
12807+        d.addCallback(_got_cap, "sdmf.txt")
12808+        def _got_json(json, version):
12809+            data = simplejson.loads(json)
12810+            data = data[1]
12811+
12812+            self.failUnlessIn("mutable-type", data)
12813+            self.failUnlessEqual(data['mutable-type'], version)
12814+        d.addCallback(_got_json, "sdmf")
12815+        d.addCallback(lambda ignored:
12816+            self.POST(self.public_url + \
12817+                      "/foo?t=upload&mutable=true&mutable-type=mdmf",
12818+                      file=("mdmf.txt", self.NEWFILE_CONTENTS * 300000)))
12819+        d.addCallback(_got_cap, "mdmf.txt")
12820+        d.addCallback(_got_json, "mdmf")
12821+        return d
12822+
12823     def test_POST_upload_mutable(self):
12824         # this creates a mutable file
12825         d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
12826hunk ./src/allmydata/test/test_web.py 1985
12827             self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
12828         d.addCallback(_got_headers)
12829 
12830-        # make sure that size errors are displayed correctly for overwrite
12831-        d.addCallback(lambda res:
12832-                      self.shouldFail2(error.Error,
12833-                                       "test_POST_upload_mutable-toobig",
12834-                                       "413 Request Entity Too Large",
12835-                                       "SDMF is limited to one segment, and 10001 > 10000",
12836-                                       self.POST,
12837-                                       self.public_url + "/foo", t="upload",
12838-                                       mutable="true",
12839-                                       file=("new.txt",
12840-                                             "b" * (self.s.MUTABLE_SIZELIMIT+1)),
12841-                                       ))
12842-
12843+        # make sure that outdated size limits aren't enforced anymore.
12844+        d.addCallback(lambda ignored:
12845+            self.POST(self.public_url + "/foo", t="upload",
12846+                      mutable="true",
12847+                      file=("new.txt",
12848+                            "b" * (self.s.MUTABLE_SIZELIMIT+1))))
12849         d.addErrback(self.dump_error)
12850         return d
12851 
12852hunk ./src/allmydata/test/test_web.py 1995
12853     def test_POST_upload_mutable_toobig(self):
12854-        d = self.shouldFail2(error.Error,
12855-                             "test_POST_upload_mutable_toobig",
12856-                             "413 Request Entity Too Large",
12857-                             "SDMF is limited to one segment, and 10001 > 10000",
12858-                             self.POST,
12859-                             self.public_url + "/foo",
12860-                             t="upload", mutable="true",
12861-                             file=("new.txt",
12862-                                   "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
12863+        # SDMF had a size limti that was removed a while ago. MDMF has
12864+        # never had a size limit. Test to make sure that we do not
12865+        # encounter errors when trying to upload large mutable files,
12866+        # since there should be no coded prohibitions regarding large
12867+        # mutable files.
12868+        d = self.POST(self.public_url + "/foo",
12869+                      t="upload", mutable="true",
12870+                      file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
12871         return d
12872 
12873     def dump_error(self, f):
12874hunk ./src/allmydata/test/test_web.py 3005
12875                                                       contents))
12876         return d
12877 
12878+    def test_PUT_NEWFILEURL_mdmf(self):
12879+        new_contents = self.NEWFILE_CONTENTS * 300000
12880+        d = self.PUT(self.public_url + \
12881+                     "/foo/mdmf.txt?mutable=true&mutable-type=mdmf",
12882+                     new_contents)
12883+        d.addCallback(lambda ignored:
12884+            self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
12885+        def _got_json(json):
12886+            data = simplejson.loads(json)
12887+            data = data[1]
12888+            self.failUnlessIn("mutable-type", data)
12889+            self.failUnlessEqual(data['mutable-type'], "mdmf")
12890+        d.addCallback(_got_json)
12891+        return d
12892+
12893+    def test_PUT_NEWFILEURL_sdmf(self):
12894+        new_contents = self.NEWFILE_CONTENTS * 300000
12895+        d = self.PUT(self.public_url + \
12896+                     "/foo/sdmf.txt?mutable=true&mutable-type=sdmf",
12897+                     new_contents)
12898+        d.addCallback(lambda ignored:
12899+            self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
12900+        def _got_json(json):
12901+            data = simplejson.loads(json)
12902+            data = data[1]
12903+            self.failUnlessIn("mutable-type", data)
12904+            self.failUnlessEqual(data['mutable-type'], "sdmf")
12905+        d.addCallback(_got_json)
12906+        return d
12907+
12908     def test_PUT_NEWFILEURL_uri_replace(self):
12909         contents, n, new_uri = self.makefile(8)
12910         d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
12911hunk ./src/allmydata/test/test_web.py 3156
12912         d.addCallback(_done)
12913         return d
12914 
12915+
12916+    def test_PUT_update_at_offset(self):
12917+        file_contents = "test file" * 100000 # about 900 KiB
12918+        d = self.PUT("/uri?mutable=true", file_contents)
12919+        def _then(filecap):
12920+            self.filecap = filecap
12921+            new_data = file_contents[:100]
12922+            new = "replaced and so on"
12923+            new_data += new
12924+            new_data += file_contents[len(new_data):]
12925+            assert len(new_data) == len(file_contents)
12926+            self.new_data = new_data
12927+        d.addCallback(_then)
12928+        d.addCallback(lambda ignored:
12929+            self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
12930+                     "replaced and so on"))
12931+        def _get_data(filecap):
12932+            n = self.s.create_node_from_uri(filecap)
12933+            return n.download_best_version()
12934+        d.addCallback(_get_data)
12935+        d.addCallback(lambda results:
12936+            self.failUnlessEqual(results, self.new_data))
12937+        # Now try appending things to the file
12938+        d.addCallback(lambda ignored:
12939+            self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
12940+                     "puppies" * 100))
12941+        d.addCallback(_get_data)
12942+        d.addCallback(lambda results:
12943+            self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
12944+        return d
12945+
12946+
12947+    def test_PUT_update_at_offset_immutable(self):
12948+        file_contents = "Test file" * 100000
12949+        d = self.PUT("/uri", file_contents)
12950+        def _then(filecap):
12951+            self.filecap = filecap
12952+        d.addCallback(_then)
12953+        d.addCallback(lambda ignored:
12954+            self.shouldHTTPError("test immutable update",
12955+                                 400, "Bad Request",
12956+                                 "immutable",
12957+                                 self.PUT,
12958+                                 "/uri/%s?offset=50" % self.filecap,
12959+                                 "foo"))
12960+        return d
12961+
12962+
12963     def test_bad_method(self):
12964         url = self.webish_url + self.public_url + "/foo/bar.txt"
12965         d = self.shouldHTTPError("test_bad_method",
12966hunk ./src/allmydata/test/test_web.py 3473
12967         def _stash_mutable_uri(n, which):
12968             self.uris[which] = n.get_uri()
12969             assert isinstance(self.uris[which], str)
12970-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
12971+        d.addCallback(lambda ign:
12972+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12973         d.addCallback(_stash_mutable_uri, "corrupt")
12974         d.addCallback(lambda ign:
12975                       c0.upload(upload.Data("literal", convergence="")))
12976hunk ./src/allmydata/test/test_web.py 3620
12977         def _stash_mutable_uri(n, which):
12978             self.uris[which] = n.get_uri()
12979             assert isinstance(self.uris[which], str)
12980-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
12981+        d.addCallback(lambda ign:
12982+            c0.create_mutable_file(publish.MutableData(DATA+"3")))
12983         d.addCallback(_stash_mutable_uri, "corrupt")
12984 
12985         def _compute_fileurls(ignored):
12986hunk ./src/allmydata/test/test_web.py 4283
12987         def _stash_mutable_uri(n, which):
12988             self.uris[which] = n.get_uri()
12989             assert isinstance(self.uris[which], str)
12990-        d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
12991+        d.addCallback(lambda ign:
12992+            c0.create_mutable_file(publish.MutableData(DATA+"2")))
12993         d.addCallback(_stash_mutable_uri, "mutable")
12994 
12995         def _compute_fileurls(ignored):
12996hunk ./src/allmydata/test/test_web.py 4383
12997                                                         convergence="")))
12998         d.addCallback(_stash_uri, "small")
12999 
13000-        d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
13001+        d.addCallback(lambda ign:
13002+            c0.create_mutable_file(publish.MutableData("mutable")))
13003         d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
13004         d.addCallback(_stash_uri, "mutable")
13005 
13006}
13007[resolve conflicts between 393-MDMF patches and trunk as of 1.8.2
13008"Brian Warner <warner@lothar.com>"**20110220230201
13009 Ignore-this: 9bbf5d26c994e8069202331dcb4cdd95
13010] {
13011merger 0.0 (
13012merger 0.0 (
13013merger 0.0 (
13014replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13015merger 0.0 (
13016hunk ./docs/configuration.rst 384
13017-shares.needed = (int, optional) aka "k", default 3
13018-shares.total = (int, optional) aka "N", N >= k, default 10
13019-shares.happy = (int, optional) 1 <= happy <= N, default 7
13020-
13021- These three values set the default encoding parameters. Each time a new file
13022- is uploaded, erasure-coding is used to break the ciphertext into separate
13023- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13024- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13025- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13026- Setting k to 1 is equivalent to simple replication (uploading N copies of
13027- the file).
13028-
13029- These values control the tradeoff between storage overhead, performance, and
13030- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13031- backend storage space (the actual value will be a bit more, because of other
13032- forms of overhead). Up to N-k shares can be lost before the file becomes
13033- unrecoverable, so assuming there are at least N servers, up to N-k servers
13034- can be offline without losing the file. So large N/k ratios are more
13035- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13036- smaller than N.
13037-
13038- Large values of N will slow down upload operations slightly, since more
13039- servers must be involved, and will slightly increase storage overhead due to
13040- the hash trees that are created. Large values of k will cause downloads to
13041- be marginally slower, because more servers must be involved. N cannot be
13042- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13043- uses.
13044-
13045- shares.happy allows you control over the distribution of your immutable file.
13046- For a successful upload, shares are guaranteed to be initially placed on
13047- at least 'shares.happy' distinct servers, the correct functioning of any
13048- k of which is sufficient to guarantee the availability of the uploaded file.
13049- This value should not be larger than the number of servers on your grid.
13050-
13051- A value of shares.happy <= k is allowed, but does not provide any redundancy
13052- if some servers fail or lose shares.
13053-
13054- (Mutable files use a different share placement algorithm that does not
13055-  consider this parameter.)
13056-
13057-
13058-== Storage Server Configuration ==
13059-
13060-[storage]
13061-enabled = (boolean, optional)
13062-
13063- If this is True, the node will run a storage server, offering space to other
13064- clients. If it is False, the node will not run a storage server, meaning
13065- that no shares will be stored on this node. Use False this for clients who
13066- do not wish to provide storage service. The default value is True.
13067-
13068-readonly = (boolean, optional)
13069-
13070- If True, the node will run a storage server but will not accept any shares,
13071- making it effectively read-only. Use this for storage servers which are
13072- being decommissioned: the storage/ directory could be mounted read-only,
13073- while shares are moved to other servers. Note that this currently only
13074- affects immutable shares. Mutable shares (used for directories) will be
13075- written and modified anyway. See ticket #390 for the current status of this
13076- bug. The default value is False.
13077-
13078-reserved_space = (str, optional)
13079-
13080- If provided, this value defines how much disk space is reserved: the storage
13081- server will not accept any share which causes the amount of free disk space
13082- to drop below this value. (The free space is measured by a call to statvfs(2)
13083- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13084- user account under which the storage server runs.)
13085-
13086- This string contains a number, with an optional case-insensitive scale
13087- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13088- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13089- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13090-
13091-expire.enabled =
13092-expire.mode =
13093-expire.override_lease_duration =
13094-expire.cutoff_date =
13095-expire.immutable =
13096-expire.mutable =
13097-
13098- These settings control garbage-collection, in which the server will delete
13099- shares that no longer have an up-to-date lease on them. Please see the
13100- neighboring "garbage-collection.txt" document for full details.
13101-
13102-
13103-== Running A Helper ==
13104+Running A Helper
13105+================
13106hunk ./docs/configuration.rst 424
13107+mutable.format = sdmf or mdmf
13108+
13109+ This value tells Tahoe-LAFS what the default mutable file format should
13110+ be. If mutable.format=sdmf, then newly created mutable files will be in
13111+ the old SDMF format. This is desirable for clients that operate on
13112+ grids where some peers run older versions of Tahoe-LAFS, as these older
13113+ versions cannot read the new MDMF mutable file format. If
13114+ mutable.format = mdmf, then newly created mutable files will use the
13115+ new MDMF format, which supports efficient in-place modification and
13116+ streaming downloads. You can overwrite this value using a special
13117+ mutable-type parameter in the webapi. If you do not specify a value
13118+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
13119+
13120+ Note that this parameter only applies to mutable files. Mutable
13121+ directories, which are stored as mutable files, are not controlled by
13122+ this parameter and will always use SDMF. We may revisit this decision
13123+ in future versions of Tahoe-LAFS.
13124)
13125)
13126hunk ./docs/configuration.rst 324
13127+Frontend Configuration
13128+======================
13129+
13130+The Tahoe client process can run a variety of frontend file-access protocols.
13131+You will use these to create and retrieve files from the virtual filesystem.
13132+Configuration details for each are documented in the following
13133+protocol-specific guides:
13134+
13135+HTTP
13136+
13137+    Tahoe runs a webserver by default on port 3456. This interface provides a
13138+    human-oriented "WUI", with pages to create, modify, and browse
13139+    directories and files, as well as a number of pages to check on the
13140+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
13141+    with a REST-ful HTTP interface that can be used by other programs
13142+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
13143+    details, and the ``web.port`` and ``web.static`` config variables above.
13144+    The `<frontends/download-status.rst>`_ document also describes a few WUI
13145+    status pages.
13146+
13147+CLI
13148+
13149+    The main "bin/tahoe" executable includes subcommands for manipulating the
13150+    filesystem, uploading/downloading files, and creating/running Tahoe
13151+    nodes. See `<frontends/CLI.rst>`_ for details.
13152+
13153+FTP, SFTP
13154+
13155+    Tahoe can also run both FTP and SFTP servers, and map a username/password
13156+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
13157+    for instructions on configuring these services, and the ``[ftpd]`` and
13158+    ``[sftpd]`` sections of ``tahoe.cfg``.
13159+
13160)
13161hunk ./docs/configuration.rst 324
13162+``mutable.format = sdmf or mdmf``
13163+
13164+    This value tells Tahoe what the default mutable file format should
13165+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
13166+    in the old SDMF format. This is desirable for clients that operate on
13167+    grids where some peers run older versions of Tahoe, as these older
13168+    versions cannot read the new MDMF mutable file format. If
13169+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
13170+    the new MDMF format, which supports efficient in-place modification and
13171+    streaming downloads. You can overwrite this value using a special
13172+    mutable-type parameter in the webapi. If you do not specify a value here,
13173+    Tahoe will use SDMF for all newly-created mutable files.
13174+
13175+    Note that this parameter only applies to mutable files. Mutable
13176+    directories, which are stored as mutable files, are not controlled by
13177+    this parameter and will always use SDMF. We may revisit this decision
13178+    in future versions of Tahoe-LAFS.
13179+
13180)
13181merger 0.0 (
13182merger 0.0 (
13183hunk ./docs/configuration.rst 324
13184+``mutable.format = sdmf or mdmf``
13185+
13186+    This value tells Tahoe what the default mutable file format should
13187+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
13188+    in the old SDMF format. This is desirable for clients that operate on
13189+    grids where some peers run older versions of Tahoe, as these older
13190+    versions cannot read the new MDMF mutable file format. If
13191+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
13192+    the new MDMF format, which supports efficient in-place modification and
13193+    streaming downloads. You can overwrite this value using a special
13194+    mutable-type parameter in the webapi. If you do not specify a value here,
13195+    Tahoe will use SDMF for all newly-created mutable files.
13196+
13197+    Note that this parameter only applies to mutable files. Mutable
13198+    directories, which are stored as mutable files, are not controlled by
13199+    this parameter and will always use SDMF. We may revisit this decision
13200+    in future versions of Tahoe-LAFS.
13201+
13202merger 0.0 (
13203merger 0.0 (
13204replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13205merger 0.0 (
13206hunk ./docs/configuration.rst 384
13207-shares.needed = (int, optional) aka "k", default 3
13208-shares.total = (int, optional) aka "N", N >= k, default 10
13209-shares.happy = (int, optional) 1 <= happy <= N, default 7
13210-
13211- These three values set the default encoding parameters. Each time a new file
13212- is uploaded, erasure-coding is used to break the ciphertext into separate
13213- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13214- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13215- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13216- Setting k to 1 is equivalent to simple replication (uploading N copies of
13217- the file).
13218-
13219- These values control the tradeoff between storage overhead, performance, and
13220- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13221- backend storage space (the actual value will be a bit more, because of other
13222- forms of overhead). Up to N-k shares can be lost before the file becomes
13223- unrecoverable, so assuming there are at least N servers, up to N-k servers
13224- can be offline without losing the file. So large N/k ratios are more
13225- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13226- smaller than N.
13227-
13228- Large values of N will slow down upload operations slightly, since more
13229- servers must be involved, and will slightly increase storage overhead due to
13230- the hash trees that are created. Large values of k will cause downloads to
13231- be marginally slower, because more servers must be involved. N cannot be
13232- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13233- uses.
13234-
13235- shares.happy allows you control over the distribution of your immutable file.
13236- For a successful upload, shares are guaranteed to be initially placed on
13237- at least 'shares.happy' distinct servers, the correct functioning of any
13238- k of which is sufficient to guarantee the availability of the uploaded file.
13239- This value should not be larger than the number of servers on your grid.
13240-
13241- A value of shares.happy <= k is allowed, but does not provide any redundancy
13242- if some servers fail or lose shares.
13243-
13244- (Mutable files use a different share placement algorithm that does not
13245-  consider this parameter.)
13246-
13247-
13248-== Storage Server Configuration ==
13249-
13250-[storage]
13251-enabled = (boolean, optional)
13252-
13253- If this is True, the node will run a storage server, offering space to other
13254- clients. If it is False, the node will not run a storage server, meaning
13255- that no shares will be stored on this node. Use False this for clients who
13256- do not wish to provide storage service. The default value is True.
13257-
13258-readonly = (boolean, optional)
13259-
13260- If True, the node will run a storage server but will not accept any shares,
13261- making it effectively read-only. Use this for storage servers which are
13262- being decommissioned: the storage/ directory could be mounted read-only,
13263- while shares are moved to other servers. Note that this currently only
13264- affects immutable shares. Mutable shares (used for directories) will be
13265- written and modified anyway. See ticket #390 for the current status of this
13266- bug. The default value is False.
13267-
13268-reserved_space = (str, optional)
13269-
13270- If provided, this value defines how much disk space is reserved: the storage
13271- server will not accept any share which causes the amount of free disk space
13272- to drop below this value. (The free space is measured by a call to statvfs(2)
13273- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13274- user account under which the storage server runs.)
13275-
13276- This string contains a number, with an optional case-insensitive scale
13277- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13278- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13279- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13280-
13281-expire.enabled =
13282-expire.mode =
13283-expire.override_lease_duration =
13284-expire.cutoff_date =
13285-expire.immutable =
13286-expire.mutable =
13287-
13288- These settings control garbage-collection, in which the server will delete
13289- shares that no longer have an up-to-date lease on them. Please see the
13290- neighboring "garbage-collection.txt" document for full details.
13291-
13292-
13293-== Running A Helper ==
13294+Running A Helper
13295+================
13296hunk ./docs/configuration.rst 424
13297+mutable.format = sdmf or mdmf
13298+
13299+ This value tells Tahoe-LAFS what the default mutable file format should
13300+ be. If mutable.format=sdmf, then newly created mutable files will be in
13301+ the old SDMF format. This is desirable for clients that operate on
13302+ grids where some peers run older versions of Tahoe-LAFS, as these older
13303+ versions cannot read the new MDMF mutable file format. If
13304+ mutable.format = mdmf, then newly created mutable files will use the
13305+ new MDMF format, which supports efficient in-place modification and
13306+ streaming downloads. You can overwrite this value using a special
13307+ mutable-type parameter in the webapi. If you do not specify a value
13308+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
13309+
13310+ Note that this parameter only applies to mutable files. Mutable
13311+ directories, which are stored as mutable files, are not controlled by
13312+ this parameter and will always use SDMF. We may revisit this decision
13313+ in future versions of Tahoe-LAFS.
13314)
13315)
13316hunk ./docs/configuration.rst 324
13317+Frontend Configuration
13318+======================
13319+
13320+The Tahoe client process can run a variety of frontend file-access protocols.
13321+You will use these to create and retrieve files from the virtual filesystem.
13322+Configuration details for each are documented in the following
13323+protocol-specific guides:
13324+
13325+HTTP
13326+
13327+    Tahoe runs a webserver by default on port 3456. This interface provides a
13328+    human-oriented "WUI", with pages to create, modify, and browse
13329+    directories and files, as well as a number of pages to check on the
13330+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
13331+    with a REST-ful HTTP interface that can be used by other programs
13332+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
13333+    details, and the ``web.port`` and ``web.static`` config variables above.
13334+    The `<frontends/download-status.rst>`_ document also describes a few WUI
13335+    status pages.
13336+
13337+CLI
13338+
13339+    The main "bin/tahoe" executable includes subcommands for manipulating the
13340+    filesystem, uploading/downloading files, and creating/running Tahoe
13341+    nodes. See `<frontends/CLI.rst>`_ for details.
13342+
13343+FTP, SFTP
13344+
13345+    Tahoe can also run both FTP and SFTP servers, and map a username/password
13346+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
13347+    for instructions on configuring these services, and the ``[ftpd]`` and
13348+    ``[sftpd]`` sections of ``tahoe.cfg``.
13349+
13350)
13351)
13352hunk ./docs/configuration.rst 402
13353-shares.needed = (int, optional) aka "k", default 3
13354-shares.total = (int, optional) aka "N", N >= k, default 10
13355-shares.happy = (int, optional) 1 <= happy <= N, default 7
13356-
13357- These three values set the default encoding parameters. Each time a new file
13358- is uploaded, erasure-coding is used to break the ciphertext into separate
13359- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13360- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13361- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13362- Setting k to 1 is equivalent to simple replication (uploading N copies of
13363- the file).
13364-
13365- These values control the tradeoff between storage overhead, performance, and
13366- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13367- backend storage space (the actual value will be a bit more, because of other
13368- forms of overhead). Up to N-k shares can be lost before the file becomes
13369- unrecoverable, so assuming there are at least N servers, up to N-k servers
13370- can be offline without losing the file. So large N/k ratios are more
13371- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13372- smaller than N.
13373-
13374- Large values of N will slow down upload operations slightly, since more
13375- servers must be involved, and will slightly increase storage overhead due to
13376- the hash trees that are created. Large values of k will cause downloads to
13377- be marginally slower, because more servers must be involved. N cannot be
13378- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13379- uses.
13380-
13381- shares.happy allows you control over the distribution of your immutable file.
13382- For a successful upload, shares are guaranteed to be initially placed on
13383- at least 'shares.happy' distinct servers, the correct functioning of any
13384- k of which is sufficient to guarantee the availability of the uploaded file.
13385- This value should not be larger than the number of servers on your grid.
13386-
13387- A value of shares.happy <= k is allowed, but does not provide any redundancy
13388- if some servers fail or lose shares.
13389-
13390- (Mutable files use a different share placement algorithm that does not
13391-  consider this parameter.)
13392-
13393-
13394-== Storage Server Configuration ==
13395-
13396-[storage]
13397-enabled = (boolean, optional)
13398-
13399- If this is True, the node will run a storage server, offering space to other
13400- clients. If it is False, the node will not run a storage server, meaning
13401- that no shares will be stored on this node. Use False this for clients who
13402- do not wish to provide storage service. The default value is True.
13403-
13404-readonly = (boolean, optional)
13405-
13406- If True, the node will run a storage server but will not accept any shares,
13407- making it effectively read-only. Use this for storage servers which are
13408- being decommissioned: the storage/ directory could be mounted read-only,
13409- while shares are moved to other servers. Note that this currently only
13410- affects immutable shares. Mutable shares (used for directories) will be
13411- written and modified anyway. See ticket #390 for the current status of this
13412- bug. The default value is False.
13413-
13414-reserved_space = (str, optional)
13415-
13416- If provided, this value defines how much disk space is reserved: the storage
13417- server will not accept any share which causes the amount of free disk space
13418- to drop below this value. (The free space is measured by a call to statvfs(2)
13419- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13420- user account under which the storage server runs.)
13421-
13422- This string contains a number, with an optional case-insensitive scale
13423- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13424- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13425- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13426-
13427-expire.enabled =
13428-expire.mode =
13429-expire.override_lease_duration =
13430-expire.cutoff_date =
13431-expire.immutable =
13432-expire.mutable =
13433-
13434- These settings control garbage-collection, in which the server will delete
13435- shares that no longer have an up-to-date lease on them. Please see the
13436- neighboring "garbage-collection.txt" document for full details.
13437-
13438-
13439-== Running A Helper ==
13440+Running A Helper
13441+================
13442)
13443merger 0.0 (
13444merger 0.0 (
13445hunk ./docs/configuration.rst 402
13446-shares.needed = (int, optional) aka "k", default 3
13447-shares.total = (int, optional) aka "N", N >= k, default 10
13448-shares.happy = (int, optional) 1 <= happy <= N, default 7
13449-
13450- These three values set the default encoding parameters. Each time a new file
13451- is uploaded, erasure-coding is used to break the ciphertext into separate
13452- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13453- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13454- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13455- Setting k to 1 is equivalent to simple replication (uploading N copies of
13456- the file).
13457-
13458- These values control the tradeoff between storage overhead, performance, and
13459- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13460- backend storage space (the actual value will be a bit more, because of other
13461- forms of overhead). Up to N-k shares can be lost before the file becomes
13462- unrecoverable, so assuming there are at least N servers, up to N-k servers
13463- can be offline without losing the file. So large N/k ratios are more
13464- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13465- smaller than N.
13466-
13467- Large values of N will slow down upload operations slightly, since more
13468- servers must be involved, and will slightly increase storage overhead due to
13469- the hash trees that are created. Large values of k will cause downloads to
13470- be marginally slower, because more servers must be involved. N cannot be
13471- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13472- uses.
13473-
13474- shares.happy allows you control over the distribution of your immutable file.
13475- For a successful upload, shares are guaranteed to be initially placed on
13476- at least 'shares.happy' distinct servers, the correct functioning of any
13477- k of which is sufficient to guarantee the availability of the uploaded file.
13478- This value should not be larger than the number of servers on your grid.
13479-
13480- A value of shares.happy <= k is allowed, but does not provide any redundancy
13481- if some servers fail or lose shares.
13482-
13483- (Mutable files use a different share placement algorithm that does not
13484-  consider this parameter.)
13485-
13486-
13487-== Storage Server Configuration ==
13488-
13489-[storage]
13490-enabled = (boolean, optional)
13491-
13492- If this is True, the node will run a storage server, offering space to other
13493- clients. If it is False, the node will not run a storage server, meaning
13494- that no shares will be stored on this node. Use False this for clients who
13495- do not wish to provide storage service. The default value is True.
13496-
13497-readonly = (boolean, optional)
13498-
13499- If True, the node will run a storage server but will not accept any shares,
13500- making it effectively read-only. Use this for storage servers which are
13501- being decommissioned: the storage/ directory could be mounted read-only,
13502- while shares are moved to other servers. Note that this currently only
13503- affects immutable shares. Mutable shares (used for directories) will be
13504- written and modified anyway. See ticket #390 for the current status of this
13505- bug. The default value is False.
13506-
13507-reserved_space = (str, optional)
13508-
13509- If provided, this value defines how much disk space is reserved: the storage
13510- server will not accept any share which causes the amount of free disk space
13511- to drop below this value. (The free space is measured by a call to statvfs(2)
13512- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13513- user account under which the storage server runs.)
13514-
13515- This string contains a number, with an optional case-insensitive scale
13516- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13517- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13518- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13519-
13520-expire.enabled =
13521-expire.mode =
13522-expire.override_lease_duration =
13523-expire.cutoff_date =
13524-expire.immutable =
13525-expire.mutable =
13526-
13527- These settings control garbage-collection, in which the server will delete
13528- shares that no longer have an up-to-date lease on them. Please see the
13529- neighboring "garbage-collection.txt" document for full details.
13530-
13531-
13532-== Running A Helper ==
13533+Running A Helper
13534+================
13535merger 0.0 (
13536hunk ./docs/configuration.rst 324
13537+``mutable.format = sdmf or mdmf``
13538+
13539+    This value tells Tahoe what the default mutable file format should
13540+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
13541+    in the old SDMF format. This is desirable for clients that operate on
13542+    grids where some peers run older versions of Tahoe, as these older
13543+    versions cannot read the new MDMF mutable file format. If
13544+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
13545+    the new MDMF format, which supports efficient in-place modification and
13546+    streaming downloads. You can overwrite this value using a special
13547+    mutable-type parameter in the webapi. If you do not specify a value here,
13548+    Tahoe will use SDMF for all newly-created mutable files.
13549+
13550+    Note that this parameter only applies to mutable files. Mutable
13551+    directories, which are stored as mutable files, are not controlled by
13552+    this parameter and will always use SDMF. We may revisit this decision
13553+    in future versions of Tahoe-LAFS.
13554+
13555merger 0.0 (
13556merger 0.0 (
13557replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13558merger 0.0 (
13559hunk ./docs/configuration.rst 384
13560-shares.needed = (int, optional) aka "k", default 3
13561-shares.total = (int, optional) aka "N", N >= k, default 10
13562-shares.happy = (int, optional) 1 <= happy <= N, default 7
13563-
13564- These three values set the default encoding parameters. Each time a new file
13565- is uploaded, erasure-coding is used to break the ciphertext into separate
13566- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
13567- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
13568- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
13569- Setting k to 1 is equivalent to simple replication (uploading N copies of
13570- the file).
13571-
13572- These values control the tradeoff between storage overhead, performance, and
13573- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
13574- backend storage space (the actual value will be a bit more, because of other
13575- forms of overhead). Up to N-k shares can be lost before the file becomes
13576- unrecoverable, so assuming there are at least N servers, up to N-k servers
13577- can be offline without losing the file. So large N/k ratios are more
13578- reliable, and small N/k ratios use less disk space. Clearly, k must never be
13579- smaller than N.
13580-
13581- Large values of N will slow down upload operations slightly, since more
13582- servers must be involved, and will slightly increase storage overhead due to
13583- the hash trees that are created. Large values of k will cause downloads to
13584- be marginally slower, because more servers must be involved. N cannot be
13585- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe
13586- uses.
13587-
13588- shares.happy allows you control over the distribution of your immutable file.
13589- For a successful upload, shares are guaranteed to be initially placed on
13590- at least 'shares.happy' distinct servers, the correct functioning of any
13591- k of which is sufficient to guarantee the availability of the uploaded file.
13592- This value should not be larger than the number of servers on your grid.
13593-
13594- A value of shares.happy <= k is allowed, but does not provide any redundancy
13595- if some servers fail or lose shares.
13596-
13597- (Mutable files use a different share placement algorithm that does not
13598-  consider this parameter.)
13599-
13600-
13601-== Storage Server Configuration ==
13602-
13603-[storage]
13604-enabled = (boolean, optional)
13605-
13606- If this is True, the node will run a storage server, offering space to other
13607- clients. If it is False, the node will not run a storage server, meaning
13608- that no shares will be stored on this node. Use False this for clients who
13609- do not wish to provide storage service. The default value is True.
13610-
13611-readonly = (boolean, optional)
13612-
13613- If True, the node will run a storage server but will not accept any shares,
13614- making it effectively read-only. Use this for storage servers which are
13615- being decommissioned: the storage/ directory could be mounted read-only,
13616- while shares are moved to other servers. Note that this currently only
13617- affects immutable shares. Mutable shares (used for directories) will be
13618- written and modified anyway. See ticket #390 for the current status of this
13619- bug. The default value is False.
13620-
13621-reserved_space = (str, optional)
13622-
13623- If provided, this value defines how much disk space is reserved: the storage
13624- server will not accept any share which causes the amount of free disk space
13625- to drop below this value. (The free space is measured by a call to statvfs(2)
13626- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
13627- user account under which the storage server runs.)
13628-
13629- This string contains a number, with an optional case-insensitive scale
13630- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
13631- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
13632- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
13633-
13634-expire.enabled =
13635-expire.mode =
13636-expire.override_lease_duration =
13637-expire.cutoff_date =
13638-expire.immutable =
13639-expire.mutable =
13640-
13641- These settings control garbage-collection, in which the server will delete
13642- shares that no longer have an up-to-date lease on them. Please see the
13643- neighboring "garbage-collection.txt" document for full details.
13644-
13645-
13646-== Running A Helper ==
13647+Running A Helper
13648+================
13649hunk ./docs/configuration.rst 424
13650+mutable.format = sdmf or mdmf
13651+
13652+ This value tells Tahoe-LAFS what the default mutable file format should
13653+ be. If mutable.format=sdmf, then newly created mutable files will be in
13654+ the old SDMF format. This is desirable for clients that operate on
13655+ grids where some peers run older versions of Tahoe-LAFS, as these older
13656+ versions cannot read the new MDMF mutable file format. If
13657+ mutable.format = mdmf, then newly created mutable files will use the
13658+ new MDMF format, which supports efficient in-place modification and
13659+ streaming downloads. You can overwrite this value using a special
13660+ mutable-type parameter in the webapi. If you do not specify a value
13661+ here, Tahoe-LAFS will use SDMF for all newly-created mutable files.
13662+
13663+ Note that this parameter only applies to mutable files. Mutable
13664+ directories, which are stored as mutable files, are not controlled by
13665+ this parameter and will always use SDMF. We may revisit this decision
13666+ in future versions of Tahoe-LAFS.
13667)
13668)
13669hunk ./docs/configuration.rst 324
13670+Frontend Configuration
13671+======================
13672+
13673+The Tahoe client process can run a variety of frontend file-access protocols.
13674+You will use these to create and retrieve files from the virtual filesystem.
13675+Configuration details for each are documented in the following
13676+protocol-specific guides:
13677+
13678+HTTP
13679+
13680+    Tahoe runs a webserver by default on port 3456. This interface provides a
13681+    human-oriented "WUI", with pages to create, modify, and browse
13682+    directories and files, as well as a number of pages to check on the
13683+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
13684+    with a REST-ful HTTP interface that can be used by other programs
13685+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
13686+    details, and the ``web.port`` and ``web.static`` config variables above.
13687+    The `<frontends/download-status.rst>`_ document also describes a few WUI
13688+    status pages.
13689+
13690+CLI
13691+
13692+    The main "bin/tahoe" executable includes subcommands for manipulating the
13693+    filesystem, uploading/downloading files, and creating/running Tahoe
13694+    nodes. See `<frontends/CLI.rst>`_ for details.
13695+
13696+FTP, SFTP
13697+
13698+    Tahoe can also run both FTP and SFTP servers, and map a username/password
13699+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
13700+    for instructions on configuring these services, and the ``[ftpd]`` and
13701+    ``[sftpd]`` sections of ``tahoe.cfg``.
13702+
13703)
13704)
13705)
13706replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
13707)
13708hunk ./src/allmydata/mutable/retrieve.py 7
13709 from zope.interface import implements
13710 from twisted.internet import defer
13711 from twisted.python import failure
13712-from foolscap.api import DeadReferenceError, eventually, fireEventually
13713-from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError
13714-from allmydata.util import hashutil, idlib, log
13715+from twisted.internet.interfaces import IPushProducer, IConsumer
13716+from foolscap.api import eventually, fireEventually
13717+from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
13718+                                 MDMF_VERSION, SDMF_VERSION
13719+from allmydata.util import hashutil, log, mathutil
13720+from allmydata.util.dictutil import DictOfSets
13721 from allmydata import hashtree, codec
13722 from allmydata.storage.server import si_b2a
13723 from pycryptopp.cipher.aes import AES
13724hunk ./src/allmydata/mutable/retrieve.py 239
13725             # KiB, so we ask for that much.
13726             # TODO: Change the cache methods to allow us to fetch all of the
13727             # data that they have, then change this method to do that.
13728-            any_cache, timestamp = self._node._read_from_cache(self.verinfo,
13729-                                                               shnum,
13730-                                                               0,
13731-                                                               1000)
13732+            any_cache = self._node._read_from_cache(self.verinfo, shnum,
13733+                                                    0, 1000)
13734             ss = self.servermap.connections[peerid]
13735             reader = MDMFSlotReadProxy(ss,
13736                                        self._storage_index,
13737hunk ./src/allmydata/mutable/retrieve.py 373
13738                  (k, n, self._num_segments, self._segment_size,
13739                   self._tail_segment_size))
13740 
13741-        # ask the cache first
13742-        got_from_cache = False
13743-        datavs = []
13744-        for (offset, length) in readv:
13745-            (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum,
13746-                                                            offset, length)
13747-            if data is not None:
13748-                datavs.append(data)
13749-        if len(datavs) == len(readv):
13750-            self.log("got data from cache")
13751-            got_from_cache = True
13752-            d = fireEventually({shnum: datavs})
13753-            # datavs is a dict mapping shnum to a pair of strings
13754+        for i in xrange(self._total_shares):
13755+            # So we don't have to do this later.
13756+            self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments)
13757+
13758+        # Our last task is to tell the downloader where to start and
13759+        # where to stop. We use three parameters for that:
13760+        #   - self._start_segment: the segment that we need to start
13761+        #     downloading from.
13762+        #   - self._current_segment: the next segment that we need to
13763+        #     download.
13764+        #   - self._last_segment: The last segment that we were asked to
13765+        #     download.
13766+        #
13767+        #  We say that the download is complete when
13768+        #  self._current_segment > self._last_segment. We use
13769+        #  self._start_segment and self._last_segment to know when to
13770+        #  strip things off of segments, and how much to strip.
13771+        if self._offset:
13772+            self.log("got offset: %d" % self._offset)
13773+            # our start segment is the first segment containing the
13774+            # offset we were given.
13775+            start = mathutil.div_ceil(self._offset,
13776+                                      self._segment_size)
13777+            # this gets us the first segment after self._offset. Then
13778+            # our start segment is the one before it.
13779+            start -= 1
13780+
13781+            assert start < self._num_segments
13782+            self._start_segment = start
13783+            self.log("got start segment: %d" % self._start_segment)
13784         else:
13785             self._start_segment = 0
13786 
13787hunk ./src/allmydata/mutable/servermap.py 7
13788 from itertools import count
13789 from twisted.internet import defer
13790 from twisted.python import failure
13791-from foolscap.api import DeadReferenceError, RemoteException, eventually
13792-from allmydata.util import base32, hashutil, idlib, log
13793+from foolscap.api import DeadReferenceError, RemoteException, eventually, \
13794+                         fireEventually
13795+from allmydata.util import base32, hashutil, idlib, log, deferredutil
13796+from allmydata.util.dictutil import DictOfSets
13797 from allmydata.storage.server import si_b2a
13798 from allmydata.interfaces import IServermapUpdaterStatus
13799 from pycryptopp.publickey import rsa
13800hunk ./src/allmydata/mutable/servermap.py 16
13801 
13802 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
13803-     DictOfSets, CorruptShareError, NeedMoreDataError
13804-from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \
13805-     SIGNED_PREFIX_LENGTH
13806+     CorruptShareError
13807+from allmydata.mutable.layout import SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
13808 
13809 class UpdateStatus:
13810     implements(IServermapUpdaterStatus)
13811hunk ./src/allmydata/mutable/servermap.py 391
13812         #  * if we need the encrypted private key, we want [-1216ish:]
13813         #   * but we can't read from negative offsets
13814         #   * the offset table tells us the 'ish', also the positive offset
13815-        # A future version of the SMDF slot format should consider using
13816-        # fixed-size slots so we can retrieve less data. For now, we'll just
13817-        # read 2000 bytes, which also happens to read enough actual data to
13818-        # pre-fetch a 9-entry dirnode.
13819+        # MDMF:
13820+        #  * Checkstring? [0:72]
13821+        #  * If we want to validate the checkstring, then [0:72], [143:?] --
13822+        #    the offset table will tell us for sure.
13823+        #  * If we need the verification key, we have to consult the offset
13824+        #    table as well.
13825+        # At this point, we don't know which we are. Our filenode can
13826+        # tell us, but it might be lying -- in some cases, we're
13827+        # responsible for telling it which kind of file it is.
13828         self._read_size = 4000
13829         if mode == MODE_CHECK:
13830             # we use unpack_prefix_and_signature, so we need 1k
13831hunk ./src/allmydata/mutable/servermap.py 633
13832         updated.
13833         """
13834         if verinfo:
13835-            self._node._add_to_cache(verinfo, shnum, 0, data, now)
13836+            self._node._add_to_cache(verinfo, shnum, 0, data)
13837 
13838 
13839     def _got_results(self, datavs, peerid, readsize, stuff, started):
13840hunk ./src/allmydata/mutable/servermap.py 664
13841 
13842         for shnum,datav in datavs.items():
13843             data = datav[0]
13844-            try:
13845-                verinfo = self._got_results_one_share(shnum, data, peerid, lp)
13846-                last_verinfo = verinfo
13847-                last_shnum = shnum
13848-                self._node._add_to_cache(verinfo, shnum, 0, data, now)
13849-            except CorruptShareError, e:
13850-                # log it and give the other shares a chance to be processed
13851-                f = failure.Failure()
13852-                self.log(format="bad share: %(f_value)s", f_value=str(f.value),
13853-                         failure=f, parent=lp, level=log.WEIRD, umid="h5llHg")
13854-                self.notify_server_corruption(peerid, shnum, str(e))
13855-                self._bad_peers.add(peerid)
13856-                self._last_failure = f
13857-                checkstring = data[:SIGNED_PREFIX_LENGTH]
13858-                self._servermap.mark_bad_share(peerid, shnum, checkstring)
13859-                self._servermap.problems.append(f)
13860-                pass
13861+            reader = MDMFSlotReadProxy(ss,
13862+                                       storage_index,
13863+                                       shnum,
13864+                                       data)
13865+            self._readers.setdefault(peerid, dict())[shnum] = reader
13866+            # our goal, with each response, is to validate the version
13867+            # information and share data as best we can at this point --
13868+            # we do this by validating the signature. To do this, we
13869+            # need to do the following:
13870+            #   - If we don't already have the public key, fetch the
13871+            #     public key. We use this to validate the signature.
13872+            if not self._node.get_pubkey():
13873+                # fetch and set the public key.
13874+                d = reader.get_verification_key(queue=True)
13875+                d.addCallback(lambda results, shnum=shnum, peerid=peerid:
13876+                    self._try_to_set_pubkey(results, peerid, shnum, lp))
13877+                # XXX: Make self._pubkey_query_failed?
13878+                d.addErrback(lambda error, shnum=shnum, peerid=peerid:
13879+                    self._got_corrupt_share(error, shnum, peerid, data, lp))
13880+            else:
13881+                # we already have the public key.
13882+                d = defer.succeed(None)
13883 
13884             # Neither of these two branches return anything of
13885             # consequence, so the first entry in our deferredlist will
13886hunk ./src/allmydata/test/test_storage.py 1
13887-import time, os.path, platform, stat, re, simplejson, struct
13888+import time, os.path, platform, stat, re, simplejson, struct, shutil
13889 
13890hunk ./src/allmydata/test/test_storage.py 3
13891-import time, os.path, stat, re, simplejson, struct
13892+import mock
13893 
13894 from twisted.trial import unittest
13895 
13896}
13897[mutable/filenode.py: fix create_mutable_file('string')
13898"Brian Warner <warner@lothar.com>"**20110221014659
13899 Ignore-this: dc6bdad761089f0199681eeb784f1001
13900] hunk ./src/allmydata/mutable/filenode.py 137
13901         if contents is None:
13902             return MutableData("")
13903 
13904+        if isinstance(contents, str):
13905+            return MutableData(contents)
13906+
13907         if IMutableUploadable.providedBy(contents):
13908             return contents
13909 
13910[resolve more conflicts with current trunk
13911"Brian Warner <warner@lothar.com>"**20110221055600
13912 Ignore-this: 77ad038a478dbf5d9b34f7a68159a3e0
13913] hunk ./src/allmydata/mutable/servermap.py 461
13914         self._queries_completed = 0
13915 
13916         sb = self._storage_broker
13917-        full_peerlist = sb.get_servers_for_index(self._storage_index)
13918+        # All of the peers, permuted by the storage index, as usual.
13919+        full_peerlist = [(s.get_serverid(), s.get_rref())
13920+                         for s in sb.get_servers_for_psi(self._storage_index)]
13921         self.full_peerlist = full_peerlist # for use later, immutable
13922         self.extra_peers = full_peerlist[:] # peers are removed as we use them
13923         self._good_peers = set() # peers who had some shares
13924[update MDMF code with StorageFarmBroker changes
13925"Brian Warner <warner@lothar.com>"**20110221061004
13926 Ignore-this: a693b201d31125b391cebe0412ddd027
13927] {
13928hunk ./src/allmydata/mutable/publish.py 203
13929         self._encprivkey = self._node.get_encprivkey()
13930 
13931         sb = self._storage_broker
13932-        full_peerlist = sb.get_servers_for_index(self._storage_index)
13933+        full_peerlist = [(s.get_serverid(), s.get_rref())
13934+                         for s in sb.get_servers_for_psi(self._storage_index)]
13935         self.full_peerlist = full_peerlist # for use later, immutable
13936         self.bad_peers = set() # peerids who have errbacked/refused requests
13937 
13938hunk ./src/allmydata/test/test_mutable.py 2538
13939             # for either a block and salt or for hashes, either of which
13940             # will exercise the error handling code.
13941             killer = FirstServerGetsKilled()
13942-            for (serverid, ss) in nm.storage_broker.get_all_servers():
13943-                ss.post_call_notifier = killer.notify
13944+            for s in nm.storage_broker.get_connected_servers():
13945+                s.get_rref().post_call_notifier = killer.notify
13946             ver = servermap.best_recoverable_version()
13947             assert ver
13948             return self._node.download_version(servermap, ver)
13949}
13950[mutable/filenode: Clean up servermap handling in MutableFileVersion
13951Kevan Carstensen <kevan@isnotajoke.com>**20110226010433
13952 Ignore-this: 2257c9f65502098789f5ea355b94f130
13953 
13954 We want to update the servermap before attempting to modify a file,
13955 which we now do. This introduced code duplication, which was addressed
13956 by refactoring the servermap update into its own method, and then
13957 eliminating duplicate servermap updates throughout the
13958 MutableFileVersion.
13959] {
13960hunk ./src/allmydata/mutable/filenode.py 19
13961 from allmydata.mutable.publish import Publish, MutableData,\
13962                                       DEFAULT_MAX_SEGMENT_SIZE, \
13963                                       TransformingUploadable
13964-from allmydata.mutable.common import MODE_READ, MODE_WRITE, UnrecoverableFileError, \
13965+from allmydata.mutable.common import MODE_READ, MODE_WRITE, MODE_CHECK, UnrecoverableFileError, \
13966      ResponseCache, UncoordinatedWriteError
13967 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
13968 from allmydata.mutable.retrieve import Retrieve
13969hunk ./src/allmydata/mutable/filenode.py 807
13970         a little bit.
13971         """
13972         log.msg("doing modify")
13973-        d = self._modify_once(modifier, first_time)
13974+        if first_time:
13975+            d = self._update_servermap()
13976+        else:
13977+            # We ran into trouble; do MODE_CHECK so we're a little more
13978+            # careful on subsequent tries.
13979+            d = self._update_servermap(mode=MODE_CHECK)
13980+
13981+        d.addCallback(lambda ignored:
13982+            self._modify_once(modifier, first_time))
13983         def _retry(f):
13984             f.trap(UncoordinatedWriteError)
13985hunk ./src/allmydata/mutable/filenode.py 818
13986+            # Uh oh, it broke. We're allowed to trust the servermap for our
13987+            # first try, but after that we need to update it. It's
13988+            # possible that we've failed due to a race with another
13989+            # uploader, and if the race is to converge correctly, we
13990+            # need to know about that upload.
13991             d2 = defer.maybeDeferred(backoffer, self, f)
13992             d2.addCallback(lambda ignored:
13993                            self._modify_and_retry(modifier,
13994hunk ./src/allmydata/mutable/filenode.py 837
13995         I attempt to apply a modifier to the contents of the mutable
13996         file.
13997         """
13998-        # XXX: This is wrong -- we could get more servers if we updated
13999-        # in MODE_ANYTHING and possibly MODE_CHECK. Probably we want to
14000-        # assert that the last update wasn't MODE_READ
14001-        assert self._servermap.last_update_mode == MODE_WRITE
14002+        assert self._servermap.last_update_mode != MODE_READ
14003 
14004         # download_to_data is serialized, so we have to call this to
14005         # avoid deadlock.
14006hunk ./src/allmydata/mutable/filenode.py 1076
14007 
14008         # Now ask for the servermap to be updated in MODE_WRITE with
14009         # this update range.
14010-        u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
14011-                             self._servermap,
14012-                             mode=MODE_WRITE,
14013-                             update_range=(start_segment, end_segment))
14014-        return u.update()
14015+        return self._update_servermap(update_range=(start_segment,
14016+                                                    end_segment))
14017 
14018 
14019     def _decode_and_decrypt_segments(self, ignored, data, offset):
14020hunk ./src/allmydata/mutable/filenode.py 1135
14021                                    segments_and_bht[1])
14022         p = Publish(self._node, self._storage_broker, self._servermap)
14023         return p.update(u, offset, segments_and_bht[2], self._version)
14024+
14025+
14026+    def _update_servermap(self, mode=MODE_WRITE, update_range=None):
14027+        """
14028+        I update the servermap. I return a Deferred that fires when the
14029+        servermap update is done.
14030+        """
14031+        if update_range:
14032+            u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
14033+                                 self._servermap,
14034+                                 mode=mode,
14035+                                 update_range=update_range)
14036+        else:
14037+            u = ServermapUpdater(self._node, self._storage_broker, Monitor(),
14038+                                 self._servermap,
14039+                                 mode=mode)
14040+        return u.update()
14041}
14042[web: Use the string "replace" to trigger whole-file replacement when processing an offset parameter.
14043Kevan Carstensen <kevan@isnotajoke.com>**20110227231643
14044 Ignore-this: 5bbf0b90d68efe20d4c531bb98a8321a
14045] {
14046hunk ./docs/frontends/webapi.rst 360
14047  To use the /uri/$FILECAP form, $FILECAP must be a write-cap for a mutable file.
14048 
14049  In the /uri/$DIRCAP/[SUBDIRS../]FILENAME form, if the target file is a
14050- writeable mutable file, that file's contents will be overwritten in-place. If
14051- it is a read-cap for a mutable file, an error will occur. If it is an
14052- immutable file, the old file will be discarded, and a new one will be put in
14053- its place. If the target file is a writable mutable file, you may also
14054- specify an "offset" parameter -- a byte offset that determines where in
14055- the mutable file the data from the HTTP request body is placed. This
14056- operation is relatively efficient for MDMF mutable files, and is
14057- relatively inefficient (but still supported) for SDMF mutable files.
14058+ writeable mutable file, that file's contents will be overwritten
14059+ in-place. If it is a read-cap for a mutable file, an error will occur.
14060+ If it is an immutable file, the old file will be discarded, and a new
14061+ one will be put in its place. If the target file is a writable mutable
14062+ file, you may also specify an "offset" parameter -- a byte offset that
14063+ determines where in the mutable file the data from the HTTP request
14064+ body is placed. This operation is relatively efficient for MDMF mutable
14065+ files, and is relatively inefficient (but still supported) for SDMF
14066+ mutable files. If no offset parameter is specified, then the entire
14067+ file is replaced with the data from the HTTP request body. For an
14068+ immutable file, the "offset" parameter is not valid.
14069 
14070  When creating a new file, if "mutable=true" is in the query arguments, the
14071  operation will create a mutable file instead of an immutable one.
14072hunk ./src/allmydata/test/test_web.py 3187
14073             self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
14074         return d
14075 
14076+    def test_PUT_update_at_invalid_offset(self):
14077+        file_contents = "test file" * 100000 # about 900 KiB
14078+        d = self.PUT("/uri?mutable=true", file_contents)
14079+        def _then(filecap):
14080+            self.filecap = filecap
14081+        d.addCallback(_then)
14082+        # Negative offsets should cause an error.
14083+        d.addCallback(lambda ignored:
14084+            self.shouldHTTPError("test mutable invalid offset negative",
14085+                                 400, "Bad Request",
14086+                                 "Invalid offset",
14087+                                 self.PUT,
14088+                                 "/uri/%s?offset=-1" % self.filecap,
14089+                                 "foo"))
14090+        return d
14091 
14092     def test_PUT_update_at_offset_immutable(self):
14093         file_contents = "Test file" * 100000
14094hunk ./src/allmydata/web/common.py 55
14095     # message? Since this call is going to be used by programmers and
14096     # their tools rather than users (through the wui), it is not
14097     # inconsistent to return that, I guess.
14098-    offset = int(offset)
14099-    return offset
14100+    return int(offset)
14101 
14102 
14103 def get_root(ctx_or_req):
14104hunk ./src/allmydata/web/filenode.py 219
14105         req = IRequest(ctx)
14106         t = get_arg(req, "t", "").strip()
14107         replace = parse_replace_arg(get_arg(req, "replace", "true"))
14108-        offset = parse_offset_arg(get_arg(req, "offset", -1))
14109+        offset = parse_offset_arg(get_arg(req, "offset", False))
14110 
14111         if not t:
14112hunk ./src/allmydata/web/filenode.py 222
14113-            if self.node.is_mutable() and offset >= 0:
14114-                return self.update_my_contents(req, offset)
14115-
14116-            elif self.node.is_mutable():
14117-                return self.replace_my_contents(req)
14118             if not replace:
14119                 # this is the early trap: if someone else modifies the
14120                 # directory while we're uploading, the add_file(overwrite=)
14121hunk ./src/allmydata/web/filenode.py 227
14122                 # call in replace_me_with_a_child will do the late trap.
14123                 raise ExistingChildError()
14124-            if offset >= 0:
14125-                raise WebError("PUT to a file: append operation invoked "
14126-                               "on an immutable cap")
14127 
14128hunk ./src/allmydata/web/filenode.py 228
14129+            if self.node.is_mutable():
14130+                if offset == False:
14131+                    return self.replace_my_contents(req)
14132+
14133+                if offset >= 0:
14134+                    return self.update_my_contents(req, offset)
14135+
14136+                raise WebError("PUT to a mutable file: Invalid offset")
14137+
14138+            else:
14139+                if offset != False:
14140+                    raise WebError("PUT to a file: append operation invoked "
14141+                                   "on an immutable cap")
14142+
14143+                assert self.parentnode and self.name
14144+                return self.replace_me_with_a_child(req, self.client, replace)
14145 
14146hunk ./src/allmydata/web/filenode.py 245
14147-            assert self.parentnode and self.name
14148-            return self.replace_me_with_a_child(req, self.client, replace)
14149         if t == "uri":
14150             if not replace:
14151                 raise ExistingChildError()
14152}
14153[docs/configuration.rst: fix more conflicts between #393 and trunk
14154Kevan Carstensen <kevan@isnotajoke.com>**20110228003426
14155 Ignore-this: 7917effdeecab00d634a06f1df8fe2cf
14156] {
14157replace ./docs/configuration.rst [A-Za-z_0-9\-\.] Tahoe Tahoe-LAFS
14158hunk ./docs/configuration.rst 324
14159     (Mutable files use a different share placement algorithm that does not
14160     currently consider this parameter.)
14161 
14162+``mutable.format = sdmf or mdmf``
14163+
14164+    This value tells Tahoe-LAFS what the default mutable file format should
14165+    be. If ``mutable.format=sdmf``, then newly created mutable files will be
14166+    in the old SDMF format. This is desirable for clients that operate on
14167+    grids where some peers run older versions of Tahoe-LAFS, as these older
14168+    versions cannot read the new MDMF mutable file format. If
14169+    ``mutable.format`` is ``mdmf``, then newly created mutable files will use
14170+    the new MDMF format, which supports efficient in-place modification and
14171+    streaming downloads. You can overwrite this value using a special
14172+    mutable-type parameter in the webapi. If you do not specify a value here,
14173+    Tahoe-LAFS will use SDMF for all newly-created mutable files.
14174+
14175+    Note that this parameter only applies to mutable files. Mutable
14176+    directories, which are stored as mutable files, are not controlled by
14177+    this parameter and will always use SDMF. We may revisit this decision
14178+    in future versions of Tahoe-LAFS.
14179+
14180+
14181+Frontend Configuration
14182+======================
14183+
14184+The Tahoe client process can run a variety of frontend file-access protocols.
14185+You will use these to create and retrieve files from the virtual filesystem.
14186+Configuration details for each are documented in the following
14187+protocol-specific guides:
14188+
14189+HTTP
14190+
14191+    Tahoe runs a webserver by default on port 3456. This interface provides a
14192+    human-oriented "WUI", with pages to create, modify, and browse
14193+    directories and files, as well as a number of pages to check on the
14194+    status of your Tahoe node. It also provides a machine-oriented "WAPI",
14195+    with a REST-ful HTTP interface that can be used by other programs
14196+    (including the CLI tools). Please see `<frontends/webapi.rst>`_ for full
14197+    details, and the ``web.port`` and ``web.static`` config variables above.
14198+    The `<frontends/download-status.rst>`_ document also describes a few WUI
14199+    status pages.
14200+
14201+CLI
14202+
14203+    The main "bin/tahoe" executable includes subcommands for manipulating the
14204+    filesystem, uploading/downloading files, and creating/running Tahoe
14205+    nodes. See `<frontends/CLI.rst>`_ for details.
14206+
14207+FTP, SFTP
14208+
14209+    Tahoe can also run both FTP and SFTP servers, and map a username/password
14210+    pair to a top-level Tahoe directory. See `<frontends/FTP-and-SFTP.rst>`_
14211+    for instructions on configuring these services, and the ``[ftpd]`` and
14212+    ``[sftpd]`` sections of ``tahoe.cfg``.
14213+
14214 
14215 Storage Server Configuration
14216 ============================
14217hunk ./docs/configuration.rst 436
14218     `<garbage-collection.rst>`_ for full details.
14219 
14220 
14221-shares.needed = (int, optional) aka "k", default 3
14222-shares.total = (int, optional) aka "N", N >= k, default 10
14223-shares.happy = (int, optional) 1 <= happy <= N, default 7
14224-
14225- These three values set the default encoding parameters. Each time a new file
14226- is uploaded, erasure-coding is used to break the ciphertext into separate
14227- pieces. There will be "N" (i.e. shares.total) pieces created, and the file
14228- will be recoverable if any "k" (i.e. shares.needed) pieces are retrieved.
14229- The default values are 3-of-10 (i.e. shares.needed = 3, shares.total = 10).
14230- Setting k to 1 is equivalent to simple replication (uploading N copies of
14231- the file).
14232-
14233- These values control the tradeoff between storage overhead, performance, and
14234- reliability. To a first approximation, a 1MB file will use (1MB*N/k) of
14235- backend storage space (the actual value will be a bit more, because of other
14236- forms of overhead). Up to N-k shares can be lost before the file becomes
14237- unrecoverable, so assuming there are at least N servers, up to N-k servers
14238- can be offline without losing the file. So large N/k ratios are more
14239- reliable, and small N/k ratios use less disk space. Clearly, k must never be
14240- smaller than N.
14241-
14242- Large values of N will slow down upload operations slightly, since more
14243- servers must be involved, and will slightly increase storage overhead due to
14244- the hash trees that are created. Large values of k will cause downloads to
14245- be marginally slower, because more servers must be involved. N cannot be
14246- larger than 256, because of the 8-bit erasure-coding algorithm that Tahoe-LAFS
14247- uses.
14248-
14249- shares.happy allows you control over the distribution of your immutable file.
14250- For a successful upload, shares are guaranteed to be initially placed on
14251- at least 'shares.happy' distinct servers, the correct functioning of any
14252- k of which is sufficient to guarantee the availability of the uploaded file.
14253- This value should not be larger than the number of servers on your grid.
14254-
14255- A value of shares.happy <= k is allowed, but does not provide any redundancy
14256- if some servers fail or lose shares.
14257-
14258- (Mutable files use a different share placement algorithm that does not
14259-  consider this parameter.)
14260-
14261-
14262-== Storage Server Configuration ==
14263-
14264-[storage]
14265-enabled = (boolean, optional)
14266-
14267- If this is True, the node will run a storage server, offering space to other
14268- clients. If it is False, the node will not run a storage server, meaning
14269- that no shares will be stored on this node. Use False this for clients who
14270- do not wish to provide storage service. The default value is True.
14271-
14272-readonly = (boolean, optional)
14273-
14274- If True, the node will run a storage server but will not accept any shares,
14275- making it effectively read-only. Use this for storage servers which are
14276- being decommissioned: the storage/ directory could be mounted read-only,
14277- while shares are moved to other servers. Note that this currently only
14278- affects immutable shares. Mutable shares (used for directories) will be
14279- written and modified anyway. See ticket #390 for the current status of this
14280- bug. The default value is False.
14281-
14282-reserved_space = (str, optional)
14283-
14284- If provided, this value defines how much disk space is reserved: the storage
14285- server will not accept any share which causes the amount of free disk space
14286- to drop below this value. (The free space is measured by a call to statvfs(2)
14287- on Unix, or GetDiskFreeSpaceEx on Windows, and is the space available to the
14288- user account under which the storage server runs.)
14289-
14290- This string contains a number, with an optional case-insensitive scale
14291- suffix like "K" or "M" or "G", and an optional "B" or "iB" suffix. So
14292- "100MB", "100M", "100000000B", "100000000", and "100000kb" all mean the same
14293- thing. Likewise, "1MiB", "1024KiB", and "1048576B" all mean the same thing.
14294-
14295-expire.enabled =
14296-expire.mode =
14297-expire.override_lease_duration =
14298-expire.cutoff_date =
14299-expire.immutable =
14300-expire.mutable =
14301-
14302- These settings control garbage-collection, in which the server will delete
14303- shares that no longer have an up-to-date lease on them. Please see the
14304- neighboring "garbage-collection.txt" document for full details.
14305-
14306-
14307-== Running A Helper ==
14308+Running A Helper
14309+================
14310 
14311 A "helper" is a regular client node that also offers the "upload helper"
14312 service.
14313}
14314[mutable/layout: remove references to the salt hash tree.
14315Kevan Carstensen <kevan@isnotajoke.com>**20110228010637
14316 Ignore-this: b3b2963ba4d0b42c78b6bba219d4deb5
14317] {
14318hunk ./src/allmydata/mutable/layout.py 577
14319     # 99          8           The offset of the EOF
14320     #
14321     # followed by salts and share data, the encrypted private key, the
14322-    # block hash tree, the salt hash tree, the share hash chain, a
14323-    # signature over the first eight fields, and a verification key.
14324+    # block hash tree, the share hash chain, a signature over the first
14325+    # eight fields, and a verification key.
14326     #
14327     # The checkstring is the first three fields -- the version number,
14328     # sequence number, root hash and root salt hash. This is consistent
14329hunk ./src/allmydata/mutable/layout.py 628
14330     #      calculate the offset for the share hash chain, and fill that
14331     #      into the offsets table.
14332     #
14333-    #   4: At the same time, we're in a position to upload the salt hash
14334-    #      tree. This is a Merkle tree over all of the salts. We use a
14335-    #      Merkle tree so that we can validate each block,salt pair as
14336-    #      we download them later. We do this using
14337-    #
14338-    #        put_salthashes(salt_hash_tree)
14339-    #
14340-    #      When you do this, I automatically put the root of the tree
14341-    #      (the hash at index 0 of the list) in its appropriate slot in
14342-    #      the signed prefix of the share.
14343-    #
14344-    #   5: We're now in a position to upload the share hash chain for
14345+    #   4: We're now in a position to upload the share hash chain for
14346     #      a share. Do that with something like:
14347     #     
14348     #        put_sharehashes(share_hash_chain)
14349hunk ./src/allmydata/mutable/layout.py 639
14350     #      The root of this tree will be put explicitly in the next
14351     #      step.
14352     #
14353-    #      TODO: Why? Why not just include it in the tree here?
14354-    #
14355-    #   6: Before putting the signature, we must first put the
14356+    #   5: Before putting the signature, we must first put the
14357     #      root_hash. Do this with:
14358     #
14359     #        put_root_hash(root_hash).
14360hunk ./src/allmydata/mutable/layout.py 872
14361             raise LayoutInvalid("I was given the wrong size block to write")
14362 
14363         # We want to write at len(MDMFHEADER) + segnum * block_size.
14364-
14365         offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
14366         data = salt + data
14367 
14368hunk ./src/allmydata/mutable/layout.py 889
14369         # tree is written, since that could cause the private key to run
14370         # into the block hash tree. Before it writes the block hash
14371         # tree, the block hash tree writing method writes the offset of
14372-        # the salt hash tree. So that's a good indicator of whether or
14373+        # the share hash chain. So that's a good indicator of whether or
14374         # not the block hash tree has been written.
14375         if "share_hash_chain" in self._offsets:
14376             raise LayoutInvalid("You must write this before the block hash tree")
14377hunk ./src/allmydata/mutable/layout.py 907
14378         The encrypted private key must be queued before the block hash
14379         tree, since we need to know how large it is to know where the
14380         block hash tree should go. The block hash tree must be put
14381-        before the salt hash tree, since its size determines the
14382+        before the share hash chain, since its size determines the
14383         offset of the share hash chain.
14384         """
14385         assert self._offsets
14386hunk ./src/allmydata/mutable/layout.py 932
14387         I queue a write vector to put the share hash chain in my
14388         argument onto the remote server.
14389 
14390-        The salt hash tree must be queued before the share hash chain,
14391-        since we need to know where the salt hash tree ends before we
14392+        The block hash tree must be queued before the share hash chain,
14393+        since we need to know where the block hash tree ends before we
14394         can know where the share hash chain starts. The share hash chain
14395         must be put before the signature, since the length of the packed
14396         share hash chain determines the offset of the signature. Also,
14397hunk ./src/allmydata/mutable/layout.py 937
14398-        semantically, you must know what the root of the salt hash tree
14399+        semantically, you must know what the root of the block hash tree
14400         is before you can generate a valid signature.
14401         """
14402         assert isinstance(sharehashes, dict)
14403hunk ./src/allmydata/mutable/layout.py 942
14404         if "share_hash_chain" not in self._offsets:
14405-            raise LayoutInvalid("You need to put the salt hash tree before "
14406+            raise LayoutInvalid("You need to put the block hash tree before "
14407                                 "you can put the share hash chain")
14408         # The signature comes after the share hash chain. If the
14409         # signature has already been written, we must not write another
14410}
14411[test_mutable.py: add test to exercise fencepost bug
14412warner@lothar.com**20110228021056
14413 Ignore-this: d2f9cf237ce6db42fb250c8ad71a4fc3
14414] {
14415hunk ./src/allmydata/test/test_mutable.py 2
14416 
14417-import os
14418+import os, re
14419 from cStringIO import StringIO
14420 from twisted.trial import unittest
14421 from twisted.internet import defer, reactor
14422hunk ./src/allmydata/test/test_mutable.py 2931
14423         self.set_up_grid()
14424         self.c = self.g.clients[0]
14425         self.nm = self.c.nodemaker
14426-        self.data = "test data" * 100000 # about 900 KiB; MDMF
14427+        self.data = "testdata " * 100000 # about 900 KiB; MDMF
14428         self.small_data = "test data" * 10 # about 90 B; SDMF
14429         return self.do_upload()
14430 
14431hunk ./src/allmydata/test/test_mutable.py 2981
14432             self.failUnlessEqual(results, new_data))
14433         return d
14434 
14435+    def test_replace_segstart1(self):
14436+        offset = 128*1024+1
14437+        new_data = "NNNN"
14438+        expected = self.data[:offset]+new_data+self.data[offset+4:]
14439+        d = self.mdmf_node.get_best_mutable_version()
14440+        d.addCallback(lambda mv:
14441+            mv.update(MutableData(new_data), offset))
14442+        d.addCallback(lambda ignored:
14443+            self.mdmf_node.download_best_version())
14444+        def _check(results):
14445+            if results != expected:
14446+                print
14447+                print "got: %s ... %s" % (results[:20], results[-20:])
14448+                print "exp: %s ... %s" % (expected[:20], expected[-20:])
14449+                self.fail("results != expected")
14450+        d.addCallback(_check)
14451+        return d
14452+
14453+    def _check_differences(self, got, expected):
14454+        # displaying arbitrary file corruption is tricky for a
14455+        # 1MB file of repeating data,, so look for likely places
14456+        # with problems and display them separately
14457+        gotmods = [mo.span() for mo in re.finditer('([A-Z]+)', got)]
14458+        expmods = [mo.span() for mo in re.finditer('([A-Z]+)', expected)]
14459+        gotspans = ["%d:%d=%s" % (start,end,got[start:end])
14460+                    for (start,end) in gotmods]
14461+        expspans = ["%d:%d=%s" % (start,end,expected[start:end])
14462+                    for (start,end) in expmods]
14463+        #print "expecting: %s" % expspans
14464+
14465+        SEGSIZE = 128*1024
14466+        if got != expected:
14467+            print "differences:"
14468+            for segnum in range(len(expected)//SEGSIZE):
14469+                start = segnum * SEGSIZE
14470+                end = (segnum+1) * SEGSIZE
14471+                got_ends = "%s .. %s" % (got[start:start+20], got[end-20:end])
14472+                exp_ends = "%s .. %s" % (expected[start:start+20], expected[end-20:end])
14473+                if got_ends != exp_ends:
14474+                    print "expected[%d]: %s" % (start, exp_ends)
14475+                    print "got     [%d]: %s" % (start, got_ends)
14476+            if expspans != gotspans:
14477+                print "expected: %s" % expspans
14478+                print "got     : %s" % gotspans
14479+            open("EXPECTED","wb").write(expected)
14480+            open("GOT","wb").write(got)
14481+            print "wrote data to EXPECTED and GOT"
14482+            self.fail("didn't get expected data")
14483+
14484+
14485+    def test_replace_locations(self):
14486+        # exercise fencepost conditions
14487+        expected = self.data
14488+        SEGSIZE = 128*1024
14489+        suspects = range(SEGSIZE-3, SEGSIZE+1)+range(2*SEGSIZE-3, 2*SEGSIZE+1)
14490+        letters = iter("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
14491+        d = defer.succeed(None)
14492+        for offset in suspects:
14493+            new_data = letters.next()*2 # "AA", then "BB", etc
14494+            expected = expected[:offset]+new_data+expected[offset+2:]
14495+            d.addCallback(lambda ign:
14496+                          self.mdmf_node.get_best_mutable_version())
14497+            def _modify(mv, offset=offset, new_data=new_data):
14498+                # close over 'offset','new_data'
14499+                md = MutableData(new_data)
14500+                return mv.update(md, offset)
14501+            d.addCallback(_modify)
14502+            d.addCallback(lambda ignored:
14503+                          self.mdmf_node.download_best_version())
14504+            d.addCallback(self._check_differences, expected)
14505+        return d
14506+
14507 
14508     def test_replace_and_extend(self):
14509         # We should be able to replace data in the middle of a mutable
14510}
14511[mutable/publish: account for offsets on segment boundaries.
14512Kevan Carstensen <kevan@isnotajoke.com>**20110228083327
14513 Ignore-this: c8758a0580fcc15a22c2f8582d758a6b
14514] {
14515hunk ./src/allmydata/mutable/filenode.py 17
14516 from pycryptopp.cipher.aes import AES
14517 
14518 from allmydata.mutable.publish import Publish, MutableData,\
14519-                                      DEFAULT_MAX_SEGMENT_SIZE, \
14520                                       TransformingUploadable
14521 from allmydata.mutable.common import MODE_READ, MODE_WRITE, MODE_CHECK, UnrecoverableFileError, \
14522      ResponseCache, UncoordinatedWriteError
14523hunk ./src/allmydata/mutable/filenode.py 1058
14524         # appending data to the file.
14525         assert offset <= self.get_size()
14526 
14527+        segsize = self._version[3]
14528         # We'll need the segment that the data starts in, regardless of
14529         # what we'll do later.
14530hunk ./src/allmydata/mutable/filenode.py 1061
14531-        start_segment = mathutil.div_ceil(offset, DEFAULT_MAX_SEGMENT_SIZE)
14532+        start_segment = mathutil.div_ceil(offset, segsize)
14533         start_segment -= 1
14534 
14535         # We only need the end segment if the data we append does not go
14536hunk ./src/allmydata/mutable/filenode.py 1069
14537         end_segment = start_segment
14538         if offset + data.get_size() < self.get_size():
14539             end_data = offset + data.get_size()
14540-            end_segment = mathutil.div_ceil(end_data, DEFAULT_MAX_SEGMENT_SIZE)
14541+            end_segment = mathutil.div_ceil(end_data, segsize)
14542             end_segment -= 1
14543         self._start_segment = start_segment
14544         self._end_segment = end_segment
14545hunk ./src/allmydata/mutable/publish.py 551
14546                                                   segment_size)
14547             self.starting_segment = mathutil.div_ceil(offset,
14548                                                       segment_size)
14549-            self.starting_segment -= 1
14550+            if offset % segment_size != 0:
14551+                self.starting_segment -= 1
14552             if offset == 0:
14553                 self.starting_segment = 0
14554 
14555}
14556[tahoe-put: raise UsageError when given a nonsensical mutable type, move option validation code to the option parser.
14557Kevan Carstensen <kevan@isnotajoke.com>**20110301030807
14558 Ignore-this: 2dc19d8bd741842eff458ca553d0bf2a
14559] {
14560hunk ./src/allmydata/scripts/cli.py 179
14561         if self.from_file == u"-":
14562             self.from_file = None
14563 
14564+        if self['mutable-type'] and self['mutable-type'] not in ("sdmf", "mdmf"):
14565+            raise usage.UsageError("%s is an invalid format" % self['mutable-type'])
14566+
14567+
14568     def getSynopsis(self):
14569         return "Usage:  %s put LOCAL_FILE REMOTE_FILE" % (os.path.basename(sys.argv[0]),)
14570 
14571hunk ./src/allmydata/scripts/tahoe_put.py 33
14572     stdout = options.stdout
14573     stderr = options.stderr
14574 
14575-    if mutable_type and mutable_type not in ('sdmf', 'mdmf'):
14576-        # Don't try to pass unsupported types to the webapi
14577-        print >>stderr, "error: %s is an invalid format" % mutable_type
14578-        return 1
14579-
14580     if nodeurl[-1] != "/":
14581         nodeurl += "/"
14582     if to_file:
14583hunk ./src/allmydata/test/test_cli.py 1008
14584         return d
14585 
14586     def test_mutable_type_invalid_format(self):
14587-        self.basedir = "cli/Put/mutable_type_invalid_format"
14588-        self.set_up_grid()
14589-        data = "data" * 100000
14590-        fn1 = os.path.join(self.basedir, "data")
14591-        fileutil.write(fn1, data)
14592-        d = self.do_cli("put", "--mutable", "--mutable-type=ldmf", fn1)
14593-        def _check_failure((rc, out, err)):
14594-            self.failIfEqual(rc, 0)
14595-            self.failUnlessIn("invalid", err)
14596-        d.addCallback(_check_failure)
14597-        return d
14598+        o = cli.PutOptions()
14599+        self.failUnlessRaises(usage.UsageError,
14600+                              o.parseOptions,
14601+                              ["--mutable", "--mutable-type=ldmf"])
14602 
14603     def test_put_with_nonexistent_alias(self):
14604         # when invoked with an alias that doesn't exist, 'tahoe put'
14605}
14606[web: use None instead of False in the case of no offset, use object identity comparison to check whether or not an offset was specified.
14607Kevan Carstensen <kevan@isnotajoke.com>**20110305010858
14608 Ignore-this: 14b7550ca95ce423c9b0b7f6f14ffd2f
14609] {
14610hunk ./src/allmydata/test/test_mutable.py 2981
14611             self.failUnlessEqual(results, new_data))
14612         return d
14613 
14614+    def test_replace_beginning(self):
14615+        # We should be able to replace data at the beginning of the file
14616+        # without truncating the file
14617+        B = "beginning"
14618+        new_data = B + self.data[len(B):]
14619+        d = self.mdmf_node.get_best_mutable_version()
14620+        d.addCallback(lambda mv: mv.update(MutableData(B), 0))
14621+        d.addCallback(lambda ignored: self.mdmf_node.download_best_version())
14622+        d.addCallback(lambda results: self.failUnlessEqual(results, new_data))
14623+        return d
14624+
14625     def test_replace_segstart1(self):
14626         offset = 128*1024+1
14627         new_data = "NNNN"
14628hunk ./src/allmydata/test/test_web.py 3185
14629         d.addCallback(_get_data)
14630         d.addCallback(lambda results:
14631             self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
14632+        # and try replacing the beginning of the file
14633+        d.addCallback(lambda ignored:
14634+            self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
14635+        d.addCallback(_get_data)
14636+        d.addCallback(lambda results:
14637+            self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
14638         return d
14639 
14640     def test_PUT_update_at_invalid_offset(self):
14641hunk ./src/allmydata/web/common.py 55
14642     # message? Since this call is going to be used by programmers and
14643     # their tools rather than users (through the wui), it is not
14644     # inconsistent to return that, I guess.
14645-    return int(offset)
14646+    if offset is not None:
14647+        offset = int(offset)
14648+
14649+    return offset
14650 
14651 
14652 def get_root(ctx_or_req):
14653hunk ./src/allmydata/web/filenode.py 219
14654         req = IRequest(ctx)
14655         t = get_arg(req, "t", "").strip()
14656         replace = parse_replace_arg(get_arg(req, "replace", "true"))
14657-        offset = parse_offset_arg(get_arg(req, "offset", False))
14658+        offset = parse_offset_arg(get_arg(req, "offset", None))
14659 
14660         if not t:
14661             if not replace:
14662hunk ./src/allmydata/web/filenode.py 229
14663                 raise ExistingChildError()
14664 
14665             if self.node.is_mutable():
14666-                if offset == False:
14667+                if offset is None:
14668                     return self.replace_my_contents(req)
14669 
14670                 if offset >= 0:
14671hunk ./src/allmydata/web/filenode.py 238
14672                 raise WebError("PUT to a mutable file: Invalid offset")
14673 
14674             else:
14675-                if offset != False:
14676+                if offset is not None:
14677                     raise WebError("PUT to a file: append operation invoked "
14678                                    "on an immutable cap")
14679 
14680}
14681[mutable/filenode: remove incorrect comments about segment boundaries
14682Kevan Carstensen <kevan@isnotajoke.com>**20110307081713
14683 Ignore-this: 7008644c3d9588815000a86edbf9c568
14684] {
14685hunk ./src/allmydata/mutable/filenode.py 1001
14686         offset. I return a Deferred that fires when this has been
14687         completed.
14688         """
14689-        # We have two cases here:
14690-        # 1. The new data will add few enough segments so that it does
14691-        #    not cross into the next power-of-two boundary.
14692-        # 2. It doesn't.
14693-        #
14694-        # In the former case, we can modify the file in place. In the
14695-        # latter case, we need to re-encode the file.
14696         new_size = data.get_size() + offset
14697         old_size = self.get_size()
14698         segment_size = self._version[3]
14699hunk ./src/allmydata/mutable/filenode.py 1011
14700         log.msg("got %d old segments, %d new segments" % \
14701                         (num_old_segments, num_new_segments))
14702 
14703-        # We also do a whole file re-encode if the file is an SDMF file.
14704+        # We do a whole file re-encode if the file is an SDMF file.
14705         if self._version[2]: # version[2] == SDMF salt, which MDMF lacks
14706             log.msg("doing re-encode instead of in-place update")
14707             return self._do_modify_update(data, offset)
14708hunk ./src/allmydata/mutable/filenode.py 1016
14709 
14710+        # Otherwise, we can replace just the parts that are changing.
14711         log.msg("updating in place")
14712         d = self._do_update_update(data, offset)
14713         d.addCallback(self._decode_and_decrypt_segments, data, offset)
14714}
14715[mutable: use integer division where appropriate
14716Kevan Carstensen <kevan@isnotajoke.com>**20110307082229
14717 Ignore-this: a8767e89d919c9f2a5d5fef3953d53f9
14718] {
14719hunk ./src/allmydata/mutable/filenode.py 1055
14720         segsize = self._version[3]
14721         # We'll need the segment that the data starts in, regardless of
14722         # what we'll do later.
14723-        start_segment = mathutil.div_ceil(offset, segsize)
14724-        start_segment -= 1
14725+        start_segment = offset // segsize
14726 
14727         # We only need the end segment if the data we append does not go
14728         # beyond the current end-of-file.
14729hunk ./src/allmydata/mutable/filenode.py 1062
14730         end_segment = start_segment
14731         if offset + data.get_size() < self.get_size():
14732             end_data = offset + data.get_size()
14733-            end_segment = mathutil.div_ceil(end_data, segsize)
14734-            end_segment -= 1
14735+            end_segment = end_data // segsize
14736+
14737         self._start_segment = start_segment
14738         self._end_segment = end_segment
14739 
14740hunk ./src/allmydata/mutable/publish.py 547
14741 
14742         # Calculate the starting segment for the upload.
14743         if segment_size:
14744+            # We use div_ceil instead of integer division here because
14745+            # it is semantically correct.
14746+            # If datalength isn't an even multiple of segment_size, but
14747+            # is larger than segment_size, datalength // segment_size
14748+            # will be the largest number such that num <= datalength and
14749+            # num % segment_size == 0. But that's not what we want,
14750+            # because it ignores the extra data. div_ceil will give us
14751+            # the right number of segments for the data that we're
14752+            # given.
14753             self.num_segments = mathutil.div_ceil(self.datalength,
14754                                                   segment_size)
14755hunk ./src/allmydata/mutable/publish.py 558
14756-            self.starting_segment = mathutil.div_ceil(offset,
14757-                                                      segment_size)
14758-            if offset % segment_size != 0:
14759-                self.starting_segment -= 1
14760-            if offset == 0:
14761-                self.starting_segment = 0
14762+
14763+            self.starting_segment = offset // segment_size
14764 
14765         else:
14766             self.num_segments = 0
14767hunk ./src/allmydata/mutable/publish.py 604
14768         self.end_segment = self.num_segments - 1
14769         # Now figure out where the last segment should be.
14770         if self.data.get_size() != self.datalength:
14771+            # We're updating a few segments in the middle of a mutable
14772+            # file, so we don't want to republish the whole thing.
14773+            # (we don't have enough data to do that even if we wanted
14774+            # to)
14775             end = self.data.get_size()
14776hunk ./src/allmydata/mutable/publish.py 609
14777-            self.end_segment = mathutil.div_ceil(end,
14778-                                                 segment_size)
14779-            self.end_segment -= 1
14780+            self.end_segment = end // segment_size
14781+            if end % segment_size == 0:
14782+                self.end_segment -= 1
14783+
14784         self.log("got start segment %d" % self.starting_segment)
14785         self.log("got end segment %d" % self.end_segment)
14786 
14787}
14788[mutable/layout.py: reorder on-disk format to aput variable-length fields at the end of the share, after a predictably long preamble
14789Kevan Carstensen <kevan@isnotajoke.com>**20110501224125
14790 Ignore-this: 8b2c5d29b8984dfe675c1a2ada5205cf
14791] {
14792hunk ./src/allmydata/mutable/layout.py 539
14793                                      self._readvs)
14794 
14795 
14796-MDMFHEADER = ">BQ32sBBQQ QQQQQQ"
14797+MDMFHEADER = ">BQ32sBBQQ QQQQQQQQ"
14798 MDMFHEADERWITHOUTOFFSETS = ">BQ32sBBQQ"
14799 MDMFHEADERSIZE = struct.calcsize(MDMFHEADER)
14800 MDMFHEADERWITHOUTOFFSETSSIZE = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
14801hunk ./src/allmydata/mutable/layout.py 545
14802 MDMFCHECKSTRING = ">BQ32s"
14803 MDMFSIGNABLEHEADER = ">BQ32sBBQQ"
14804-MDMFOFFSETS = ">QQQQQQ"
14805+MDMFOFFSETS = ">QQQQQQQQ"
14806 MDMFOFFSETS_LENGTH = struct.calcsize(MDMFOFFSETS)
14807hunk ./src/allmydata/mutable/layout.py 547
14808+# XXX Fix this.
14809+PRIVATE_KEY_SIZE = 2000
14810+SIGNATURE_SIZE = 10000
14811+VERIFICATION_KEY_SIZE = 2000
14812+# We know we won't ever have more than 256 shares.
14813+# XXX: This, too, can be
14814+SHARE_HASH_CHAIN_SIZE = HASH_SIZE * 256
14815 
14816 class MDMFSlotWriteProxy:
14817     implements(IMutableSlotWriter)
14818hunk ./src/allmydata/mutable/layout.py 577
14819     # 51          8           The data length of the original plaintext
14820     #-- end signed part --
14821     # 59          8           The offset of the encrypted private key
14822-    # 83          8           The offset of the signature
14823-    # 91          8           The offset of the verification key
14824-    # 67          8           The offset of the block hash tree
14825-    # 75          8           The offset of the share hash chain
14826-    # 99          8           The offset of the EOF
14827-    #
14828-    # followed by salts and share data, the encrypted private key, the
14829-    # block hash tree, the share hash chain, a signature over the first
14830-    # eight fields, and a verification key.
14831+    # 67          8           The offset of the signature
14832+    # 75          8           The offset of the verification key
14833+    # 83          8           The offset of the end of the v. key.
14834+    # 92          8           The offset of the share data
14835+    # 100         8           The offset of the block hash tree
14836+    # 108         8           The offset of the share hash chain
14837+    # 116         8           The offset of EOF
14838     #
14839hunk ./src/allmydata/mutable/layout.py 585
14840+    # followed by the encrypted private key, signature, verification
14841+    # key, share hash chain, data, and block hash tree. We order the
14842+    # fields that way to make smart downloaders -- downloaders which
14843+    # prempetively read a big part of the share -- possible.
14844+    #
14845     # The checkstring is the first three fields -- the version number,
14846     # sequence number, root hash and root salt hash. This is consistent
14847     # in meaning to what we have with SDMF files, except now instead of
14848hunk ./src/allmydata/mutable/layout.py 792
14849         data_size += self._tail_block_size
14850         data_size += SALT_SIZE
14851         self._offsets['enc_privkey'] = MDMFHEADERSIZE
14852-        self._offsets['enc_privkey'] += data_size
14853-        # We'll wait for the rest. Callers can now call my "put_block" and
14854-        # "set_checkstring" methods.
14855+
14856+        # We don't define offsets for these because we want them to be
14857+        # tightly packed -- this allows us to ignore the responsibility
14858+        # of padding individual values, and of removing that padding
14859+        # later. So nonconstant_start is where we start writing
14860+        # nonconstant data.
14861+        nonconstant_start = self._offsets['enc_privkey']
14862+        nonconstant_start += PRIVATE_KEY_SIZE
14863+        nonconstant_start += SIGNATURE_SIZE
14864+        nonconstant_start += VERIFICATION_KEY_SIZE
14865+        nonconstant_start += SHARE_HASH_CHAIN_SIZE
14866+
14867+        self._offsets['share_data'] = nonconstant_start
14868+
14869+        # Finally, we know how big the share data will be, so we can
14870+        # figure out where the block hash tree needs to go.
14871+        # XXX: But this will go away if Zooko wants to make it so that
14872+        # you don't need to know the size of the file before you start
14873+        # uploading it.
14874+        self._offsets['block_hash_tree'] = self._offsets['share_data'] + \
14875+                    data_size
14876+
14877+        # Done. We can snow start writing.
14878 
14879 
14880     def set_checkstring(self,
14881hunk ./src/allmydata/mutable/layout.py 891
14882         anything to be written yet.
14883         """
14884         if segnum >= self._num_segments:
14885-            raise LayoutInvalid("I won't overwrite the private key")
14886+            raise LayoutInvalid("I won't overwrite the block hash tree")
14887         if len(salt) != SALT_SIZE:
14888             raise LayoutInvalid("I was given a salt of size %d, but "
14889                                 "I wanted a salt of size %d")
14890hunk ./src/allmydata/mutable/layout.py 902
14891             raise LayoutInvalid("I was given the wrong size block to write")
14892 
14893         # We want to write at len(MDMFHEADER) + segnum * block_size.
14894-        offset = MDMFHEADERSIZE + (self._actual_block_size * segnum)
14895+        offset = self._offsets['share_data'] + \
14896+            (self._actual_block_size * segnum)
14897         data = salt + data
14898 
14899         self._writevs.append(tuple([offset, data]))
14900hunk ./src/allmydata/mutable/layout.py 922
14901         # tree, the block hash tree writing method writes the offset of
14902         # the share hash chain. So that's a good indicator of whether or
14903         # not the block hash tree has been written.
14904-        if "share_hash_chain" in self._offsets:
14905-            raise LayoutInvalid("You must write this before the block hash tree")
14906+        if "signature" in self._offsets:
14907+            raise LayoutInvalid("You can't put the encrypted private key "
14908+                                "after putting the share hash chain")
14909+
14910+        self._offsets['share_hash_chain'] = self._offsets['enc_privkey'] + \
14911+                len(encprivkey)
14912 
14913hunk ./src/allmydata/mutable/layout.py 929
14914-        self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + \
14915-            len(encprivkey)
14916         self._writevs.append(tuple([self._offsets['enc_privkey'], encprivkey]))
14917 
14918 
14919hunk ./src/allmydata/mutable/layout.py 944
14920         offset of the share hash chain.
14921         """
14922         assert self._offsets
14923+        assert "block_hash_tree" in self._offsets
14924+
14925         assert isinstance(blockhashes, list)
14926hunk ./src/allmydata/mutable/layout.py 947
14927-        if "block_hash_tree" not in self._offsets:
14928-            raise LayoutInvalid("You must put the encrypted private key "
14929-                                "before you put the block hash tree")
14930-        # If written, the share hash chain causes the signature offset
14931-        # to be defined.
14932-        if "signature" in self._offsets:
14933-            raise LayoutInvalid("You must put the block hash tree before "
14934-                                "you put the share hash chain")
14935+
14936         blockhashes_s = "".join(blockhashes)
14937hunk ./src/allmydata/mutable/layout.py 949
14938-        self._offsets['share_hash_chain'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
14939+        self._offsets['EOF'] = self._offsets['block_hash_tree'] + len(blockhashes_s)
14940 
14941         self._writevs.append(tuple([self._offsets['block_hash_tree'],
14942                                   blockhashes_s]))
14943hunk ./src/allmydata/mutable/layout.py 969
14944         is before you can generate a valid signature.
14945         """
14946         assert isinstance(sharehashes, dict)
14947+        assert self._offsets
14948         if "share_hash_chain" not in self._offsets:
14949hunk ./src/allmydata/mutable/layout.py 971
14950-            raise LayoutInvalid("You need to put the block hash tree before "
14951-                                "you can put the share hash chain")
14952+            raise LayoutInvalid("You must put the block hash tree before "
14953+                                "putting the share hash chain")
14954+
14955         # The signature comes after the share hash chain. If the
14956         # signature has already been written, we must not write another
14957         # share hash chain. The signature writes the verification key
14958hunk ./src/allmydata/mutable/layout.py 984
14959                                 "before you write the signature")
14960         sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i])
14961                                   for i in sorted(sharehashes.keys())])
14962-        self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s)
14963+        self._offsets['signature'] = self._offsets['share_hash_chain'] + \
14964+            len(sharehashes_s)
14965         self._writevs.append(tuple([self._offsets['share_hash_chain'],
14966                             sharehashes_s]))
14967 
14968hunk ./src/allmydata/mutable/layout.py 1002
14969         # Signature is defined by the routine that places the share hash
14970         # chain, so it's a good thing to look for in finding out whether
14971         # or not the share hash chain exists on the remote server.
14972-        if "signature" not in self._offsets:
14973-            raise LayoutInvalid("You need to put the share hash chain "
14974-                                "before you can put the root share hash")
14975         if len(roothash) != HASH_SIZE:
14976             raise LayoutInvalid("hashes and salts must be exactly %d bytes"
14977                                  % HASH_SIZE)
14978hunk ./src/allmydata/mutable/layout.py 1053
14979         # If we put the signature after we put the verification key, we
14980         # could end up running into the verification key, and will
14981         # probably screw up the offsets as well. So we don't allow that.
14982+        if "verification_key_end" in self._offsets:
14983+            raise LayoutInvalid("You can't put the signature after the "
14984+                                "verification key")
14985         # The method that writes the verification key defines the EOF
14986         # offset before writing the verification key, so look for that.
14987hunk ./src/allmydata/mutable/layout.py 1058
14988-        if "EOF" in self._offsets:
14989-            raise LayoutInvalid("You must write the signature before the verification key")
14990-
14991-        self._offsets['verification_key'] = self._offsets['signature'] + len(signature)
14992+        self._offsets['verification_key'] = self._offsets['signature'] +\
14993+            len(signature)
14994         self._writevs.append(tuple([self._offsets['signature'], signature]))
14995 
14996 
14997hunk ./src/allmydata/mutable/layout.py 1074
14998         if "verification_key" not in self._offsets:
14999             raise LayoutInvalid("You must put the signature before you "
15000                                 "can put the verification key")
15001-        self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key)
15002+
15003+        self._offsets['verification_key_end'] = \
15004+            self._offsets['verification_key'] + len(verification_key)
15005         self._writevs.append(tuple([self._offsets['verification_key'],
15006                             verification_key]))
15007 
15008hunk ./src/allmydata/mutable/layout.py 1102
15009         of the write vectors that I've dealt with so far to be published
15010         to the remote server, ending the write process.
15011         """
15012-        if "EOF" not in self._offsets:
15013+        if "verification_key_end" not in self._offsets:
15014             raise LayoutInvalid("You must put the verification key before "
15015                                 "you can publish the offsets")
15016         offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS)
15017hunk ./src/allmydata/mutable/layout.py 1108
15018         offsets = struct.pack(MDMFOFFSETS,
15019                               self._offsets['enc_privkey'],
15020-                              self._offsets['block_hash_tree'],
15021                               self._offsets['share_hash_chain'],
15022                               self._offsets['signature'],
15023                               self._offsets['verification_key'],
15024hunk ./src/allmydata/mutable/layout.py 1111
15025+                              self._offsets['verification_key_end'],
15026+                              self._offsets['share_data'],
15027+                              self._offsets['block_hash_tree'],
15028                               self._offsets['EOF'])
15029         self._writevs.append(tuple([offsets_offset, offsets]))
15030         encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING)
15031hunk ./src/allmydata/mutable/layout.py 1227
15032         # MDMF, though we'll be left with 4 more bytes than we
15033         # need if this ends up being MDMF. This is probably less
15034         # expensive than the cost of a second roundtrip.
15035-        readvs = [(0, 107)]
15036+        readvs = [(0, 123)]
15037         d = self._read(readvs, force_remote)
15038         d.addCallback(self._process_encoding_parameters)
15039         d.addCallback(self._process_offsets)
15040hunk ./src/allmydata/mutable/layout.py 1330
15041             read_length = MDMFOFFSETS_LENGTH
15042             end = read_offset + read_length
15043             (encprivkey,
15044-             blockhashes,
15045              sharehashes,
15046              signature,
15047              verification_key,
15048hunk ./src/allmydata/mutable/layout.py 1333
15049+             verification_key_end,
15050+             sharedata,
15051+             blockhashes,
15052              eof) = struct.unpack(MDMFOFFSETS,
15053                                   offsets[read_offset:end])
15054             self._offsets = {}
15055hunk ./src/allmydata/mutable/layout.py 1344
15056             self._offsets['share_hash_chain'] = sharehashes
15057             self._offsets['signature'] = signature
15058             self._offsets['verification_key'] = verification_key
15059+            self._offsets['verification_key_end']= \
15060+                verification_key_end
15061             self._offsets['EOF'] = eof
15062hunk ./src/allmydata/mutable/layout.py 1347
15063+            self._offsets['share_data'] = sharedata
15064 
15065 
15066     def get_block_and_salt(self, segnum, queue=False):
15067hunk ./src/allmydata/mutable/layout.py 1357
15068         """
15069         d = self._maybe_fetch_offsets_and_header()
15070         def _then(ignored):
15071-            if self._version_number == 1:
15072-                base_share_offset = MDMFHEADERSIZE
15073-            else:
15074-                base_share_offset = self._offsets['share_data']
15075+            base_share_offset = self._offsets['share_data']
15076 
15077             if segnum + 1 > self._num_segments:
15078                 raise LayoutInvalid("Not a valid segment number")
15079hunk ./src/allmydata/mutable/layout.py 1430
15080         def _then(ignored):
15081             blockhashes_offset = self._offsets['block_hash_tree']
15082             if self._version_number == 1:
15083-                blockhashes_length = self._offsets['share_hash_chain'] - blockhashes_offset
15084+                blockhashes_length = self._offsets['EOF'] - blockhashes_offset
15085             else:
15086                 blockhashes_length = self._offsets['share_data'] - blockhashes_offset
15087             readvs = [(blockhashes_offset, blockhashes_length)]
15088hunk ./src/allmydata/mutable/layout.py 1501
15089             if self._version_number == 0:
15090                 privkey_length = self._offsets['EOF'] - privkey_offset
15091             else:
15092-                privkey_length = self._offsets['block_hash_tree'] - privkey_offset
15093+                privkey_length = self._offsets['share_hash_chain'] - privkey_offset
15094             readvs = [(privkey_offset, privkey_length)]
15095             return readvs
15096         d.addCallback(_make_readvs)
15097hunk ./src/allmydata/mutable/layout.py 1549
15098         def _make_readvs(ignored):
15099             if self._version_number == 1:
15100                 vk_offset = self._offsets['verification_key']
15101-                vk_length = self._offsets['EOF'] - vk_offset
15102+                vk_length = self._offsets['verification_key_end'] - vk_offset
15103             else:
15104                 vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ")
15105                 vk_length = self._offsets['signature'] - vk_offset
15106hunk ./src/allmydata/test/test_storage.py 26
15107 from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \
15108                                      LayoutInvalid, MDMFSIGNABLEHEADER, \
15109                                      SIGNED_PREFIX, MDMFHEADER, \
15110-                                     MDMFOFFSETS, SDMFSlotWriteProxy
15111+                                     MDMFOFFSETS, SDMFSlotWriteProxy, \
15112+                                     PRIVATE_KEY_SIZE, \
15113+                                     SIGNATURE_SIZE, \
15114+                                     VERIFICATION_KEY_SIZE, \
15115+                                     SHARE_HASH_CHAIN_SIZE
15116 from allmydata.interfaces import BadWriteEnablerError
15117 from allmydata.test.common import LoggingServiceParent, ShouldFailMixin
15118 from allmydata.test.common_web import WebRenderingMixin
15119hunk ./src/allmydata/test/test_storage.py 1408
15120 
15121         # The encrypted private key comes after the shares + salts
15122         offset_size = struct.calcsize(MDMFOFFSETS)
15123-        encrypted_private_key_offset = len(data) + offset_size + len(sharedata)
15124-        # The blockhashes come after the private key
15125-        blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey)
15126-        # The sharehashes come after the salt hashes
15127-        sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s)
15128-        # The signature comes after the share hash chain
15129+        encrypted_private_key_offset = len(data) + offset_size
15130+        # The share has chain comes after the private key
15131+        sharehashes_offset = encrypted_private_key_offset + \
15132+            len(self.encprivkey)
15133+
15134+        # The signature comes after the share hash chain.
15135         signature_offset = sharehashes_offset + len(self.share_hash_chain_s)
15136hunk ./src/allmydata/test/test_storage.py 1415
15137-        # The verification key comes after the signature
15138-        verification_offset = signature_offset + len(self.signature)
15139-        # The EOF comes after the verification key
15140-        eof_offset = verification_offset + len(self.verification_key)
15141+
15142+        verification_key_offset = signature_offset + len(self.signature)
15143+        verification_key_end = verification_key_offset + \
15144+            len(self.verification_key)
15145+
15146+        share_data_offset = offset_size
15147+        share_data_offset += PRIVATE_KEY_SIZE
15148+        share_data_offset += SIGNATURE_SIZE
15149+        share_data_offset += VERIFICATION_KEY_SIZE
15150+        share_data_offset += SHARE_HASH_CHAIN_SIZE
15151+
15152+        blockhashes_offset = share_data_offset + len(sharedata)
15153+        eof_offset = blockhashes_offset + len(self.block_hash_tree_s)
15154+
15155         data += struct.pack(MDMFOFFSETS,
15156                             encrypted_private_key_offset,
15157hunk ./src/allmydata/test/test_storage.py 1431
15158-                            blockhashes_offset,
15159                             sharehashes_offset,
15160                             signature_offset,
15161hunk ./src/allmydata/test/test_storage.py 1433
15162-                            verification_offset,
15163+                            verification_key_offset,
15164+                            verification_key_end,
15165+                            share_data_offset,
15166+                            blockhashes_offset,
15167                             eof_offset)
15168hunk ./src/allmydata/test/test_storage.py 1438
15169+
15170         self.offsets = {}
15171         self.offsets['enc_privkey'] = encrypted_private_key_offset
15172         self.offsets['block_hash_tree'] = blockhashes_offset
15173hunk ./src/allmydata/test/test_storage.py 1444
15174         self.offsets['share_hash_chain'] = sharehashes_offset
15175         self.offsets['signature'] = signature_offset
15176-        self.offsets['verification_key'] = verification_offset
15177+        self.offsets['verification_key'] = verification_key_offset
15178+        self.offsets['share_data'] = share_data_offset
15179+        self.offsets['verification_key_end'] = verification_key_end
15180         self.offsets['EOF'] = eof_offset
15181hunk ./src/allmydata/test/test_storage.py 1448
15182-        # Next, we'll add in the salts and share data,
15183-        data += sharedata
15184+
15185         # the private key,
15186         data += self.encprivkey
15187hunk ./src/allmydata/test/test_storage.py 1451
15188-        # the block hash tree,
15189-        data += self.block_hash_tree_s
15190-        # the share hash chain,
15191+        # the sharehashes
15192         data += self.share_hash_chain_s
15193         # the signature,
15194         data += self.signature
15195hunk ./src/allmydata/test/test_storage.py 1457
15196         # and the verification key
15197         data += self.verification_key
15198+        # Then we'll add in gibberish until we get to the right point.
15199+        nulls = "".join([" " for i in xrange(len(data), share_data_offset)])
15200+        data += nulls
15201+
15202+        # Then the share data
15203+        data += sharedata
15204+        # the blockhashes
15205+        data += self.block_hash_tree_s
15206         return data
15207 
15208 
15209hunk ./src/allmydata/test/test_storage.py 1729
15210         return d
15211 
15212 
15213-    def test_blockhashes_after_share_hash_chain(self):
15214+    def test_private_key_after_share_hash_chain(self):
15215         mw = self._make_new_mw("si1", 0)
15216         d = defer.succeed(None)
15217hunk ./src/allmydata/test/test_storage.py 1732
15218-        # Put everything up to and including the share hash chain
15219         for i in xrange(6):
15220             d.addCallback(lambda ignored, i=i:
15221                 mw.put_block(self.block, i, self.salt))
15222hunk ./src/allmydata/test/test_storage.py 1738
15223         d.addCallback(lambda ignored:
15224             mw.put_encprivkey(self.encprivkey))
15225         d.addCallback(lambda ignored:
15226-            mw.put_blockhashes(self.block_hash_tree))
15227-        d.addCallback(lambda ignored:
15228             mw.put_sharehashes(self.share_hash_chain))
15229 
15230hunk ./src/allmydata/test/test_storage.py 1740
15231-        # Now try to put the block hash tree again.
15232+        # Now try to put the private key again.
15233         d.addCallback(lambda ignored:
15234hunk ./src/allmydata/test/test_storage.py 1742
15235-            self.shouldFail(LayoutInvalid, "test repeat salthashes",
15236-                            None,
15237-                            mw.put_blockhashes, self.block_hash_tree))
15238-        return d
15239-
15240-
15241-    def test_encprivkey_after_blockhashes(self):
15242-        mw = self._make_new_mw("si1", 0)
15243-        d = defer.succeed(None)
15244-        # Put everything up to and including the block hash tree
15245-        for i in xrange(6):
15246-            d.addCallback(lambda ignored, i=i:
15247-                mw.put_block(self.block, i, self.salt))
15248-        d.addCallback(lambda ignored:
15249-            mw.put_encprivkey(self.encprivkey))
15250-        d.addCallback(lambda ignored:
15251-            mw.put_blockhashes(self.block_hash_tree))
15252-        d.addCallback(lambda ignored:
15253-            self.shouldFail(LayoutInvalid, "out of order private key",
15254+            self.shouldFail(LayoutInvalid, "test repeat private key",
15255                             None,
15256                             mw.put_encprivkey, self.encprivkey))
15257         return d
15258hunk ./src/allmydata/test/test_storage.py 1748
15259 
15260 
15261-    def test_share_hash_chain_after_signature(self):
15262-        mw = self._make_new_mw("si1", 0)
15263-        d = defer.succeed(None)
15264-        # Put everything up to and including the signature
15265-        for i in xrange(6):
15266-            d.addCallback(lambda ignored, i=i:
15267-                mw.put_block(self.block, i, self.salt))
15268-        d.addCallback(lambda ignored:
15269-            mw.put_encprivkey(self.encprivkey))
15270-        d.addCallback(lambda ignored:
15271-            mw.put_blockhashes(self.block_hash_tree))
15272-        d.addCallback(lambda ignored:
15273-            mw.put_sharehashes(self.share_hash_chain))
15274-        d.addCallback(lambda ignored:
15275-            mw.put_root_hash(self.root_hash))
15276-        d.addCallback(lambda ignored:
15277-            mw.put_signature(self.signature))
15278-        # Now try to put the share hash chain again. This should fail
15279-        d.addCallback(lambda ignored:
15280-            self.shouldFail(LayoutInvalid, "out of order share hash chain",
15281-                            None,
15282-                            mw.put_sharehashes, self.share_hash_chain))
15283-        return d
15284-
15285-
15286     def test_signature_after_verification_key(self):
15287         mw = self._make_new_mw("si1", 0)
15288         d = defer.succeed(None)
15289hunk ./src/allmydata/test/test_storage.py 1877
15290         mw = self._make_new_mw("si1", 0)
15291         # Test writing some blocks.
15292         read = self.ss.remote_slot_readv
15293-        expected_sharedata_offset = struct.calcsize(MDMFHEADER)
15294+        expected_private_key_offset = struct.calcsize(MDMFHEADER)
15295+        expected_sharedata_offset = struct.calcsize(MDMFHEADER) + \
15296+                                    PRIVATE_KEY_SIZE + \
15297+                                    SIGNATURE_SIZE + \
15298+                                    VERIFICATION_KEY_SIZE + \
15299+                                    SHARE_HASH_CHAIN_SIZE
15300         written_block_size = 2 + len(self.salt)
15301         written_block = self.block + self.salt
15302         for i in xrange(6):
15303hunk ./src/allmydata/test/test_storage.py 1903
15304                 self.failUnlessEqual(read("si1", [0], [(expected_sharedata_offset + (i * written_block_size), written_block_size)]),
15305                                 {0: [written_block]})
15306 
15307-            expected_private_key_offset = expected_sharedata_offset + \
15308-                                      len(written_block) * 6
15309             self.failUnlessEqual(len(self.encprivkey), 7)
15310             self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]),
15311                                  {0: [self.encprivkey]})
15312hunk ./src/allmydata/test/test_storage.py 1907
15313 
15314-            expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey)
15315+            expected_block_hash_offset = expected_sharedata_offset + \
15316+                        (6 * written_block_size)
15317             self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6)
15318             self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]),
15319                                  {0: [self.block_hash_tree_s]})
15320hunk ./src/allmydata/test/test_storage.py 1913
15321 
15322-            expected_share_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s)
15323+            expected_share_hash_offset = expected_private_key_offset + len(self.encprivkey)
15324             self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]),
15325                                  {0: [self.share_hash_chain_s]})
15326 
15327hunk ./src/allmydata/test/test_storage.py 1919
15328             self.failUnlessEqual(read("si1", [0], [(9, 32)]),
15329                                  {0: [self.root_hash]})
15330-            expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s)
15331+            expected_signature_offset = expected_share_hash_offset + \
15332+                len(self.share_hash_chain_s)
15333             self.failUnlessEqual(len(self.signature), 9)
15334             self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]),
15335                                  {0: [self.signature]})
15336hunk ./src/allmydata/test/test_storage.py 1941
15337             self.failUnlessEqual(n, 10)
15338             self.failUnlessEqual(segsize, 6)
15339             self.failUnlessEqual(datalen, 36)
15340-            expected_eof_offset = expected_verification_key_offset + len(self.verification_key)
15341+            expected_eof_offset = expected_block_hash_offset + \
15342+                len(self.block_hash_tree_s)
15343 
15344             # Check the version number to make sure that it is correct.
15345             expected_version_number = struct.pack(">B", 1)
15346hunk ./src/allmydata/test/test_storage.py 1969
15347             expected_offset = struct.pack(">Q", expected_private_key_offset)
15348             self.failUnlessEqual(read("si1", [0], [(59, 8)]),
15349                                  {0: [expected_offset]})
15350-            expected_offset = struct.pack(">Q", expected_block_hash_offset)
15351+            expected_offset = struct.pack(">Q", expected_share_hash_offset)
15352             self.failUnlessEqual(read("si1", [0], [(67, 8)]),
15353                                  {0: [expected_offset]})
15354hunk ./src/allmydata/test/test_storage.py 1972
15355-            expected_offset = struct.pack(">Q", expected_share_hash_offset)
15356+            expected_offset = struct.pack(">Q", expected_signature_offset)
15357             self.failUnlessEqual(read("si1", [0], [(75, 8)]),
15358                                  {0: [expected_offset]})
15359hunk ./src/allmydata/test/test_storage.py 1975
15360-            expected_offset = struct.pack(">Q", expected_signature_offset)
15361+            expected_offset = struct.pack(">Q", expected_verification_key_offset)
15362             self.failUnlessEqual(read("si1", [0], [(83, 8)]),
15363                                  {0: [expected_offset]})
15364hunk ./src/allmydata/test/test_storage.py 1978
15365-            expected_offset = struct.pack(">Q", expected_verification_key_offset)
15366+            expected_offset = struct.pack(">Q", expected_verification_key_offset + len(self.verification_key))
15367             self.failUnlessEqual(read("si1", [0], [(91, 8)]),
15368                                  {0: [expected_offset]})
15369hunk ./src/allmydata/test/test_storage.py 1981
15370-            expected_offset = struct.pack(">Q", expected_eof_offset)
15371+            expected_offset = struct.pack(">Q", expected_sharedata_offset)
15372             self.failUnlessEqual(read("si1", [0], [(99, 8)]),
15373                                  {0: [expected_offset]})
15374hunk ./src/allmydata/test/test_storage.py 1984
15375+            expected_offset = struct.pack(">Q", expected_block_hash_offset)
15376+            self.failUnlessEqual(read("si1", [0], [(107, 8)]),
15377+                                 {0: [expected_offset]})
15378+            expected_offset = struct.pack(">Q", expected_eof_offset)
15379+            self.failUnlessEqual(read("si1", [0], [(115, 8)]),
15380+                                 {0: [expected_offset]})
15381         d.addCallback(_check_publish)
15382         return d
15383 
15384hunk ./src/allmydata/test/test_storage.py 2117
15385         for i in xrange(6):
15386             d.addCallback(lambda ignored, i=i:
15387                 mw0.put_block(self.block, i, self.salt))
15388-        # Try to write the block hashes before writing the encrypted
15389-        # private key
15390-        d.addCallback(lambda ignored:
15391-            self.shouldFail(LayoutInvalid, "block hashes before key",
15392-                            None, mw0.put_blockhashes,
15393-                            self.block_hash_tree))
15394-
15395-        # Write the private key.
15396-        d.addCallback(lambda ignored:
15397-            mw0.put_encprivkey(self.encprivkey))
15398-
15399 
15400hunk ./src/allmydata/test/test_storage.py 2118
15401-        # Try to write the share hash chain without writing the block
15402-        # hash tree
15403+        # Try to write the share hash chain without writing the
15404+        # encrypted private key
15405         d.addCallback(lambda ignored:
15406             self.shouldFail(LayoutInvalid, "share hash chain before "
15407hunk ./src/allmydata/test/test_storage.py 2122
15408-                                           "salt hash tree",
15409+                                           "private key",
15410                             None,
15411                             mw0.put_sharehashes, self.share_hash_chain))
15412hunk ./src/allmydata/test/test_storage.py 2125
15413-
15414-        # Try to write the root hash and without writing either the
15415-        # block hashes or the or the share hashes
15416+        # Write the private key.
15417         d.addCallback(lambda ignored:
15418hunk ./src/allmydata/test/test_storage.py 2127
15419-            self.shouldFail(LayoutInvalid, "root hash before share hashes",
15420-                            None,
15421-                            mw0.put_root_hash, self.root_hash))
15422+            mw0.put_encprivkey(self.encprivkey))
15423 
15424         # Now write the block hashes and try again
15425         d.addCallback(lambda ignored:
15426hunk ./src/allmydata/test/test_storage.py 2133
15427             mw0.put_blockhashes(self.block_hash_tree))
15428 
15429-        d.addCallback(lambda ignored:
15430-            self.shouldFail(LayoutInvalid, "root hash before share hashes",
15431-                            None, mw0.put_root_hash, self.root_hash))
15432-
15433         # We haven't yet put the root hash on the share, so we shouldn't
15434         # be able to sign it.
15435         d.addCallback(lambda ignored:
15436hunk ./src/allmydata/test/test_storage.py 2378
15437         # This should be enough to fill in both the encoding parameters
15438         # and the table of offsets, which will complete the version
15439         # information tuple.
15440-        d.addCallback(_make_mr, 107)
15441+        d.addCallback(_make_mr, 123)
15442         d.addCallback(lambda mr:
15443             mr.get_verinfo())
15444         def _check_verinfo(verinfo):
15445hunk ./src/allmydata/test/test_storage.py 2412
15446         d.addCallback(_check_verinfo)
15447         # This is not enough data to read a block and a share, so the
15448         # wrapper should attempt to read this from the remote server.
15449-        d.addCallback(_make_mr, 107)
15450+        d.addCallback(_make_mr, 123)
15451         d.addCallback(lambda mr:
15452             mr.get_block_and_salt(0))
15453         def _check_block_and_salt((block, salt)):
15454hunk ./src/allmydata/test/test_storage.py 2420
15455             self.failUnlessEqual(salt, self.salt)
15456             self.failUnlessEqual(self.rref.read_count, 1)
15457         # This should be enough data to read one block.
15458-        d.addCallback(_make_mr, 249)
15459+        d.addCallback(_make_mr, 123 + PRIVATE_KEY_SIZE + SIGNATURE_SIZE + VERIFICATION_KEY_SIZE + SHARE_HASH_CHAIN_SIZE + 140)
15460         d.addCallback(lambda mr:
15461             mr.get_block_and_salt(0))
15462         d.addCallback(_check_block_and_salt)
15463hunk ./src/allmydata/test/test_storage.py 2438
15464         # This should be enough to get us the encoding parameters,
15465         # offset table, and everything else we need to build a verinfo
15466         # string.
15467-        d.addCallback(_make_mr, 107)
15468+        d.addCallback(_make_mr, 123)
15469         d.addCallback(lambda mr:
15470             mr.get_verinfo())
15471         def _check_verinfo(verinfo):
15472hunk ./src/allmydata/test/test_storage.py 2473
15473             self.failUnlessEqual(self.rref.read_count, 0)
15474         d.addCallback(_check_verinfo)
15475         # This shouldn't be enough to read any share data.
15476-        d.addCallback(_make_mr, 107)
15477+        d.addCallback(_make_mr, 123)
15478         d.addCallback(lambda mr:
15479             mr.get_block_and_salt(0))
15480         def _check_block_and_salt((block, salt)):
15481}
15482[uri.py: Add MDMF cap
15483Kevan Carstensen <kevan@isnotajoke.com>**20110501224249
15484 Ignore-this: a6d1046d33f5cc811c5e8b10af925f33
15485] {
15486hunk ./src/allmydata/interfaces.py 546
15487 
15488 class IMutableFileURI(Interface):
15489     """I am a URI which represents a mutable filenode."""
15490+    def get_extension_params():
15491+        """Return the extension parameters in the URI"""
15492 
15493 class IDirectoryURI(Interface):
15494     pass
15495hunk ./src/allmydata/test/test_uri.py 2
15496 
15497+import re
15498 from twisted.trial import unittest
15499 from allmydata import uri
15500 from allmydata.util import hashutil, base32
15501hunk ./src/allmydata/test/test_uri.py 259
15502         uri.CHKFileURI.init_from_string(fileURI)
15503 
15504 class Mutable(testutil.ReallyEqualMixin, unittest.TestCase):
15505-    def test_pack(self):
15506-        writekey = "\x01" * 16
15507-        fingerprint = "\x02" * 32
15508+    def setUp(self):
15509+        self.writekey = "\x01" * 16
15510+        self.fingerprint = "\x02" * 32
15511+        self.readkey = hashutil.ssk_readkey_hash(self.writekey)
15512+        self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
15513 
15514hunk ./src/allmydata/test/test_uri.py 265
15515-        u = uri.WriteableSSKFileURI(writekey, fingerprint)
15516-        self.failUnlessReallyEqual(u.writekey, writekey)
15517-        self.failUnlessReallyEqual(u.fingerprint, fingerprint)
15518+    def test_pack(self):
15519+        u = uri.WriteableSSKFileURI(self.writekey, self.fingerprint)
15520+        self.failUnlessReallyEqual(u.writekey, self.writekey)
15521+        self.failUnlessReallyEqual(u.fingerprint, self.fingerprint)
15522         self.failIf(u.is_readonly())
15523         self.failUnless(u.is_mutable())
15524         self.failUnless(IURI.providedBy(u))
15525hunk ./src/allmydata/test/test_uri.py 281
15526         self.failUnlessReallyEqual(u, u_h)
15527 
15528         u2 = uri.from_string(u.to_string())
15529-        self.failUnlessReallyEqual(u2.writekey, writekey)
15530-        self.failUnlessReallyEqual(u2.fingerprint, fingerprint)
15531+        self.failUnlessReallyEqual(u2.writekey, self.writekey)
15532+        self.failUnlessReallyEqual(u2.fingerprint, self.fingerprint)
15533         self.failIf(u2.is_readonly())
15534         self.failUnless(u2.is_mutable())
15535         self.failUnless(IURI.providedBy(u2))
15536hunk ./src/allmydata/test/test_uri.py 297
15537         self.failUnless(isinstance(u2imm, uri.UnknownURI), u2imm)
15538 
15539         u3 = u2.get_readonly()
15540-        readkey = hashutil.ssk_readkey_hash(writekey)
15541-        self.failUnlessReallyEqual(u3.fingerprint, fingerprint)
15542+        readkey = hashutil.ssk_readkey_hash(self.writekey)
15543+        self.failUnlessReallyEqual(u3.fingerprint, self.fingerprint)
15544         self.failUnlessReallyEqual(u3.readkey, readkey)
15545         self.failUnless(u3.is_readonly())
15546         self.failUnless(u3.is_mutable())
15547hunk ./src/allmydata/test/test_uri.py 317
15548         u3_h = uri.ReadonlySSKFileURI.init_from_human_encoding(he)
15549         self.failUnlessReallyEqual(u3, u3_h)
15550 
15551-        u4 = uri.ReadonlySSKFileURI(readkey, fingerprint)
15552-        self.failUnlessReallyEqual(u4.fingerprint, fingerprint)
15553+        u4 = uri.ReadonlySSKFileURI(readkey, self.fingerprint)
15554+        self.failUnlessReallyEqual(u4.fingerprint, self.fingerprint)
15555         self.failUnlessReallyEqual(u4.readkey, readkey)
15556         self.failUnless(u4.is_readonly())
15557         self.failUnless(u4.is_mutable())
15558hunk ./src/allmydata/test/test_uri.py 350
15559         self.failUnlessReallyEqual(u5, u5_h)
15560 
15561 
15562+    def test_writable_mdmf_cap(self):
15563+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15564+        cap = u1.to_string()
15565+        u = uri.WritableMDMFFileURI.init_from_string(cap)
15566+
15567+        self.failUnless(IMutableFileURI.providedBy(u))
15568+        self.failUnlessReallyEqual(u.fingerprint, self.fingerprint)
15569+        self.failUnlessReallyEqual(u.writekey, self.writekey)
15570+        self.failUnless(u.is_mutable())
15571+        self.failIf(u.is_readonly())
15572+        self.failUnlessEqual(cap, u.to_string())
15573+
15574+        # Now get a readonly cap from the writable cap, and test that it
15575+        # degrades gracefully.
15576+        ru = u.get_readonly()
15577+        self.failUnlessReallyEqual(self.readkey, ru.readkey)
15578+        self.failUnlessReallyEqual(self.fingerprint, ru.fingerprint)
15579+        self.failUnless(ru.is_mutable())
15580+        self.failUnless(ru.is_readonly())
15581+
15582+        # Now get a verifier cap.
15583+        vu = ru.get_verify_cap()
15584+        self.failUnlessReallyEqual(self.storage_index, vu.storage_index)
15585+        self.failUnlessReallyEqual(self.fingerprint, vu.fingerprint)
15586+        self.failUnless(IVerifierURI.providedBy(vu))
15587+
15588+    def test_readonly_mdmf_cap(self):
15589+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15590+        cap = u1.to_string()
15591+        u2 = uri.ReadonlyMDMFFileURI.init_from_string(cap)
15592+
15593+        self.failUnlessReallyEqual(u2.fingerprint, self.fingerprint)
15594+        self.failUnlessReallyEqual(u2.readkey, self.readkey)
15595+        self.failUnless(u2.is_readonly())
15596+        self.failUnless(u2.is_mutable())
15597+
15598+        vu = u2.get_verify_cap()
15599+        self.failUnlessEqual(u2.storage_index, self.storage_index)
15600+        self.failUnlessEqual(u2.fingerprint, self.fingerprint)
15601+
15602+    def test_create_writable_mdmf_cap_from_readcap(self):
15603+        # we shouldn't be able to create a writable MDMF cap given only a
15604+        # readcap.
15605+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15606+        cap = u1.to_string()
15607+        self.failUnlessRaises(uri.BadURIError,
15608+                              uri.WritableMDMFFileURI.init_from_string,
15609+                              cap)
15610+
15611+    def test_create_writable_mdmf_cap_from_verifycap(self):
15612+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15613+        cap = u1.to_string()
15614+        self.failUnlessRaises(uri.BadURIError,
15615+                              uri.WritableMDMFFileURI.init_from_string,
15616+                              cap)
15617+
15618+    def test_create_readonly_mdmf_cap_from_verifycap(self):
15619+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15620+        cap = u1.to_string()
15621+        self.failUnlessRaises(uri.BadURIError,
15622+                              uri.ReadonlyMDMFFileURI.init_from_string,
15623+                              cap)
15624+
15625+    def test_mdmf_verifier_cap(self):
15626+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15627+        self.failUnless(u1.is_readonly())
15628+        self.failIf(u1.is_mutable())
15629+        self.failUnlessReallyEqual(self.storage_index, u1.storage_index)
15630+        self.failUnlessReallyEqual(self.fingerprint, u1.fingerprint)
15631+
15632+        cap = u1.to_string()
15633+        u2 = uri.MDMFVerifierURI.init_from_string(cap)
15634+        self.failUnless(u2.is_readonly())
15635+        self.failIf(u2.is_mutable())
15636+        self.failUnlessReallyEqual(self.storage_index, u2.storage_index)
15637+        self.failUnlessReallyEqual(self.fingerprint, u2.fingerprint)
15638+
15639+        u3 = u2.get_readonly()
15640+        self.failUnlessReallyEqual(u3, u2)
15641+
15642+        u4 = u2.get_verify_cap()
15643+        self.failUnlessReallyEqual(u4, u2)
15644+
15645+    def test_mdmf_cap_extra_information(self):
15646+        # MDMF caps can be arbitrarily extended after the fingerprint
15647+        # and key/storage index fields.
15648+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15649+        self.failUnlessEqual([], u1.get_extension_params())
15650+
15651+        cap = u1.to_string()
15652+        # Now let's append some fields. Say, 131073 (the segment size)
15653+        # and 3 (the "k" encoding parameter).
15654+        expected_extensions = []
15655+        for e in ('131073', '3'):
15656+            cap += (":%s" % e)
15657+            expected_extensions.append(e)
15658+
15659+            u2 = uri.WritableMDMFFileURI.init_from_string(cap)
15660+            self.failUnlessReallyEqual(self.writekey, u2.writekey)
15661+            self.failUnlessReallyEqual(self.fingerprint, u2.fingerprint)
15662+            self.failIf(u2.is_readonly())
15663+            self.failUnless(u2.is_mutable())
15664+
15665+            c2 = u2.to_string()
15666+            u2n = uri.WritableMDMFFileURI.init_from_string(c2)
15667+            self.failUnlessReallyEqual(u2, u2n)
15668+
15669+            # We should get the extra back when we ask for it.
15670+            self.failUnlessEqual(expected_extensions, u2.get_extension_params())
15671+
15672+            # These should be preserved through cap attenuation, too.
15673+            u3 = u2.get_readonly()
15674+            self.failUnlessReallyEqual(self.readkey, u3.readkey)
15675+            self.failUnlessReallyEqual(self.fingerprint, u3.fingerprint)
15676+            self.failUnless(u3.is_readonly())
15677+            self.failUnless(u3.is_mutable())
15678+            self.failUnlessEqual(expected_extensions, u3.get_extension_params())
15679+
15680+            c3 = u3.to_string()
15681+            u3n = uri.ReadonlyMDMFFileURI.init_from_string(c3)
15682+            self.failUnlessReallyEqual(u3, u3n)
15683+
15684+            u4 = u3.get_verify_cap()
15685+            self.failUnlessReallyEqual(self.storage_index, u4.storage_index)
15686+            self.failUnlessReallyEqual(self.fingerprint, u4.fingerprint)
15687+            self.failUnless(u4.is_readonly())
15688+            self.failIf(u4.is_mutable())
15689+
15690+            c4 = u4.to_string()
15691+            u4n = uri.MDMFVerifierURI.init_from_string(c4)
15692+            self.failUnlessReallyEqual(u4n, u4)
15693+
15694+            self.failUnlessEqual(expected_extensions, u4.get_extension_params())
15695+
15696+
15697+    def test_sdmf_cap_extra_information(self):
15698+        # For interface consistency, we define a method to get
15699+        # extensions for SDMF files as well. This method must always
15700+        # return no extensions, since SDMF files were not created with
15701+        # extensions and cannot be modified to include extensions
15702+        # without breaking older clients.
15703+        u1 = uri.WriteableSSKFileURI(self.writekey, self.fingerprint)
15704+        cap = u1.to_string()
15705+        u2 = uri.WriteableSSKFileURI.init_from_string(cap)
15706+        self.failUnlessEqual([], u2.get_extension_params())
15707+
15708+    def test_extension_character_range(self):
15709+        # As written now, we shouldn't put things other than numbers in
15710+        # the extension fields.
15711+        writecap = uri.WritableMDMFFileURI(self.writekey, self.fingerprint).to_string()
15712+        readcap  = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint).to_string()
15713+        vcap     = uri.MDMFVerifierURI(self.storage_index, self.fingerprint).to_string()
15714+        self.failUnlessRaises(uri.BadURIError,
15715+                              uri.WritableMDMFFileURI.init_from_string,
15716+                              ("%s:invalid" % writecap))
15717+        self.failUnlessRaises(uri.BadURIError,
15718+                              uri.ReadonlyMDMFFileURI.init_from_string,
15719+                              ("%s:invalid" % readcap))
15720+        self.failUnlessRaises(uri.BadURIError,
15721+                              uri.MDMFVerifierURI.init_from_string,
15722+                              ("%s:invalid" % vcap))
15723+
15724+
15725+    def test_mdmf_valid_human_encoding(self):
15726+        # What's a human encoding? Well, it's of the form:
15727+        base = "https://127.0.0.1:3456/uri/"
15728+        # With a cap on the end. For each of the cap types, we need to
15729+        # test that a valid cap (with and without the traditional
15730+        # separators) is recognized and accepted by the classes.
15731+        w1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15732+        w2 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15733+                                     ['131073', '3'])
15734+        r1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15735+        r2 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15736+                                     ['131073', '3'])
15737+        v1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15738+        v2 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15739+                                 ['131073', '3'])
15740+
15741+        # These will yield six different caps.
15742+        for o in (w1, w2, r1 , r2, v1, v2):
15743+            url = base + o.to_string()
15744+            o1 = o.__class__.init_from_human_encoding(url)
15745+            self.failUnlessReallyEqual(o1, o)
15746+
15747+            # Note that our cap will, by default, have : as separators.
15748+            # But it's expected that users from, e.g., the WUI, will
15749+            # have %3A as a separator. We need to make sure that the
15750+            # initialization routine handles that, too.
15751+            cap = o.to_string()
15752+            cap = re.sub(":", "%3A", cap)
15753+            url = base + cap
15754+            o2 = o.__class__.init_from_human_encoding(url)
15755+            self.failUnlessReallyEqual(o2, o)
15756+
15757+
15758+    def test_mdmf_human_encoding_invalid_base(self):
15759+        # What's a human encoding? Well, it's of the form:
15760+        base = "https://127.0.0.1:3456/foo/bar/bazuri/"
15761+        # With a cap on the end. For each of the cap types, we need to
15762+        # test that a valid cap (with and without the traditional
15763+        # separators) is recognized and accepted by the classes.
15764+        w1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15765+        w2 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15766+                                     ['131073', '3'])
15767+        r1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15768+        r2 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15769+                                     ['131073', '3'])
15770+        v1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15771+        v2 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15772+                                 ['131073', '3'])
15773+
15774+        # These will yield six different caps.
15775+        for o in (w1, w2, r1 , r2, v1, v2):
15776+            url = base + o.to_string()
15777+            self.failUnlessRaises(uri.BadURIError,
15778+                                  o.__class__.init_from_human_encoding,
15779+                                  url)
15780+
15781+    def test_mdmf_human_encoding_invalid_cap(self):
15782+        base = "https://127.0.0.1:3456/uri/"
15783+        # With a cap on the end. For each of the cap types, we need to
15784+        # test that a valid cap (with and without the traditional
15785+        # separators) is recognized and accepted by the classes.
15786+        w1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15787+        w2 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15788+                                     ['131073', '3'])
15789+        r1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15790+        r2 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15791+                                     ['131073', '3'])
15792+        v1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15793+        v2 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15794+                                 ['131073', '3'])
15795+
15796+        # These will yield six different caps.
15797+        for o in (w1, w2, r1 , r2, v1, v2):
15798+            # not exhaustive, obviously...
15799+            url = base + o.to_string() + "foobarbaz"
15800+            url2 = base + "foobarbaz" + o.to_string()
15801+            url3 = base + o.to_string()[:25] + "foo" + o.to_string()[:25]
15802+            for u in (url, url2, url3):
15803+                self.failUnlessRaises(uri.BadURIError,
15804+                                      o.__class__.init_from_human_encoding,
15805+                                      u)
15806+
15807+    def test_mdmf_from_string(self):
15808+        # Make sure that the from_string utility function works with
15809+        # MDMF caps.
15810+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint)
15811+        cap = u1.to_string()
15812+        self.failUnless(uri.is_uri(cap))
15813+        u2 = uri.from_string(cap)
15814+        self.failUnlessReallyEqual(u1, u2)
15815+        u3 = uri.from_string_mutable_filenode(cap)
15816+        self.failUnlessEqual(u3, u1)
15817+
15818+        # XXX: We should refactor the extension field into setUp
15819+        u1 = uri.WritableMDMFFileURI(self.writekey, self.fingerprint,
15820+                                     ['131073', '3'])
15821+        cap = u1.to_string()
15822+        self.failUnless(uri.is_uri(cap))
15823+        u2 = uri.from_string(cap)
15824+        self.failUnlessReallyEqual(u1, u2)
15825+        u3 = uri.from_string_mutable_filenode(cap)
15826+        self.failUnlessEqual(u3, u1)
15827+
15828+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint)
15829+        cap = u1.to_string()
15830+        self.failUnless(uri.is_uri(cap))
15831+        u2 = uri.from_string(cap)
15832+        self.failUnlessReallyEqual(u1, u2)
15833+        u3 = uri.from_string_mutable_filenode(cap)
15834+        self.failUnlessEqual(u3, u1)
15835+
15836+        u1 = uri.ReadonlyMDMFFileURI(self.readkey, self.fingerprint,
15837+                                     ['131073', '3'])
15838+        cap = u1.to_string()
15839+        self.failUnless(uri.is_uri(cap))
15840+        u2 = uri.from_string(cap)
15841+        self.failUnlessReallyEqual(u1, u2)
15842+        u3 = uri.from_string_mutable_filenode(cap)
15843+        self.failUnlessEqual(u3, u1)
15844+
15845+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint)
15846+        cap = u1.to_string()
15847+        self.failUnless(uri.is_uri(cap))
15848+        u2 = uri.from_string(cap)
15849+        self.failUnlessReallyEqual(u1, u2)
15850+        u3 = uri.from_string_verifier(cap)
15851+        self.failUnlessEqual(u3, u1)
15852+
15853+        u1 = uri.MDMFVerifierURI(self.storage_index, self.fingerprint,
15854+                                 ['131073', '3'])
15855+        cap = u1.to_string()
15856+        self.failUnless(uri.is_uri(cap))
15857+        u2 = uri.from_string(cap)
15858+        self.failUnlessReallyEqual(u1, u2)
15859+        u3 = uri.from_string_verifier(cap)
15860+        self.failUnlessEqual(u3, u1)
15861+
15862+
15863 class Dirnode(testutil.ReallyEqualMixin, unittest.TestCase):
15864     def test_pack(self):
15865         writekey = "\x01" * 16
15866hunk ./src/allmydata/uri.py 31
15867 SEP='(?::|%3A)'
15868 NUMBER='([0-9]+)'
15869 NUMBER_IGNORE='(?:[0-9]+)'
15870+OPTIONAL_EXTENSION_FIELD = '(' + SEP + '[0-9' + SEP + ']+|)'
15871 
15872 # "human-encoded" URIs are allowed to come with a leading
15873 # 'http://127.0.0.1:(8123|3456)/uri/' that will be ignored.
15874hunk ./src/allmydata/uri.py 297
15875     def get_verify_cap(self):
15876         return SSKVerifierURI(self.storage_index, self.fingerprint)
15877 
15878+    def get_extension_params(self):
15879+        return []
15880 
15881 class ReadonlySSKFileURI(_BaseURI):
15882     implements(IURI, IMutableFileURI)
15883hunk ./src/allmydata/uri.py 354
15884     def get_verify_cap(self):
15885         return SSKVerifierURI(self.storage_index, self.fingerprint)
15886 
15887+    def get_extension_params(self):
15888+        return []
15889 
15890 class SSKVerifierURI(_BaseURI):
15891     implements(IVerifierURI)
15892hunk ./src/allmydata/uri.py 401
15893     def get_verify_cap(self):
15894         return self
15895 
15896+    def get_extension_params(self):
15897+        return []
15898+
15899+class WritableMDMFFileURI(_BaseURI):
15900+    implements(IURI, IMutableFileURI)
15901+
15902+    BASE_STRING='URI:MDMF:'
15903+    STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
15904+    HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
15905+
15906+    def __init__(self, writekey, fingerprint, params=[]):
15907+        self.writekey = writekey
15908+        self.readkey = hashutil.ssk_readkey_hash(writekey)
15909+        self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
15910+        assert len(self.storage_index) == 16
15911+        self.fingerprint = fingerprint
15912+        self.extension = params
15913+
15914+    @classmethod
15915+    def init_from_human_encoding(cls, uri):
15916+        mo = cls.HUMAN_RE.search(uri)
15917+        if not mo:
15918+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
15919+        params = filter(lambda x: x != '', re.split(SEP, mo.group(3)))
15920+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
15921+
15922+    @classmethod
15923+    def init_from_string(cls, uri):
15924+        mo = cls.STRING_RE.search(uri)
15925+        if not mo:
15926+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
15927+        params = mo.group(3)
15928+        params = filter(lambda x: x != '', params.split(":"))
15929+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
15930+
15931+    def to_string(self):
15932+        assert isinstance(self.writekey, str)
15933+        assert isinstance(self.fingerprint, str)
15934+        ret = 'URI:MDMF:%s:%s' % (base32.b2a(self.writekey),
15935+                                  base32.b2a(self.fingerprint))
15936+        if self.extension:
15937+            ret += ":"
15938+            ret += ":".join(self.extension)
15939+
15940+        return ret
15941+
15942+    def __repr__(self):
15943+        return "<%s %s>" % (self.__class__.__name__, self.abbrev())
15944+
15945+    def abbrev(self):
15946+        return base32.b2a(self.writekey[:5])
15947+
15948+    def abbrev_si(self):
15949+        return base32.b2a(self.storage_index)[:5]
15950+
15951+    def is_readonly(self):
15952+        return False
15953+
15954+    def is_mutable(self):
15955+        return True
15956+
15957+    def get_readonly(self):
15958+        return ReadonlyMDMFFileURI(self.readkey, self.fingerprint, self.extension)
15959+
15960+    def get_verify_cap(self):
15961+        return MDMFVerifierURI(self.storage_index, self.fingerprint, self.extension)
15962+
15963+    def get_extension_params(self):
15964+        return self.extension
15965+
15966+class ReadonlyMDMFFileURI(_BaseURI):
15967+    implements(IURI, IMutableFileURI)
15968+
15969+    BASE_STRING='URI:MDMF-RO:'
15970+    STRING_RE=re.compile('^' +BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
15971+    HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF-RO'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
15972+
15973+    def __init__(self, readkey, fingerprint, params=[]):
15974+        self.readkey = readkey
15975+        self.storage_index = hashutil.ssk_storage_index_hash(self.readkey)
15976+        assert len(self.storage_index) == 16
15977+        self.fingerprint = fingerprint
15978+        self.extension = params
15979+
15980+    @classmethod
15981+    def init_from_human_encoding(cls, uri):
15982+        mo = cls.HUMAN_RE.search(uri)
15983+        if not mo:
15984+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
15985+        params = mo.group(3)
15986+        params = filter(lambda x: x!= '', re.split(SEP, params))
15987+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
15988+
15989+    @classmethod
15990+    def init_from_string(cls, uri):
15991+        mo = cls.STRING_RE.search(uri)
15992+        if not mo:
15993+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
15994+
15995+        params = mo.group(3)
15996+        params = filter(lambda x: x != '', params.split(":"))
15997+        return cls(base32.a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
15998+
15999+    def to_string(self):
16000+        assert isinstance(self.readkey, str)
16001+        assert isinstance(self.fingerprint, str)
16002+        ret = 'URI:MDMF-RO:%s:%s' % (base32.b2a(self.readkey),
16003+                                     base32.b2a(self.fingerprint))
16004+        if self.extension:
16005+            ret += ":"
16006+            ret += ":".join(self.extension)
16007+
16008+        return ret
16009+
16010+    def __repr__(self):
16011+        return "<%s %s>" % (self.__class__.__name__, self.abbrev())
16012+
16013+    def abbrev(self):
16014+        return base32.b2a(self.readkey[:5])
16015+
16016+    def abbrev_si(self):
16017+        return base32.b2a(self.storage_index)[:5]
16018+
16019+    def is_readonly(self):
16020+        return True
16021+
16022+    def is_mutable(self):
16023+        return True
16024+
16025+    def get_readonly(self):
16026+        return self
16027+
16028+    def get_verify_cap(self):
16029+        return MDMFVerifierURI(self.storage_index, self.fingerprint, self.extension)
16030+
16031+    def get_extension_params(self):
16032+        return self.extension
16033+
16034+class MDMFVerifierURI(_BaseURI):
16035+    implements(IVerifierURI)
16036+
16037+    BASE_STRING='URI:MDMF-Verifier:'
16038+    STRING_RE=re.compile('^'+BASE_STRING+BASE32STR_128bits+':'+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
16039+    HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'MDMF-Verifier'+SEP+BASE32STR_128bits+SEP+BASE32STR_256bits+OPTIONAL_EXTENSION_FIELD+'$')
16040+
16041+    def __init__(self, storage_index, fingerprint, params=[]):
16042+        assert len(storage_index) == 16
16043+        self.storage_index = storage_index
16044+        self.fingerprint = fingerprint
16045+        self.extension = params
16046+
16047+    @classmethod
16048+    def init_from_human_encoding(cls, uri):
16049+        mo = cls.HUMAN_RE.search(uri)
16050+        if not mo:
16051+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
16052+        params = mo.group(3)
16053+        params = filter(lambda x: x != '', re.split(SEP, params))
16054+        return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
16055+
16056+    @classmethod
16057+    def init_from_string(cls, uri):
16058+        mo = cls.STRING_RE.search(uri)
16059+        if not mo:
16060+            raise BadURIError("'%s' doesn't look like a %s cap" % (uri, cls))
16061+        params = mo.group(3)
16062+        params = filter(lambda x: x != '', params.split(":"))
16063+        return cls(si_a2b(mo.group(1)), base32.a2b(mo.group(2)), params)
16064+
16065+    def to_string(self):
16066+        assert isinstance(self.storage_index, str)
16067+        assert isinstance(self.fingerprint, str)
16068+        ret = 'URI:MDMF-Verifier:%s:%s' % (si_b2a(self.storage_index),
16069+                                           base32.b2a(self.fingerprint))
16070+        if self.extension:
16071+            ret += ':'
16072+            ret += ":".join(self.extension)
16073+
16074+        return ret
16075+
16076+    def is_readonly(self):
16077+        return True
16078+
16079+    def is_mutable(self):
16080+        return False
16081+
16082+    def get_readonly(self):
16083+        return self
16084+
16085+    def get_verify_cap(self):
16086+        return self
16087+
16088+    def get_extension_params(self):
16089+        return self.extension
16090+
16091 class _DirectoryBaseURI(_BaseURI):
16092     implements(IURI, IDirnodeURI)
16093     def __init__(self, filenode_uri=None):
16094hunk ./src/allmydata/uri.py 831
16095             kind = "URI:SSK-RO readcap to a mutable file"
16096         elif s.startswith('URI:SSK-Verifier:'):
16097             return SSKVerifierURI.init_from_string(s)
16098+        elif s.startswith('URI:MDMF:'):
16099+            return WritableMDMFFileURI.init_from_string(s)
16100+        elif s.startswith('URI:MDMF-RO:'):
16101+            return ReadonlyMDMFFileURI.init_from_string(s)
16102+        elif s.startswith('URI:MDMF-Verifier:'):
16103+            return MDMFVerifierURI.init_from_string(s)
16104         elif s.startswith('URI:DIR2:'):
16105             if can_be_writeable:
16106                 return DirectoryURI.init_from_string(s)
16107}
16108[nodemaker, mutable/filenode: train nodemaker and filenode to handle MDMF caps
16109Kevan Carstensen <kevan@isnotajoke.com>**20110501224523
16110 Ignore-this: 1f3b4581eb583e7bb93d234182bda395
16111] {
16112hunk ./src/allmydata/mutable/filenode.py 12
16113      IMutableFileVersion, IWritable
16114 from allmydata.util import hashutil, log, consumer, deferredutil, mathutil
16115 from allmydata.util.assertutil import precondition
16116-from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI
16117+from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI, \
16118+                          WritableMDMFFileURI, ReadonlyMDMFFileURI
16119 from allmydata.monitor import Monitor
16120 from pycryptopp.cipher.aes import AES
16121 
16122hunk ./src/allmydata/mutable/filenode.py 75
16123         # set to this default value in case neither of those things happen,
16124         # or in case the servermap can't find any shares to tell us what
16125         # to publish as.
16126-        # TODO: Set this back to None, and find out why the tests fail
16127-        #       with it set to None.
16128+        # XXX: Version should come in via the constructor.
16129         self._protocol_version = None
16130 
16131         # all users of this MutableFileNode go through the serializer. This
16132hunk ./src/allmydata/mutable/filenode.py 95
16133         # verification key, nor things like 'k' or 'N'. If and when someone
16134         # wants to get our contents, we'll pull from shares and fill those
16135         # in.
16136-        assert isinstance(filecap, (ReadonlySSKFileURI, WriteableSSKFileURI))
16137+        if isinstance(filecap, (WritableMDMFFileURI, ReadonlyMDMFFileURI)):
16138+            self._protocol_version = MDMF_VERSION
16139+        elif isinstance(filecap, (ReadonlySSKFileURI, WriteableSSKFileURI)):
16140+            self._protocol_version = SDMF_VERSION
16141+
16142         self._uri = filecap
16143         self._writekey = None
16144hunk ./src/allmydata/mutable/filenode.py 102
16145-        if isinstance(filecap, WriteableSSKFileURI):
16146+
16147+        if not filecap.is_readonly() and filecap.is_mutable():
16148             self._writekey = self._uri.writekey
16149         self._readkey = self._uri.readkey
16150         self._storage_index = self._uri.storage_index
16151hunk ./src/allmydata/mutable/filenode.py 131
16152         self._writekey = hashutil.ssk_writekey_hash(privkey_s)
16153         self._encprivkey = self._encrypt_privkey(self._writekey, privkey_s)
16154         self._fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
16155-        self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
16156+        if self._protocol_version == MDMF_VERSION:
16157+            self._uri = WritableMDMFFileURI(self._writekey, self._fingerprint)
16158+        else:
16159+            self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
16160         self._readkey = self._uri.readkey
16161         self._storage_index = self._uri.storage_index
16162         initial_contents = self._get_initial_contents(contents)
16163hunk ./src/allmydata/nodemaker.py 82
16164             return self._create_immutable(cap)
16165         if isinstance(cap, uri.CHKFileVerifierURI):
16166             return self._create_immutable_verifier(cap)
16167-        if isinstance(cap, (uri.ReadonlySSKFileURI, uri.WriteableSSKFileURI)):
16168+        if isinstance(cap, (uri.ReadonlySSKFileURI, uri.WriteableSSKFileURI,
16169+                            uri.WritableMDMFFileURI, uri.ReadonlyMDMFFileURI)):
16170             return self._create_mutable(cap)
16171         if isinstance(cap, (uri.DirectoryURI,
16172                             uri.ReadonlyDirectoryURI,
16173hunk ./src/allmydata/test/test_mutable.py 196
16174                     offset2 = 0
16175                 if offset1 == "pubkey" and IV:
16176                     real_offset = 107
16177-                elif offset1 == "share_data" and not IV:
16178-                    real_offset = 107
16179                 elif offset1 in o:
16180                     real_offset = o[offset1]
16181                 else:
16182hunk ./src/allmydata/test/test_mutable.py 270
16183         return d
16184 
16185 
16186+    def test_mdmf_filenode_cap(self):
16187+        # Test that an MDMF filenode, once created, returns an MDMF URI.
16188+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16189+        def _created(n):
16190+            self.failUnless(isinstance(n, MutableFileNode))
16191+            cap = n.get_cap()
16192+            self.failUnless(isinstance(cap, uri.WritableMDMFFileURI))
16193+            rcap = n.get_readcap()
16194+            self.failUnless(isinstance(rcap, uri.ReadonlyMDMFFileURI))
16195+            vcap = n.get_verify_cap()
16196+            self.failUnless(isinstance(vcap, uri.MDMFVerifierURI))
16197+        d.addCallback(_created)
16198+        return d
16199+
16200+
16201+    def test_create_from_mdmf_writecap(self):
16202+        # Test that the nodemaker is capable of creating an MDMF
16203+        # filenode given an MDMF cap.
16204+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16205+        def _created(n):
16206+            self.failUnless(isinstance(n, MutableFileNode))
16207+            s = n.get_uri()
16208+            self.failUnless(s.startswith("URI:MDMF"))
16209+            n2 = self.nodemaker.create_from_cap(s)
16210+            self.failUnless(isinstance(n2, MutableFileNode))
16211+            self.failUnlessEqual(n.get_storage_index(), n2.get_storage_index())
16212+            self.failUnlessEqual(n.get_uri(), n2.get_uri())
16213+        d.addCallback(_created)
16214+        return d
16215+
16216+
16217+    def test_create_from_mdmf_writecap_with_extensions(self):
16218+        # Test that the nodemaker is capable of creating an MDMF
16219+        # filenode when given a writecap with extension parameters in
16220+        # them.
16221+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16222+        def _created(n):
16223+            self.failUnless(isinstance(n, MutableFileNode))
16224+            s = n.get_uri()
16225+            s2 = "%s:3:131073" % s
16226+            n2 = self.nodemaker.create_from_cap(s2)
16227+
16228+            self.failUnlessEqual(n2.get_storage_index(), n.get_storage_index())
16229+            self.failUnlessEqual(n.get_writekey(), n2.get_writekey())
16230+        d.addCallback(_created)
16231+        return d
16232+
16233+
16234+    def test_create_from_mdmf_readcap(self):
16235+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16236+        def _created(n):
16237+            self.failUnless(isinstance(n, MutableFileNode))
16238+            s = n.get_readonly_uri()
16239+            n2 = self.nodemaker.create_from_cap(s)
16240+            self.failUnless(isinstance(n2, MutableFileNode))
16241+
16242+            # Check that it's a readonly node
16243+            self.failUnless(n2.is_readonly())
16244+        d.addCallback(_created)
16245+        return d
16246+
16247+
16248+    def test_create_from_mdmf_readcap_with_extensions(self):
16249+        # We should be able to create an MDMF filenode with the
16250+        # extension parameters without it breaking.
16251+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16252+        def _created(n):
16253+            self.failUnless(isinstance(n, MutableFileNode))
16254+            s = n.get_readonly_uri()
16255+            s = "%s:3:131073" % s
16256+
16257+            n2 = self.nodemaker.create_from_cap(s)
16258+            self.failUnless(isinstance(n2, MutableFileNode))
16259+            self.failUnless(n2.is_readonly())
16260+            self.failUnlessEqual(n.get_storage_index(), n2.get_storage_index())
16261+        d.addCallback(_created)
16262+        return d
16263+
16264+
16265+    def test_internal_version_from_cap(self):
16266+        # MutableFileNodes and MutableFileVersions have an internal
16267+        # switch that tells them whether they're dealing with an SDMF or
16268+        # MDMF mutable file when they start doing stuff. We want to make
16269+        # sure that this is set appropriately given an MDMF cap.
16270+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16271+        def _created(n):
16272+            self.uri = n.get_uri()
16273+            self.failUnlessEqual(n._protocol_version, MDMF_VERSION)
16274+
16275+            n2 = self.nodemaker.create_from_cap(self.uri)
16276+            self.failUnlessEqual(n2._protocol_version, MDMF_VERSION)
16277+        d.addCallback(_created)
16278+        return d
16279+
16280+
16281     def test_serialize(self):
16282         n = MutableFileNode(None, None, {"k": 3, "n": 10}, None)
16283         calls = []
16284hunk ./src/allmydata/test/test_mutable.py 464
16285         return d
16286 
16287 
16288+    def test_download_from_mdmf_cap(self):
16289+        # We should be able to download an MDMF file given its cap
16290+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16291+        def _created(node):
16292+            self.uri = node.get_uri()
16293+
16294+            return node.overwrite(MutableData("contents1" * 100000))
16295+        def _then(ignored):
16296+            node = self.nodemaker.create_from_cap(self.uri)
16297+            return node.download_best_version()
16298+        def _downloaded(data):
16299+            self.failUnlessEqual(data, "contents1" * 100000)
16300+        d.addCallback(_created)
16301+        d.addCallback(_then)
16302+        d.addCallback(_downloaded)
16303+        return d
16304+
16305+
16306     def test_mdmf_write_count(self):
16307         # Publishing an MDMF file should only cause one write for each
16308         # share that is to be published. Otherwise, we introduce
16309hunk ./src/allmydata/test/test_mutable.py 1735
16310     def test_verify_mdmf_bad_encprivkey(self):
16311         d = self.publish_mdmf()
16312         d.addCallback(lambda ignored:
16313-            corrupt(None, self._storage, "enc_privkey", [1]))
16314+            corrupt(None, self._storage, "enc_privkey", [0]))
16315         d.addCallback(lambda ignored:
16316             self._fn.check(Monitor(), verify=True))
16317         d.addCallback(self.check_bad, "test_verify_mdmf_bad_encprivkey")
16318hunk ./src/allmydata/test/test_mutable.py 2843
16319         return d
16320 
16321 
16322+    def test_version_extension_api(self):
16323+        # We need to define an API by which an uploader can set the
16324+        # extension parameters, and by which a downloader can retrieve
16325+        # extensions.
16326+        self.failUnless(False)
16327+
16328+
16329+    def test_extensions_from_cap(self):
16330+        self.failUnless(False)
16331+
16332+
16333+    def test_extensions_from_upload(self):
16334+        self.failUnless(False)
16335+
16336+
16337+    def test_cap_after_upload(self):
16338+        self.failUnless(False)
16339+
16340+
16341     def test_get_writekey(self):
16342         d = self.mdmf_node.get_best_mutable_version()
16343         d.addCallback(lambda bv:
16344}
16345[mutable/retrieve: fix typo in paused check
16346Kevan Carstensen <kevan@isnotajoke.com>**20110515225946
16347 Ignore-this: a9c7f3bdbab2f8248f8b6a64f574e7c4
16348] hunk ./src/allmydata/mutable/retrieve.py 207
16349         """
16350         if self._paused:
16351             d = defer.Deferred()
16352-            self._pause_defered.addCallback(lambda ignored: d.callback(res))
16353+            self._pause_deferred.addCallback(lambda ignored: d.callback(res))
16354             return d
16355         return defer.succeed(res)
16356 
16357[scripts/tahoe_put.py: teach tahoe put about MDMF caps
16358Kevan Carstensen <kevan@isnotajoke.com>**20110515230008
16359 Ignore-this: 1522f434f651683c924e37251a3c1bfd
16360] hunk ./src/allmydata/scripts/tahoe_put.py 49
16361         #  DIRCAP:./subdir/foo : DIRCAP/subdir/foo
16362         #  MUTABLE-FILE-WRITECAP : filecap
16363 
16364-        # FIXME: this shouldn't rely on a particular prefix.
16365-        if to_file.startswith("URI:SSK:"):
16366+        # FIXME: don't hardcode cap format.
16367+        if to_file.startswith("URI:MDMF:") or to_file.startswith("URI:SSK:"):
16368             url = nodeurl + "uri/%s" % urllib.quote(to_file)
16369         else:
16370             try:
16371[test/common.py: fix some MDMF-related bugs in common test fixtures
16372Kevan Carstensen <kevan@isnotajoke.com>**20110515230038
16373 Ignore-this: ab5ffe4789bb5e6ed5f54b91b760bac9
16374] {
16375hunk ./src/allmydata/test/common.py 199
16376                  default_encoding_parameters, history):
16377         self.init_from_cap(make_mutable_file_cap())
16378     def create(self, contents, key_generator=None, keysize=None):
16379+        if self.file_types[self.storage_index] == MDMF_VERSION and \
16380+            isinstance(self.my_uri, (uri.ReadonlySSKFileURI,
16381+                                 uri.WriteableSSKFileURI)):
16382+            self.init_from_cap(make_mdmf_mutable_file_cap())
16383         initial_contents = self._get_initial_contents(contents)
16384         data = initial_contents.read(initial_contents.get_size())
16385         data = "".join(data)
16386hunk ./src/allmydata/test/common.py 220
16387         return contents(self)
16388     def init_from_cap(self, filecap):
16389         assert isinstance(filecap, (uri.WriteableSSKFileURI,
16390-                                    uri.ReadonlySSKFileURI))
16391+                                    uri.ReadonlySSKFileURI,
16392+                                    uri.WritableMDMFFileURI,
16393+                                    uri.ReadonlyMDMFFileURI))
16394         self.my_uri = filecap
16395         self.storage_index = self.my_uri.get_storage_index()
16396hunk ./src/allmydata/test/common.py 225
16397+        if isinstance(filecap, (uri.WritableMDMFFileURI,
16398+                                uri.ReadonlyMDMFFileURI)):
16399+            self.file_types[self.storage_index] = MDMF_VERSION
16400+
16401+        else:
16402+            self.file_types[self.storage_index] = SDMF_VERSION
16403+
16404         return self
16405     def get_cap(self):
16406         return self.my_uri
16407hunk ./src/allmydata/test/common.py 249
16408         return self.my_uri.get_readonly().to_string()
16409     def get_verify_cap(self):
16410         return self.my_uri.get_verify_cap()
16411+    def get_repair_cap(self):
16412+        if self.my_uri.is_readonly():
16413+            return None
16414+        return self.my_uri
16415     def is_readonly(self):
16416         return self.my_uri.is_readonly()
16417     def is_mutable(self):
16418hunk ./src/allmydata/test/common.py 406
16419 def make_mutable_file_cap():
16420     return uri.WriteableSSKFileURI(writekey=os.urandom(16),
16421                                    fingerprint=os.urandom(32))
16422-def make_mutable_file_uri():
16423-    return make_mutable_file_cap().to_string()
16424+
16425+def make_mdmf_mutable_file_cap():
16426+    return uri.WritableMDMFFileURI(writekey=os.urandom(16),
16427+                                   fingerprint=os.urandom(32))
16428+
16429+def make_mutable_file_uri(mdmf=False):
16430+    if mdmf:
16431+        uri = make_mdmf_mutable_file_cap()
16432+    else:
16433+        uri = make_mutable_file_cap()
16434+
16435+    return uri.to_string()
16436 
16437 def make_verifier_uri():
16438     return uri.SSKVerifierURI(storage_index=os.urandom(16),
16439hunk ./src/allmydata/test/common.py 423
16440                               fingerprint=os.urandom(32)).to_string()
16441 
16442+def create_mutable_filenode(contents, mdmf=False):
16443+    # XXX: All of these arguments are kind of stupid.
16444+    if mdmf:
16445+        cap = make_mdmf_mutable_file_cap()
16446+    else:
16447+        cap = make_mutable_file_cap()
16448+
16449+    filenode = FakeMutableFileNode(None, None, None, None)
16450+    filenode.init_from_cap(cap)
16451+    FakeMutableFileNode.all_contents[filenode.storage_index] = contents
16452+    return filenode
16453+
16454+
16455 class FakeDirectoryNode(dirnode.DirectoryNode):
16456     """This offers IDirectoryNode, but uses a FakeMutableFileNode for the
16457     backing store, so it doesn't go to the grid. The child data is still
16458}
16459[test/test_cli: Alter existing MDMF tests to test for MDMF caps
16460Kevan Carstensen <kevan@isnotajoke.com>**20110515230054
16461 Ignore-this: a90d089e1afb0f261710083c2be6b2fa
16462] {
16463hunk ./src/allmydata/test/test_cli.py 13
16464 from allmydata.util import fileutil, hashutil, base32
16465 from allmydata import uri
16466 from allmydata.immutable import upload
16467+from allmydata.interfaces import MDMF_VERSION, SDMF_VERSION
16468 from allmydata.mutable.publish import MutableData
16469 from allmydata.dirnode import normalize
16470 
16471hunk ./src/allmydata/test/test_cli.py 33
16472 from allmydata.test.common_util import StallMixin, ReallyEqualMixin
16473 from allmydata.test.no_network import GridTestMixin
16474 from twisted.internet import threads # CLI tests use deferToThread
16475+from twisted.internet import defer # List uses a DeferredList in one place.
16476 from twisted.python import usage
16477 
16478 from allmydata.util.assertutil import precondition
16479hunk ./src/allmydata/test/test_cli.py 969
16480         d.addCallback(lambda (rc,out,err): self.failUnlessReallyEqual(out, DATA2))
16481         return d
16482 
16483+    def _check_mdmf_json(self, (rc, json, err)):
16484+         self.failUnlessEqual(rc, 0)
16485+         self.failUnlessEqual(err, "")
16486+         self.failUnlessIn('"mutable-type": "mdmf"', json)
16487+         # We also want a valid MDMF cap to be in the json.
16488+         self.failUnlessIn("URI:MDMF", json)
16489+         self.failUnlessIn("URI:MDMF-RO", json)
16490+         self.failUnlessIn("URI:MDMF-Verifier", json)
16491+
16492+    def _check_sdmf_json(self, (rc, json, err)):
16493+        self.failUnlessEqual(rc, 0)
16494+        self.failUnlessEqual(err, "")
16495+        self.failUnlessIn('"mutable-type": "sdmf"', json)
16496+        # We also want to see the appropriate SDMF caps.
16497+        self.failUnlessIn("URI:SSK", json)
16498+        self.failUnlessIn("URI:SSK-RO", json)
16499+        self.failUnlessIn("URI:SSK-Verifier", json)
16500+
16501     def test_mutable_type(self):
16502         self.basedir = "cli/Put/mutable_type"
16503         self.set_up_grid()
16504hunk ./src/allmydata/test/test_cli.py 999
16505                         fn1, "tahoe:uploaded.txt"))
16506         d.addCallback(lambda ignored:
16507             self.do_cli("ls", "--json", "tahoe:uploaded.txt"))
16508-        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
16509+        d.addCallback(self._check_mdmf_json)
16510         d.addCallback(lambda ignored:
16511             self.do_cli("put", "--mutable", "--mutable-type=sdmf",
16512                         fn1, "tahoe:uploaded2.txt"))
16513hunk ./src/allmydata/test/test_cli.py 1005
16514         d.addCallback(lambda ignored:
16515             self.do_cli("ls", "--json", "tahoe:uploaded2.txt"))
16516-        d.addCallback(lambda (rc, json, err):
16517-            self.failUnlessIn("sdmf", json))
16518+        d.addCallback(self._check_sdmf_json)
16519         return d
16520 
16521     def test_mutable_type_unlinked(self):
16522hunk ./src/allmydata/test/test_cli.py 1017
16523         d = self.do_cli("put", "--mutable", "--mutable-type=mdmf", fn1)
16524         d.addCallback(lambda (rc, cap, err):
16525             self.do_cli("ls", "--json", cap))
16526-        d.addCallback(lambda (rc, json, err): self.failUnlessIn("mdmf", json))
16527+        d.addCallback(self._check_mdmf_json)
16528         d.addCallback(lambda ignored:
16529             self.do_cli("put", "--mutable", "--mutable-type=sdmf", fn1))
16530         d.addCallback(lambda (rc, cap, err):
16531hunk ./src/allmydata/test/test_cli.py 1022
16532             self.do_cli("ls", "--json", cap))
16533-        d.addCallback(lambda (rc, json, err):
16534-            self.failUnlessIn("sdmf", json))
16535+        d.addCallback(self._check_sdmf_json)
16536         return d
16537 
16538hunk ./src/allmydata/test/test_cli.py 1025
16539+    def test_put_to_mdmf_cap(self):
16540+        self.basedir = "cli/Put/put_to_mdmf_cap"
16541+        self.set_up_grid()
16542+        data = "data" * 100000
16543+        fn1 = os.path.join(self.basedir, "data")
16544+        fileutil.write(fn1, data)
16545+        d = self.do_cli("put", "--mutable", "--mutable-type=mdmf", fn1)
16546+        def _got_cap((rc, out, err)):
16547+            self.failUnlessEqual(rc, 0)
16548+            self.cap = out
16549+        d.addCallback(_got_cap)
16550+        # Now try to write something to the cap using put.
16551+        data2 = "data2" * 100000
16552+        fn2 = os.path.join(self.basedir, "data2")
16553+        fileutil.write(fn2, data2)
16554+        d.addCallback(lambda ignored:
16555+            self.do_cli("put", fn2, self.cap))
16556+        def _got_put((rc, out, err)):
16557+            self.failUnlessEqual(rc, 0)
16558+            self.failUnlessIn(self.cap, out)
16559+        d.addCallback(_got_put)
16560+        # Now get the cap. We should see the data we just put there.
16561+        d.addCallback(lambda ignored:
16562+            self.do_cli("get", self.cap))
16563+        def _got_data((rc, out, err)):
16564+            self.failUnlessEqual(rc, 0)
16565+            self.failUnlessEqual(out, data2)
16566+        d.addCallback(_got_data)
16567+        return d
16568+
16569+    def test_put_to_sdmf_cap(self):
16570+        self.basedir = "cli/Put/put_to_sdmf_cap"
16571+        self.set_up_grid()
16572+        data = "data" * 100000
16573+        fn1 = os.path.join(self.basedir, "data")
16574+        fileutil.write(fn1, data)
16575+        d = self.do_cli("put", "--mutable", "--mutable-type=sdmf", fn1)
16576+        def _got_cap((rc, out, err)):
16577+            self.failUnlessEqual(rc, 0)
16578+            self.cap = out
16579+        d.addCallback(_got_cap)
16580+        # Now try to write something to the cap using put.
16581+        data2 = "data2" * 100000
16582+        fn2 = os.path.join(self.basedir, "data2")
16583+        fileutil.write(fn2, data2)
16584+        d.addCallback(lambda ignored:
16585+            self.do_cli("put", fn2, self.cap))
16586+        def _got_put((rc, out, err)):
16587+            self.failUnlessEqual(rc, 0)
16588+            self.failUnlessIn(self.cap, out)
16589+        d.addCallback(_got_put)
16590+        # Now get the cap. We should see the data we just put there.
16591+        d.addCallback(lambda ignored:
16592+            self.do_cli("get", self.cap))
16593+        def _got_data((rc, out, err)):
16594+            self.failUnlessEqual(rc, 0)
16595+            self.failUnlessEqual(out, data2)
16596+        d.addCallback(_got_data)
16597+        return d
16598+
16599     def test_mutable_type_invalid_format(self):
16600         o = cli.PutOptions()
16601         self.failUnlessRaises(usage.UsageError,
16602hunk ./src/allmydata/test/test_cli.py 1318
16603         d.addCallback(_check)
16604         return d
16605 
16606+    def _create_directory_structure(self):
16607+        # Create a simple directory structure that we can use for MDMF,
16608+        # SDMF, and immutable testing.
16609+        assert self.g
16610+
16611+        client = self.g.clients[0]
16612+        # Create a dirnode
16613+        d = client.create_dirnode()
16614+        def _got_rootnode(n):
16615+            # Add a few nodes.
16616+            self._dircap = n.get_uri()
16617+            nm = n._nodemaker
16618+            # The uploaders may run at the same time, so we need two
16619+            # MutableData instances or they'll fight over offsets &c and
16620+            # break.
16621+            mutable_data = MutableData("data" * 100000)
16622+            mutable_data2 = MutableData("data" * 100000)
16623+            # Add both kinds of mutable node.
16624+            d1 = nm.create_mutable_file(mutable_data,
16625+                                        version=MDMF_VERSION)
16626+            d2 = nm.create_mutable_file(mutable_data2,
16627+                                        version=SDMF_VERSION)
16628+            # Add an immutable node. We do this through the directory,
16629+            # with add_file.
16630+            immutable_data = upload.Data("immutable data" * 100000,
16631+                                         convergence="")
16632+            d3 = n.add_file(u"immutable", immutable_data)
16633+            ds = [d1, d2, d3]
16634+            dl = defer.DeferredList(ds)
16635+            def _made_files((r1, r2, r3)):
16636+                self.failUnless(r1[0])
16637+                self.failUnless(r2[0])
16638+                self.failUnless(r3[0])
16639+
16640+                # r1, r2, and r3 contain nodes.
16641+                mdmf_node = r1[1]
16642+                sdmf_node = r2[1]
16643+                imm_node = r3[1]
16644+
16645+                self._mdmf_uri = mdmf_node.get_uri()
16646+                self._mdmf_readonly_uri = mdmf_node.get_readonly_uri()
16647+                self._sdmf_uri = mdmf_node.get_uri()
16648+                self._sdmf_readonly_uri = sdmf_node.get_readonly_uri()
16649+                self._imm_uri = imm_node.get_uri()
16650+
16651+                d1 = n.set_node(u"mdmf", mdmf_node)
16652+                d2 = n.set_node(u"sdmf", sdmf_node)
16653+                return defer.DeferredList([d1, d2])
16654+            # We can now list the directory by listing self._dircap.
16655+            dl.addCallback(_made_files)
16656+            return dl
16657+        d.addCallback(_got_rootnode)
16658+        return d
16659+
16660+    def test_list_mdmf(self):
16661+        # 'tahoe ls' should include MDMF files.
16662+        self.basedir = "cli/List/list_mdmf"
16663+        self.set_up_grid()
16664+        d = self._create_directory_structure()
16665+        d.addCallback(lambda ignored:
16666+            self.do_cli("ls", self._dircap))
16667+        def _got_ls((rc, out, err)):
16668+            self.failUnlessEqual(rc, 0)
16669+            self.failUnlessEqual(err, "")
16670+            self.failUnlessIn("immutable", out)
16671+            self.failUnlessIn("mdmf", out)
16672+            self.failUnlessIn("sdmf", out)
16673+        d.addCallback(_got_ls)
16674+        return d
16675+
16676+    def test_list_mdmf_json(self):
16677+        # 'tahoe ls' should include MDMF caps when invoked with MDMF
16678+        # caps.
16679+        self.basedir = "cli/List/list_mdmf_json"
16680+        self.set_up_grid()
16681+        d = self._create_directory_structure()
16682+        d.addCallback(lambda ignored:
16683+            self.do_cli("ls", "--json", self._dircap))
16684+        def _got_json((rc, out, err)):
16685+            self.failUnlessEqual(rc, 0)
16686+            self.failUnlessEqual(err, "")
16687+            self.failUnlessIn(self._mdmf_uri, out)
16688+            self.failUnlessIn(self._mdmf_readonly_uri, out)
16689+            self.failUnlessIn(self._sdmf_uri, out)
16690+            self.failUnlessIn(self._sdmf_readonly_uri, out)
16691+            self.failUnlessIn(self._imm_uri, out)
16692+            self.failUnlessIn('"mutable-type": "sdmf"', out)
16693+            self.failUnlessIn('"mutable-type": "mdmf"', out)
16694+        d.addCallback(_got_json)
16695+        return d
16696+
16697 
16698 class Mv(GridTestMixin, CLITestMixin, unittest.TestCase):
16699     def test_mv_behavior(self):
16700}
16701[test/test_mutable.py: write a test for pausing during retrieval, write support structure for that test
16702Kevan Carstensen <kevan@isnotajoke.com>**20110515230207
16703 Ignore-this: 8884ef3ad5be59dbc870ed14002ac45
16704] {
16705hunk ./src/allmydata/test/test_mutable.py 6
16706 from cStringIO import StringIO
16707 from twisted.trial import unittest
16708 from twisted.internet import defer, reactor
16709+from twisted.internet.interfaces import IConsumer
16710+from zope.interface import implements
16711 from allmydata import uri, client
16712 from allmydata.nodemaker import NodeMaker
16713 from allmydata.util import base32, consumer
16714hunk ./src/allmydata/test/test_mutable.py 466
16715         return d
16716 
16717 
16718+    def test_retrieve_pause(self):
16719+        # We should make sure that the retriever is able to pause
16720+        # correctly.
16721+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16722+        def _created(node):
16723+            self.node = node
16724+
16725+            return node.overwrite(MutableData("contents1" * 100000))
16726+        d.addCallback(_created)
16727+        # Now we'll retrieve it into a pausing consumer.
16728+        d.addCallback(lambda ignored:
16729+            self.node.get_best_mutable_version())
16730+        def _got_version(version):
16731+            self.c = PausingConsumer()
16732+            return version.read(self.c)
16733+        d.addCallback(_got_version)
16734+        d.addCallback(lambda ignored:
16735+            self.failUnlessEqual(self.c.data, "contents1" * 100000))
16736+        return d
16737+    test_retrieve_pause.timeout = 25
16738+
16739+
16740     def test_download_from_mdmf_cap(self):
16741         # We should be able to download an MDMF file given its cap
16742         d = self.nodemaker.create_mutable_file(version=MDMF_VERSION)
16743hunk ./src/allmydata/test/test_mutable.py 944
16744                     index = versionmap[shnum]
16745                     shares[peerid][shnum] = oldshares[index][peerid][shnum]
16746 
16747+class PausingConsumer:
16748+    implements(IConsumer)
16749+    def __init__(self):
16750+        self.data = ""
16751+        self.already_paused = False
16752+
16753+    def registerProducer(self, producer, streaming):
16754+        self.producer = producer
16755+        self.producer.resumeProducing()
16756 
16757hunk ./src/allmydata/test/test_mutable.py 954
16758+    def unregisterProducer(self):
16759+        self.producer = None
16760+
16761+    def _unpause(self, ignored):
16762+        self.producer.resumeProducing()
16763+
16764+    def write(self, data):
16765+        self.data += data
16766+        if not self.already_paused:
16767+           self.producer.pauseProducing()
16768+           self.already_paused = True
16769+           reactor.callLater(15, self._unpause, None)
16770 
16771 
16772 class Servermap(unittest.TestCase, PublishMixin):
16773}
16774[test/test_mutable.py: implement cap type checking
16775Kevan Carstensen <kevan@isnotajoke.com>**20110515230326
16776 Ignore-this: 64cf51b809605061047c8a1b02f5e212
16777] hunk ./src/allmydata/test/test_mutable.py 2904
16778 
16779 
16780     def test_cap_after_upload(self):
16781-        self.failUnless(False)
16782+        # If we create a new mutable file and upload things to it, and
16783+        # it's an MDMF file, we should get an MDMF cap back from that
16784+        # file and should be able to use that.
16785+        # That's essentially what MDMF node is, so just check that.
16786+        mdmf_uri = self.mdmf_node.get_uri()
16787+        cap = uri.from_string(mdmf_uri)
16788+        self.failUnless(isinstance(cap, uri.WritableMDMFFileURI))
16789+        readonly_mdmf_uri = self.mdmf_node.get_readonly_uri()
16790+        cap = uri.from_string(readonly_mdmf_uri)
16791+        self.failUnless(isinstance(cap, uri.ReadonlyMDMFFileURI))
16792 
16793 
16794     def test_get_writekey(self):
16795[test/test_web: add MDMF cap tests
16796Kevan Carstensen <kevan@isnotajoke.com>**20110515230358
16797 Ignore-this: ace5af3bdc9b65c3f6964c8fe056816
16798] {
16799hunk ./src/allmydata/test/test_web.py 27
16800 from allmydata.util.netstring import split_netstring
16801 from allmydata.util.encodingutil import to_str
16802 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
16803-     create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
16804+     create_chk_filenode, WebErrorMixin, ShouldFailMixin, \
16805+     make_mutable_file_uri, create_mutable_filenode
16806 from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
16807 from allmydata.mutable import servermap, publish, retrieve
16808 import allmydata.test.common_util as testutil
16809hunk ./src/allmydata/test/test_web.py 203
16810             foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
16811             self._bar_txt_verifycap = n.get_verify_cap().to_string()
16812 
16813+            # sdmf
16814+            # XXX: Do we ever use this?
16815+            self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
16816+
16817+            foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
16818+
16819+            # mdmf
16820+            self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
16821+            assert self._quux_txt_uri.startswith("URI:MDMF")
16822+            foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
16823+
16824             foo.set_uri(u"empty", res[3][1].get_uri(),
16825                         res[3][1].get_readonly_uri())
16826             sub_uri = res[4][1].get_uri()
16827hunk ./src/allmydata/test/test_web.py 245
16828             # public/
16829             # public/foo/
16830             # public/foo/bar.txt
16831+            # public/foo/baz.txt
16832+            # public/foo/quux.txt
16833             # public/foo/blockingfile
16834             # public/foo/empty/
16835             # public/foo/sub/
16836hunk ./src/allmydata/test/test_web.py 267
16837         n = create_chk_filenode(contents)
16838         return contents, n, n.get_uri()
16839 
16840+    def makefile_mutable(self, number, mdmf=False):
16841+        contents = "contents of mutable file %s\n" % number
16842+        n = create_mutable_filenode(contents, mdmf)
16843+        return contents, n, n.get_uri(), n.get_readonly_uri()
16844+
16845     def tearDown(self):
16846         return self.s.stopService()
16847 
16848hunk ./src/allmydata/test/test_web.py 278
16849     def failUnlessIsBarDotTxt(self, res):
16850         self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
16851 
16852+    def failUnlessIsQuuxDotTxt(self, res):
16853+        self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
16854+
16855+    def failUnlessIsBazDotTxt(self, res):
16856+        self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
16857+
16858     def failUnlessIsBarJSON(self, res):
16859         data = simplejson.loads(res)
16860         self.failUnless(isinstance(data, list))
16861hunk ./src/allmydata/test/test_web.py 295
16862         self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
16863         self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
16864 
16865+    def failUnlessIsQuuxJSON(self, res):
16866+        data = simplejson.loads(res)
16867+        self.failUnless(isinstance(data, list))
16868+        self.failUnlessEqual(data[0], "filenode")
16869+        self.failUnless(isinstance(data[1], dict))
16870+        metadata = data[1]
16871+        return self.failUnlessIsQuuxDotTxtMetadata(metadata)
16872+
16873+    def failUnlessIsQuuxDotTxtMetadata(self, metadata):
16874+        self.failUnless(metadata['mutable'])
16875+        self.failUnless("rw_uri" in metadata)
16876+        self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
16877+        self.failUnless("ro_uri" in metadata)
16878+        self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
16879+        self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
16880+
16881     def failUnlessIsFooJSON(self, res):
16882         data = simplejson.loads(res)
16883         self.failUnless(isinstance(data, list))
16884hunk ./src/allmydata/test/test_web.py 324
16885 
16886         kidnames = sorted([unicode(n) for n in data[1]["children"]])
16887         self.failUnlessEqual(kidnames,
16888-                             [u"bar.txt", u"blockingfile", u"empty",
16889-                              u"n\u00fc.txt", u"sub"])
16890+                             [u"bar.txt", u"baz.txt", u"blockingfile",
16891+                              u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
16892         kids = dict( [(unicode(name),value)
16893                       for (name,value)
16894                       in data[1]["children"].iteritems()] )
16895hunk ./src/allmydata/test/test_web.py 346
16896                                    self._bar_txt_metadata["tahoe"]["linkcrtime"])
16897         self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
16898                                    self._bar_txt_uri)
16899+        self.failUnlessIn("quux.txt", kids)
16900+        self.failUnlessReallyEqual(kids[u"quux.txt"][1]["rw_uri"],
16901+                                   self._quux_txt_uri)
16902+        self.failUnlessReallyEqual(kids[u"quux.txt"][1]["ro_uri"],
16903+                                   self._quux_txt_readonly_uri)
16904 
16905     def GET(self, urlpath, followRedirect=False, return_response=False,
16906             **kwargs):
16907hunk ./src/allmydata/test/test_web.py 851
16908         d.addCallback(self.failUnlessIsBarDotTxt)
16909         return d
16910 
16911+    def test_GET_FILE_URI_mdmf(self):
16912+        base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
16913+        d = self.GET(base)
16914+        d.addCallback(self.failUnlessIsQuuxDotTxt)
16915+        return d
16916+
16917+    def test_GET_FILE_URI_mdmf_extensions(self):
16918+        base = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
16919+        d = self.GET(base)
16920+        d.addCallback(self.failUnlessIsQuuxDotTxt)
16921+        return d
16922+
16923+    def test_GET_FILE_URI_mdmf_readonly(self):
16924+        base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
16925+        d = self.GET(base)
16926+        d.addCallback(self.failUnlessIsQuuxDotTxt)
16927+        return d
16928+
16929     def test_GET_FILE_URI_badchild(self):
16930         base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
16931         errmsg = "Files have no children, certainly not named 'boguschild'"
16932hunk ./src/allmydata/test/test_web.py 885
16933                              self.PUT, base, "")
16934         return d
16935 
16936+    def test_PUT_FILE_URI_mdmf(self):
16937+        base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
16938+        self._quux_new_contents = "new_contents"
16939+        d = self.GET(base)
16940+        d.addCallback(lambda res:
16941+            self.failUnlessIsQuuxDotTxt(res))
16942+        d.addCallback(lambda ignored:
16943+            self.PUT(base, self._quux_new_contents))
16944+        d.addCallback(lambda ignored:
16945+            self.GET(base))
16946+        d.addCallback(lambda res:
16947+            self.failUnlessReallyEqual(res, self._quux_new_contents))
16948+        return d
16949+
16950+    def test_PUT_FILE_URI_mdmf_extensions(self):
16951+        base = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
16952+        self._quux_new_contents = "new_contents"
16953+        d = self.GET(base)
16954+        d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
16955+        d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
16956+        d.addCallback(lambda ignored: self.GET(base))
16957+        d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
16958+                                                       res))
16959+        return d
16960+
16961+    def test_PUT_FILE_URI_mdmf_readonly(self):
16962+        # We're not allowed to PUT things to a readonly cap.
16963+        base = "/uri/%s" % self._quux_txt_readonly_uri
16964+        d = self.GET(base)
16965+        d.addCallback(lambda res:
16966+            self.failUnlessIsQuuxDotTxt(res))
16967+        # What should we get here? We get a 500 error now; that's not right.
16968+        d.addCallback(lambda ignored:
16969+            self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
16970+                             "400 Bad Request", "read-only cap",
16971+                             self.PUT, base, "new data"))
16972+        return d
16973+
16974+    def test_PUT_FILE_URI_sdmf_readonly(self):
16975+        # We're not allowed to put things to a readonly cap.
16976+        base = "/uri/%s" % self._baz_txt_readonly_uri
16977+        d = self.GET(base)
16978+        d.addCallback(lambda res:
16979+            self.failUnlessIsBazDotTxt(res))
16980+        d.addCallback(lambda ignored:
16981+            self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
16982+                             "400 Bad Request", "read-only cap",
16983+                             self.PUT, base, "new_data"))
16984+        return d
16985+
16986     # TODO: version of this with a Unicode filename
16987     def test_GET_FILEURL_save(self):
16988         d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
16989hunk ./src/allmydata/test/test_web.py 951
16990         d.addBoth(self.should404, "test_GET_FILEURL_missing")
16991         return d
16992 
16993+    def test_GET_FILEURL_info_mdmf(self):
16994+        d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
16995+        def _got(res):
16996+            self.failUnlessIn("mutable file (mdmf)", res)
16997+            self.failUnlessIn(self._quux_txt_uri, res)
16998+            self.failUnlessIn(self._quux_txt_readonly_uri, res)
16999+        d.addCallback(_got)
17000+        return d
17001+
17002+    def test_GET_FILEURL_info_mdmf_readonly(self):
17003+        d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
17004+        def _got(res):
17005+            self.failUnlessIn("mutable file (mdmf)", res)
17006+            self.failIfIn(self._quux_txt_uri, res)
17007+            self.failUnlessIn(self._quux_txt_readonly_uri, res)
17008+        d.addCallback(_got)
17009+        return d
17010+
17011+    def test_GET_FILEURL_info_sdmf(self):
17012+        d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
17013+        def _got(res):
17014+            self.failUnlessIn("mutable file (sdmf)", res)
17015+            self.failUnlessIn(self._baz_txt_uri, res)
17016+        d.addCallback(_got)
17017+        return d
17018+
17019+    def test_GET_FILEURL_info_mdmf_extensions(self):
17020+        d = self.GET("/uri/%s:3:131073?t=info" % self._quux_txt_uri)
17021+        def _got(res):
17022+            self.failUnlessIn("mutable file (mdmf)", res)
17023+            self.failUnlessIn(self._quux_txt_uri, res)
17024+            self.failUnlessIn(self._quux_txt_readonly_uri, res)
17025+        d.addCallback(_got)
17026+        return d
17027+
17028     def test_PUT_overwrite_only_files(self):
17029         # create a directory, put a file in that directory.
17030         contents, n, filecap = self.makefile(8)
17031hunk ./src/allmydata/test/test_web.py 1033
17032         contents = self.NEWFILE_CONTENTS * 300000
17033         d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
17034                      contents)
17035+        def _got_filecap(filecap):
17036+            self.failUnless(filecap.startswith("URI:MDMF"))
17037+            return filecap
17038+        d.addCallback(_got_filecap)
17039         d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
17040         d.addCallback(lambda json: self.failUnlessIn("mdmf", json))
17041         return d
17042hunk ./src/allmydata/test/test_web.py 1203
17043         d.addCallback(_got_json, "sdmf")
17044         return d
17045 
17046+    def test_GET_FILEURL_json_mdmf_extensions(self):
17047+        # A GET invoked against a URL that includes an MDMF cap with
17048+        # extensions should fetch the same JSON information as a GET
17049+        # invoked against a bare cap.
17050+        self._quux_txt_uri = "%s:3:131073" % self._quux_txt_uri
17051+        self._quux_txt_readonly_uri = "%s:3:131073" % self._quux_txt_readonly_uri
17052+        d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
17053+        d.addCallback(self.failUnlessIsQuuxJSON)
17054+        return d
17055+
17056+    def test_GET_FILEURL_json_mdmf(self):
17057+        d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
17058+        d.addCallback(self.failUnlessIsQuuxJSON)
17059+        return d
17060+
17061     def test_GET_FILEURL_json_missing(self):
17062         d = self.GET(self.public_url + "/foo/missing?json")
17063         d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
17064hunk ./src/allmydata/test/test_web.py 1262
17065             self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
17066             self.failUnlessIn("mutable-type-mdmf", res)
17067             self.failUnlessIn("mutable-type-sdmf", res)
17068+            self.failUnlessIn("quux", res)
17069         d.addCallback(_check)
17070         return d
17071 
17072hunk ./src/allmydata/test/test_web.py 1520
17073         d.addCallback(self.get_operation_results, "127", "json")
17074         def _got_json(stats):
17075             expected = {"count-immutable-files": 3,
17076-                        "count-mutable-files": 0,
17077+                        "count-mutable-files": 2,
17078                         "count-literal-files": 0,
17079hunk ./src/allmydata/test/test_web.py 1522
17080-                        "count-files": 3,
17081+                        "count-files": 5,
17082                         "count-directories": 3,
17083                         "size-immutable-files": 57,
17084                         "size-literal-files": 0,
17085hunk ./src/allmydata/test/test_web.py 1528
17086                         #"size-directories": 1912, # varies
17087                         #"largest-directory": 1590,
17088-                        "largest-directory-children": 5,
17089+                        "largest-directory-children": 7,
17090                         "largest-immutable-file": 19,
17091                         }
17092             for k,v in expected.iteritems():
17093hunk ./src/allmydata/test/test_web.py 1545
17094         def _check(res):
17095             self.failUnless(res.endswith("\n"))
17096             units = [simplejson.loads(t) for t in res[:-1].split("\n")]
17097-            self.failUnlessReallyEqual(len(units), 7)
17098+            self.failUnlessReallyEqual(len(units), 9)
17099             self.failUnlessEqual(units[-1]["type"], "stats")
17100             first = units[0]
17101             self.failUnlessEqual(first["path"], [])
17102hunk ./src/allmydata/test/test_web.py 1556
17103             self.failIfEqual(baz["storage-index"], None)
17104             self.failIfEqual(baz["verifycap"], None)
17105             self.failIfEqual(baz["repaircap"], None)
17106+            # XXX: Add quux and baz to this test.
17107             return
17108         d.addCallback(_check)
17109         return d
17110hunk ./src/allmydata/test/test_web.py 2002
17111         d.addCallback(lambda ignored:
17112             self.POST("/uri?t=upload&mutable=true&mutable-type=mdmf",
17113                       file=('mdmf.txt', self.NEWFILE_CONTENTS * 300000)))
17114+        def _got_filecap(filecap):
17115+            self.failUnless(filecap.startswith("URI:MDMF"))
17116+            return filecap
17117+        d.addCallback(_got_filecap)
17118         d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
17119         d.addCallback(_got_json, "mdmf")
17120         return d
17121hunk ./src/allmydata/test/test_web.py 2019
17122             filenameu = unicode(filename)
17123             self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
17124             return self.GET(self.public_url + "/foo/%s?t=json" % filename)
17125+        def _got_mdmf_cap(filecap):
17126+            self.failUnless(filecap.startswith("URI:MDMF"))
17127+            return filecap
17128         d.addCallback(_got_cap, "sdmf.txt")
17129         def _got_json(json, version):
17130             data = simplejson.loads(json)
17131hunk ./src/allmydata/test/test_web.py 2034
17132             self.POST(self.public_url + \
17133                       "/foo?t=upload&mutable=true&mutable-type=mdmf",
17134                       file=("mdmf.txt", self.NEWFILE_CONTENTS * 300000)))
17135+        d.addCallback(_got_mdmf_cap)
17136         d.addCallback(_got_cap, "mdmf.txt")
17137         d.addCallback(_got_json, "mdmf")
17138         return d
17139hunk ./src/allmydata/test/test_web.py 2268
17140         # make sure that nothing was added
17141         d.addCallback(lambda res:
17142                       self.failUnlessNodeKeysAre(self._foo_node,
17143-                                                 [u"bar.txt", u"blockingfile",
17144-                                                  u"empty", u"n\u00fc.txt",
17145+                                                 [u"bar.txt", u"baz.txt", u"blockingfile",
17146+                                                  u"empty", u"n\u00fc.txt", u"quux.txt",
17147                                                   u"sub"]))
17148         return d
17149 
17150hunk ./src/allmydata/test/test_web.py 2391
17151         d.addCallback(_check3)
17152         return d
17153 
17154+    def test_POST_FILEURL_mdmf_check(self):
17155+        quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
17156+        d = self.POST(quux_url, t="check")
17157+        def _check(res):
17158+            self.failUnlessIn("Healthy", res)
17159+        d.addCallback(_check)
17160+        quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
17161+        d.addCallback(lambda ignored:
17162+            self.POST(quux_extension_url, t="check"))
17163+        d.addCallback(_check)
17164+        return d
17165+
17166+    def test_POST_FILEURL_mdmf_check_and_repair(self):
17167+        quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
17168+        d = self.POST(quux_url, t="check", repair="true")
17169+        def _check(res):
17170+            self.failUnlessIn("Healthy", res)
17171+        d.addCallback(_check)
17172+        quux_extension_url = "/uri/%s" %\
17173+            urllib.quote("%s:3:131073" % self._quux_txt_uri)
17174+        d.addCallback(lambda ignored:
17175+            self.POST(quux_extension_url, t="check", repair="true"))
17176+        d.addCallback(_check)
17177+        return d
17178+
17179     def wait_for_operation(self, ignored, ophandle):
17180         url = "/operations/" + ophandle
17181         url += "?t=status&output=JSON"
17182hunk ./src/allmydata/test/test_web.py 2461
17183         d.addCallback(self.wait_for_operation, "123")
17184         def _check_json(data):
17185             self.failUnlessReallyEqual(data["finished"], True)
17186-            self.failUnlessReallyEqual(data["count-objects-checked"], 8)
17187-            self.failUnlessReallyEqual(data["count-objects-healthy"], 8)
17188+            self.failUnlessReallyEqual(data["count-objects-checked"], 10)
17189+            self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
17190         d.addCallback(_check_json)
17191         d.addCallback(self.get_operation_results, "123", "html")
17192         def _check_html(res):
17193hunk ./src/allmydata/test/test_web.py 2466
17194-            self.failUnless("Objects Checked: <span>8</span>" in res)
17195-            self.failUnless("Objects Healthy: <span>8</span>" in res)
17196+            self.failUnless("Objects Checked: <span>10</span>" in res)
17197+            self.failUnless("Objects Healthy: <span>10</span>" in res)
17198         d.addCallback(_check_html)
17199 
17200         d.addCallback(lambda res:
17201hunk ./src/allmydata/test/test_web.py 2496
17202         d.addCallback(self.wait_for_operation, "124")
17203         def _check_json(data):
17204             self.failUnlessReallyEqual(data["finished"], True)
17205-            self.failUnlessReallyEqual(data["count-objects-checked"], 8)
17206-            self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 8)
17207+            self.failUnlessReallyEqual(data["count-objects-checked"], 10)
17208+            self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
17209             self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
17210             self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
17211             self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
17212hunk ./src/allmydata/test/test_web.py 2503
17213             self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
17214             self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
17215-            self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 8)
17216+            self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
17217             self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
17218             self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
17219         d.addCallback(_check_json)
17220hunk ./src/allmydata/test/test_web.py 2509
17221         d.addCallback(self.get_operation_results, "124", "html")
17222         def _check_html(res):
17223-            self.failUnless("Objects Checked: <span>8</span>" in res)
17224+            self.failUnless("Objects Checked: <span>10</span>" in res)
17225 
17226hunk ./src/allmydata/test/test_web.py 2511
17227-            self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
17228+            self.failUnless("Objects Healthy (before repair): <span>10</span>" in res)
17229             self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
17230             self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
17231 
17232hunk ./src/allmydata/test/test_web.py 2519
17233             self.failUnless("Repairs Successful: <span>0</span>" in res)
17234             self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
17235 
17236-            self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
17237+            self.failUnless("Objects Healthy (after repair): <span>10</span>" in res)
17238             self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
17239             self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
17240         d.addCallback(_check_html)
17241hunk ./src/allmydata/test/test_web.py 2649
17242         filecap3 = node3.get_readonly_uri()
17243         node4 = self.s.create_node_from_uri(make_mutable_file_uri())
17244         dircap = DirectoryNode(node4, None, None).get_uri()
17245+        mdmfcap = make_mutable_file_uri(mdmf=True)
17246         litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
17247         emptydircap = "URI:DIR2-LIT:"
17248         newkids = {u"child-imm":        ["filenode", {"rw_uri": filecap1,
17249hunk ./src/allmydata/test/test_web.py 2666
17250                                                       "ro_uri": self._make_readonly(dircap)}],
17251                    u"dirchild-lit":     ["dirnode",  {"ro_uri": litdircap}],
17252                    u"dirchild-empty":   ["dirnode",  {"ro_uri": emptydircap}],
17253+                   u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
17254+                                                        "ro_uri": self._make_readonly(mdmfcap)}],
17255                    }
17256         return newkids, {'filecap1': filecap1,
17257                          'filecap2': filecap2,
17258hunk ./src/allmydata/test/test_web.py 2677
17259                          'unknown_immcap': unknown_immcap,
17260                          'dircap': dircap,
17261                          'litdircap': litdircap,
17262-                         'emptydircap': emptydircap}
17263+                         'emptydircap': emptydircap,
17264+                         'mdmfcap': mdmfcap}
17265 
17266     def _create_immutable_children(self):
17267         contents, n, filecap1 = self.makefile(12)
17268hunk ./src/allmydata/test/test_web.py 3224
17269             data = data[1]
17270             self.failUnlessIn("mutable-type", data)
17271             self.failUnlessEqual(data['mutable-type'], "mdmf")
17272+            self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
17273+            self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
17274         d.addCallback(_got_json)
17275         return d
17276 
17277}
17278[web/filenode.py: complain if a PUT is requested with a readonly cap
17279Kevan Carstensen <kevan@isnotajoke.com>**20110515230421
17280 Ignore-this: e2f05201f3b008e157062ed187eacbb9
17281] hunk ./src/allmydata/web/filenode.py 229
17282                 raise ExistingChildError()
17283 
17284             if self.node.is_mutable():
17285+                # Are we a readonly filenode? We shouldn't allow callers
17286+                # to try to replace us if we are.
17287+                if self.node.is_readonly():
17288+                    raise WebError("PUT to a mutable file: replace or update"
17289+                                   " requested with read-only cap")
17290                 if offset is None:
17291                     return self.replace_my_contents(req)
17292 
17293[web/info.py: Display mutable type information when describing a mutable file
17294Kevan Carstensen <kevan@isnotajoke.com>**20110515230444
17295 Ignore-this: ce5ad22b494effe6c15e49471fae0d99
17296] {
17297hunk ./src/allmydata/web/info.py 8
17298 from nevow.inevow import IRequest
17299 
17300 from allmydata.util import base32
17301-from allmydata.interfaces import IDirectoryNode, IFileNode
17302+from allmydata.interfaces import IDirectoryNode, IFileNode, MDMF_VERSION, SDMF_VERSION
17303 from allmydata.web.common import getxmlfile
17304 from allmydata.mutable.common import UnrecoverableFileError # TODO: move
17305 
17306hunk ./src/allmydata/web/info.py 31
17307             si = node.get_storage_index()
17308             if si:
17309                 if node.is_mutable():
17310-                    return "mutable file"
17311+                    ret = "mutable file"
17312+                    if node.get_version() == MDMF_VERSION:
17313+                        ret += " (mdmf)"
17314+                    else:
17315+                        ret += " (sdmf)"
17316+                    return ret
17317                 return "immutable file"
17318             return "immutable LIT file"
17319         return "unknown"
17320}
17321[Add MDMF dirnodes
17322Kevan Carstensen <kevan@isnotajoke.com>**20110531011707
17323 Ignore-this: 4c4538e42059965ded85d1bfe85cbbb2
17324] {
17325hunk ./src/allmydata/client.py 493
17326         # may get an opaque node if there were any problems.
17327         return self.nodemaker.create_from_cap(write_uri, read_uri, deep_immutable=deep_immutable, name=name)
17328 
17329-    def create_dirnode(self, initial_children={}):
17330-        d = self.nodemaker.create_new_mutable_directory(initial_children)
17331+    def create_dirnode(self, initial_children={}, version=SDMF_VERSION):
17332+        d = self.nodemaker.create_new_mutable_directory(initial_children, version=version)
17333         return d
17334 
17335     def create_immutable_dirnode(self, children, convergence=None):
17336hunk ./src/allmydata/nodemaker.py 103
17337         d.addCallback(lambda res: n)
17338         return d
17339 
17340-    def create_new_mutable_directory(self, initial_children={}):
17341-        # mutable directories will always be SDMF for now, to help
17342-        # compatibility with older clients.
17343-        version = SDMF_VERSION
17344+    def create_new_mutable_directory(self, initial_children={},
17345+                                     version=SDMF_VERSION):
17346         # initial_children must have metadata (i.e. {} instead of None)
17347         for (name, (node, metadata)) in initial_children.iteritems():
17348             precondition(isinstance(metadata, dict),
17349hunk ./src/allmydata/uri.py 733
17350         return None
17351 
17352 
17353+class MDMFDirectoryURI(_DirectoryBaseURI):
17354+    implements(IDirectoryURI)
17355+
17356+    BASE_STRING='URI:DIR2-MDMF:'
17357+    BASE_STRING_RE=re.compile('^'+BASE_STRING)
17358+    BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF'+SEP)
17359+    INNER_URI_CLASS=WritableMDMFFileURI
17360+
17361+    def __init__(self, filenode_uri=None):
17362+        if filenode_uri:
17363+            assert not filenode_uri.is_readonly()
17364+        _DirectoryBaseURI.__init__(self, filenode_uri)
17365+
17366+    def is_readonly(self):
17367+        return False
17368+
17369+    def get_readonly(self):
17370+        return ReadonlyMDMFDirectoryURI(self._filenode_uri.get_readonly())
17371+
17372+    def get_verify_cap(self):
17373+        return MDMFDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
17374+
17375+
17376+class ReadonlyMDMFDirectoryURI(_DirectoryBaseURI):
17377+    implements(IReadonlyDirectoryURI)
17378+
17379+    BASE_STRING='URI:DIR2-MDMF-RO:'
17380+    BASE_STRING_RE=re.compile('^'+BASE_STRING)
17381+    BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF-RO'+SEP)
17382+    INNER_URI_CLASS=ReadonlyMDMFFileURI
17383+
17384+    def __init__(self, filenode_uri=None):
17385+        if filenode_uri:
17386+            assert filenode_uri.is_readonly()
17387+        _DirectoryBaseURI.__init__(self, filenode_uri)
17388+
17389+    def is_readonly(self):
17390+        return True
17391+
17392+    def get_readonly(self):
17393+        return self
17394+
17395+    def get_verify_cap(self):
17396+        return MDMFDirectoryURIVerifier(self._filenode_uri.get_verify_cap())
17397+
17398 def wrap_dirnode_cap(filecap):
17399     if isinstance(filecap, WriteableSSKFileURI):
17400         return DirectoryURI(filecap)
17401hunk ./src/allmydata/uri.py 787
17402         return ImmutableDirectoryURI(filecap)
17403     if isinstance(filecap, LiteralFileURI):
17404         return LiteralDirectoryURI(filecap)
17405+    if isinstance(filecap, WritableMDMFFileURI):
17406+        return MDMFDirectoryURI(filecap)
17407+    if isinstance(filecap, ReadonlyMDMFFileURI):
17408+        return ReadonlyMDMFDirectoryURI(filecap)
17409     assert False, "cannot interpret as a directory cap: %s" % filecap.__class__
17410 
17411hunk ./src/allmydata/uri.py 793
17412+class MDMFDirectoryURIVerifier(_DirectoryBaseURI):
17413+    implements(IVerifierURI)
17414+
17415+    BASE_STRING='URI:DIR2-MDMF-Verifier:'
17416+    BASE_STRING_RE=re.compile('^'+BASE_STRING)
17417+    BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-MDMF-Verifier'+SEP)
17418+    INNER_URI_CLASS=MDMFVerifierURI
17419+
17420+    def __init__(self, filenode_uri=None):
17421+        if filenode_uri:
17422+            assert IVerifierURI.providedBy(filenode_uri)
17423+        self._filenode_uri = filenode_uri
17424+
17425+    def get_filenode_cap(self):
17426+        return self._filenode_uri
17427+
17428+    def is_mutable(self):
17429+        return False
17430 
17431 class DirectoryURIVerifier(_DirectoryBaseURI):
17432     implements(IVerifierURI)
17433hunk ./src/allmydata/uri.py 918
17434             return ImmutableDirectoryURI.init_from_string(s)
17435         elif s.startswith('URI:DIR2-LIT:'):
17436             return LiteralDirectoryURI.init_from_string(s)
17437+        elif s.startswith('URI:DIR2-MDMF:'):
17438+            if can_be_writeable:
17439+                return MDMFDirectoryURI.init_from_string(s)
17440+            kind = "URI:DIR2-MDMF directory writecap"
17441+        elif s.startswith('URI:DIR2-MDMF-RO:'):
17442+            if can_be_mutable:
17443+                return ReadonlyMDMFDirectoryURI.init_from_string(s)
17444+            kind = "URI:DIR2-MDMF-RO readcap to a mutable directory"
17445         elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable:
17446             # For testing how future writeable caps would behave in read-only contexts.
17447             kind = "x-tahoe-future-test-writeable: testing cap"
17448}
17449[Add tests for MDMF directories
17450Kevan Carstensen <kevan@isnotajoke.com>**20110531011758
17451 Ignore-this: 54be9cb15e9bd05363328c8f95024279
17452] {
17453hunk ./src/allmydata/test/test_dirnode.py 14
17454 from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \
17455      ExistingChildError, NoSuchChildError, MustNotBeUnknownRWError, \
17456      MustBeDeepImmutableError, MustBeReadonlyError, \
17457-     IDeepCheckResults, IDeepCheckAndRepairResults
17458+     IDeepCheckResults, IDeepCheckAndRepairResults, \
17459+     MDMF_VERSION, SDMF_VERSION
17460 from allmydata.mutable.filenode import MutableFileNode
17461 from allmydata.mutable.common import UncoordinatedWriteError
17462 from allmydata.util import hashutil, base32
17463hunk ./src/allmydata/test/test_dirnode.py 61
17464               testutil.ReallyEqualMixin, testutil.ShouldFailMixin, testutil.StallMixin, ErrorMixin):
17465     timeout = 480 # It occasionally takes longer than 240 seconds on Francois's arm box.
17466 
17467-    def test_basic(self):
17468-        self.basedir = "dirnode/Dirnode/test_basic"
17469-        self.set_up_grid()
17470+    def _do_create_test(self, mdmf=False):
17471         c = self.g.clients[0]
17472hunk ./src/allmydata/test/test_dirnode.py 63
17473-        d = c.create_dirnode()
17474-        def _done(res):
17475-            self.failUnless(isinstance(res, dirnode.DirectoryNode))
17476-            self.failUnless(res.is_mutable())
17477-            self.failIf(res.is_readonly())
17478-            self.failIf(res.is_unknown())
17479-            self.failIf(res.is_allowed_in_immutable_directory())
17480-            res.raise_error()
17481-            rep = str(res)
17482-            self.failUnless("RW-MUT" in rep)
17483-        d.addCallback(_done)
17484+
17485+        self.expected_manifest = []
17486+        self.expected_verifycaps = set()
17487+        self.expected_storage_indexes = set()
17488+
17489+        d = None
17490+        if mdmf:
17491+            d = c.create_dirnode(version=MDMF_VERSION)
17492+        else:
17493+            d = c.create_dirnode()
17494+        def _then(n):
17495+            # /
17496+            self.rootnode = n
17497+            backing_node = n._node
17498+            if mdmf:
17499+                self.failUnlessEqual(backing_node.get_version(),
17500+                                     MDMF_VERSION)
17501+            else:
17502+                self.failUnlessEqual(backing_node.get_version(),
17503+                                     SDMF_VERSION)
17504+            self.failUnless(n.is_mutable())
17505+            u = n.get_uri()
17506+            self.failUnless(u)
17507+            cap_formats = []
17508+            if mdmf:
17509+                cap_formats = ["URI:DIR2-MDMF:",
17510+                               "URI:DIR2-MDMF-RO:",
17511+                               "URI:DIR2-MDMF-Verifier:"]
17512+            else:
17513+                cap_formats = ["URI:DIR2:",
17514+                               "URI:DIR2-RO",
17515+                               "URI:DIR2-Verifier:"]
17516+            rw, ro, v = cap_formats
17517+            self.failUnless(u.startswith(rw), u)
17518+            u_ro = n.get_readonly_uri()
17519+            self.failUnless(u_ro.startswith(ro), u_ro)
17520+            u_v = n.get_verify_cap().to_string()
17521+            self.failUnless(u_v.startswith(v), u_v)
17522+            u_r = n.get_repair_cap().to_string()
17523+            self.failUnlessReallyEqual(u_r, u)
17524+            self.expected_manifest.append( ((), u) )
17525+            self.expected_verifycaps.add(u_v)
17526+            si = n.get_storage_index()
17527+            self.expected_storage_indexes.add(base32.b2a(si))
17528+            expected_si = n._uri.get_storage_index()
17529+            self.failUnlessReallyEqual(si, expected_si)
17530+
17531+            d = n.list()
17532+            d.addCallback(lambda res: self.failUnlessEqual(res, {}))
17533+            d.addCallback(lambda res: n.has_child(u"missing"))
17534+            d.addCallback(lambda res: self.failIf(res))
17535+
17536+            fake_file_uri = make_mutable_file_uri()
17537+            other_file_uri = make_mutable_file_uri()
17538+            m = c.nodemaker.create_from_cap(fake_file_uri)
17539+            ffu_v = m.get_verify_cap().to_string()
17540+            self.expected_manifest.append( ((u"child",) , m.get_uri()) )
17541+            self.expected_verifycaps.add(ffu_v)
17542+            self.expected_storage_indexes.add(base32.b2a(m.get_storage_index()))
17543+            d.addCallback(lambda res: n.set_uri(u"child",
17544+                                                fake_file_uri, fake_file_uri))
17545+            d.addCallback(lambda res:
17546+                          self.shouldFail(ExistingChildError, "set_uri-no",
17547+                                          "child 'child' already exists",
17548+                                          n.set_uri, u"child",
17549+                                          other_file_uri, other_file_uri,
17550+                                          overwrite=False))
17551+            # /
17552+            # /child = mutable
17553+
17554+            d.addCallback(lambda res: n.create_subdirectory(u"subdir"))
17555+
17556+            # /
17557+            # /child = mutable
17558+            # /subdir = directory
17559+            def _created(subdir):
17560+                self.failUnless(isinstance(subdir, dirnode.DirectoryNode))
17561+                self.subdir = subdir
17562+                new_v = subdir.get_verify_cap().to_string()
17563+                assert isinstance(new_v, str)
17564+                self.expected_manifest.append( ((u"subdir",), subdir.get_uri()) )
17565+                self.expected_verifycaps.add(new_v)
17566+                si = subdir.get_storage_index()
17567+                self.expected_storage_indexes.add(base32.b2a(si))
17568+            d.addCallback(_created)
17569+
17570+            d.addCallback(lambda res:
17571+                          self.shouldFail(ExistingChildError, "mkdir-no",
17572+                                          "child 'subdir' already exists",
17573+                                          n.create_subdirectory, u"subdir",
17574+                                          overwrite=False))
17575+
17576+            d.addCallback(lambda res: n.list())
17577+            d.addCallback(lambda children:
17578+                          self.failUnlessReallyEqual(set(children.keys()),
17579+                                                     set([u"child", u"subdir"])))
17580+
17581+            d.addCallback(lambda res: n.start_deep_stats().when_done())
17582+            def _check_deepstats(stats):
17583+                self.failUnless(isinstance(stats, dict))
17584+                expected = {"count-immutable-files": 0,
17585+                            "count-mutable-files": 1,
17586+                            "count-literal-files": 0,
17587+                            "count-files": 1,
17588+                            "count-directories": 2,
17589+                            "size-immutable-files": 0,
17590+                            "size-literal-files": 0,
17591+                            #"size-directories": 616, # varies
17592+                            #"largest-directory": 616,
17593+                            "largest-directory-children": 2,
17594+                            "largest-immutable-file": 0,
17595+                            }
17596+                for k,v in expected.iteritems():
17597+                    self.failUnlessReallyEqual(stats[k], v,
17598+                                               "stats[%s] was %s, not %s" %
17599+                                               (k, stats[k], v))
17600+                self.failUnless(stats["size-directories"] > 500,
17601+                                stats["size-directories"])
17602+                self.failUnless(stats["largest-directory"] > 500,
17603+                                stats["largest-directory"])
17604+                self.failUnlessReallyEqual(stats["size-files-histogram"], [])
17605+            d.addCallback(_check_deepstats)
17606+
17607+            d.addCallback(lambda res: n.build_manifest().when_done())
17608+            def _check_manifest(res):
17609+                manifest = res["manifest"]
17610+                self.failUnlessReallyEqual(sorted(manifest),
17611+                                           sorted(self.expected_manifest))
17612+                stats = res["stats"]
17613+                _check_deepstats(stats)
17614+                self.failUnlessReallyEqual(self.expected_verifycaps,
17615+                                           res["verifycaps"])
17616+                self.failUnlessReallyEqual(self.expected_storage_indexes,
17617+                                           res["storage-index"])
17618+            d.addCallback(_check_manifest)
17619+
17620+            def _add_subsubdir(res):
17621+                return self.subdir.create_subdirectory(u"subsubdir")
17622+            d.addCallback(_add_subsubdir)
17623+            # /
17624+            # /child = mutable
17625+            # /subdir = directory
17626+            # /subdir/subsubdir = directory
17627+            d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir"))
17628+            d.addCallback(lambda subsubdir:
17629+                          self.failUnless(isinstance(subsubdir,
17630+                                                     dirnode.DirectoryNode)))
17631+            d.addCallback(lambda res: n.get_child_at_path(u""))
17632+            d.addCallback(lambda res: self.failUnlessReallyEqual(res.get_uri(),
17633+                                                                 n.get_uri()))
17634+
17635+            d.addCallback(lambda res: n.get_metadata_for(u"child"))
17636+            d.addCallback(lambda metadata:
17637+                          self.failUnlessEqual(set(metadata.keys()),
17638+                                               set(["tahoe"])))
17639+
17640+            d.addCallback(lambda res:
17641+                          self.shouldFail(NoSuchChildError, "gcamap-no",
17642+                                          "nope",
17643+                                          n.get_child_and_metadata_at_path,
17644+                                          u"subdir/nope"))
17645+            d.addCallback(lambda res:
17646+                          n.get_child_and_metadata_at_path(u""))
17647+            def _check_child_and_metadata1(res):
17648+                child, metadata = res
17649+                self.failUnless(isinstance(child, dirnode.DirectoryNode))
17650+                # edge-metadata needs at least one path segment
17651+                self.failUnlessEqual(set(metadata.keys()), set([]))
17652+            d.addCallback(_check_child_and_metadata1)
17653+            d.addCallback(lambda res:
17654+                          n.get_child_and_metadata_at_path(u"child"))
17655+
17656+            def _check_child_and_metadata2(res):
17657+                child, metadata = res
17658+                self.failUnlessReallyEqual(child.get_uri(),
17659+                                           fake_file_uri)
17660+                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
17661+            d.addCallback(_check_child_and_metadata2)
17662+
17663+            d.addCallback(lambda res:
17664+                          n.get_child_and_metadata_at_path(u"subdir/subsubdir"))
17665+            def _check_child_and_metadata3(res):
17666+                child, metadata = res
17667+                self.failUnless(isinstance(child, dirnode.DirectoryNode))
17668+                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
17669+            d.addCallback(_check_child_and_metadata3)
17670+
17671+            # set_uri + metadata
17672+            # it should be possible to add a child without any metadata
17673+            d.addCallback(lambda res: n.set_uri(u"c2",
17674+                                                fake_file_uri, fake_file_uri,
17675+                                                {}))
17676+            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
17677+            d.addCallback(lambda metadata:
17678+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
17679+
17680+            # You can't override the link timestamps.
17681+            d.addCallback(lambda res: n.set_uri(u"c2",
17682+                                                fake_file_uri, fake_file_uri,
17683+                                                { 'tahoe': {'linkcrtime': "bogus"}}))
17684+            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
17685+            def _has_good_linkcrtime(metadata):
17686+                self.failUnless(metadata.has_key('tahoe'))
17687+                self.failUnless(metadata['tahoe'].has_key('linkcrtime'))
17688+                self.failIfEqual(metadata['tahoe']['linkcrtime'], 'bogus')
17689+            d.addCallback(_has_good_linkcrtime)
17690+
17691+            # if we don't set any defaults, the child should get timestamps
17692+            d.addCallback(lambda res: n.set_uri(u"c3",
17693+                                                fake_file_uri, fake_file_uri))
17694+            d.addCallback(lambda res: n.get_metadata_for(u"c3"))
17695+            d.addCallback(lambda metadata:
17696+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
17697+
17698+            # we can also add specific metadata at set_uri() time
17699+            d.addCallback(lambda res: n.set_uri(u"c4",
17700+                                                fake_file_uri, fake_file_uri,
17701+                                                {"key": "value"}))
17702+            d.addCallback(lambda res: n.get_metadata_for(u"c4"))
17703+            d.addCallback(lambda metadata:
17704+                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
17705+                                              (metadata['key'] == "value"), metadata))
17706+
17707+            d.addCallback(lambda res: n.delete(u"c2"))
17708+            d.addCallback(lambda res: n.delete(u"c3"))
17709+            d.addCallback(lambda res: n.delete(u"c4"))
17710+
17711+            # set_node + metadata
17712+            # it should be possible to add a child without any metadata except for timestamps
17713+            d.addCallback(lambda res: n.set_node(u"d2", n, {}))
17714+            d.addCallback(lambda res: c.create_dirnode())
17715+            d.addCallback(lambda n2:
17716+                          self.shouldFail(ExistingChildError, "set_node-no",
17717+                                          "child 'd2' already exists",
17718+                                          n.set_node, u"d2", n2,
17719+                                          overwrite=False))
17720+            d.addCallback(lambda res: n.get_metadata_for(u"d2"))
17721+            d.addCallback(lambda metadata:
17722+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
17723+
17724+            # if we don't set any defaults, the child should get timestamps
17725+            d.addCallback(lambda res: n.set_node(u"d3", n))
17726+            d.addCallback(lambda res: n.get_metadata_for(u"d3"))
17727+            d.addCallback(lambda metadata:
17728+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
17729+
17730+            # we can also add specific metadata at set_node() time
17731+            d.addCallback(lambda res: n.set_node(u"d4", n,
17732+                                                {"key": "value"}))
17733+            d.addCallback(lambda res: n.get_metadata_for(u"d4"))
17734+            d.addCallback(lambda metadata:
17735+                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
17736+                                          (metadata["key"] == "value"), metadata))
17737+
17738+            d.addCallback(lambda res: n.delete(u"d2"))
17739+            d.addCallback(lambda res: n.delete(u"d3"))
17740+            d.addCallback(lambda res: n.delete(u"d4"))
17741+
17742+            # metadata through set_children()
17743+            d.addCallback(lambda res:
17744+                          n.set_children({
17745+                              u"e1": (fake_file_uri, fake_file_uri),
17746+                              u"e2": (fake_file_uri, fake_file_uri, {}),
17747+                              u"e3": (fake_file_uri, fake_file_uri,
17748+                                      {"key": "value"}),
17749+                              }))
17750+            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
17751+            d.addCallback(lambda res:
17752+                          self.shouldFail(ExistingChildError, "set_children-no",
17753+                                          "child 'e1' already exists",
17754+                                          n.set_children,
17755+                                          { u"e1": (other_file_uri,
17756+                                                    other_file_uri),
17757+                                            u"new": (other_file_uri,
17758+                                                     other_file_uri),
17759+                                            },
17760+                                          overwrite=False))
17761+            # and 'new' should not have been created
17762+            d.addCallback(lambda res: n.list())
17763+            d.addCallback(lambda children: self.failIf(u"new" in children))
17764+            d.addCallback(lambda res: n.get_metadata_for(u"e1"))
17765+            d.addCallback(lambda metadata:
17766+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
17767+            d.addCallback(lambda res: n.get_metadata_for(u"e2"))
17768+            d.addCallback(lambda metadata:
17769+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
17770+            d.addCallback(lambda res: n.get_metadata_for(u"e3"))
17771+            d.addCallback(lambda metadata:
17772+                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
17773+                                          (metadata["key"] == "value"), metadata))
17774+
17775+            d.addCallback(lambda res: n.delete(u"e1"))
17776+            d.addCallback(lambda res: n.delete(u"e2"))
17777+            d.addCallback(lambda res: n.delete(u"e3"))
17778+
17779+            # metadata through set_nodes()
17780+            d.addCallback(lambda res:
17781+                          n.set_nodes({ u"f1": (n, None),
17782+                                        u"f2": (n, {}),
17783+                                        u"f3": (n, {"key": "value"}),
17784+                                        }))
17785+            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
17786+            d.addCallback(lambda res:
17787+                          self.shouldFail(ExistingChildError, "set_nodes-no",
17788+                                          "child 'f1' already exists",
17789+                                          n.set_nodes, { u"f1": (n, None),
17790+                                                         u"new": (n, None), },
17791+                                          overwrite=False))
17792+            # and 'new' should not have been created
17793+            d.addCallback(lambda res: n.list())
17794+            d.addCallback(lambda children: self.failIf(u"new" in children))
17795+            d.addCallback(lambda res: n.get_metadata_for(u"f1"))
17796+            d.addCallback(lambda metadata:
17797+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
17798+            d.addCallback(lambda res: n.get_metadata_for(u"f2"))
17799+            d.addCallback(lambda metadata:
17800+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
17801+            d.addCallback(lambda res: n.get_metadata_for(u"f3"))
17802+            d.addCallback(lambda metadata:
17803+                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
17804+                                          (metadata["key"] == "value"), metadata))
17805+
17806+            d.addCallback(lambda res: n.delete(u"f1"))
17807+            d.addCallback(lambda res: n.delete(u"f2"))
17808+            d.addCallback(lambda res: n.delete(u"f3"))
17809+
17810+
17811+            d.addCallback(lambda res:
17812+                          n.set_metadata_for(u"child",
17813+                                             {"tags": ["web2.0-compatible"], "tahoe": {"bad": "mojo"}}))
17814+            d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
17815+            d.addCallback(lambda metadata:
17816+                          self.failUnless((set(metadata.keys()) == set(["tags", "tahoe"])) and
17817+                                          metadata["tags"] == ["web2.0-compatible"] and
17818+                                          "bad" not in metadata["tahoe"], metadata))
17819+
17820+            d.addCallback(lambda res:
17821+                          self.shouldFail(NoSuchChildError, "set_metadata_for-nosuch", "",
17822+                                          n.set_metadata_for, u"nosuch", {}))
17823+
17824+
17825+            def _start(res):
17826+                self._start_timestamp = time.time()
17827+            d.addCallback(_start)
17828+            # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all
17829+            # floats to hundredeths (it uses str(num) instead of repr(num)).
17830+            # simplejson-1.7.3 does not have this bug. To prevent this bug
17831+            # from causing the test to fail, stall for more than a few
17832+            # hundrededths of a second.
17833+            d.addCallback(self.stall, 0.1)
17834+            d.addCallback(lambda res: n.add_file(u"timestamps",
17835+                                                 upload.Data("stamp me", convergence="some convergence string")))
17836+            d.addCallback(self.stall, 0.1)
17837+            def _stop(res):
17838+                self._stop_timestamp = time.time()
17839+            d.addCallback(_stop)
17840+
17841+            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
17842+            def _check_timestamp1(metadata):
17843+                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
17844+                tahoe_md = metadata["tahoe"]
17845+                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
17846+
17847+                self.failUnlessGreaterOrEqualThan(tahoe_md["linkcrtime"],
17848+                                                  self._start_timestamp)
17849+                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
17850+                                                  tahoe_md["linkcrtime"])
17851+                self.failUnlessGreaterOrEqualThan(tahoe_md["linkmotime"],
17852+                                                  self._start_timestamp)
17853+                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
17854+                                                  tahoe_md["linkmotime"])
17855+                # Our current timestamp rules say that replacing an existing
17856+                # child should preserve the 'linkcrtime' but update the
17857+                # 'linkmotime'
17858+                self._old_linkcrtime = tahoe_md["linkcrtime"]
17859+                self._old_linkmotime = tahoe_md["linkmotime"]
17860+            d.addCallback(_check_timestamp1)
17861+            d.addCallback(self.stall, 2.0) # accomodate low-res timestamps
17862+            d.addCallback(lambda res: n.set_node(u"timestamps", n))
17863+            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
17864+            def _check_timestamp2(metadata):
17865+                self.failUnlessIn("tahoe", metadata)
17866+                tahoe_md = metadata["tahoe"]
17867+                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
17868+
17869+                self.failUnlessReallyEqual(tahoe_md["linkcrtime"], self._old_linkcrtime)
17870+                self.failUnlessGreaterThan(tahoe_md["linkmotime"], self._old_linkmotime)
17871+                return n.delete(u"timestamps")
17872+            d.addCallback(_check_timestamp2)
17873+
17874+            d.addCallback(lambda res: n.delete(u"subdir"))
17875+            d.addCallback(lambda old_child:
17876+                          self.failUnlessReallyEqual(old_child.get_uri(),
17877+                                                     self.subdir.get_uri()))
17878+
17879+            d.addCallback(lambda res: n.list())
17880+            d.addCallback(lambda children:
17881+                          self.failUnlessReallyEqual(set(children.keys()),
17882+                                                     set([u"child"])))
17883+
17884+            uploadable1 = upload.Data("some data", convergence="converge")
17885+            d.addCallback(lambda res: n.add_file(u"newfile", uploadable1))
17886+            d.addCallback(lambda newnode:
17887+                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
17888+            uploadable2 = upload.Data("some data", convergence="stuff")
17889+            d.addCallback(lambda res:
17890+                          self.shouldFail(ExistingChildError, "add_file-no",
17891+                                          "child 'newfile' already exists",
17892+                                          n.add_file, u"newfile",
17893+                                          uploadable2,
17894+                                          overwrite=False))
17895+            d.addCallback(lambda res: n.list())
17896+            d.addCallback(lambda children:
17897+                          self.failUnlessReallyEqual(set(children.keys()),
17898+                                                     set([u"child", u"newfile"])))
17899+            d.addCallback(lambda res: n.get_metadata_for(u"newfile"))
17900+            d.addCallback(lambda metadata:
17901+                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
17902+
17903+            uploadable3 = upload.Data("some data", convergence="converge")
17904+            d.addCallback(lambda res: n.add_file(u"newfile-metadata",
17905+                                                 uploadable3,
17906+                                                 {"key": "value"}))
17907+            d.addCallback(lambda newnode:
17908+                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
17909+            d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata"))
17910+            d.addCallback(lambda metadata:
17911+                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
17912+                                              (metadata['key'] == "value"), metadata))
17913+            d.addCallback(lambda res: n.delete(u"newfile-metadata"))
17914+
17915+            d.addCallback(lambda res: n.create_subdirectory(u"subdir2"))
17916+            def _created2(subdir2):
17917+                self.subdir2 = subdir2
17918+                # put something in the way, to make sure it gets overwritten
17919+                return subdir2.add_file(u"child", upload.Data("overwrite me",
17920+                                                              "converge"))
17921+            d.addCallback(_created2)
17922+
17923+            d.addCallback(lambda res:
17924+                          n.move_child_to(u"child", self.subdir2))
17925+            d.addCallback(lambda res: n.list())
17926+            d.addCallback(lambda children:
17927+                          self.failUnlessReallyEqual(set(children.keys()),
17928+                                                     set([u"newfile", u"subdir2"])))
17929+            d.addCallback(lambda res: self.subdir2.list())
17930+            d.addCallback(lambda children:
17931+                          self.failUnlessReallyEqual(set(children.keys()),
17932+                                                     set([u"child"])))
17933+            d.addCallback(lambda res: self.subdir2.get(u"child"))
17934+            d.addCallback(lambda child:
17935+                          self.failUnlessReallyEqual(child.get_uri(),
17936+                                                     fake_file_uri))
17937+
17938+            # move it back, using new_child_name=
17939+            d.addCallback(lambda res:
17940+                          self.subdir2.move_child_to(u"child", n, u"newchild"))
17941+            d.addCallback(lambda res: n.list())
17942+            d.addCallback(lambda children:
17943+                          self.failUnlessReallyEqual(set(children.keys()),
17944+                                                     set([u"newchild", u"newfile",
17945+                                                          u"subdir2"])))
17946+            d.addCallback(lambda res: self.subdir2.list())
17947+            d.addCallback(lambda children:
17948+                          self.failUnlessReallyEqual(set(children.keys()), set([])))
17949+
17950+            # now make sure that we honor overwrite=False
17951+            d.addCallback(lambda res:
17952+                          self.subdir2.set_uri(u"newchild",
17953+                                               other_file_uri, other_file_uri))
17954+
17955+            d.addCallback(lambda res:
17956+                          self.shouldFail(ExistingChildError, "move_child_to-no",
17957+                                          "child 'newchild' already exists",
17958+                                          n.move_child_to, u"newchild",
17959+                                          self.subdir2,
17960+                                          overwrite=False))
17961+            d.addCallback(lambda res: self.subdir2.get(u"newchild"))
17962+            d.addCallback(lambda child:
17963+                          self.failUnlessReallyEqual(child.get_uri(),
17964+                                                     other_file_uri))
17965+
17966+
17967+            # Setting the no-write field should diminish a mutable cap to read-only
17968+            # (for both files and directories).
17969+
17970+            d.addCallback(lambda ign: n.set_uri(u"mutable", other_file_uri, other_file_uri))
17971+            d.addCallback(lambda ign: n.get(u"mutable"))
17972+            d.addCallback(lambda mutable: self.failIf(mutable.is_readonly(), mutable))
17973+            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
17974+            d.addCallback(lambda ign: n.get(u"mutable"))
17975+            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
17976+            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
17977+            d.addCallback(lambda ign: n.get(u"mutable"))
17978+            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
17979+
17980+            d.addCallback(lambda ign: n.get(u"subdir2"))
17981+            d.addCallback(lambda subdir2: self.failIf(subdir2.is_readonly()))
17982+            d.addCallback(lambda ign: n.set_metadata_for(u"subdir2", {"no-write": True}))
17983+            d.addCallback(lambda ign: n.get(u"subdir2"))
17984+            d.addCallback(lambda subdir2: self.failUnless(subdir2.is_readonly(), subdir2))
17985+
17986+            d.addCallback(lambda ign: n.set_uri(u"mutable_ro", other_file_uri, other_file_uri,
17987+                                                metadata={"no-write": True}))
17988+            d.addCallback(lambda ign: n.get(u"mutable_ro"))
17989+            d.addCallback(lambda mutable_ro: self.failUnless(mutable_ro.is_readonly(), mutable_ro))
17990+
17991+            d.addCallback(lambda ign: n.create_subdirectory(u"subdir_ro", metadata={"no-write": True}))
17992+            d.addCallback(lambda ign: n.get(u"subdir_ro"))
17993+            d.addCallback(lambda subdir_ro: self.failUnless(subdir_ro.is_readonly(), subdir_ro))
17994+
17995+            return d
17996+
17997+        d.addCallback(_then)
17998+
17999+        d.addErrback(self.explain_error)
18000         return d
18001 
18002hunk ./src/allmydata/test/test_dirnode.py 581
18003-    def test_initial_children(self):
18004-        self.basedir = "dirnode/Dirnode/test_initial_children"
18005-        self.set_up_grid()
18006+
18007+    def _do_initial_children_test(self, mdmf=False):
18008         c = self.g.clients[0]
18009         nm = c.nodemaker
18010 
18011hunk ./src/allmydata/test/test_dirnode.py 597
18012                 u"empty_litdir": (nm.create_from_cap(empty_litdir_uri), {}),
18013                 u"tiny_litdir": (nm.create_from_cap(tiny_litdir_uri), {}),
18014                 }
18015-        d = c.create_dirnode(kids)
18016-       
18017+        d = None
18018+        if mdmf:
18019+            d = c.create_dirnode(kids, version=MDMF_VERSION)
18020+        else:
18021+            d = c.create_dirnode(kids)
18022         def _created(dn):
18023             self.failUnless(isinstance(dn, dirnode.DirectoryNode))
18024hunk ./src/allmydata/test/test_dirnode.py 604
18025+            backing_node = dn._node
18026+            if mdmf:
18027+                self.failUnlessEqual(backing_node.get_version(),
18028+                                     MDMF_VERSION)
18029+            else:
18030+                self.failUnlessEqual(backing_node.get_version(),
18031+                                     SDMF_VERSION)
18032             self.failUnless(dn.is_mutable())
18033             self.failIf(dn.is_readonly())
18034             self.failIf(dn.is_unknown())
18035hunk ./src/allmydata/test/test_dirnode.py 619
18036             rep = str(dn)
18037             self.failUnless("RW-MUT" in rep)
18038             return dn.list()
18039-        d.addCallback(_created)
18040-       
18041+
18042         def _check_kids(children):
18043             self.failUnlessReallyEqual(set(children.keys()),
18044                                        set([one_nfc, u"two", u"mut", u"fut", u"fro",
18045hunk ./src/allmydata/test/test_dirnode.py 623
18046-                                            u"fut-unic", u"fro-unic", u"empty_litdir", u"tiny_litdir"]))
18047+                                        u"fut-unic", u"fro-unic", u"empty_litdir", u"tiny_litdir"]))
18048             one_node, one_metadata = children[one_nfc]
18049             two_node, two_metadata = children[u"two"]
18050             mut_node, mut_metadata = children[u"mut"]
18051hunk ./src/allmydata/test/test_dirnode.py 683
18052             d2.addCallback(lambda children: children[u"short"][0].read(MemAccum()))
18053             d2.addCallback(lambda accum: self.failUnlessReallyEqual(accum.data, "The end."))
18054             return d2
18055-
18056+        d.addCallback(_created)
18057         d.addCallback(_check_kids)
18058 
18059         d.addCallback(lambda ign: nm.create_new_mutable_directory(kids))
18060hunk ./src/allmydata/test/test_dirnode.py 707
18061                                       bad_kids2))
18062         return d
18063 
18064+    def _do_basic_test(self, mdmf=False):
18065+        c = self.g.clients[0]
18066+        d = None
18067+        if mdmf:
18068+            d = c.create_dirnode(version=MDMF_VERSION)
18069+        else:
18070+            d = c.create_dirnode()
18071+        def _done(res):
18072+            self.failUnless(isinstance(res, dirnode.DirectoryNode))
18073+            self.failUnless(res.is_mutable())
18074+            self.failIf(res.is_readonly())
18075+            self.failIf(res.is_unknown())
18076+            self.failIf(res.is_allowed_in_immutable_directory())
18077+            res.raise_error()
18078+            rep = str(res)
18079+            self.failUnless("RW-MUT" in rep)
18080+        d.addCallback(_done)
18081+        return d
18082+
18083+    def test_basic(self):
18084+        self.basedir = "dirnode/Dirnode/test_basic"
18085+        self.set_up_grid()
18086+        return self._do_basic_test()
18087+
18088+    def test_basic_mdmf(self):
18089+        self.basedir = "dirnode/Dirnode/test_basic_mdmf"
18090+        self.set_up_grid()
18091+        return self._do_basic_test(mdmf=True)
18092+
18093+    def test_initial_children(self):
18094+        self.basedir = "dirnode/Dirnode/test_initial_children"
18095+        self.set_up_grid()
18096+        return self._do_initial_children_test()
18097+
18098     def test_immutable(self):
18099         self.basedir = "dirnode/Dirnode/test_immutable"
18100         self.set_up_grid()
18101hunk ./src/allmydata/test/test_dirnode.py 1185
18102     def test_create(self):
18103         self.basedir = "dirnode/Dirnode/test_create"
18104         self.set_up_grid()
18105-        c = self.g.clients[0]
18106-
18107-        self.expected_manifest = []
18108-        self.expected_verifycaps = set()
18109-        self.expected_storage_indexes = set()
18110-
18111-        d = c.create_dirnode()
18112-        def _then(n):
18113-            # /
18114-            self.rootnode = n
18115-            self.failUnless(n.is_mutable())
18116-            u = n.get_uri()
18117-            self.failUnless(u)
18118-            self.failUnless(u.startswith("URI:DIR2:"), u)
18119-            u_ro = n.get_readonly_uri()
18120-            self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro)
18121-            u_v = n.get_verify_cap().to_string()
18122-            self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v)
18123-            u_r = n.get_repair_cap().to_string()
18124-            self.failUnlessReallyEqual(u_r, u)
18125-            self.expected_manifest.append( ((), u) )
18126-            self.expected_verifycaps.add(u_v)
18127-            si = n.get_storage_index()
18128-            self.expected_storage_indexes.add(base32.b2a(si))
18129-            expected_si = n._uri.get_storage_index()
18130-            self.failUnlessReallyEqual(si, expected_si)
18131-
18132-            d = n.list()
18133-            d.addCallback(lambda res: self.failUnlessEqual(res, {}))
18134-            d.addCallback(lambda res: n.has_child(u"missing"))
18135-            d.addCallback(lambda res: self.failIf(res))
18136-
18137-            fake_file_uri = make_mutable_file_uri()
18138-            other_file_uri = make_mutable_file_uri()
18139-            m = c.nodemaker.create_from_cap(fake_file_uri)
18140-            ffu_v = m.get_verify_cap().to_string()
18141-            self.expected_manifest.append( ((u"child",) , m.get_uri()) )
18142-            self.expected_verifycaps.add(ffu_v)
18143-            self.expected_storage_indexes.add(base32.b2a(m.get_storage_index()))
18144-            d.addCallback(lambda res: n.set_uri(u"child",
18145-                                                fake_file_uri, fake_file_uri))
18146-            d.addCallback(lambda res:
18147-                          self.shouldFail(ExistingChildError, "set_uri-no",
18148-                                          "child 'child' already exists",
18149-                                          n.set_uri, u"child",
18150-                                          other_file_uri, other_file_uri,
18151-                                          overwrite=False))
18152-            # /
18153-            # /child = mutable
18154-
18155-            d.addCallback(lambda res: n.create_subdirectory(u"subdir"))
18156-
18157-            # /
18158-            # /child = mutable
18159-            # /subdir = directory
18160-            def _created(subdir):
18161-                self.failUnless(isinstance(subdir, dirnode.DirectoryNode))
18162-                self.subdir = subdir
18163-                new_v = subdir.get_verify_cap().to_string()
18164-                assert isinstance(new_v, str)
18165-                self.expected_manifest.append( ((u"subdir",), subdir.get_uri()) )
18166-                self.expected_verifycaps.add(new_v)
18167-                si = subdir.get_storage_index()
18168-                self.expected_storage_indexes.add(base32.b2a(si))
18169-            d.addCallback(_created)
18170-
18171-            d.addCallback(lambda res:
18172-                          self.shouldFail(ExistingChildError, "mkdir-no",
18173-                                          "child 'subdir' already exists",
18174-                                          n.create_subdirectory, u"subdir",
18175-                                          overwrite=False))
18176-
18177-            d.addCallback(lambda res: n.list())
18178-            d.addCallback(lambda children:
18179-                          self.failUnlessReallyEqual(set(children.keys()),
18180-                                                     set([u"child", u"subdir"])))
18181-
18182-            d.addCallback(lambda res: n.start_deep_stats().when_done())
18183-            def _check_deepstats(stats):
18184-                self.failUnless(isinstance(stats, dict))
18185-                expected = {"count-immutable-files": 0,
18186-                            "count-mutable-files": 1,
18187-                            "count-literal-files": 0,
18188-                            "count-files": 1,
18189-                            "count-directories": 2,
18190-                            "size-immutable-files": 0,
18191-                            "size-literal-files": 0,
18192-                            #"size-directories": 616, # varies
18193-                            #"largest-directory": 616,
18194-                            "largest-directory-children": 2,
18195-                            "largest-immutable-file": 0,
18196-                            }
18197-                for k,v in expected.iteritems():
18198-                    self.failUnlessReallyEqual(stats[k], v,
18199-                                               "stats[%s] was %s, not %s" %
18200-                                               (k, stats[k], v))
18201-                self.failUnless(stats["size-directories"] > 500,
18202-                                stats["size-directories"])
18203-                self.failUnless(stats["largest-directory"] > 500,
18204-                                stats["largest-directory"])
18205-                self.failUnlessReallyEqual(stats["size-files-histogram"], [])
18206-            d.addCallback(_check_deepstats)
18207-
18208-            d.addCallback(lambda res: n.build_manifest().when_done())
18209-            def _check_manifest(res):
18210-                manifest = res["manifest"]
18211-                self.failUnlessReallyEqual(sorted(manifest),
18212-                                           sorted(self.expected_manifest))
18213-                stats = res["stats"]
18214-                _check_deepstats(stats)
18215-                self.failUnlessReallyEqual(self.expected_verifycaps,
18216-                                           res["verifycaps"])
18217-                self.failUnlessReallyEqual(self.expected_storage_indexes,
18218-                                           res["storage-index"])
18219-            d.addCallback(_check_manifest)
18220-
18221-            def _add_subsubdir(res):
18222-                return self.subdir.create_subdirectory(u"subsubdir")
18223-            d.addCallback(_add_subsubdir)
18224-            # /
18225-            # /child = mutable
18226-            # /subdir = directory
18227-            # /subdir/subsubdir = directory
18228-            d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir"))
18229-            d.addCallback(lambda subsubdir:
18230-                          self.failUnless(isinstance(subsubdir,
18231-                                                     dirnode.DirectoryNode)))
18232-            d.addCallback(lambda res: n.get_child_at_path(u""))
18233-            d.addCallback(lambda res: self.failUnlessReallyEqual(res.get_uri(),
18234-                                                                 n.get_uri()))
18235-
18236-            d.addCallback(lambda res: n.get_metadata_for(u"child"))
18237-            d.addCallback(lambda metadata:
18238-                          self.failUnlessEqual(set(metadata.keys()),
18239-                                               set(["tahoe"])))
18240-
18241-            d.addCallback(lambda res:
18242-                          self.shouldFail(NoSuchChildError, "gcamap-no",
18243-                                          "nope",
18244-                                          n.get_child_and_metadata_at_path,
18245-                                          u"subdir/nope"))
18246-            d.addCallback(lambda res:
18247-                          n.get_child_and_metadata_at_path(u""))
18248-            def _check_child_and_metadata1(res):
18249-                child, metadata = res
18250-                self.failUnless(isinstance(child, dirnode.DirectoryNode))
18251-                # edge-metadata needs at least one path segment
18252-                self.failUnlessEqual(set(metadata.keys()), set([]))
18253-            d.addCallback(_check_child_and_metadata1)
18254-            d.addCallback(lambda res:
18255-                          n.get_child_and_metadata_at_path(u"child"))
18256-
18257-            def _check_child_and_metadata2(res):
18258-                child, metadata = res
18259-                self.failUnlessReallyEqual(child.get_uri(),
18260-                                           fake_file_uri)
18261-                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
18262-            d.addCallback(_check_child_and_metadata2)
18263-
18264-            d.addCallback(lambda res:
18265-                          n.get_child_and_metadata_at_path(u"subdir/subsubdir"))
18266-            def _check_child_and_metadata3(res):
18267-                child, metadata = res
18268-                self.failUnless(isinstance(child, dirnode.DirectoryNode))
18269-                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
18270-            d.addCallback(_check_child_and_metadata3)
18271-
18272-            # set_uri + metadata
18273-            # it should be possible to add a child without any metadata
18274-            d.addCallback(lambda res: n.set_uri(u"c2",
18275-                                                fake_file_uri, fake_file_uri,
18276-                                                {}))
18277-            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
18278-            d.addCallback(lambda metadata:
18279-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18280-
18281-            # You can't override the link timestamps.
18282-            d.addCallback(lambda res: n.set_uri(u"c2",
18283-                                                fake_file_uri, fake_file_uri,
18284-                                                { 'tahoe': {'linkcrtime': "bogus"}}))
18285-            d.addCallback(lambda res: n.get_metadata_for(u"c2"))
18286-            def _has_good_linkcrtime(metadata):
18287-                self.failUnless(metadata.has_key('tahoe'))
18288-                self.failUnless(metadata['tahoe'].has_key('linkcrtime'))
18289-                self.failIfEqual(metadata['tahoe']['linkcrtime'], 'bogus')
18290-            d.addCallback(_has_good_linkcrtime)
18291-
18292-            # if we don't set any defaults, the child should get timestamps
18293-            d.addCallback(lambda res: n.set_uri(u"c3",
18294-                                                fake_file_uri, fake_file_uri))
18295-            d.addCallback(lambda res: n.get_metadata_for(u"c3"))
18296-            d.addCallback(lambda metadata:
18297-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18298-
18299-            # we can also add specific metadata at set_uri() time
18300-            d.addCallback(lambda res: n.set_uri(u"c4",
18301-                                                fake_file_uri, fake_file_uri,
18302-                                                {"key": "value"}))
18303-            d.addCallback(lambda res: n.get_metadata_for(u"c4"))
18304-            d.addCallback(lambda metadata:
18305-                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18306-                                              (metadata['key'] == "value"), metadata))
18307-
18308-            d.addCallback(lambda res: n.delete(u"c2"))
18309-            d.addCallback(lambda res: n.delete(u"c3"))
18310-            d.addCallback(lambda res: n.delete(u"c4"))
18311-
18312-            # set_node + metadata
18313-            # it should be possible to add a child without any metadata except for timestamps
18314-            d.addCallback(lambda res: n.set_node(u"d2", n, {}))
18315-            d.addCallback(lambda res: c.create_dirnode())
18316-            d.addCallback(lambda n2:
18317-                          self.shouldFail(ExistingChildError, "set_node-no",
18318-                                          "child 'd2' already exists",
18319-                                          n.set_node, u"d2", n2,
18320-                                          overwrite=False))
18321-            d.addCallback(lambda res: n.get_metadata_for(u"d2"))
18322-            d.addCallback(lambda metadata:
18323-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18324-
18325-            # if we don't set any defaults, the child should get timestamps
18326-            d.addCallback(lambda res: n.set_node(u"d3", n))
18327-            d.addCallback(lambda res: n.get_metadata_for(u"d3"))
18328-            d.addCallback(lambda metadata:
18329-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18330-
18331-            # we can also add specific metadata at set_node() time
18332-            d.addCallback(lambda res: n.set_node(u"d4", n,
18333-                                                {"key": "value"}))
18334-            d.addCallback(lambda res: n.get_metadata_for(u"d4"))
18335-            d.addCallback(lambda metadata:
18336-                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18337-                                          (metadata["key"] == "value"), metadata))
18338-
18339-            d.addCallback(lambda res: n.delete(u"d2"))
18340-            d.addCallback(lambda res: n.delete(u"d3"))
18341-            d.addCallback(lambda res: n.delete(u"d4"))
18342-
18343-            # metadata through set_children()
18344-            d.addCallback(lambda res:
18345-                          n.set_children({
18346-                              u"e1": (fake_file_uri, fake_file_uri),
18347-                              u"e2": (fake_file_uri, fake_file_uri, {}),
18348-                              u"e3": (fake_file_uri, fake_file_uri,
18349-                                      {"key": "value"}),
18350-                              }))
18351-            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
18352-            d.addCallback(lambda res:
18353-                          self.shouldFail(ExistingChildError, "set_children-no",
18354-                                          "child 'e1' already exists",
18355-                                          n.set_children,
18356-                                          { u"e1": (other_file_uri,
18357-                                                    other_file_uri),
18358-                                            u"new": (other_file_uri,
18359-                                                     other_file_uri),
18360-                                            },
18361-                                          overwrite=False))
18362-            # and 'new' should not have been created
18363-            d.addCallback(lambda res: n.list())
18364-            d.addCallback(lambda children: self.failIf(u"new" in children))
18365-            d.addCallback(lambda res: n.get_metadata_for(u"e1"))
18366-            d.addCallback(lambda metadata:
18367-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18368-            d.addCallback(lambda res: n.get_metadata_for(u"e2"))
18369-            d.addCallback(lambda metadata:
18370-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18371-            d.addCallback(lambda res: n.get_metadata_for(u"e3"))
18372-            d.addCallback(lambda metadata:
18373-                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18374-                                          (metadata["key"] == "value"), metadata))
18375-
18376-            d.addCallback(lambda res: n.delete(u"e1"))
18377-            d.addCallback(lambda res: n.delete(u"e2"))
18378-            d.addCallback(lambda res: n.delete(u"e3"))
18379-
18380-            # metadata through set_nodes()
18381-            d.addCallback(lambda res:
18382-                          n.set_nodes({ u"f1": (n, None),
18383-                                        u"f2": (n, {}),
18384-                                        u"f3": (n, {"key": "value"}),
18385-                                        }))
18386-            d.addCallback(lambda n2: self.failUnlessIdentical(n2, n))
18387-            d.addCallback(lambda res:
18388-                          self.shouldFail(ExistingChildError, "set_nodes-no",
18389-                                          "child 'f1' already exists",
18390-                                          n.set_nodes, { u"f1": (n, None),
18391-                                                         u"new": (n, None), },
18392-                                          overwrite=False))
18393-            # and 'new' should not have been created
18394-            d.addCallback(lambda res: n.list())
18395-            d.addCallback(lambda children: self.failIf(u"new" in children))
18396-            d.addCallback(lambda res: n.get_metadata_for(u"f1"))
18397-            d.addCallback(lambda metadata:
18398-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18399-            d.addCallback(lambda res: n.get_metadata_for(u"f2"))
18400-            d.addCallback(lambda metadata:
18401-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18402-            d.addCallback(lambda res: n.get_metadata_for(u"f3"))
18403-            d.addCallback(lambda metadata:
18404-                          self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18405-                                          (metadata["key"] == "value"), metadata))
18406-
18407-            d.addCallback(lambda res: n.delete(u"f1"))
18408-            d.addCallback(lambda res: n.delete(u"f2"))
18409-            d.addCallback(lambda res: n.delete(u"f3"))
18410-
18411-
18412-            d.addCallback(lambda res:
18413-                          n.set_metadata_for(u"child",
18414-                                             {"tags": ["web2.0-compatible"], "tahoe": {"bad": "mojo"}}))
18415-            d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
18416-            d.addCallback(lambda metadata:
18417-                          self.failUnless((set(metadata.keys()) == set(["tags", "tahoe"])) and
18418-                                          metadata["tags"] == ["web2.0-compatible"] and
18419-                                          "bad" not in metadata["tahoe"], metadata))
18420-
18421-            d.addCallback(lambda res:
18422-                          self.shouldFail(NoSuchChildError, "set_metadata_for-nosuch", "",
18423-                                          n.set_metadata_for, u"nosuch", {}))
18424-
18425-
18426-            def _start(res):
18427-                self._start_timestamp = time.time()
18428-            d.addCallback(_start)
18429-            # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all
18430-            # floats to hundredeths (it uses str(num) instead of repr(num)).
18431-            # simplejson-1.7.3 does not have this bug. To prevent this bug
18432-            # from causing the test to fail, stall for more than a few
18433-            # hundrededths of a second.
18434-            d.addCallback(self.stall, 0.1)
18435-            d.addCallback(lambda res: n.add_file(u"timestamps",
18436-                                                 upload.Data("stamp me", convergence="some convergence string")))
18437-            d.addCallback(self.stall, 0.1)
18438-            def _stop(res):
18439-                self._stop_timestamp = time.time()
18440-            d.addCallback(_stop)
18441-
18442-            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
18443-            def _check_timestamp1(metadata):
18444-                self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))
18445-                tahoe_md = metadata["tahoe"]
18446-                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
18447-
18448-                self.failUnlessGreaterOrEqualThan(tahoe_md["linkcrtime"],
18449-                                                  self._start_timestamp)
18450-                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
18451-                                                  tahoe_md["linkcrtime"])
18452-                self.failUnlessGreaterOrEqualThan(tahoe_md["linkmotime"],
18453-                                                  self._start_timestamp)
18454-                self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
18455-                                                  tahoe_md["linkmotime"])
18456-                # Our current timestamp rules say that replacing an existing
18457-                # child should preserve the 'linkcrtime' but update the
18458-                # 'linkmotime'
18459-                self._old_linkcrtime = tahoe_md["linkcrtime"]
18460-                self._old_linkmotime = tahoe_md["linkmotime"]
18461-            d.addCallback(_check_timestamp1)
18462-            d.addCallback(self.stall, 2.0) # accomodate low-res timestamps
18463-            d.addCallback(lambda res: n.set_node(u"timestamps", n))
18464-            d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
18465-            def _check_timestamp2(metadata):
18466-                self.failUnlessIn("tahoe", metadata)
18467-                tahoe_md = metadata["tahoe"]
18468-                self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"]))
18469-
18470-                self.failUnlessReallyEqual(tahoe_md["linkcrtime"], self._old_linkcrtime)
18471-                self.failUnlessGreaterThan(tahoe_md["linkmotime"], self._old_linkmotime)
18472-                return n.delete(u"timestamps")
18473-            d.addCallback(_check_timestamp2)
18474-
18475-            d.addCallback(lambda res: n.delete(u"subdir"))
18476-            d.addCallback(lambda old_child:
18477-                          self.failUnlessReallyEqual(old_child.get_uri(),
18478-                                                     self.subdir.get_uri()))
18479-
18480-            d.addCallback(lambda res: n.list())
18481-            d.addCallback(lambda children:
18482-                          self.failUnlessReallyEqual(set(children.keys()),
18483-                                                     set([u"child"])))
18484-
18485-            uploadable1 = upload.Data("some data", convergence="converge")
18486-            d.addCallback(lambda res: n.add_file(u"newfile", uploadable1))
18487-            d.addCallback(lambda newnode:
18488-                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
18489-            uploadable2 = upload.Data("some data", convergence="stuff")
18490-            d.addCallback(lambda res:
18491-                          self.shouldFail(ExistingChildError, "add_file-no",
18492-                                          "child 'newfile' already exists",
18493-                                          n.add_file, u"newfile",
18494-                                          uploadable2,
18495-                                          overwrite=False))
18496-            d.addCallback(lambda res: n.list())
18497-            d.addCallback(lambda children:
18498-                          self.failUnlessReallyEqual(set(children.keys()),
18499-                                                     set([u"child", u"newfile"])))
18500-            d.addCallback(lambda res: n.get_metadata_for(u"newfile"))
18501-            d.addCallback(lambda metadata:
18502-                          self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])))
18503-
18504-            uploadable3 = upload.Data("some data", convergence="converge")
18505-            d.addCallback(lambda res: n.add_file(u"newfile-metadata",
18506-                                                 uploadable3,
18507-                                                 {"key": "value"}))
18508-            d.addCallback(lambda newnode:
18509-                          self.failUnless(IImmutableFileNode.providedBy(newnode)))
18510-            d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata"))
18511-            d.addCallback(lambda metadata:
18512-                              self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and
18513-                                              (metadata['key'] == "value"), metadata))
18514-            d.addCallback(lambda res: n.delete(u"newfile-metadata"))
18515-
18516-            d.addCallback(lambda res: n.create_subdirectory(u"subdir2"))
18517-            def _created2(subdir2):
18518-                self.subdir2 = subdir2
18519-                # put something in the way, to make sure it gets overwritten
18520-                return subdir2.add_file(u"child", upload.Data("overwrite me",
18521-                                                              "converge"))
18522-            d.addCallback(_created2)
18523-
18524-            d.addCallback(lambda res:
18525-                          n.move_child_to(u"child", self.subdir2))
18526-            d.addCallback(lambda res: n.list())
18527-            d.addCallback(lambda children:
18528-                          self.failUnlessReallyEqual(set(children.keys()),
18529-                                                     set([u"newfile", u"subdir2"])))
18530-            d.addCallback(lambda res: self.subdir2.list())
18531-            d.addCallback(lambda children:
18532-                          self.failUnlessReallyEqual(set(children.keys()),
18533-                                                     set([u"child"])))
18534-            d.addCallback(lambda res: self.subdir2.get(u"child"))
18535-            d.addCallback(lambda child:
18536-                          self.failUnlessReallyEqual(child.get_uri(),
18537-                                                     fake_file_uri))
18538-
18539-            # move it back, using new_child_name=
18540-            d.addCallback(lambda res:
18541-                          self.subdir2.move_child_to(u"child", n, u"newchild"))
18542-            d.addCallback(lambda res: n.list())
18543-            d.addCallback(lambda children:
18544-                          self.failUnlessReallyEqual(set(children.keys()),
18545-                                                     set([u"newchild", u"newfile",
18546-                                                          u"subdir2"])))
18547-            d.addCallback(lambda res: self.subdir2.list())
18548-            d.addCallback(lambda children:
18549-                          self.failUnlessReallyEqual(set(children.keys()), set([])))
18550-
18551-            # now make sure that we honor overwrite=False
18552-            d.addCallback(lambda res:
18553-                          self.subdir2.set_uri(u"newchild",
18554-                                               other_file_uri, other_file_uri))
18555-
18556-            d.addCallback(lambda res:
18557-                          self.shouldFail(ExistingChildError, "move_child_to-no",
18558-                                          "child 'newchild' already exists",
18559-                                          n.move_child_to, u"newchild",
18560-                                          self.subdir2,
18561-                                          overwrite=False))
18562-            d.addCallback(lambda res: self.subdir2.get(u"newchild"))
18563-            d.addCallback(lambda child:
18564-                          self.failUnlessReallyEqual(child.get_uri(),
18565-                                                     other_file_uri))
18566-
18567-
18568-            # Setting the no-write field should diminish a mutable cap to read-only
18569-            # (for both files and directories).
18570-
18571-            d.addCallback(lambda ign: n.set_uri(u"mutable", other_file_uri, other_file_uri))
18572-            d.addCallback(lambda ign: n.get(u"mutable"))
18573-            d.addCallback(lambda mutable: self.failIf(mutable.is_readonly(), mutable))
18574-            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
18575-            d.addCallback(lambda ign: n.get(u"mutable"))
18576-            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
18577-            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
18578-            d.addCallback(lambda ign: n.get(u"mutable"))
18579-            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
18580-
18581-            d.addCallback(lambda ign: n.get(u"subdir2"))
18582-            d.addCallback(lambda subdir2: self.failIf(subdir2.is_readonly()))
18583-            d.addCallback(lambda ign: n.set_metadata_for(u"subdir2", {"no-write": True}))
18584-            d.addCallback(lambda ign: n.get(u"subdir2"))
18585-            d.addCallback(lambda subdir2: self.failUnless(subdir2.is_readonly(), subdir2))
18586-
18587-            d.addCallback(lambda ign: n.set_uri(u"mutable_ro", other_file_uri, other_file_uri,
18588-                                                metadata={"no-write": True}))
18589-            d.addCallback(lambda ign: n.get(u"mutable_ro"))
18590-            d.addCallback(lambda mutable_ro: self.failUnless(mutable_ro.is_readonly(), mutable_ro))
18591-
18592-            d.addCallback(lambda ign: n.create_subdirectory(u"subdir_ro", metadata={"no-write": True}))
18593-            d.addCallback(lambda ign: n.get(u"subdir_ro"))
18594-            d.addCallback(lambda subdir_ro: self.failUnless(subdir_ro.is_readonly(), subdir_ro))
18595-
18596-            return d
18597-
18598-        d.addCallback(_then)
18599-
18600-        d.addErrback(self.explain_error)
18601-        return d
18602+        return self._do_create_test()
18603 
18604     def test_update_metadata(self):
18605         (t1, t2, t3) = (626644800.0, 634745640.0, 892226160.0)
18606hunk ./src/allmydata/test/test_dirnode.py 1236
18607         d.addCallback(_then)
18608         return d
18609 
18610+    def test_create_mdmf(self):
18611+        self.basedir = "dirnode/Dirnode/test_mdmf"
18612+        self.set_up_grid()
18613+        return self._do_create_test(mdmf=True)
18614+
18615+    def test_mdmf_initial_children(self):
18616+        self.basedir = "dirnode/Dirnode/test_mdmf"
18617+        self.set_up_grid()
18618+        return self._do_initial_children_test(mdmf=True)
18619+
18620 class MinimalFakeMutableFile:
18621     def get_writekey(self):
18622         return "writekey"
18623hunk ./src/allmydata/test/test_dirnode.py 1618
18624             self.failUnless(n.get_readonly_uri().startswith("imm."), i)
18625 
18626 
18627+
18628 class DeepStats(testutil.ReallyEqualMixin, unittest.TestCase):
18629     timeout = 240 # It takes longer than 120 seconds on Francois's arm box.
18630     def test_stats(self):
18631hunk ./src/allmydata/test/test_uri.py 795
18632         self.failUnlessReallyEqual(u1.get_storage_index(), None)
18633         self.failUnlessReallyEqual(u1.abbrev_si(), "<LIT>")
18634 
18635+    def test_mdmf(self):
18636+        writekey = "\x01" * 16
18637+        fingerprint = "\x02" * 32
18638+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
18639+        d1 = uri.MDMFDirectoryURI(uri1)
18640+        self.failIf(d1.is_readonly())
18641+        self.failUnless(d1.is_mutable())
18642+        self.failUnless(IURI.providedBy(d1))
18643+        self.failUnless(IDirnodeURI.providedBy(d1))
18644+        d1_uri = d1.to_string()
18645+
18646+        d2 = uri.from_string(d1_uri)
18647+        self.failUnlessIsInstance(d2, uri.MDMFDirectoryURI)
18648+        self.failIf(d2.is_readonly())
18649+        self.failUnless(d2.is_mutable())
18650+        self.failUnless(IURI.providedBy(d2))
18651+        self.failUnless(IDirnodeURI.providedBy(d2))
18652+
18653+        # It doesn't make sense to ask for a deep immutable URI for a
18654+        # mutable directory, and we should get back a result to that
18655+        # effect.
18656+        d3 = uri.from_string(d2.to_string(), deep_immutable=True)
18657+        self.failUnlessIsInstance(d3, uri.UnknownURI)
18658+
18659+    def test_mdmf_with_extensions(self):
18660+        writekey = "\x01" * 16
18661+        fingerprint = "\x02" * 32
18662+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
18663+        d1 = uri.MDMFDirectoryURI(uri1)
18664+        d1_uri = d1.to_string()
18665+        # Add some extensions, verify that the URI is interpreted
18666+        # correctly.
18667+        d1_uri += ":3:131073"
18668+        uri2 = uri.from_string(d1_uri)
18669+        self.failUnlessIsInstance(uri2, uri.MDMFDirectoryURI)
18670+        self.failUnless(IURI.providedBy(uri2))
18671+        self.failUnless(IDirnodeURI.providedBy(uri2))
18672+        self.failUnless(uri1.is_mutable())
18673+        self.failIf(uri1.is_readonly())
18674+
18675+        d2_uri = uri2.to_string()
18676+        self.failUnlessIn(":3:131073", d2_uri)
18677+
18678+        # Now attenuate, verify that the extensions persist
18679+        ro_uri = uri2.get_readonly()
18680+        self.failUnlessIsInstance(ro_uri, uri.ReadonlyMDMFDirectoryURI)
18681+        self.failUnless(ro_uri.is_mutable())
18682+        self.failUnless(ro_uri.is_readonly())
18683+        self.failUnless(IURI.providedBy(ro_uri))
18684+        self.failUnless(IDirnodeURI.providedBy(ro_uri))
18685+        ro_uri_str = ro_uri.to_string()
18686+        self.failUnlessIn(":3:131073", ro_uri_str)
18687+
18688+    def test_mdmf_attenuation(self):
18689+        writekey = "\x01" * 16
18690+        fingerprint = "\x02" * 32
18691+
18692+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
18693+        d1 = uri.MDMFDirectoryURI(uri1)
18694+        self.failUnless(d1.is_mutable())
18695+        self.failIf(d1.is_readonly())
18696+        self.failUnless(IURI.providedBy(d1))
18697+        self.failUnless(IDirnodeURI.providedBy(d1))
18698+
18699+        d1_uri = d1.to_string()
18700+        d1_uri_from_fn = uri.MDMFDirectoryURI(d1.get_filenode_cap()).to_string()
18701+        self.failUnlessEqual(d1_uri_from_fn, d1_uri)
18702+
18703+        uri2 = uri.from_string(d1_uri)
18704+        self.failUnlessIsInstance(uri2, uri.MDMFDirectoryURI)
18705+        self.failUnless(IURI.providedBy(uri2))
18706+        self.failUnless(IDirnodeURI.providedBy(uri2))
18707+        self.failUnless(uri2.is_mutable())
18708+        self.failIf(uri2.is_readonly())
18709+
18710+        ro = uri2.get_readonly()
18711+        self.failUnlessIsInstance(ro, uri.ReadonlyMDMFDirectoryURI)
18712+        self.failUnless(ro.is_mutable())
18713+        self.failUnless(ro.is_readonly())
18714+        self.failUnless(IURI.providedBy(ro))
18715+        self.failUnless(IDirnodeURI.providedBy(ro))
18716+
18717+        ro_uri = ro.to_string()
18718+        n = uri.from_string(ro_uri, deep_immutable=True)
18719+        self.failUnlessIsInstance(n, uri.UnknownURI)
18720+
18721+        fn_cap = ro.get_filenode_cap()
18722+        fn_ro_cap = fn_cap.get_readonly()
18723+        d3 = uri.ReadonlyMDMFDirectoryURI(fn_ro_cap)
18724+        self.failUnlessEqual(ro.to_string(), d3.to_string())
18725+        self.failUnless(ro.is_mutable())
18726+        self.failUnless(ro.is_readonly())
18727+
18728+    def test_mdmf_verifier(self):
18729+        # I'm not sure what I want to write here yet.
18730+        writekey = "\x01" * 16
18731+        fingerprint = "\x02" * 32
18732+        uri1 = uri.WritableMDMFFileURI(writekey, fingerprint)
18733+        d1 = uri.MDMFDirectoryURI(uri1)
18734+        v1 = d1.get_verify_cap()
18735+        self.failUnlessIsInstance(v1, uri.MDMFDirectoryURIVerifier)
18736+        self.failIf(v1.is_mutable())
18737+
18738+        d2 = uri.from_string(d1.to_string())
18739+        v2 = d2.get_verify_cap()
18740+        self.failUnlessIsInstance(v2, uri.MDMFDirectoryURIVerifier)
18741+        self.failIf(v2.is_mutable())
18742+        self.failUnlessEqual(v2.to_string(), v1.to_string())
18743+
18744+        # Now attenuate and make sure that works correctly.
18745+        r3 = d2.get_readonly()
18746+        v3 = r3.get_verify_cap()
18747+        self.failUnlessIsInstance(v3, uri.MDMFDirectoryURIVerifier)
18748+        self.failIf(v3.is_mutable())
18749+        self.failUnlessEqual(v3.to_string(), v1.to_string())
18750+        r4 = uri.from_string(r3.to_string())
18751+        v4 = r4.get_verify_cap()
18752+        self.failUnlessIsInstance(v4, uri.MDMFDirectoryURIVerifier)
18753+        self.failIf(v4.is_mutable())
18754+        self.failUnlessEqual(v4.to_string(), v3.to_string())
18755+
18756}
18757[uri: teach mutable URI objects how to allow other objects to give them extension parameters
18758Kevan Carstensen <kevan@isnotajoke.com>**20110531012036
18759 Ignore-this: 96c06cee1efe5a92a5ed8d87ca09a7dd
18760] {
18761hunk ./src/allmydata/uri.py 300
18762     def get_extension_params(self):
18763         return []
18764 
18765+    def set_extension_params(self, params):
18766+        pass
18767+
18768 class ReadonlySSKFileURI(_BaseURI):
18769     implements(IURI, IMutableFileURI)
18770 
18771hunk ./src/allmydata/uri.py 360
18772     def get_extension_params(self):
18773         return []
18774 
18775+    def set_extension_params(self, params):
18776+        pass
18777+
18778 class SSKVerifierURI(_BaseURI):
18779     implements(IVerifierURI)
18780 
18781hunk ./src/allmydata/uri.py 410
18782     def get_extension_params(self):
18783         return []
18784 
18785+    def set_extension_params(self, params):
18786+        pass
18787+
18788 class WritableMDMFFileURI(_BaseURI):
18789     implements(IURI, IMutableFileURI)
18790 
18791hunk ./src/allmydata/uri.py 480
18792     def get_extension_params(self):
18793         return self.extension
18794 
18795+    def set_extension_params(self, params):
18796+        params = map(str, params)
18797+        self.extension = params
18798+
18799 class ReadonlyMDMFFileURI(_BaseURI):
18800     implements(IURI, IMutableFileURI)
18801 
18802hunk ./src/allmydata/uri.py 552
18803     def get_extension_params(self):
18804         return self.extension
18805 
18806+    def set_extension_params(self, params):
18807+        params = map(str, params)
18808+        self.extension = params
18809+
18810 class MDMFVerifierURI(_BaseURI):
18811     implements(IVerifierURI)
18812 
18813}
18814[interfaces: working update to interfaces.py for extension handling
18815Kevan Carstensen <kevan@isnotajoke.com>**20110531012201
18816 Ignore-this: 559c43cbf14eec7ac163ebd00c0b7a36
18817] {
18818hunk ./src/allmydata/interfaces.py 549
18819     def get_extension_params():
18820         """Return the extension parameters in the URI"""
18821 
18822+    def set_extension_params():
18823+        """Set the extension parameters that should be in the URI"""
18824+
18825 class IDirectoryURI(Interface):
18826     pass
18827 
18828hunk ./src/allmydata/interfaces.py 1049
18829         writer-visible data using this writekey.
18830         """
18831 
18832-    def set_version(version):
18833-        """Tahoe-LAFS supports SDMF and MDMF mutable files. By default,
18834-        we upload in SDMF for reasons of compatibility. If you want to
18835-        change this, set_version will let you do that.
18836-
18837-        To say that this file should be uploaded in SDMF, pass in a 0. To
18838-        say that the file should be uploaded as MDMF, pass in a 1.
18839-        """
18840-
18841     def get_version():
18842         """Returns the mutable file protocol version."""
18843 
18844}
18845[mutable/publish: tell filenodes about encoding parameters so they can be put in the cap
18846Kevan Carstensen <kevan@isnotajoke.com>**20110531012447
18847 Ignore-this: cf19f07a6913208a327604457466f2f2
18848] hunk ./src/allmydata/mutable/publish.py 1146
18849         self.log("Publish done, success")
18850         self._status.set_status("Finished")
18851         self._status.set_progress(1.0)
18852+        # Get k and segsize, then give them to the caller.
18853+        hints = {}
18854+        hints['segsize'] = self.segment_size
18855+        hints['k'] = self.required_shares
18856+        self._node.set_downloader_hints(hints)
18857         eventually(self.done_deferred.callback, res)
18858 
18859     def _failure(self):
18860[mutable/servermap: caps imply protocol version, so the servermap doesn't need to tell the filenode what it is anymore.
18861Kevan Carstensen <kevan@isnotajoke.com>**20110531012557
18862 Ignore-this: 9925f5dde5452db92cdbc4a7d6adf1c1
18863] hunk ./src/allmydata/mutable/servermap.py 877
18864         # and the versionmap
18865         self.versionmap.add(verinfo, (shnum, peerid, timestamp))
18866 
18867-        # It's our job to set the protocol version of our parent
18868-        # filenode if it isn't already set.
18869-        if not self._node.get_version():
18870-            # The first byte of the prefix is the version.
18871-            v = struct.unpack(">B", prefix[:1])[0]
18872-            self.log("got version %d" % v)
18873-            self._node.set_version(v)
18874-
18875         return verinfo
18876 
18877 
18878[mutable/filenode: pass downloader hints between publisher, MutableFileNode, and MutableFileVersion as convenient
18879Kevan Carstensen <kevan@isnotajoke.com>**20110531012641
18880 Ignore-this: 672c586891abfa38397bcdf90b64ca72
18881 
18882 We still need to work on making this more thorough; i.e., passing hints
18883 when other operations change encoding parameters.
18884] {
18885hunk ./src/allmydata/mutable/filenode.py 75
18886         # set to this default value in case neither of those things happen,
18887         # or in case the servermap can't find any shares to tell us what
18888         # to publish as.
18889-        # XXX: Version should come in via the constructor.
18890         self._protocol_version = None
18891 
18892         # all users of this MutableFileNode go through the serializer. This
18893hunk ./src/allmydata/mutable/filenode.py 83
18894         # forever without consuming more and more memory.
18895         self._serializer = defer.succeed(None)
18896 
18897+        # Starting with MDMF, we can get these from caps if they're
18898+        # there. Leave them alone for now; they'll be filled in by my
18899+        # init_from_cap method if necessary.
18900+        self._downloader_hints = {}
18901+
18902     def __repr__(self):
18903         if hasattr(self, '_uri'):
18904             return "<%s %x %s %s>" % (self.__class__.__name__, id(self), self.is_readonly() and 'RO' or 'RW', self._uri.abbrev())
18905hunk ./src/allmydata/mutable/filenode.py 120
18906         # if possible, otherwise by the first peer that Publish talks to.
18907         self._privkey = None
18908         self._encprivkey = None
18909+
18910+        # Starting with MDMF caps, we allowed arbitrary extensions in
18911+        # caps. If we were initialized with a cap that had extensions,
18912+        # we want to remember them so we can tell MutableFileVersions
18913+        # about them.
18914+        extensions = self._uri.get_extension_params()
18915+        if extensions:
18916+            extensions = map(int, extensions)
18917+            suspected_k, suspected_segsize = extensions
18918+            self._downloader_hints['k'] = suspected_k
18919+            self._downloader_hints['segsize'] = suspected_segsize
18920+
18921         return self
18922 
18923hunk ./src/allmydata/mutable/filenode.py 134
18924-    def create_with_keys(self, (pubkey, privkey), contents):
18925+    def create_with_keys(self, (pubkey, privkey), contents,
18926+                         version=SDMF_VERSION):
18927         """Call this to create a brand-new mutable file. It will create the
18928         shares, find homes for them, and upload the initial contents (created
18929         with the same rules as IClient.create_mutable_file() ). Returns a
18930hunk ./src/allmydata/mutable/filenode.py 148
18931         self._writekey = hashutil.ssk_writekey_hash(privkey_s)
18932         self._encprivkey = self._encrypt_privkey(self._writekey, privkey_s)
18933         self._fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)
18934-        if self._protocol_version == MDMF_VERSION:
18935+        if version == MDMF_VERSION:
18936             self._uri = WritableMDMFFileURI(self._writekey, self._fingerprint)
18937hunk ./src/allmydata/mutable/filenode.py 150
18938-        else:
18939+            self._protocol_version = version
18940+        elif version == SDMF_VERSION:
18941             self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
18942hunk ./src/allmydata/mutable/filenode.py 153
18943+            self._protocol_version = version
18944         self._readkey = self._uri.readkey
18945         self._storage_index = self._uri.storage_index
18946         initial_contents = self._get_initial_contents(contents)
18947hunk ./src/allmydata/mutable/filenode.py 365
18948                                      self._readkey,
18949                                      history=self._history)
18950             assert mfv.is_readonly()
18951+            mfv.set_downloader_hints(self._downloader_hints)
18952             # our caller can use this to download the contents of the
18953             # mutable file.
18954             return mfv
18955hunk ./src/allmydata/mutable/filenode.py 520
18956                                      self._secret_holder,
18957                                      history=self._history)
18958             assert not mfv.is_readonly()
18959+            mfv.set_downloader_hints(self._downloader_hints)
18960             return mfv
18961 
18962         return d.addCallback(_build_version)
18963hunk ./src/allmydata/mutable/filenode.py 549
18964         new_contents as an argument. I return a Deferred that eventually
18965         fires with the results of my replacement process.
18966         """
18967+        # TODO: Update downloader hints.
18968         return self._do_serialized(self._overwrite, new_contents)
18969 
18970 
18971hunk ./src/allmydata/mutable/filenode.py 563
18972         return d
18973 
18974 
18975-
18976     def upload(self, new_contents, servermap):
18977         """
18978         I overwrite the contents of the best recoverable version of this
18979hunk ./src/allmydata/mutable/filenode.py 570
18980         creating/updating our own servermap. I return a Deferred that
18981         fires with the results of my upload.
18982         """
18983+        # TODO: Update downloader hints
18984         return self._do_serialized(self._upload, new_contents, servermap)
18985 
18986 
18987hunk ./src/allmydata/mutable/filenode.py 582
18988         Deferred that eventually fires with an UploadResults instance
18989         describing this process.
18990         """
18991+        # TODO: Update downloader hints.
18992         return self._do_serialized(self._modify, modifier, backoffer)
18993 
18994 
18995hunk ./src/allmydata/mutable/filenode.py 650
18996         return u.update()
18997 
18998 
18999-    def set_version(self, version):
19000+    #def set_version(self, version):
19001         # I can be set in two ways:
19002         #  1. When the node is created.
19003         #  2. (for an existing share) when the Servermap is updated
19004hunk ./src/allmydata/mutable/filenode.py 655
19005         #     before I am read.
19006-        assert version in (MDMF_VERSION, SDMF_VERSION)
19007-        self._protocol_version = version
19008+    #    assert version in (MDMF_VERSION, SDMF_VERSION)
19009+    #    self._protocol_version = version
19010 
19011 
19012     def get_version(self):
19013hunk ./src/allmydata/mutable/filenode.py 691
19014         """
19015         assert self._pubkey, "update_servermap must be called before publish"
19016 
19017+        # Define IPublishInvoker with a set_downloader_hints method?
19018+        # Then have the publisher call that method when it's done publishing?
19019         p = Publish(self, self._storage_broker, servermap)
19020         if self._history:
19021             self._history.notify_publish(p.get_status(),
19022hunk ./src/allmydata/mutable/filenode.py 702
19023         return d
19024 
19025 
19026+    def set_downloader_hints(self, hints):
19027+        self._downloader_hints = hints
19028+        extensions = hints.values()
19029+        self._uri.set_extension_params(extensions)
19030+
19031+
19032     def _did_upload(self, res, size):
19033         self._most_recent_size = size
19034         return res
19035hunk ./src/allmydata/mutable/filenode.py 769
19036         return self._writekey
19037 
19038 
19039+    def set_downloader_hints(self, hints):
19040+        """
19041+        I set the downloader hints.
19042+        """
19043+        assert isinstance(hints, dict)
19044+
19045+        self._downloader_hints = hints
19046+
19047+
19048+    def get_downloader_hints(self):
19049+        """
19050+        I return the downloader hints.
19051+        """
19052+        return self._downloader_hints
19053+
19054+
19055     def overwrite(self, new_contents):
19056         """
19057         I overwrite the contents of this mutable file version with the
19058hunk ./src/allmydata/nodemaker.py 97
19059                             version=SDMF_VERSION):
19060         n = MutableFileNode(self.storage_broker, self.secret_holder,
19061                             self.default_encoding_parameters, self.history)
19062-        n.set_version(version)
19063         d = self.key_generator.generate(keysize)
19064hunk ./src/allmydata/nodemaker.py 98
19065-        d.addCallback(n.create_with_keys, contents)
19066+        d.addCallback(n.create_with_keys, contents, version=version)
19067         d.addCallback(lambda res: n)
19068         return d
19069 
19070}
19071[test: change test fixtures to work with our new extension passing API; add, change, and delete tests as appropriate to reflect the fact that caps without hints are now the exception rather than the norm
19072Kevan Carstensen <kevan@isnotajoke.com>**20110531012739
19073 Ignore-this: 30ebf79b5f6c17f40fa4385de12070a0
19074] {
19075hunk ./src/allmydata/test/common.py 198
19076     def __init__(self, storage_broker, secret_holder,
19077                  default_encoding_parameters, history):
19078         self.init_from_cap(make_mutable_file_cap())
19079-    def create(self, contents, key_generator=None, keysize=None):
19080-        if self.file_types[self.storage_index] == MDMF_VERSION and \
19081+        self._k = default_encoding_parameters['k']
19082+        self._segsize = default_encoding_parameters['max_segment_size']
19083+    def create(self, contents, key_generator=None, keysize=None,
19084+               version=SDMF_VERSION):
19085+        if version == MDMF_VERSION and \
19086             isinstance(self.my_uri, (uri.ReadonlySSKFileURI,
19087                                  uri.WriteableSSKFileURI)):
19088             self.init_from_cap(make_mdmf_mutable_file_cap())
19089hunk ./src/allmydata/test/common.py 206
19090+        self.file_types[self.storage_index] = version
19091         initial_contents = self._get_initial_contents(contents)
19092         data = initial_contents.read(initial_contents.get_size())
19093         data = "".join(data)
19094hunk ./src/allmydata/test/common.py 211
19095         self.all_contents[self.storage_index] = data
19096+        self.my_uri.set_extension_params([self._k, self._segsize])
19097         return defer.succeed(self)
19098     def _get_initial_contents(self, contents):
19099         if contents is None:
19100hunk ./src/allmydata/test/common.py 283
19101     def get_servermap(self, mode):
19102         return defer.succeed(None)
19103 
19104-    def set_version(self, version):
19105-        assert version in (SDMF_VERSION, MDMF_VERSION)
19106-        self.file_types[self.storage_index] = version
19107-
19108     def get_version(self):
19109         assert self.storage_index in self.file_types
19110         return self.file_types[self.storage_index]
19111hunk ./src/allmydata/test/common.py 361
19112         new_data = new_contents.read(new_contents.get_size())
19113         new_data = "".join(new_data)
19114         self.all_contents[self.storage_index] = new_data
19115+        self.my_uri.set_extension_params([self._k, self._segsize])
19116         return defer.succeed(None)
19117     def modify(self, modifier):
19118         # this does not implement FileTooLargeError, but the real one does
19119hunk ./src/allmydata/test/common.py 371
19120         old_contents = self.all_contents[self.storage_index]
19121         new_data = modifier(old_contents, None, True)
19122         self.all_contents[self.storage_index] = new_data
19123+        self.my_uri.set_extension_params([self._k, self._segsize])
19124         return None
19125 
19126     # As actually implemented, MutableFilenode and MutableFileVersion
19127hunk ./src/allmydata/test/common.py 433
19128     else:
19129         cap = make_mutable_file_cap()
19130 
19131-    filenode = FakeMutableFileNode(None, None, None, None)
19132+    encoding_params = {}
19133+    encoding_params['k'] = 3
19134+    encoding_params['max_segment_size'] = 128*1024
19135+
19136+    filenode = FakeMutableFileNode(None, None, encoding_params, None)
19137     filenode.init_from_cap(cap)
19138hunk ./src/allmydata/test/common.py 439
19139-    FakeMutableFileNode.all_contents[filenode.storage_index] = contents
19140+    if mdmf:
19141+        filenode.create(MutableData(contents), version=MDMF_VERSION)
19142+    else:
19143+        filenode.create(MutableData(contents), version=SDMF_VERSION)
19144     return filenode
19145 
19146 
19147hunk ./src/allmydata/test/test_cli.py 1053
19148             self.failUnlessEqual(rc, 0)
19149             self.failUnlessEqual(out, data2)
19150         d.addCallback(_got_data)
19151+        # Now strip the extension information off of the cap and try
19152+        # to put something to it.
19153+        def _make_bare_cap(ignored):
19154+            cap = self.cap.split(":")
19155+            cap = ":".join(cap[:len(cap) - 2])
19156+            self.cap = cap
19157+        d.addCallback(_make_bare_cap)
19158+        data3 = "data3" * 100000
19159+        fn3 = os.path.join(self.basedir, "data3")
19160+        fileutil.write(fn3, data3)
19161+        d.addCallback(lambda ignored:
19162+            self.do_cli("put", fn3, self.cap))
19163+        d.addCallback(lambda ignored:
19164+            self.do_cli("get", self.cap))
19165+        def _got_data3((rc, out, err)):
19166+            self.failUnlessEqual(rc, 0)
19167+            self.failUnlessEqual(out, data3)
19168+        d.addCallback(_got_data3)
19169         return d
19170 
19171     def test_put_to_sdmf_cap(self):
19172hunk ./src/allmydata/test/test_mutable.py 311
19173         def _created(n):
19174             self.failUnless(isinstance(n, MutableFileNode))
19175             s = n.get_uri()
19176-            s2 = "%s:3:131073" % s
19177-            n2 = self.nodemaker.create_from_cap(s2)
19178+            # We need to cheat a little and delete the nodemaker's
19179+            # cache, otherwise we'll get the same node instance back.
19180+            self.failUnlessIn(":3:131073", s)
19181+            n2 = self.nodemaker.create_from_cap(s)
19182 
19183             self.failUnlessEqual(n2.get_storage_index(), n.get_storage_index())
19184             self.failUnlessEqual(n.get_writekey(), n2.get_writekey())
19185hunk ./src/allmydata/test/test_mutable.py 318
19186+            hints = n2._downloader_hints
19187+            self.failUnlessEqual(hints['k'], 3)
19188+            self.failUnlessEqual(hints['segsize'], 131073)
19189         d.addCallback(_created)
19190         return d
19191 
19192hunk ./src/allmydata/test/test_mutable.py 346
19193         def _created(n):
19194             self.failUnless(isinstance(n, MutableFileNode))
19195             s = n.get_readonly_uri()
19196-            s = "%s:3:131073" % s
19197+            self.failUnlessIn(":3:131073", s)
19198 
19199             n2 = self.nodemaker.create_from_cap(s)
19200             self.failUnless(isinstance(n2, MutableFileNode))
19201hunk ./src/allmydata/test/test_mutable.py 352
19202             self.failUnless(n2.is_readonly())
19203             self.failUnlessEqual(n.get_storage_index(), n2.get_storage_index())
19204+            hints = n2._downloader_hints
19205+            self.failUnlessEqual(hints["k"], 3)
19206+            self.failUnlessEqual(hints["segsize"], 131073)
19207         d.addCallback(_created)
19208         return d
19209 
19210hunk ./src/allmydata/test/test_mutable.py 514
19211         return d
19212 
19213 
19214+    def test_create_and_download_from_bare_mdmf_cap(self):
19215+        # MDMF caps have extension parameters on them by default. We
19216+        # need to make sure that they work without extension parameters.
19217+        contents = MutableData("contents" * 100000)
19218+        d = self.nodemaker.create_mutable_file(version=MDMF_VERSION,
19219+                                               contents=contents)
19220+        def _created(node):
19221+            uri = node.get_uri()
19222+            self._created = node
19223+            self.failUnlessIn(":3:131073", uri)
19224+            # Now strip that off the end of the uri, then try creating
19225+            # and downloading the node again.
19226+            bare_uri = uri.replace(":3:131073", "")
19227+            assert ":3:131073" not in bare_uri
19228+
19229+            return self.nodemaker.create_from_cap(bare_uri)
19230+        d.addCallback(_created)
19231+        def _created_bare(node):
19232+            self.failUnlessEqual(node.get_writekey(),
19233+                                 self._created.get_writekey())
19234+            self.failUnlessEqual(node.get_readkey(),
19235+                                 self._created.get_readkey())
19236+            self.failUnlessEqual(node.get_storage_index(),
19237+                                 self._created.get_storage_index())
19238+            return node.download_best_version()
19239+        d.addCallback(_created_bare)
19240+        d.addCallback(lambda data:
19241+            self.failUnlessEqual(data, "contents" * 100000))
19242+        return d
19243+
19244+
19245     def test_mdmf_write_count(self):
19246         # Publishing an MDMF file should only cause one write for each
19247         # share that is to be published. Otherwise, we introduce
19248hunk ./src/allmydata/test/test_mutable.py 2155
19249         # and set the encoding parameters to something completely different
19250         fn2._required_shares = k
19251         fn2._total_shares = n
19252-        # Normally a servermap update would occur before a publish.
19253-        # Here, it doesn't, so we have to do it ourselves.
19254-        fn2.set_version(version)
19255 
19256         s = self._storage
19257         s._peers = {} # clear existing storage
19258hunk ./src/allmydata/test/test_mutable.py 2928
19259         # We need to define an API by which an uploader can set the
19260         # extension parameters, and by which a downloader can retrieve
19261         # extensions.
19262-        self.failUnless(False)
19263+        d = self.mdmf_node.get_best_mutable_version()
19264+        def _got_version(version):
19265+            hints = version.get_downloader_hints()
19266+            # Should be empty at this point.
19267+            self.failUnlessIn("k", hints)
19268+            self.failUnlessEqual(hints['k'], 3)
19269+            self.failUnlessIn('segsize', hints)
19270+            self.failUnlessEqual(hints['segsize'], 131073)
19271+        d.addCallback(_got_version)
19272+        return d
19273 
19274 
19275     def test_extensions_from_cap(self):
19276hunk ./src/allmydata/test/test_mutable.py 2941
19277-        self.failUnless(False)
19278+        # If we initialize a mutable file with a cap that has extension
19279+        # parameters in it and then grab the extension parameters using
19280+        # our API, we should see that they're set correctly.
19281+        mdmf_uri = self.mdmf_node.get_uri()
19282+        new_node = self.nm.create_from_cap(mdmf_uri)
19283+        d = new_node.get_best_mutable_version()
19284+        def _got_version(version):
19285+            hints = version.get_downloader_hints()
19286+            self.failUnlessIn("k", hints)
19287+            self.failUnlessEqual(hints["k"], 3)
19288+            self.failUnlessIn("segsize", hints)
19289+            self.failUnlessEqual(hints["segsize"], 131073)
19290+        d.addCallback(_got_version)
19291+        return d
19292 
19293 
19294     def test_extensions_from_upload(self):
19295hunk ./src/allmydata/test/test_mutable.py 2958
19296-        self.failUnless(False)
19297+        # If we create a new mutable file with some contents, we should
19298+        # get back an MDMF cap with the right hints in place.
19299+        contents = "foo bar baz" * 100000
19300+        d = self.nm.create_mutable_file(contents, version=MDMF_VERSION)
19301+        def _got_mutable_file(n):
19302+            rw_uri = n.get_uri()
19303+            expected_k = str(self.c.DEFAULT_ENCODING_PARAMETERS['k'])
19304+            self.failUnlessIn(expected_k, rw_uri)
19305+            # XXX: Get this more intelligently.
19306+            self.failUnlessIn("131073", rw_uri)
19307+
19308+            ro_uri = n.get_readonly_uri()
19309+            self.failUnlessIn(expected_k, ro_uri)
19310+            self.failUnlessIn("131073", ro_uri)
19311+        d.addCallback(_got_mutable_file)
19312+        return d
19313 
19314 
19315     def test_cap_after_upload(self):
19316hunk ./src/allmydata/test/test_web.py 52
19317         return stats
19318 
19319 class FakeNodeMaker(NodeMaker):
19320+    encoding_params = {
19321+        'k': 3,
19322+        'n': 10,
19323+        'happy': 7,
19324+        'max_segment_size':128*1024 # 1024=KiB
19325+    }
19326     def _create_lit(self, cap):
19327         return FakeCHKFileNode(cap)
19328     def _create_immutable(self, cap):
19329hunk ./src/allmydata/test/test_web.py 63
19330         return FakeCHKFileNode(cap)
19331     def _create_mutable(self, cap):
19332-        return FakeMutableFileNode(None, None, None, None).init_from_cap(cap)
19333+        return FakeMutableFileNode(None,
19334+                                   None,
19335+                                   self.encoding_params, None).init_from_cap(cap)
19336     def create_mutable_file(self, contents="", keysize=None,
19337                             version=SDMF_VERSION):
19338hunk ./src/allmydata/test/test_web.py 68
19339-        n = FakeMutableFileNode(None, None, None, None)
19340-        n.set_version(version)
19341-        return n.create(contents)
19342+        n = FakeMutableFileNode(None, None, self.encoding_params, None)
19343+        return n.create(contents, version=version)
19344 
19345 class FakeUploader(service.Service):
19346     name = "uploader"
19347hunk ./src/allmydata/test/test_web.py 302
19348         self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
19349         self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
19350 
19351-    def failUnlessIsQuuxJSON(self, res):
19352+    def failUnlessIsQuuxJSON(self, res, readonly=False):
19353         data = simplejson.loads(res)
19354         self.failUnless(isinstance(data, list))
19355         self.failUnlessEqual(data[0], "filenode")
19356hunk ./src/allmydata/test/test_web.py 308
19357         self.failUnless(isinstance(data[1], dict))
19358         metadata = data[1]
19359-        return self.failUnlessIsQuuxDotTxtMetadata(metadata)
19360+        return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
19361 
19362hunk ./src/allmydata/test/test_web.py 310
19363-    def failUnlessIsQuuxDotTxtMetadata(self, metadata):
19364+    def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
19365         self.failUnless(metadata['mutable'])
19366hunk ./src/allmydata/test/test_web.py 312
19367-        self.failUnless("rw_uri" in metadata)
19368-        self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
19369+        if readonly:
19370+            self.failIf("rw_uri" in metadata)
19371+        else:
19372+            self.failUnless("rw_uri" in metadata)
19373+            self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
19374         self.failUnless("ro_uri" in metadata)
19375         self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
19376         self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
19377hunk ./src/allmydata/test/test_web.py 873
19378         d.addCallback(self.failUnlessIsQuuxDotTxt)
19379         return d
19380 
19381+    def test_GET_FILE_URI_mdmf_bare_cap(self):
19382+        cap_elements = self._quux_txt_uri.split(":")
19383+        # 6 == expected cap length with two extensions.
19384+        self.failUnlessEqual(len(cap_elements), 6)
19385+
19386+        # Now lop off the extension parameters and stitch everything
19387+        # back together
19388+        quux_uri = ":".join(cap_elements[:len(cap_elements) - 2])
19389+
19390+        # Now GET that. We should get back quux.
19391+        base = "/uri/%s" % urllib.quote(quux_uri)
19392+        d = self.GET(base)
19393+        d.addCallback(self.failUnlessIsQuuxDotTxt)
19394+        return d
19395+
19396     def test_GET_FILE_URI_mdmf_readonly(self):
19397         base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
19398         d = self.GET(base)
19399hunk ./src/allmydata/test/test_web.py 935
19400                                                        res))
19401         return d
19402 
19403+    def test_PUT_FILE_URI_mdmf_bare_cap(self):
19404+        elements = self._quux_txt_uri.split(":")
19405+        self.failUnlessEqual(len(elements), 6)
19406+
19407+        quux_uri = ":".join(elements[:len(elements) - 2])
19408+        base = "/uri/%s" % urllib.quote(quux_uri)
19409+        self._quux_new_contents = "new_contents" * 50000
19410+
19411+        d = self.GET(base)
19412+        d.addCallback(self.failUnlessIsQuuxDotTxt)
19413+        d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
19414+        d.addCallback(lambda ignored: self.GET(base))
19415+        d.addCallback(lambda res:
19416+            self.failUnlessEqual(res, self._quux_new_contents))
19417+        return d
19418+
19419     def test_PUT_FILE_URI_mdmf_readonly(self):
19420         # We're not allowed to PUT things to a readonly cap.
19421         base = "/uri/%s" % self._quux_txt_readonly_uri
19422hunk ./src/allmydata/test/test_web.py 1027
19423         d.addCallback(_got)
19424         return d
19425 
19426+    def test_GET_FILEURL_info_mdmf_bare_cap(self):
19427+        elements = self._quux_txt_uri.split(":")
19428+        self.failUnlessEqual(len(elements), 6)
19429+
19430+        quux_uri = ":".join(elements[:len(elements) - 2])
19431+        base = "/uri/%s?t=info" % urllib.quote(quux_uri)
19432+        d = self.GET(base)
19433+        def _got(res):
19434+            self.failUnlessIn("mutable file (mdmf)", res)
19435+            self.failUnlessIn(quux_uri, res)
19436+        d.addCallback(_got)
19437+        return d
19438+
19439     def test_PUT_overwrite_only_files(self):
19440         # create a directory, put a file in that directory.
19441         contents, n, filecap = self.makefile(8)
19442hunk ./src/allmydata/test/test_web.py 1267
19443         d.addCallback(self.failUnlessIsQuuxJSON)
19444         return d
19445 
19446+    def test_GET_FILEURL_json_mdmf_bare_cap(self):
19447+        elements = self._quux_txt_uri.split(":")
19448+        self.failUnlessEqual(len(elements), 6)
19449+
19450+        quux_uri = ":".join(elements[:len(elements) - 2])
19451+        # so failUnlessIsQuuxJSON will work.
19452+        self._quux_txt_uri = quux_uri
19453+
19454+        # we need to alter the readonly URI in the same way, again so
19455+        # failUnlessIsQuuxJSON will work
19456+        elements = self._quux_txt_readonly_uri.split(":")
19457+        self.failUnlessEqual(len(elements), 6)
19458+        quux_ro_uri = ":".join(elements[:len(elements) - 2])
19459+        self._quux_txt_readonly_uri = quux_ro_uri
19460+
19461+        base = "/uri/%s?t=json" % urllib.quote(quux_uri)
19462+        d = self.GET(base)
19463+        d.addCallback(self.failUnlessIsQuuxJSON)
19464+        return d
19465+
19466+    def test_GET_FILEURL_json_mdmf_bare_readonly_cap(self):
19467+        elements = self._quux_txt_readonly_uri.split(":")
19468+        self.failUnlessEqual(len(elements), 6)
19469+
19470+        quux_readonly_uri = ":".join(elements[:len(elements) - 2])
19471+        # so failUnlessIsQuuxJSON will work
19472+        self._quux_txt_readonly_uri = quux_readonly_uri
19473+        base = "/uri/%s?t=json" % quux_readonly_uri
19474+        d = self.GET(base)
19475+        # XXX: We may need to make a method that knows how to check for
19476+        # readonly JSON, or else alter that one so that it knows how to
19477+        # do that.
19478+        d.addCallback(self.failUnlessIsQuuxJSON, readonly=True)
19479+        return d
19480+
19481     def test_GET_FILEURL_json_mdmf(self):
19482         d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
19483         d.addCallback(self.failUnlessIsQuuxJSON)
19484}
19485
19486Context:
19487
19488[server.py:  get_latencies now reports percentiles _only_ if there are sufficient observations for the interpretation of the percentile to be unambiguous.
19489wilcoxjg@gmail.com**20110527120135
19490 Ignore-this: 2e7029764bffc60e26f471d7c2b6611e
19491 interfaces.py:  modified the return type of RIStatsProvider.get_stats to allow for None as a return value
19492 NEWS.rst, stats.py: documentation of change to get_latencies
19493 stats.rst: now documents percentile modification in get_latencies
19494 test_storage.py:  test_latencies now expects None in output categories that contain too few samples for the associated percentile to be unambiguously reported.
19495 fixes #1392
19496] 
19497[docs: revert link in relnotes.txt from NEWS.rst to NEWS, since the former did not exist at revision 5000.
19498david-sarah@jacaranda.org**20110517011214
19499 Ignore-this: 6a5be6e70241e3ec0575641f64343df7
19500] 
19501[docs: convert NEWS to NEWS.rst and change all references to it.
19502david-sarah@jacaranda.org**20110517010255
19503 Ignore-this: a820b93ea10577c77e9c8206dbfe770d
19504] 
19505[docs: remove out-of-date docs/testgrid/introducer.furl and containing directory. fixes #1404
19506david-sarah@jacaranda.org**20110512140559
19507 Ignore-this: 784548fc5367fac5450df1c46890876d
19508] 
19509[scripts/common.py: don't assume that the default alias is always 'tahoe' (it is, but the API of get_alias doesn't say so). refs #1342
19510david-sarah@jacaranda.org**20110130164923
19511 Ignore-this: a271e77ce81d84bb4c43645b891d92eb
19512] 
19513[setup: don't catch all Exception from check_requirement(), but only PackagingError and ImportError
19514zooko@zooko.com**20110128142006
19515 Ignore-this: 57d4bc9298b711e4bc9dc832c75295de
19516 I noticed this because I had accidentally inserted a bug which caused AssertionError to be raised from check_requirement().
19517] 
19518[M-x whitespace-cleanup
19519zooko@zooko.com**20110510193653
19520 Ignore-this: dea02f831298c0f65ad096960e7df5c7
19521] 
19522[docs: fix typo in running.rst, thanks to arch_o_median
19523zooko@zooko.com**20110510193633
19524 Ignore-this: ca06de166a46abbc61140513918e79e8
19525] 
19526[relnotes.txt: don't claim to work on Cygwin (which has been untested for some time). refs #1342
19527david-sarah@jacaranda.org**20110204204902
19528 Ignore-this: 85ef118a48453d93fa4cddc32d65b25b
19529] 
19530[relnotes.txt: forseeable -> foreseeable. refs #1342
19531david-sarah@jacaranda.org**20110204204116
19532 Ignore-this: 746debc4d82f4031ebf75ab4031b3a9
19533] 
19534[replace remaining .html docs with .rst docs
19535zooko@zooko.com**20110510191650
19536 Ignore-this: d557d960a986d4ac8216d1677d236399
19537 Remove install.html (long since deprecated).
19538 Also replace some obsolete references to install.html with references to quickstart.rst.
19539 Fix some broken internal references within docs/historical/historical_known_issues.txt.
19540 Thanks to Ravi Pinjala and Patrick McDonald.
19541 refs #1227
19542] 
19543[docs: FTP-and-SFTP.rst: fix a minor error and update the information about which version of Twisted fixes #1297
19544zooko@zooko.com**20110428055232
19545 Ignore-this: b63cfb4ebdbe32fb3b5f885255db4d39
19546] 
19547[munin tahoe_files plugin: fix incorrect file count
19548francois@ctrlaltdel.ch**20110428055312
19549 Ignore-this: 334ba49a0bbd93b4a7b06a25697aba34
19550 fixes #1391
19551] 
19552[corrected "k must never be smaller than N" to "k must never be greater than N"
19553secorp@allmydata.org**20110425010308
19554 Ignore-this: 233129505d6c70860087f22541805eac
19555] 
19556[Fix a test failure in test_package_initialization on Python 2.4.x due to exceptions being stringified differently than in later versions of Python. refs #1389
19557david-sarah@jacaranda.org**20110411190738
19558 Ignore-this: 7847d26bc117c328c679f08a7baee519
19559] 
19560[tests: add test for including the ImportError message and traceback entry in the summary of errors from importing dependencies. refs #1389
19561david-sarah@jacaranda.org**20110410155844
19562 Ignore-this: fbecdbeb0d06a0f875fe8d4030aabafa
19563] 
19564[allmydata/__init__.py: preserve the message and last traceback entry (file, line number, function, and source line) of ImportErrors in the package versions string. fixes #1389
19565david-sarah@jacaranda.org**20110410155705
19566 Ignore-this: 2f87b8b327906cf8bfca9440a0904900
19567] 
19568[remove unused variable detected by pyflakes
19569zooko@zooko.com**20110407172231
19570 Ignore-this: 7344652d5e0720af822070d91f03daf9
19571] 
19572[allmydata/__init__.py: Nicer reporting of unparseable version numbers in dependencies. fixes #1388
19573david-sarah@jacaranda.org**20110401202750
19574 Ignore-this: 9c6bd599259d2405e1caadbb3e0d8c7f
19575] 
19576[update FTP-and-SFTP.rst: the necessary patch is included in Twisted-10.1
19577Brian Warner <warner@lothar.com>**20110325232511
19578 Ignore-this: d5307faa6900f143193bfbe14e0f01a
19579] 
19580[control.py: remove all uses of s.get_serverid()
19581warner@lothar.com**20110227011203
19582 Ignore-this: f80a787953bd7fa3d40e828bde00e855
19583] 
19584[web: remove some uses of s.get_serverid(), not all
19585warner@lothar.com**20110227011159
19586 Ignore-this: a9347d9cf6436537a47edc6efde9f8be
19587] 
19588[immutable/downloader/fetcher.py: remove all get_serverid() calls
19589warner@lothar.com**20110227011156
19590 Ignore-this: fb5ef018ade1749348b546ec24f7f09a
19591] 
19592[immutable/downloader/fetcher.py: fix diversity bug in server-response handling
19593warner@lothar.com**20110227011153
19594 Ignore-this: bcd62232c9159371ae8a16ff63d22c1b
19595 
19596 When blocks terminate (either COMPLETE or CORRUPT/DEAD/BADSEGNUM), the
19597 _shares_from_server dict was being popped incorrectly (using shnum as the
19598 index instead of serverid). I'm still thinking through the consequences of
19599 this bug. It was probably benign and really hard to detect. I think it would
19600 cause us to incorrectly believe that we're pulling too many shares from a
19601 server, and thus prefer a different server rather than asking for a second
19602 share from the first server. The diversity code is intended to spread out the
19603 number of shares simultaneously being requested from each server, but with
19604 this bug, it might be spreading out the total number of shares requested at
19605 all, not just simultaneously. (note that SegmentFetcher is scoped to a single
19606 segment, so the effect doesn't last very long).
19607] 
19608[immutable/downloader/share.py: reduce get_serverid(), one left, update ext deps
19609warner@lothar.com**20110227011150
19610 Ignore-this: d8d56dd8e7b280792b40105e13664554
19611 
19612 test_download.py: create+check MyShare instances better, make sure they share
19613 Server objects, now that finder.py cares
19614] 
19615[immutable/downloader/finder.py: reduce use of get_serverid(), one left
19616warner@lothar.com**20110227011146
19617 Ignore-this: 5785be173b491ae8a78faf5142892020
19618] 
19619[immutable/offloaded.py: reduce use of get_serverid() a bit more
19620warner@lothar.com**20110227011142
19621 Ignore-this: b48acc1b2ae1b311da7f3ba4ffba38f
19622] 
19623[immutable/upload.py: reduce use of get_serverid()
19624warner@lothar.com**20110227011138
19625 Ignore-this: ffdd7ff32bca890782119a6e9f1495f6
19626] 
19627[immutable/checker.py: remove some uses of s.get_serverid(), not all
19628warner@lothar.com**20110227011134
19629 Ignore-this: e480a37efa9e94e8016d826c492f626e
19630] 
19631[add remaining get_* methods to storage_client.Server, NoNetworkServer, and
19632warner@lothar.com**20110227011132
19633 Ignore-this: 6078279ddf42b179996a4b53bee8c421
19634 MockIServer stubs
19635] 
19636[upload.py: rearrange _make_trackers a bit, no behavior changes
19637warner@lothar.com**20110227011128
19638 Ignore-this: 296d4819e2af452b107177aef6ebb40f
19639] 
19640[happinessutil.py: finally rename merge_peers to merge_servers
19641warner@lothar.com**20110227011124
19642 Ignore-this: c8cd381fea1dd888899cb71e4f86de6e
19643] 
19644[test_upload.py: factor out FakeServerTracker
19645warner@lothar.com**20110227011120
19646 Ignore-this: 6c182cba90e908221099472cc159325b
19647] 
19648[test_upload.py: server-vs-tracker cleanup
19649warner@lothar.com**20110227011115
19650 Ignore-this: 2915133be1a3ba456e8603885437e03
19651] 
19652[happinessutil.py: server-vs-tracker cleanup
19653warner@lothar.com**20110227011111
19654 Ignore-this: b856c84033562d7d718cae7cb01085a9
19655] 
19656[upload.py: more tracker-vs-server cleanup
19657warner@lothar.com**20110227011107
19658 Ignore-this: bb75ed2afef55e47c085b35def2de315
19659] 
19660[upload.py: fix var names to avoid confusion between 'trackers' and 'servers'
19661warner@lothar.com**20110227011103
19662 Ignore-this: 5d5e3415b7d2732d92f42413c25d205d
19663] 
19664[refactor: s/peer/server/ in immutable/upload, happinessutil.py, test_upload
19665warner@lothar.com**20110227011100
19666 Ignore-this: 7ea858755cbe5896ac212a925840fe68
19667 
19668 No behavioral changes, just updating variable/method names and log messages.
19669 The effects outside these three files should be minimal: some exception
19670 messages changed (to say "server" instead of "peer"), and some internal class
19671 names were changed. A few things still use "peer" to minimize external
19672 changes, like UploadResults.timings["peer_selection"] and
19673 happinessutil.merge_peers, which can be changed later.
19674] 
19675[storage_client.py: clean up test_add_server/test_add_descriptor, remove .test_servers
19676warner@lothar.com**20110227011056
19677 Ignore-this: efad933e78179d3d5fdcd6d1ef2b19cc
19678] 
19679[test_client.py, upload.py:: remove KiB/MiB/etc constants, and other dead code
19680warner@lothar.com**20110227011051
19681 Ignore-this: dc83c5794c2afc4f81e592f689c0dc2d
19682] 
19683[test: increase timeout on a network test because Francois's ARM machine hit that timeout
19684zooko@zooko.com**20110317165909
19685 Ignore-this: 380c345cdcbd196268ca5b65664ac85b
19686 I'm skeptical that the test was proceeding correctly but ran out of time. It seems more likely that it had gotten hung. But if we raise the timeout to an even more extravagant number then we can be even more certain that the test was never going to finish.
19687] 
19688[docs/configuration.rst: add a "Frontend Configuration" section
19689Brian Warner <warner@lothar.com>**20110222014323
19690 Ignore-this: 657018aa501fe4f0efef9851628444ca
19691 
19692 this points to docs/frontends/*.rst, which were previously underlinked
19693] 
19694[web/filenode.py: avoid calling req.finish() on closed HTTP connections. Closes #1366
19695"Brian Warner <warner@lothar.com>"**20110221061544
19696 Ignore-this: 799d4de19933f2309b3c0c19a63bb888
19697] 
19698[Add unit tests for cross_check_pkg_resources_versus_import, and a regression test for ref #1355. This requires a little refactoring to make it testable.
19699david-sarah@jacaranda.org**20110221015817
19700 Ignore-this: 51d181698f8c20d3aca58b057e9c475a
19701] 
19702[allmydata/__init__.py: .name was used in place of the correct .__name__ when printing an exception. Also, robustify string formatting by using %r instead of %s in some places. fixes #1355.
19703david-sarah@jacaranda.org**20110221020125
19704 Ignore-this: b0744ed58f161bf188e037bad077fc48
19705] 
19706[Refactor StorageFarmBroker handling of servers
19707Brian Warner <warner@lothar.com>**20110221015804
19708 Ignore-this: 842144ed92f5717699b8f580eab32a51
19709 
19710 Pass around IServer instance instead of (peerid, rref) tuple. Replace
19711 "descriptor" with "server". Other replacements:
19712 
19713  get_all_servers -> get_connected_servers/get_known_servers
19714  get_servers_for_index -> get_servers_for_psi (now returns IServers)
19715 
19716 This change still needs to be pushed further down: lots of code is now
19717 getting the IServer and then distributing (peerid, rref) internally.
19718 Instead, it ought to distribute the IServer internally and delay
19719 extracting a serverid or rref until the last moment.
19720 
19721 no_network.py was updated to retain parallelism.
19722] 
19723[TAG allmydata-tahoe-1.8.2
19724warner@lothar.com**20110131020101] 
19725Patch bundle hash:
19726f42fa7d5f2314cf7be98a4f5065e7096b6f4745d