]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/interfaces.py
Implementation, tests and docs for blacklists. This version allows listing directorie...
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / interfaces.py
index dca8ea3b26c9dcf9aa50a8abf71fb3dc972eb1c4..69ca62390614d5e55cca8a29b8a01f4468a823a8 100644 (file)
@@ -1,10 +1,13 @@
 
 from zope.interface import Interface
-from foolscap.schema import StringConstraint, ListOf, TupleOf, SetOf, DictOf, \
-     ChoiceOf, IntegerConstraint
-from foolscap import RemoteInterface, Referenceable
+from foolscap.api import StringConstraint, ListOf, TupleOf, SetOf, DictOf, \
+     ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
 
 HASH_SIZE=32
+SALT_SIZE=16
+
+SDMF_VERSION=0
+MDMF_VERSION=1
 
 Hash = StringConstraint(maxLength=HASH_SIZE,
                         minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
@@ -16,6 +19,8 @@ URI = StringConstraint(300) # kind of arbitrary
 
 MAX_BUCKETS = 256  # per peer -- zfec offers at most 256 shares per file
 
+DEFAULT_MAX_SEGMENT_SIZE = 128*1024
+
 ShareData = StringConstraint(None)
 URIExtensionData = StringConstraint(1000)
 Number = IntegerConstraint(8) # 2**(8*8) == 16EiB ~= 18e18 ~= 18 exabytes
@@ -55,6 +60,18 @@ class RIBucketReader(RemoteInterface):
     def read(offset=Offset, length=ReadSize):
         return ShareData
 
+    def advise_corrupt_share(reason=str):
+        """Clients who discover hash failures in shares that they have
+        downloaded from me will use this method to inform me about the
+        failures. I will record their concern so that my operator can
+        manually inspect the shares in question. I return None.
+
+        This is a wrapper around RIStorageServer.advise_corrupt_share(),
+        which is tied to a specific share, and therefore does not need the
+        extra share-identifying arguments. Please see that method for full
+        documentation.
+        """
+
 TestVector = ListOf(TupleOf(Offset, ReadSize, str, str))
 # elements are (offset, length, operator, specimen)
 # operator is one of "lt, le, eq, ne, ge, gt"
@@ -74,21 +91,11 @@ ReadData = ListOf(ShareData)
 class RIStorageServer(RemoteInterface):
     __remote_name__ = "RIStorageServer.tahoe.allmydata.com"
 
-    def get_versions():
+    def get_version():
         """
-        Return a tuple of (my_version, oldest_supported) strings.  Each string can be parsed by
-        a pyutil.version_class.Version instance or a distutils.version.LooseVersion instance,
-        and then compared. The first goal is to make sure that nodes are not confused by
-        speaking to an incompatible peer. The second goal is to enable the development of
-        backwards-compatibility code.
-
-        The meaning of the oldest_supported element is that if you treat this storage server as
-        though it were of that version, then you will not be disappointed.
-
-        The precise meaning of this method might change in incompatible ways until we get the
-        whole compatibility scheme nailed down.
+        Return a dictionary of version information.
         """
-        return TupleOf(str, str)
+        return DictOf(str, Any())
 
     def allocate_buckets(storage_index=StorageIndex,
                          renew_secret=LeaseRenewSecret,
@@ -109,8 +116,8 @@ class RIStorageServer(RemoteInterface):
         @param canary: If the canary is lost before close(), the bucket is
                        deleted.
         @return: tuple of (alreadygot, allocated), where alreadygot is what we
-                 already have and is what we hereby agree to accept. New
-                 leases are added for shares in both lists.
+                 already have and allocated is what we hereby agree to accept.
+                 New leases are added for shares in both lists.
         """
         return TupleOf(SetOf(int, maxLength=MAX_BUCKETS),
                        DictOf(int, RIBucketWriter, maxKeys=MAX_BUCKETS))
@@ -120,24 +127,41 @@ class RIStorageServer(RemoteInterface):
                   cancel_secret=LeaseCancelSecret):
         """
         Add a new lease on the given bucket. If the renew_secret matches an
-        existing lease, that lease will be renewed instead.
+        existing lease, that lease will be renewed instead. If there is no
+        bucket for the given storage_index, return silently. (note that in
+        tahoe-1.3.0 and earlier, IndexError was raised if there was no
+        bucket)
         """
-        return None
+        return Any() # returns None now, but future versions might change
 
     def renew_lease(storage_index=StorageIndex, renew_secret=LeaseRenewSecret):
         """
-        Renew the lease on a given bucket. Some networks will use this, some
-        will not.
+        Renew the lease on a given bucket, resetting the timer to 31 days.
+        Some networks will use this, some will not. If there is no bucket for
+        the given storage_index, IndexError will be raised.
+
+        For mutable shares, if the given renew_secret does not match an
+        existing lease, IndexError will be raised with a note listing the
+        server-nodeids on the existing leases, so leases on migrated shares
+        can be renewed or cancelled. For immutable shares, IndexError
+        (without the note) will be raised.
         """
-        return None
+        return Any()
 
     def cancel_lease(storage_index=StorageIndex,
                      cancel_secret=LeaseCancelSecret):
         """
         Cancel the lease on a given bucket. If this was the last lease on the
-        bucket, the bucket will be deleted.
+        bucket, the bucket will be deleted. If there is no bucket for the
+        given storage_index, IndexError will be raised.
+
+        For mutable shares, if the given cancel_secret does not match an
+        existing lease, IndexError will be raised with a note listing the
+        server-nodeids on the existing leases, so leases on migrated shares
+        can be renewed or cancelled. For immutable shares, IndexError
+        (without the note) will be raised.
         """
-        return None
+        return Any()
 
     def get_buckets(storage_index=StorageIndex):
         return DictOf(int, RIBucketReader, maxKeys=MAX_BUCKETS)
@@ -205,7 +229,7 @@ class RIStorageServer(RemoteInterface):
         can be used to pre-allocate space for a series of upcoming writes, or
         truncate existing data. If the container is growing, new_length will
         be applied before datav. If the container is shrinking, it will be
-        applied afterwards.
+        applied afterwards. If new_length==0, the share will be deleted.
 
         The read vector is used to extract data from all known shares,
         *before* any writes have been applied. The same vector is used for
@@ -230,6 +254,23 @@ class RIStorageServer(RemoteInterface):
         """
         return TupleOf(bool, DictOf(int, ReadData))
 
+    def advise_corrupt_share(share_type=str, storage_index=StorageIndex,
+                             shnum=int, reason=str):
+        """Clients who discover hash failures in shares that they have
+        downloaded from me will use this method to inform me about the
+        failures. I will record their concern so that my operator can
+        manually inspect the shares in question. I return None.
+
+        'share_type' is either 'mutable' or 'immutable'. 'storage_index' is a
+        (binary) storage index string, and 'shnum' is the integer share
+        number. 'reason' is a human-readable explanation of the problem,
+        probably including some expected hash values and the computed ones
+        which did not match. Corruption advisories for mutable shares should
+        include a hash of the public key (the same value that appears in the
+        mutable-file verify-cap), since the current share format does not
+        store that on disk.
+        """
+
 class IStorageBucketWriter(Interface):
     """
     Objects of this kind live on the client side.
@@ -287,29 +328,24 @@ class IStorageBucketWriter(Interface):
 
 class IStorageBucketReader(Interface):
 
-    def get_block(blocknum=int):
+    def get_block_data(blocknum=int, blocksize=int, size=int):
         """Most blocks will be the same size. The last block might be shorter
         than the others.
 
         @return: ShareData
         """
 
-    def get_plaintext_hashes():
-        """
-        @return: ListOf(Hash)
-        """
-
     def get_crypttext_hashes():
         """
         @return: ListOf(Hash)
         """
 
-    def get_block_hashes():
+    def get_block_hashes(at_least_these=SetOf(int)):
         """
         @return: ListOf(Hash)
         """
 
-    def get_share_hashes():
+    def get_share_hashes(at_least_these=SetOf(int)):
         """
         @return: ListOf(TupleOf(int, Hash))
         """
@@ -319,15 +355,137 @@ class IStorageBucketReader(Interface):
         @return: URIExtensionData
         """
 
+class IStorageBroker(Interface):
+    def get_servers_for_psi(peer_selection_index):
+        """
+        @return: list of IServer instances
+        """
+    def get_connected_servers():
+        """
+        @return: frozenset of connected IServer instances
+        """
+    def get_known_servers():
+        """
+        @return: frozenset of IServer instances
+        """
+    def get_all_serverids():
+        """
+        @return: frozenset of serverid strings
+        """
+    def get_nickname_for_serverid(serverid):
+        """
+        @return: unicode nickname, or None
+        """
+
+    # methods moved from IntroducerClient, need review
+    def get_all_connections():
+        """Return a frozenset of (nodeid, service_name, rref) tuples, one for
+        each active connection we've established to a remote service. This is
+        mostly useful for unit tests that need to wait until a certain number
+        of connections have been made."""
+
+    def get_all_connectors():
+        """Return a dict that maps from (nodeid, service_name) to a
+        RemoteServiceConnector instance for all services that we are actively
+        trying to connect to. Each RemoteServiceConnector has the following
+        public attributes::
+
+          service_name: the type of service provided, like 'storage'
+          announcement_time: when we first heard about this service
+          last_connect_time: when we last established a connection
+          last_loss_time: when we last lost a connection
+
+          version: the peer's version, from the most recent connection
+          oldest_supported: the peer's oldest supported version, same
+
+          rref: the RemoteReference, if connected, otherwise None
+          remote_host: the IAddress, if connected, otherwise None
+
+        This method is intended for monitoring interfaces, such as a web page
+        which describes connecting and connected peers.
+        """
+
+    def get_all_peerids():
+        """Return a frozenset of all peerids to whom we have a connection (to
+        one or more services) established. Mostly useful for unit tests."""
 
+    def get_all_connections_for(service_name):
+        """Return a frozenset of (nodeid, service_name, rref) tuples, one
+        for each active connection that provides the given SERVICE_NAME."""
 
-# hm, we need a solution for forward references in schemas
-from foolscap.schema import Any
+    def get_permuted_peers(service_name, key):
+        """Returns an ordered list of (peerid, rref) tuples, selecting from
+        the connections that provide SERVICE_NAME, using a hash-based
+        permutation keyed by KEY. This randomizes the service list in a
+        repeatable way, to distribute load over many peers.
+        """
+
+
+class IMutableSlotWriter(Interface):
+    """
+    The interface for a writer around a mutable slot on a remote server.
+    """
+    def set_checkstring(checkstring, *args):
+        """
+        Set the checkstring that I will pass to the remote server when
+        writing.
+
+            @param checkstring A packed checkstring to use.
+
+        Note that implementations can differ in which semantics they
+        wish to support for set_checkstring -- they can, for example,
+        build the checkstring themselves from its constituents, or
+        some other thing.
+        """
+
+    def get_checkstring():
+        """
+        Get the checkstring that I think currently exists on the remote
+        server.
+        """
+
+    def put_block(data, segnum, salt):
+        """
+        Add a block and salt to the share.
+        """
+
+    def put_encprivey(encprivkey):
+        """
+        Add the encrypted private key to the share.
+        """
+
+    def put_blockhashes(blockhashes=list):
+        """
+        Add the block hash tree to the share.
+        """
+
+    def put_sharehashes(sharehashes=dict):
+        """
+        Add the share hash chain to the share.
+        """
+
+    def get_signable():
+        """
+        Return the part of the share that needs to be signed.
+        """
+
+    def put_signature(signature):
+        """
+        Add the signature to the share.
+        """
+
+    def put_verification_key(verification_key):
+        """
+        Add the verification key to the share.
+        """
+
+    def finish_publishing():
+        """
+        Do anything necessary to finish writing the share to a remote
+        server. I require that no further publishing needs to take place
+        after this method has been called.
+        """
 
-FileNode_ = Any() # TODO: foolscap needs constraints on copyables
-DirectoryNode_ = Any() # TODO: same
-AnyNode_ = ChoiceOf(FileNode_, DirectoryNode_)
-EncryptedThing = str
 
 class IURI(Interface):
     def init_from_string(uri):
@@ -344,11 +502,12 @@ class IURI(Interface):
         """Return True if the data can be modified by *somebody* (perhaps
         someone who has a more powerful URI than this one)."""
 
+    # TODO: rename to get_read_cap()
     def get_readonly():
         """Return another IURI instance, which represents a read-only form of
         this one. If is_readonly() is True, this returns self."""
 
-    def get_verifier():
+    def get_verify_cap():
         """Return an instance that provides IVerifierURI, which can be used
         to check on the availability of the file or directory, without
         providing enough capabilities to actually read or modify the
@@ -360,7 +519,7 @@ class IURI(Interface):
         """Return a string of printable ASCII characters, suitable for
         passing into init_from_string."""
 
-class IVerifierURI(Interface):
+class IVerifierURI(Interface, IURI):
     def init_from_string(uri):
         """Accept a string (as created by my to_string() method) and populate
         this instance with its data. I am not normally called directly,
@@ -374,42 +533,240 @@ class IVerifierURI(Interface):
 class IDirnodeURI(Interface):
     """I am a URI which represents a dirnode."""
 
-
 class IFileURI(Interface):
     """I am a URI which represents a filenode."""
     def get_size():
         """Return the length (in bytes) of the file that I represent."""
 
+class IImmutableFileURI(IFileURI):
+    pass
+
 class IMutableFileURI(Interface):
     """I am a URI which represents a mutable filenode."""
-class INewDirectoryURI(Interface):
+    def get_extension_params():
+        """Return the extension parameters in the URI"""
+
+    def set_extension_params():
+        """Set the extension parameters that should be in the URI"""
+
+class IDirectoryURI(Interface):
     pass
-class IReadonlyNewDirectoryURI(Interface):
+
+class IReadonlyDirectoryURI(Interface):
     pass
 
+class CapConstraintError(Exception):
+    """A constraint on a cap was violated."""
 
-class IFilesystemNode(Interface):
-    def get_uri():
+class MustBeDeepImmutableError(CapConstraintError):
+    """Mutable children cannot be added to an immutable directory.
+    Also, caps obtained from an immutable directory can trigger this error
+    if they are later found to refer to a mutable object and then used."""
+
+class MustBeReadonlyError(CapConstraintError):
+    """Known write caps cannot be specified in a ro_uri field. Also,
+    caps obtained from a ro_uri field can trigger this error if they
+    are later found to be write caps and then used."""
+
+class MustNotBeUnknownRWError(CapConstraintError):
+    """Cannot add an unknown child cap specified in a rw_uri field."""
+
+
+class IReadable(Interface):
+    """I represent a readable object -- either an immutable file, or a
+    specific version of a mutable file.
+    """
+
+    def is_readonly():
+        """Return True if this reference provides mutable access to the given
+        file or directory (i.e. if you can modify it), or False if not. Note
+        that even if this reference is read-only, someone else may hold a
+        read-write reference to it.
+
+        For an IReadable returned by get_best_readable_version(), this will
+        always return True, but for instances of subinterfaces such as
+        IMutableFileVersion, it may return False."""
+
+    def is_mutable():
+        """Return True if this file or directory is mutable (by *somebody*,
+        not necessarily you), False if it is is immutable. Note that a file
+        might be mutable overall, but your reference to it might be
+        read-only. On the other hand, all references to an immutable file
+        will be read-only; there are no read-write references to an immutable
+        file."""
+
+    def get_storage_index():
+        """Return the storage index of the file."""
+
+    def get_size():
+        """Return the length (in bytes) of this readable object."""
+
+    def download_to_data():
+        """Download all of the file contents. I return a Deferred that fires
+        with the contents as a byte string."""
+
+    def read(consumer, offset=0, size=None):
+        """Download a portion (possibly all) of the file's contents, making
+        them available to the given IConsumer. Return a Deferred that fires
+        (with the consumer) when the consumer is unregistered (either because
+        the last byte has been given to it, or because the consumer threw an
+        exception during write(), possibly because it no longer wants to
+        receive data). The portion downloaded will start at 'offset' and
+        contain 'size' bytes (or the remainder of the file if size==None).
+
+        The consumer will be used in non-streaming mode: an IPullProducer
+        will be attached to it.
+
+        The consumer will not receive data right away: several network trips
+        must occur first. The order of events will be::
+
+         consumer.registerProducer(p, streaming)
+          (if streaming == False)::
+           consumer does p.resumeProducing()
+            consumer.write(data)
+           consumer does p.resumeProducing()
+            consumer.write(data).. (repeat until all data is written)
+         consumer.unregisterProducer()
+         deferred.callback(consumer)
+
+        If a download error occurs, or an exception is raised by
+        consumer.registerProducer() or consumer.write(), I will call
+        consumer.unregisterProducer() and then deliver the exception via
+        deferred.errback(). To cancel the download, the consumer should call
+        p.stopProducing(), which will result in an exception being delivered
+        via deferred.errback().
+
+        See src/allmydata/util/consumer.py for an example of a simple
+        download-to-memory consumer.
+        """
+
+
+class IWritable(Interface):
+    """
+    I define methods that callers can use to update SDMF and MDMF
+    mutable files on a Tahoe-LAFS grid.
+    """
+    # XXX: For the moment, we have only this. It is possible that we
+    #      want to move overwrite() and modify() in here too.
+    def update(data, offset):
+        """
+        I write the data from my data argument to the MDMF file,
+        starting at offset. I continue writing data until my data
+        argument is exhausted, appending data to the file as necessary.
+        """
+        # assert IMutableUploadable.providedBy(data)
+        # to append data: offset=node.get_size_of_best_version()
+        # do we want to support compacting MDMF?
+        # for an MDMF file, this can be done with O(data.get_size())
+        # memory. For an SDMF file, any modification takes
+        # O(node.get_size_of_best_version()).
+
+
+class IMutableFileVersion(IReadable):
+    """I provide access to a particular version of a mutable file. The
+    access is read/write if I was obtained from a filenode derived from
+    a write cap, or read-only if the filenode was derived from a read cap.
+    """
+
+    def get_sequence_number():
+        """Return the sequence number of this version."""
+
+    def get_servermap():
+        """Return the IMutableFileServerMap instance that was used to create
+        this object.
         """
-        Return the URI that can be used by others to get access to this
-        node. If this node is read-only, the URI will only offer read-only
-        access. If this node is read-write, the URI will offer read-write
-        access.
 
-        If you have read-write access to a node and wish to share merely
-        read-only access with others, use get_readonly_uri().
+    def get_writekey():
+        """Return this filenode's writekey, or None if the node does not have
+        write-capability. This may be used to assist with data structures
+        that need to make certain data available only to writers, such as the
+        read-write child caps in dirnodes. The recommended process is to have
+        reader-visible data be submitted to the filenode in the clear (where
+        it will be encrypted by the filenode using the readkey), but encrypt
+        writer-visible data using this writekey.
         """
 
-    def get_readonly_uri():
-        """Return the directory URI that can be used by others to get
-        read-only access to this directory node. The result is a read-only
-        URI, regardless of whether this dirnode is read-only or read-write.
+    # TODO: Can this be overwrite instead of replace?
+    def replace(new_contents):
+        """Replace the contents of the mutable file, provided that no other
+        node has published (or is attempting to publish, concurrently) a
+        newer version of the file than this one.
+
+        I will avoid modifying any share that is different than the version
+        given by get_sequence_number(). However, if another node is writing
+        to the file at the same time as me, I may manage to update some shares
+        while they update others. If I see any evidence of this, I will signal
+        UncoordinatedWriteError, and the file will be left in an inconsistent
+        state (possibly the version you provided, possibly the old version,
+        possibly somebody else's version, and possibly a mix of shares from
+        all of these).
 
-        If you have merely read-only access to this dirnode,
-        get_readonly_uri() will return the same thing as get_uri().
+        The recommended response to UncoordinatedWriteError is to either
+        return it to the caller (since they failed to coordinate their
+        writes), or to attempt some sort of recovery. It may be sufficient to
+        wait a random interval (with exponential backoff) and repeat your
+        operation. If I do not signal UncoordinatedWriteError, then I was
+        able to write the new version without incident.
+
+        I return a Deferred that fires (with a PublishStatus object) when the
+        update has completed.
+        """
+
+    def modify(modifier_cb):
+        """Modify the contents of the file, by downloading this version,
+        applying the modifier function (or bound method), then uploading
+        the new version. This will succeed as long as no other node
+        publishes a version between the download and the upload.
+        I return a Deferred that fires (with a PublishStatus object) when
+        the update is complete.
+
+        The modifier callable will be given three arguments: a string (with
+        the old contents), a 'first_time' boolean, and a servermap. As with
+        download_to_data(), the old contents will be from this version,
+        but the modifier can use the servermap to make other decisions
+        (such as refusing to apply the delta if there are multiple parallel
+        versions, or if there is evidence of a newer unrecoverable version).
+        'first_time' will be True the first time the modifier is called,
+        and False on any subsequent calls.
+
+        The callable should return a string with the new contents. The
+        callable must be prepared to be called multiple times, and must
+        examine the input string to see if the change that it wants to make
+        is already present in the old version. If it does not need to make
+        any changes, it can either return None, or return its input string.
+
+        If the modifier raises an exception, it will be returned in the
+        errback.
         """
 
-    def get_verifier():
+
+# The hierarchy looks like this:
+#  IFilesystemNode
+#   IFileNode
+#    IMutableFileNode
+#    IImmutableFileNode
+#   IDirectoryNode
+
+class IFilesystemNode(Interface):
+    def get_cap():
+        """Return the strongest 'cap instance' associated with this node.
+        (writecap for writeable-mutable files/directories, readcap for
+        immutable or readonly-mutable files/directories). To convert this
+        into a string, call .to_string() on the result."""
+
+    def get_readcap():
+        """Return a readonly cap instance for this node. For immutable or
+        readonly nodes, get_cap() and get_readcap() return the same thing."""
+
+    def get_repair_cap():
+        """Return an IURI instance that can be used to repair the file, or
+        None if this node cannot be repaired (either because it is not
+        distributed, like a LIT file, or because the node does not represent
+        sufficient authority to create a repair-cap, like a read-only RSA
+        mutable file node [which cannot create the correct write-enablers]).
+        """
+
+    def get_verify_cap():
         """Return an IVerifierURI instance that represents the
         'verifiy/refresh capability' for this node. The holder of this
         capability will be able to renew the lease for this node, protecting
@@ -417,10 +774,34 @@ class IFilesystemNode(Interface):
         it holds a share for the file or directory.
         """
 
+    def get_uri():
+        """Return the URI string corresponding to the strongest cap associated
+        with this node. If this node is read-only, the URI will only offer
+        read-only access. If this node is read-write, the URI will offer
+        read-write access.
+
+        If you have read-write access to a node and wish to share merely
+        read-only access with others, use get_readonly_uri().
+        """
+
+    def get_write_uri():
+        """Return the URI string that can be used by others to get write
+        access to this node, if it is writeable. If this is a read-only node,
+        return None."""
+
+    def get_readonly_uri():
+        """Return the URI string that can be used by others to get read-only
+        access to this node. The result is a read-only URI, regardless of
+        whether this node is read-only or read-write.
+
+        If you have merely read-only access to this node, get_readonly_uri()
+        will return the same thing as get_uri().
+        """
+
     def get_storage_index():
         """Return a string with the (binary) storage index in use on this
         download. This may be None if there is no storage index (i.e. LIT
-        files)."""
+        files and directories)."""
 
     def is_readonly():
         """Return True if this reference provides mutable access to the given
@@ -437,21 +818,75 @@ class IFilesystemNode(Interface):
         file.
         """
 
-class IMutableFilesystemNode(IFilesystemNode):
-    pass
+    def is_unknown():
+        """Return True if this is an unknown node."""
 
-class IFileNode(IFilesystemNode):
-    def download(target):
-        """Download the file's contents to a given IDownloadTarget"""
+    def is_allowed_in_immutable_directory():
+        """Return True if this node is allowed as a child of a deep-immutable
+        directory. This is true if either the node is of a known-immutable type,
+        or it is unknown and read-only.
+        """
 
-    def download_to_data():
-        """Download the file's contents. Return a Deferred that fires
-        with those contents."""
+    def raise_error():
+        """Raise any error associated with this node."""
 
+    # XXX: These may not be appropriate outside the context of an IReadable.
     def get_size():
-        """Return the length (in bytes) of the data this node represents."""
+        """Return the length (in bytes) of the data this node represents. For
+        directory nodes, I return the size of the backing store. I return
+        synchronously and do not consult the network, so for mutable objects,
+        I will return the most recently observed size for the object, or None
+        if I don't remember a size. Use get_current_size, which returns a
+        Deferred, if you want more up-to-date information."""
+
+    def get_current_size():
+        """I return a Deferred that fires with the length (in bytes) of the
+        data this node represents.
+        """
+
+class IFileNode(IFilesystemNode):
+    """I am a node which represents a file: a sequence of bytes. I am not a
+    container, like IDirectoryNode."""
+    def get_best_readable_version():
+        """Return a Deferred that fires with an IReadable for the 'best'
+        available version of the file. The IReadable provides only read
+        access, even if this filenode was derived from a write cap.
+
+        For an immutable file, there is only one version. For a mutable
+        file, the 'best' version is the recoverable version with the
+        highest sequence number. If no uncoordinated writes have occurred,
+        and if enough shares are available, then this will be the most
+        recent version that has been uploaded. If no version is recoverable,
+        the Deferred will errback with an UnrecoverableFileError.
+        """
+
+    def download_best_version():
+        """Download the contents of the version that would be returned
+        by get_best_readable_version(). This is equivalent to calling
+        download_to_data() on the IReadable given by that method.
+
+        I return a Deferred that fires with a byte string when the file
+        has been fully downloaded. To support streaming download, use
+        the 'read' method of IReadable. If no version is recoverable,
+        the Deferred will errback with an UnrecoverableFileError.
+        """
+
+    def get_size_of_best_version():
+        """Find the size of the version that would be returned by
+        get_best_readable_version().
+
+        I return a Deferred that fires with an integer. If no version
+        is recoverable, the Deferred will errback with an
+        UnrecoverableFileError.
+        """
+
+
+class IImmutableFileNode(IFileNode, IReadable):
+    """I am a node representing an immutable file. Immutable files have
+    only one version"""
 
-class IMutableFileNode(IFileNode, IMutableFilesystemNode):
+
+class IMutableFileNode(IFileNode):
     """I provide access to a 'mutable file', which retains its identity
     regardless of what contents are put in it.
 
@@ -510,28 +945,18 @@ class IMutableFileNode(IFileNode, IMutableFilesystemNode):
     only be retrieved and updated all-at-once, as a single big string. Future
     versions of our mutable files will remove this restriction.
     """
-
-    def download_best_version():
-        """Download the 'best' available version of the file, meaning one of
-        the recoverable versions with the highest sequence number. If no
+    def get_best_mutable_version():
+        """Return a Deferred that fires with an IMutableFileVersion for
+        the 'best' available version of the file. The best version is
+        the recoverable version with the highest sequence number. If no
         uncoordinated writes have occurred, and if enough shares are
-        available, then this will be the most recent version that has been
-        uploaded.
+        available, then this will be the most recent version that has
+        been uploaded.
 
-        I return a Deferred that fires with a (contents, servermap) pair. The
-        servermap is updated with MODE_READ. The contents will be the version
-        of the file indicated by servermap.best_recoverable_version(). If no
-        version is recoverable, the Deferred will errback with
+        If no version is recoverable, the Deferred will errback with an
         UnrecoverableFileError.
         """
 
-    def get_size_of_best_version():
-        """Find the size of the version that would be downloaded with
-        download_best_version(), without actually downloading the whole file.
-
-        I return a Deferred that fires with an integer.
-        """
-
     def overwrite(new_contents):
         """Unconditionally replace the contents of the mutable file with new
         ones. This simply chains get_servermap(MODE_WRITE) and upload(). This
@@ -549,12 +974,14 @@ class IMutableFileNode(IFileNode, IMutableFilesystemNode):
         uploading the new version. I return a Deferred that fires (with a
         PublishStatus object) when the update is complete.
 
-        The modifier callable will be given two arguments: a string (with the
-        old contents) and a servermap. As with download_best_version(), the
-        old contents will be from the best recoverable version, but the
-        modifier can use the servermap to make other decisions (such as
-        refusing to apply the delta if there are multiple parallel versions,
-        or if there is evidence of a newer unrecoverable version).
+        The modifier callable will be given three arguments: a string (with
+        the old contents), a 'first_time' boolean, and a servermap. As with
+        download_best_version(), the old contents will be from the best
+        recoverable version, but the modifier can use the servermap to make
+        other decisions (such as refusing to apply the delta if there are
+        multiple parallel versions, or if there is evidence of a newer
+        unrecoverable version). 'first_time' will be True the first time the
+        modifier is called, and False on any subsequent calls.
 
         The callable should return a string with the new contents. The
         callable must be prepared to be called multiple times, and must
@@ -566,7 +993,6 @@ class IMutableFileNode(IFileNode, IMutableFilesystemNode):
         errback.
         """
 
-
     def get_servermap(mode):
         """Return a Deferred that fires with an IMutableFileServerMap
         instance, updated using the given mode.
@@ -620,14 +1046,41 @@ class IMutableFileNode(IFileNode, IMutableFilesystemNode):
         writer-visible data using this writekey.
         """
 
+    def get_version():
+        """Returns the mutable file protocol version."""
+
+class NotEnoughSharesError(Exception):
+    """Download was unable to get enough shares"""
+
+class NoSharesError(Exception):
+    """Download was unable to get any shares at all."""
+
+class UploadUnhappinessError(Exception):
+    """Upload was unable to satisfy 'servers_of_happiness'"""
+
+class UnableToFetchCriticalDownloadDataError(Exception):
+    """I was unable to fetch some piece of critical data which is supposed to
+    be identically present in all shares."""
+
+class NoServersError(Exception):
+    """Upload wasn't given any servers to work with, usually indicating a
+    network or Introducer problem."""
+
 class ExistingChildError(Exception):
     """A directory node was asked to add or replace a child that already
     exists, and overwrite= was set to False."""
 
-class IDirectoryNode(IMutableFilesystemNode):
-    """I represent a name-to-child mapping, holding the tahoe equivalent of a
-    directory. All child names are unicode strings, and all children are some
-    sort of IFilesystemNode (either files or subdirectories).
+class NoSuchChildError(Exception):
+    """A directory node was asked to fetch a child which does not exist."""
+
+class ChildOfWrongTypeError(Exception):
+    """An operation was attempted on a child of the wrong type (file or directory)."""
+
+class IDirectoryNode(IFilesystemNode):
+    """I represent a filesystem node that is a container, with a
+    name-to-child mapping, holding the tahoe equivalent of a directory. All
+    child names are unicode strings, and all children are some sort of
+    IFilesystemNode (a file, subdirectory, or unknown node).
     """
 
     def get_uri():
@@ -651,8 +1104,8 @@ class IDirectoryNode(IMutableFilesystemNode):
     def list():
         """I return a Deferred that fires with a dictionary mapping child
         name (a unicode string) to (node, metadata_dict) tuples, in which
-        'node' is either an IFileNode or IDirectoryNode, and 'metadata_dict'
-        is a dictionary of metadata."""
+        'node' is an IFilesystemNode and 'metadata_dict' is a dictionary of
+        metadata."""
 
     def has_child(name):
         """I return a Deferred that fires with a boolean, True if there
@@ -661,69 +1114,77 @@ class IDirectoryNode(IMutableFilesystemNode):
 
     def get(name):
         """I return a Deferred that fires with a specific named child node,
-        either an IFileNode or an IDirectoryNode. The child name must be a
-        unicode string."""
+        which is an IFilesystemNode. The child name must be a unicode string.
+        I raise NoSuchChildError if I do not have a child by that name."""
 
     def get_metadata_for(name):
-        """I return a Deferred that fires with the metadata dictionary for a
-        specific named child node. This metadata is stored in the *edge*, not
-        in the child, so it is attached to the parent dirnode rather than the
-        child dir-or-file-node. The child name must be a unicode string."""
+        """I return a Deferred that fires with the metadata dictionary for
+        a specific named child node. The child name must be a unicode string.
+        This metadata is stored in the *edge*, not in the child, so it is
+        attached to the parent dirnode rather than the child node.
+        I raise NoSuchChildError if I do not have a child by that name."""
 
     def set_metadata_for(name, metadata):
         """I replace any existing metadata for the named child with the new
         metadata. The child name must be a unicode string. This metadata is
         stored in the *edge*, not in the child, so it is attached to the
-        parent dirnode rather than the child dir-or-file-node. I return a
-        Deferred (that fires with this dirnode) when the operation is
-        complete."""
+        parent dirnode rather than the child node. I return a Deferred
+        (that fires with this dirnode) when the operation is complete.
+        I raise NoSuchChildError if I do not have a child by that name."""
 
     def get_child_at_path(path):
-        """Transform a child path into an IDirectoryNode or IFileNode.
+        """Transform a child path into an IFilesystemNode.
 
         I perform a recursive series of 'get' operations to find the named
         descendant node. I return a Deferred that fires with the node, or
-        errbacks with IndexError if the node could not be found.
+        errbacks with NoSuchChildError if the node could not be found.
 
         The path can be either a single string (slash-separated) or a list of
         path-name elements. All elements must be unicode strings.
         """
 
     def get_child_and_metadata_at_path(path):
-        """Transform a child path into an IDirectoryNode/IFileNode and
-        metadata.
+        """Transform a child path into an IFilesystemNode and metadata.
 
         I am like get_child_at_path(), but my Deferred fires with a tuple of
         (node, metadata). The metadata comes from the last edge. If the path
         is empty, the metadata will be an empty dictionary.
         """
 
-    def set_uri(name, child_uri, metadata=None, overwrite=True):
-        """I add a child (by URI) at the specific name. I return a Deferred
-        that fires when the operation finishes. If overwrite= is True, I will
-        replace any existing child of the same name, otherwise an existing
-        child will cause me to return ExistingChildError. The child name must
-        be a unicode string.
+    def set_uri(name, writecap, readcap=None, metadata=None, overwrite=True):
+        """I add a child (by writecap+readcap) at the specific name. I return
+        a Deferred that fires when the operation finishes. If overwrite= is
+        True, I will replace any existing child of the same name, otherwise
+        an existing child will cause me to return ExistingChildError. The
+        child name must be a unicode string.
 
-        The child_uri could be for a file, or for a directory (either
-        read-write or read-only, using a URI that came from get_uri() ).
+        The child caps could be for a file, or for a directory. If you have
+        both the writecap and readcap, you should provide both arguments.
+        If you have only one cap and don't know whether it is read-only,
+        provide it as the writecap argument and leave the readcap as None.
+        If you have only one cap that is known to be read-only, provide it
+        as the readcap argument and leave the writecap as None.
+        The filecaps are typically obtained from an IFilesystemNode with
+        get_uri() and get_readonly_uri().
 
         If metadata= is provided, I will use it as the metadata for the named
         edge. This will replace any existing metadata. If metadata= is left
         as the default value of None, I will set ['mtime'] to the current
         time, and I will set ['ctime'] to the current time if there was not
         already a child by this name present. This roughly matches the
-        ctime/mtime semantics of traditional filesystems.
+        ctime/mtime semantics of traditional filesystems.  See the
+        "About the metadata" section of webapi.txt for futher information.
 
         If this directory node is read-only, the Deferred will errback with a
-        NotMutableError."""
+        NotWriteableError."""
 
     def set_children(entries, overwrite=True):
-        """Add multiple (name, child_uri) pairs (or (name, child_uri,
-        metadata) triples) to a directory node. Returns a Deferred that fires
-        (with None) when the operation finishes. This is equivalent to
-        calling set_uri() multiple times, but is much more efficient. All
-        child names must be unicode strings.
+        """Add multiple children (by writecap+readcap) to a directory node.
+        Takes a dictionary, with childname as keys and (writecap, readcap)
+        tuples (or (writecap, readcap, metadata) triples) as values. Returns
+        a Deferred that fires (with this dirnode) when the operation
+        finishes. This is equivalent to calling set_uri() multiple times, but
+        is much more efficient. All child names must be unicode strings.
         """
 
     def set_node(name, child, metadata=None, overwrite=True):
@@ -731,44 +1192,52 @@ class IDirectoryNode(IMutableFilesystemNode):
         when the operation finishes. This Deferred will fire with the child
         node that was just added. I will replace any existing child of the
         same name. The child name must be a unicode string. The 'child'
-        instance must be an instance providing IDirectoryNode or IFileNode.
+        instance must be an instance providing IFilesystemNode.
 
         If metadata= is provided, I will use it as the metadata for the named
         edge. This will replace any existing metadata. If metadata= is left
         as the default value of None, I will set ['mtime'] to the current
         time, and I will set ['ctime'] to the current time if there was not
         already a child by this name present. This roughly matches the
-        ctime/mtime semantics of traditional filesystems.
+        ctime/mtime semantics of traditional filesystems. See the
+        "About the metadata" section of webapi.txt for futher information.
 
         If this directory node is read-only, the Deferred will errback with a
-        NotMutableError."""
+        NotWriteableError."""
 
     def set_nodes(entries, overwrite=True):
-        """Add multiple (name, child_node) pairs (or (name, child_node,
-        metadata) triples) to a directory node. Returns a Deferred that fires
-        (with None) when the operation finishes. This is equivalent to
-        calling set_node() multiple times, but is much more efficient. All
-        child names must be unicode strings."""
-
+        """Add multiple children to a directory node. Takes a dict mapping
+        unicode childname to (child_node, metdata) tuples. If metdata=None,
+        the original metadata is left unmodified. Returns a Deferred that
+        fires (with this dirnode) when the operation finishes. This is
+        equivalent to calling set_node() multiple times, but is much more
+        efficient."""
 
     def add_file(name, uploadable, metadata=None, overwrite=True):
         """I upload a file (using the given IUploadable), then attach the
-        resulting FileNode to the directory at the given name. I set metadata
-        the same way as set_uri and set_node. The child name must be a
-        unicode string.
+        resulting ImmutableFileNode to the directory at the given name. I set
+        metadata the same way as set_uri and set_node. The child name must be
+        unicode string.
 
         I return a Deferred that fires (with the IFileNode of the uploaded
         file) when the operation completes."""
 
-    def delete(name):
+    def delete(name, must_exist=True, must_be_directory=False, must_be_file=False):
         """I remove the child at the specific name. I return a Deferred that
         fires when the operation finishes. The child name must be a unicode
-        string."""
-
-    def create_empty_directory(name, overwrite=True):
-        """I create and attach an empty directory at the given name. The
-        child name must be a unicode string. I return a Deferred that fires
-        when the operation finishes."""
+        string. If must_exist is True and I do not have a child by that name,
+        I raise NoSuchChildError. If must_be_directory is True and the child
+        is a file, or if must_be_file is True and the child is a directory,
+        I raise ChildOfWrongTypeError."""
+
+    def create_subdirectory(name, initial_children={}, overwrite=True, metadata=None):
+        """I create and attach a directory at the given name. The new
+        directory can be empty, or it can be populated with children
+        according to 'initial_children', which takes a dictionary in the same
+        format as set_nodes (i.e. mapping unicode child name to (childnode,
+        metadata) tuples). The child name must be a unicode string. I return
+        a Deferred that fires (with the new directory node) when the
+        operation finishes."""
 
     def move_child_to(current_child_name, new_parent, new_child_name=None,
                       overwrite=True):
@@ -776,15 +1245,32 @@ class IDirectoryNode(IMutableFilesystemNode):
         is referenced by name. On the new parent, the child will live under
         'new_child_name', which defaults to 'current_child_name'. TODO: what
         should we do about metadata? I return a Deferred that fires when the
-        operation finishes. The child name must be a unicode string."""
+        operation finishes. The child name must be a unicode string. I raise
+        NoSuchChildError if I do not have a child by that name."""
 
     def build_manifest():
-        """Return a Monitor. The Monitor's results will be a list of (path,
-        cap) tuples for nodes (directories and files) reachable from this
-        one. 'path' will be a tuple of unicode strings. The origin dirnode
-        will be represented by an empty path tuple. The Monitor will also
-        have an .origin_si attribute with the (binary) storage index of the
-        starting point.
+        """I generate a table of everything reachable from this directory.
+        I also compute deep-stats as described below.
+
+        I return a Monitor. The Monitor's results will be a dictionary with
+        four elements:
+
+         res['manifest']: a list of (path, cap) tuples for all nodes
+                          (directories and files) reachable from this one.
+                          'path' will be a tuple of unicode strings. The
+                          origin dirnode will be represented by an empty path
+                          tuple.
+         res['verifycaps']: a list of (printable) verifycap strings, one for
+                            each reachable non-LIT node. This is a set:
+                            it will contain no duplicates.
+         res['storage-index']: a list of (base32) storage index strings,
+                               one for each reachable non-LIT node. This is
+                               a set: it will contain no duplicates.
+         res['stats']: a dictionary, the same that is generated by
+                       start_deep_stats() below.
+
+        The Monitor will also have an .origin_si attribute with the (binary)
+        storage index of the starting point.
         """
 
     def start_deep_stats():
@@ -863,6 +1349,9 @@ class ICodecEncoder(Interface):
         may be invoked.
         """
 
+    def get_params():
+        """Return the 3-tuple of data_size, required_shares, max_shares"""
+
     def get_encoder_type():
         """Return a short string that describes the type of this encoder.
 
@@ -871,23 +1360,6 @@ class ICodecEncoder(Interface):
         encoder class, and this encoder is an instance of that class.
         """
 
-    def get_serialized_params(): # TODO: maybe, maybe not
-        """Return a string that describes the parameters of this encoder.
-
-        This string can be passed to the decoder to prepare it for handling
-        the encoded shares we create. It might contain more information than
-        was presented to set_params(), if there is some flexibility of
-        parameter choice.
-
-        This string is intended to be embedded in the URI, so there are
-        several restrictions on its contents. At the moment I'm thinking that
-        this means it may contain hex digits and hyphens, and nothing else.
-        The idea is that the URI contains something like '%s:%s:%s' %
-        (encoder.get_encoder_name(), encoder.get_serialized_params(),
-        b2a(crypttext_hash)), and this is enough information to construct a
-        compatible decoder.
-        """
-
     def get_block_size():
         """Return the length of the shares that encode() will produce.
         """
@@ -934,20 +1406,56 @@ class ICodecEncoder(Interface):
         encode(), unless of course it already happens to be an even multiple
         of required_shares in length.)
 
-         ALSO: the requirement to break up your data into 'required_shares'
-         chunks before calling encode() feels a bit surprising, at least from
-         the point of view of a user who doesn't know how FEC works. It feels
-         like an implementation detail that has leaked outside the
-         abstraction barrier. Can you imagine a use case in which the data to
-         be encoded might already be available in pre-segmented chunks, such
-         that it is faster or less work to make encode() take a list rather
-         than splitting a single string?
-
-         ALSO ALSO: I think 'inshares' is a misleading term, since encode()
-         is supposed to *produce* shares, so what it *accepts* should be
-         something other than shares. Other places in this interface use the
-         word 'data' for that-which-is-not-shares.. maybe we should use that
-         term?
+        Note: the requirement to break up your data into
+        'required_shares' chunks of exactly the right length before
+        calling encode() is surprising from point of view of a user
+        who doesn't know how FEC works. It feels like an
+        implementation detail that has leaked outside the abstraction
+        barrier. Is there a use case in which the data to be encoded
+        might already be available in pre-segmented chunks, such that
+        it is faster or less work to make encode() take a list rather
+        than splitting a single string?
+
+        Yes, there is: suppose you are uploading a file with K=64,
+        N=128, segsize=262,144. Then each in-share will be of size
+        4096. If you use this .encode() API then your code could first
+        read each successive 4096-byte chunk from the file and store
+        each one in a Python string and store each such Python string
+        in a Python list. Then you could call .encode(), passing that
+        list as "inshares". The encoder would generate the other 64
+        "secondary shares" and return to you a new list containing
+        references to the same 64 Python strings that you passed in
+        (as the primary shares) plus references to the new 64 Python
+        strings.
+
+        (You could even imagine that your code could use readv() so
+        that the operating system can arrange to get all of those
+        bytes copied from the file into the Python list of Python
+        strings as efficiently as possible instead of having a loop
+        written in C or in Python to copy the next part of the file
+        into the next string.)
+
+        On the other hand if you instead use the .encode_proposal()
+        API (above), then your code can first read in all of the
+        262,144 bytes of the segment from the file into a Python
+        string, then call .encode_proposal() passing the segment data
+        as the "data" argument. The encoder would basically first
+        split the "data" argument into a list of 64 in-shares of 4096
+        byte each, and then do the same thing that .encode() does. So
+        this would result in a little bit more copying of data and a
+        little bit higher of a "maximum memory usage" during the
+        process, although it might or might not make a practical
+        difference for our current use cases.
+
+        Note that "inshares" is a strange name for the parameter if
+        you think of the parameter as being just for feeding in data
+        to the codec. It makes more sense if you think of the result
+        of this encoding as being the set of shares from inshares plus
+        an extra set of "secondary shares" (or "check shares"). It is
+        a surprising name! If the API is going to be surprising then
+        the name should be surprising. If we switch to
+        encode_proposal() above then we should also switch to an
+        unsurprising name.
 
         'desired_share_ids', if provided, is required to be a sequence of
         ints, each of which is required to be >= 0 and < max_shares. If not
@@ -997,13 +1505,13 @@ class ICodecEncoder(Interface):
 
 
 class ICodecDecoder(Interface):
-    def set_serialized_params(params):
-        """Set up the parameters of this encoder, from a string returned by
-        encoder.get_serialized_params()."""
+    def set_params(data_size, required_shares, max_shares):
+        """Set the params. They have to be exactly the same ones that were
+        used for encoding."""
 
     def get_needed_shares():
         """Return the number of shares needed to reconstruct the data.
-        set_serialized_params() is required to be called before this."""
+        set_params() is required to be called before this."""
 
     def decode(some_shares, their_shareids):
         """Decode a partial list of shares into data.
@@ -1054,7 +1562,7 @@ class IEncoder(Interface):
     def set_params(params):
         """Override the default encoding parameters. 'params' is a tuple of
         (k,d,n), where 'k' is the number of required shares, 'd' is the
-        shares_of_happiness, and 'n' is the total number of shares that will
+        servers_of_happiness, and 'n' is the total number of shares that will
         be created.
 
         Encoding parameters can be set in three ways. 1: The Encoder class
@@ -1087,7 +1595,7 @@ class IEncoder(Interface):
                          pushed.
 
         'share_counts': return a tuple describing how many shares are used:
-                        (needed_shares, shares_of_happiness, total_shares)
+                        (needed_shares, servers_of_happiness, total_shares)
 
         'num_segments': return an int with the number of segments that
                         will be encoded.
@@ -1120,11 +1628,13 @@ class IEncoder(Interface):
         Once this is called, set_size() and set_params() may not be called.
         """
 
-    def set_shareholders(shareholders):
+    def set_shareholders(shareholders, servermap):
         """Tell the encoder where to put the encoded shares. 'shareholders'
         must be a dictionary that maps share number (an integer ranging from
-        0 to n-1) to an instance that provides IStorageBucketWriter. This
-        must be performed before start() can be called."""
+        0 to n-1) to an instance that provides IStorageBucketWriter.
+        'servermap' is a dictionary that maps share number (as defined above)
+        to a set of peerids. This must be performed before start() can be
+        called."""
 
     def start():
         """Begin the encode/upload process. This involves reading encrypted
@@ -1134,10 +1644,9 @@ class IEncoder(Interface):
         set_encrypted_uploadable() and set_shareholders() must be called
         before this can be invoked.
 
-        This returns a Deferred that fires with a tuple of
-        (uri_extension_hash, needed_shares, total_shares, size) when the
-        upload process is complete. This information, plus the encryption
-        key, is sufficient to construct the URI.
+        This returns a Deferred that fires with a verify cap when the upload
+        process is complete. The verifycap, plus the encryption key, is
+        sufficient to construct the read cap.
         """
 
 class IDecoder(Interface):
@@ -1194,11 +1703,11 @@ class IDownloadTarget(Interface):
         is a Failure object indicating what went wrong. No further methods
         will be invoked on the IDownloadTarget after fail()."""
     def register_canceller(cb):
-        """The FileDownloader uses this to register a no-argument function
+        """The CiphertextDownloader uses this to register a no-argument function
         that the target can call to cancel the download. Once this canceller
         is invoked, no further calls to write() or close() will be made."""
     def finish():
-        """When the FileDownloader is done, this finish() function will be
+        """When the CiphertextDownloader is done, this finish() function will be
         called. Whatever it returns will be returned to the invoker of
         Downloader.download.
         """
@@ -1250,11 +1759,12 @@ class IEncryptedUploadable(Interface):
         plaintext hashes, but don't need the redundant encrypted data)."""
 
     def get_plaintext_hashtree_leaves(first, last, num_segments):
-        """Get the leaf nodes of a merkle hash tree over the plaintext
-        segments, i.e. get the tagged hashes of the given segments. The
-        segment size is expected to be generated by the IEncryptedUploadable
-        before any plaintext is read or ciphertext produced, so that the
-        segment hashes can be generated with only a single pass.
+        """OBSOLETE; Get the leaf nodes of a merkle hash tree over the
+        plaintext segments, i.e. get the tagged hashes of the given segments.
+        The segment size is expected to be generated by the
+        IEncryptedUploadable before any plaintext is read or ciphertext
+        produced, so that the segment hashes can be generated with only a
+        single pass.
 
         This returns a Deferred which fires with a sequence of hashes, using:
 
@@ -1270,7 +1780,7 @@ class IEncryptedUploadable(Interface):
         """
 
     def get_plaintext_hash():
-        """Get the hash of the whole plaintext.
+        """OBSOLETE; Get the hash of the whole plaintext.
 
         This returns a Deferred which fires with a tagged SHA-256 hash of the
         whole plaintext, obtained from hashutil.plaintext_hash(data).
@@ -1355,7 +1865,11 @@ class IUploadable(Interface):
 
         If the data must be acquired through multiple internal read
         operations, returning a list instead of a single string may help to
-        reduce string copies.
+        reduce string copies. However, the length of the concatenated strings
+        must equal the amount of data requested, unless EOF is encountered.
+        Long reads, or short reads without EOF, are not allowed. read()
+        should return the same amount of data as a local disk file read, just
+        in a different shape and asynchronously.
 
         'length' will typically be equal to (min(get_size(),1MB)/req_shares),
         so a 10kB file means length=3kB, 100kB file means length=30kB,
@@ -1371,16 +1885,53 @@ class IUploadable(Interface):
         """The upload is finished, and whatever filehandle was in use may be
         closed."""
 
+
+class IMutableUploadable(Interface):
+    """
+    I represent content that is due to be uploaded to a mutable filecap.
+    """
+    # This is somewhat simpler than the IUploadable interface above
+    # because mutable files do not need to be concerned with possibly
+    # generating a CHK, nor with per-file keys. It is a subset of the
+    # methods in IUploadable, though, so we could just as well implement
+    # the mutable uploadables as IUploadables that don't happen to use
+    # those methods (with the understanding that the unused methods will
+    # never be called on such objects)
+    def get_size():
+        """
+        Returns a Deferred that fires with the size of the content held
+        by the uploadable.
+        """
+
+    def read(length):
+        """
+        Returns a list of strings which, when concatenated, are the next
+        length bytes of the file, or fewer if there are fewer bytes
+        between the current location and the end of the file.
+        """
+
+    def close():
+        """
+        The process that used the Uploadable is finished using it, so
+        the uploadable may be closed.
+        """
+
 class IUploadResults(Interface):
     """I am returned by upload() methods. I contain a number of public
     attributes which can be read to determine the results of the upload. Some
     of these are functional, some are timing information. All of these may be
-    None.::
+    None.
 
      .file_size : the size of the file, in bytes
      .uri : the CHK read-cap for the file
      .ciphertext_fetched : how many bytes were fetched by the helper
-     .sharemap : dict mapping share number to placement string
+     .sharemap: dict mapping share identifier to set of serverids
+                   (binary strings). This indicates which servers were given
+                   which shares. For immutable files, the shareid is an
+                   integer (the share number, from 0 to N-1). For mutable
+                   files, it is a string of the form 'seq%d-%s-sh%d',
+                   containing the sequence number, the roothash, and the
+                   share number.
      .servermap : dict mapping server peerid to a set of share numbers
      .timings : dict of timing information, mapping name to seconds (float)
        total : total upload time, start to finish
@@ -1405,8 +1956,8 @@ class IDownloadResults(Interface):
      .file_size : the size of the file, in bytes
      .servers_used : set of server peerids that were used during download
      .server_problems : dict mapping server peerid to a problem string. Only
-                        servers that had problems (bad hashes, disconnects) are
-                        listed here.
+                        servers that had problems (bad hashes, disconnects)
+                        are listed here.
      .servermap : dict mapping server peerid to a set of share numbers. Only
                   servers that had any shares are listed here.
      .timings : dict of timing information, mapping name to seconds (float)
@@ -1426,18 +1977,18 @@ class IDownloadResults(Interface):
 class IUploader(Interface):
     def upload(uploadable):
         """Upload the file. 'uploadable' must impement IUploadable. This
-        returns a Deferred which fires with an UploadResults instance, from
+        returns a Deferred which fires with an IUploadResults instance, from
         which the URI of the file can be obtained as results.uri ."""
 
     def upload_ssk(write_capability, new_version, uploadable):
         """TODO: how should this work?"""
 
 class ICheckable(Interface):
-    def check(monitor, verify=False):
-        """Check upon my health, optionally repairing any problems.
+    def check(monitor, verify=False, add_lease=False):
+        """Check up on my health, optionally repairing any problems.
 
         This returns a Deferred that fires with an instance that provides
-        ICheckerResults, or None if the object is non-distributed (i.e. LIT
+        ICheckResults, or None if the object is non-distributed (i.e. LIT
         files).
 
         The monitor will be checked periodically to see if the operation has
@@ -1467,13 +2018,21 @@ class ICheckable(Interface):
         failures during retrieval, or is malicious or buggy, then
         verification will detect the problem, but checking will not.
 
+        If add_lease=True, I will ensure that an up-to-date lease is present
+        on each share. The lease secrets will be derived from by node secret
+        (in BASEDIR/private/secret), so either I will add a new lease to the
+        share, or I will merely renew the lease that I already had. In a
+        future version of the storage-server protocol (once Accounting has
+        been implemented), there may be additional options here to define the
+        kind of lease that is obtained (which account number to claim, etc).
+
         TODO: any problems seen during checking will be reported to the
         health-manager.furl, a centralized object which is responsible for
         figuring out why files are unhealthy so corrective action can be
         taken.
         """
 
-    def check_and_repair(monitor, verify=False):
+    def check_and_repair(monitor, verify=False, add_lease=False):
         """Like check(), but if the file/directory is not healthy, attempt to
         repair the damage.
 
@@ -1487,16 +2046,22 @@ class ICheckable(Interface):
         ICheckAndRepairResults."""
 
 class IDeepCheckable(Interface):
-    def start_deep_check(verify=False):
+    def start_deep_check(verify=False, add_lease=False):
         """Check upon the health of me and everything I can reach.
 
         This is a recursive form of check(), useable only on dirnodes.
 
         I return a Monitor, with results that are an IDeepCheckResults
         object.
+
+        TODO: If any of the directories I traverse are unrecoverable, the
+        Monitor will report failure. If any of the files I check upon are
+        unrecoverable, those problems will be reported in the
+        IDeepCheckResults as usual, and the Monitor will not report a
+        failure.
         """
 
-    def start_deep_check_and_repair(verify=False):
+    def start_deep_check_and_repair(verify=False, add_lease=False):
         """Check upon the health of me and everything I can reach. Repair
         anything that isn't healthy.
 
@@ -1505,9 +2070,15 @@ class IDeepCheckable(Interface):
 
         I return a Monitor, with results that are an
         IDeepCheckAndRepairResults object.
+
+        TODO: If any of the directories I traverse are unrecoverable, the
+        Monitor will report failure. If any of the files I check upon are
+        unrecoverable, those problems will be reported in the
+        IDeepCheckResults as usual, and the Monitor will not report a
+        failure.
         """
 
-class ICheckerResults(Interface):
+class ICheckResults(Interface):
     """I contain the detailed results of a check/verify operation.
     """
 
@@ -1515,25 +2086,31 @@ class ICheckerResults(Interface):
         """Return a string with the (binary) storage index."""
     def get_storage_index_string():
         """Return a string with the (printable) abbreviated storage index."""
+    def get_uri():
+        """Return the (string) URI of the object that was checked."""
 
     def is_healthy():
         """Return a boolean, True if the file/dir is fully healthy, False if
         it is damaged in any way. Non-distributed LIT files always return
         True."""
 
+    def is_recoverable():
+        """Return a boolean, True if the file/dir can be recovered, False if
+        not. Unrecoverable files are obviously unhealthy. Non-distributed LIT
+        files always return True."""
+
     def needs_rebalancing():
         """Return a boolean, True if the file/dir's reliability could be
         improved by moving shares to new servers. Non-distributed LIT files
-        always returne False."""
+        always return False."""
 
 
     def get_data():
-        """Return a dictionary that describes the state of the file/dir.
-        Non-distributed LIT files always return an empty dictionary. Normal
-        files and directories return a dictionary with the following keys
-        (note that these use base32-encoded strings rather than binary ones)
-        (also note that for mutable files, these counts are for the 'best'
-        version)::
+        """Return a dictionary that describes the state of the file/dir. LIT
+        files always return an empty dictionary. Normal files and directories
+        return a dictionary with the following keys (note that these use
+        binary strings rather than base32-encoded ones) (also note that for
+        mutable files, these counts are for the 'best' version):
 
          count-shares-good: the number of distinct good shares that were found
          count-shares-needed: 'k', the number of shares required for recovery
@@ -1550,9 +2127,20 @@ class ICheckerResults(Interface):
                               that was found to be corrupt. Each share
                               locator is a list of (serverid, storage_index,
                               sharenum).
+         count-incompatible-shares: the number of shares which are of a share
+                                    format unknown to this checker
+         list-incompatible-shares: a list of 'share locators', one for each
+                                   share that was found to be of an unknown
+                                   format. Each share locator is a list of
+                                   (serverid, storage_index, sharenum).
          servers-responding: list of (binary) storage server identifiers,
                              one for each server which responded to the share
-                             query.
+                             query (even if they said they didn't have
+                             shares, and even if they said they did have
+                             shares but then didn't send them when asked, or
+                             dropped the connection, or returned a Failure,
+                             and even if they said they did have shares and
+                             sent incorrect ones when asked)
          sharemap: dict mapping share identifier to list of serverids
                    (binary strings). This indicates which servers are holding
                    which shares. For immutable files, the shareid is an
@@ -1604,16 +2192,19 @@ class ICheckAndRepairResults(Interface):
     def get_storage_index_string():
         """Return a string with the (printable) abbreviated storage index."""
     def get_repair_attempted():
-        """Return a boolean, True if a repair was attempted."""
+        """Return a boolean, True if a repair was attempted. We might not
+        attempt to repair the file because it was healthy, or healthy enough
+        (i.e. some shares were missing but not enough to exceed some
+        threshold), or because we don't know how to repair this object."""
     def get_repair_successful():
         """Return a boolean, True if repair was attempted and the file/dir
         was fully healthy afterwards. False if no repair was attempted or if
         a repair attempt failed."""
     def get_pre_repair_results():
-        """Return an ICheckerResults instance that describes the state of the
+        """Return an ICheckResults instance that describes the state of the
         file/dir before any repair was attempted."""
     def get_post_repair_results():
-        """Return an ICheckerResults instance that describes the state of the
+        """Return an ICheckResults instance that describes the state of the
         file/dir after any repair was attempted. If no repair was attempted,
         the pre-repair and post-repair results will be identical."""
 
@@ -1634,6 +2225,7 @@ class IDeepCheckResults(Interface):
              count-objects-healthy: how many of those objects were completely
                                     healthy
              count-objects-unhealthy: how many were damaged in some way
+             count-objects-unrecoverable: how many were unrecoverable
              count-corrupt-shares: how many shares were found to have
                                    corruption, summed over all objects
                                    examined
@@ -1646,11 +2238,11 @@ class IDeepCheckResults(Interface):
         """
     def get_all_results():
         """Return a dictionary mapping pathname (a tuple of strings, ready to
-        be slash-joined) to an ICheckerResults instance, one for each object
+        be slash-joined) to an ICheckResults instance, one for each object
         that was checked."""
 
     def get_results_for_storage_index(storage_index):
-        """Retrive the ICheckerResults instance for the given (binary)
+        """Retrive the ICheckResults instance for the given (binary)
         storage index. Raises KeyError if there are no results for that
         storage index."""
 
@@ -1676,11 +2268,14 @@ class IDeepCheckAndRepairResults(Interface):
                                                repair)
              count-objects-unhealthy-pre-repair: how many were damaged in
                                                  some way
+             count-objects-unrecoverable-pre-repair: how many were unrecoverable
              count-objects-healthy-post-repair: how many of those objects were
                                                 completely healthy (after any
                                                 repair)
              count-objects-unhealthy-post-repair: how many were damaged in
                                                   some way
+             count-objects-unrecoverable-post-repair: how many were
+                                                      unrecoverable
              count-repairs-attempted: repairs were attempted on this many
                                       objects. The count-repairs- keys will
                                       always be provided, however unless
@@ -1720,17 +2315,22 @@ class IDeepCheckAndRepairResults(Interface):
         be slash-joined) to an ICheckAndRepairResults instance, one for each
         object that was checked."""
 
+    def get_results_for_storage_index(storage_index):
+        """Retrive the ICheckAndRepairResults instance for the given (binary)
+        storage index. Raises KeyError if there are no results for that
+        storage index."""
+
 
 class IRepairable(Interface):
-    def repair(checker_results):
+    def repair(check_results):
         """Attempt to repair the given object. Returns a Deferred that fires
         with a IRepairResults object.
 
-        I must be called with an object that implements ICheckerResults, as
+        I must be called with an object that implements ICheckResults, as
         proof that you have actually discovered a problem with this file. I
         will use the data in the checker results to guide the repair process,
         such as which servers provided bad data and should therefore be
-        avoided. The ICheckerResults object is inside the
+        avoided. The ICheckResults object is inside the
         ICheckAndRepairResults object, which is returned by the
         ICheckable.check() method::
 
@@ -1744,6 +2344,10 @@ class IRepairable(Interface):
 
 class IRepairResults(Interface):
     """I contain the results of a repair operation."""
+    def get_successful(self):
+        """Returns a boolean: True if the repair made the file healthy, False
+        if not. Repair failure generally indicates a file that has been
+        damaged beyond repair."""
 
 
 class IClient(Interface):
@@ -1755,29 +2359,82 @@ class IClient(Interface):
         """
 
     def create_mutable_file(contents=""):
-        """Create a new mutable file with contents, get back the URI string.
-        @param contents: the initial contents to place in the file.
-        @return: a Deferred that fires with tne (string) SSK URI for the new
-                 file.
+        """Create a new mutable file (with initial) contents, get back the
+        new node instance.
+
+        @param contents: (bytestring, callable, or None): this provides the
+        initial contents of the mutable file. If 'contents' is a bytestring,
+        it will be used as-is. If 'contents' is a callable, it will be
+        invoked with the new MutableFileNode instance and is expected to
+        return a bytestring with the initial contents of the file (the
+        callable can use node.get_writekey() to decide how to encrypt the
+        initial contents, e.g. for a brand new dirnode with initial
+        children). contents=None is equivalent to an empty string. Using
+        content_maker= is more efficient than creating a mutable file and
+        setting its contents in two separate operations.
+
+        @return: a Deferred that fires with an IMutableFileNode instance.
         """
 
-    def create_empty_dirnode():
-        """Create a new dirnode, empty and unattached.
+    def create_dirnode(initial_children={}):
+        """Create a new unattached dirnode, possibly with initial children.
+
+        @param initial_children: dict with keys that are unicode child names,
+        and values that are (childnode, metadata) tuples.
+
         @return: a Deferred that fires with the new IDirectoryNode instance.
         """
 
-    def create_node_from_uri(uri):
+    def create_node_from_uri(uri, rouri):
         """Create a new IFilesystemNode instance from the uri, synchronously.
-        @param uri: a string or IURI-providing instance. This could be for a
-                    LiteralFileNode, a CHK file node, a mutable file node, or
-                    a directory node
-        @return: an instance that provides IFilesystemNode (or more usefully one
-                 of its subclasses). File-specifying URIs will result in
-                 IFileNode or IMutableFileNode -providing instances, like
-                 FileNode, LiteralFileNode, or MutableFileNode.
-                 Directory-specifying URIs will result in
-                 IDirectoryNode-providing instances, like NewDirectoryNode.
-        """
+        @param uri: a string or IURI-providing instance, or None. This could
+                    be for a LiteralFileNode, a CHK file node, a mutable file
+                    node, or a directory node
+        @param rouri: a string or IURI-providing instance, or None. If the
+                      main uri is None, I will use the rouri instead. If I
+                      recognize the format of the main uri, I will ignore the
+                      rouri (because it can be derived from the writecap).
+
+        @return: an instance that provides IFilesystemNode (or more usefully
+                 one of its subclasses). File-specifying URIs will result in
+                 IFileNode-providing instances, like ImmutableFileNode,
+                 LiteralFileNode, or MutableFileNode. Directory-specifying
+                 URIs will result in IDirectoryNode-providing instances, like
+                 DirectoryNode.
+        """
+
+class INodeMaker(Interface):
+    """The NodeMaker is used to create IFilesystemNode instances. It can
+    accept a filecap/dircap string and return the node right away. It can
+    also create new nodes (i.e. upload a file, or create a mutable file)
+    asynchronously. Once you have one of these nodes, you can use other
+    methods to determine whether it is a file or directory, and to download
+    or modify its contents.
+
+    The NodeMaker encapsulates all the authorities that these
+    IFilesystemNodes require (like references to the StorageFarmBroker). Each
+    Tahoe process will typically have a single NodeMaker, but unit tests may
+    create simplified/mocked forms for testing purposes.
+    """
+    def create_from_cap(writecap, readcap=None, **kwargs):
+        """I create an IFilesystemNode from the given writecap/readcap. I can
+        only provide nodes for existing file/directory objects: use my other
+        methods to create new objects. I return synchronously."""
+
+    def create_mutable_file(contents=None, keysize=None):
+        """I create a new mutable file, and return a Deferred which will fire
+        with the IMutableFileNode instance when it is ready. If contents= is
+        provided (a bytestring), it will be used as the initial contents of
+        the new file, otherwise the file will contain zero bytes. keysize= is
+        for use by unit tests, to create mutable files that are smaller than
+        usual."""
+
+    def create_new_mutable_directory(initial_children={}):
+        """I create a new mutable directory, and return a Deferred which will
+        fire with the IDirectoryNode instance when it is ready. If
+        initial_children= is provided (a dict mapping unicode child name to
+        (childnode, metadata_dict) tuples), the directory will be populated
+        with those children, otherwise it will be empty."""
 
 class IClientStatus(Interface):
     def list_all_uploads():
@@ -1887,7 +2544,9 @@ class RIControlClient(RemoteInterface):
         storage servers.
         """
 
-    def upload_from_file_to_uri(filename=str, convergence=ChoiceOf(None, StringConstraint(2**20))):
+    def upload_from_file_to_uri(filename=str,
+                                convergence=ChoiceOf(None,
+                                                     StringConstraint(2**20))):
         """Upload a file to the grid. This accepts a filename (which must be
         absolute) that points to a file on the node's local disk. The node will
         read the contents of this file, upload it to the grid, then return the
@@ -1932,7 +2591,7 @@ class RIControlClient(RemoteInterface):
         @return: a dictionary mapping peerid to a float (RTT time in seconds)
         """
 
-        return DictOf(Nodeid, float)
+        return DictOf(str, float)
 
 UploadResults = Any() #DictOf(str, str)
 
@@ -1948,12 +2607,6 @@ class RIEncryptedUploadable(RemoteInterface):
     def read_encrypted(offset=Offset, length=ReadSize):
         return ListOf(str)
 
-    def get_plaintext_hashtree_leaves(first=int, last=int, num_segments=int):
-        return ListOf(Hash)
-
-    def get_plaintext_hash():
-        return Hash
-
     def close():
         return None
 
@@ -1961,6 +2614,12 @@ class RIEncryptedUploadable(RemoteInterface):
 class RICHKUploadHelper(RemoteInterface):
     __remote_name__ = "RIUploadHelper.tahoe.allmydata.com"
 
+    def get_version():
+        """
+        Return a dictionary of version information.
+        """
+        return DictOf(str, Any())
+
     def upload(reader=RIEncryptedUploadable):
         return UploadResults
 
@@ -1968,6 +2627,12 @@ class RICHKUploadHelper(RemoteInterface):
 class RIHelper(RemoteInterface):
     __remote_name__ = "RIHelper.tahoe.allmydata.com"
 
+    def get_version():
+        """
+        Return a dictionary of version information.
+        """
+        return DictOf(str, Any())
+
     def upload_chk(si=StorageIndex):
         """See if a file with a given storage index needs uploading. The
         helper will ask the appropriate storage servers to see if the file
@@ -1994,12 +2659,13 @@ class RIStatsProvider(RemoteInterface):
 
     def get_stats():
         """
-        returns a dictionary containing 'counters' and 'stats', each a dictionary
-        with string counter/stat name keys, and numeric values.  counters are
-        monotonically increasing measures of work done, and stats are instantaneous
-        measures (potentially time averaged internally)
+        returns a dictionary containing 'counters' and 'stats', each a
+        dictionary with string counter/stat name keys, and numeric or None values.
+        counters are monotonically increasing measures of work done, and
+        stats are instantaneous measures (potentially time averaged
+        internally)
         """
-        return DictOf(str, DictOf(str, ChoiceOf(float, int, long)))
+        return DictOf(str, DictOf(str, ChoiceOf(float, int, long, None)))
 
 class RIStatsGatherer(RemoteInterface):
     __remote_name__ = "RIStatsGatherer.tahoe.allmydata.com"
@@ -2040,3 +2706,19 @@ class RIKeyGenerator(RemoteInterface):
 class FileTooLargeError(Exception):
     pass
 
+class IValidatedThingProxy(Interface):
+    def start():
+        """ Acquire a thing and validate it. Return a deferred which is
+        eventually fired with self if the thing is valid or errbacked if it
+        can't be acquired or validated."""
+
+class InsufficientVersionError(Exception):
+    def __init__(self, needed, got):
+        self.needed = needed
+        self.got = got
+    def __repr__(self):
+        return "InsufficientVersionError(need '%s', got %s)" % (self.needed,
+                                                                self.got)
+
+class EmptyPathnameComponentError(Exception):
+    """The webapi disallows empty pathname components."""