From a8832b11b6e365564d1b53f35bd885353d550841 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Fri, 20 Jan 2023 14:29:17 -0500 Subject: [PATCH 01/18] Start adapting language to narrow down possible interpretations --- docs/proposed/http-storage-node-protocol.rst | 113 ++++++++++++++----- 1 file changed, 84 insertions(+), 29 deletions(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index aee201cf5..397d64ec2 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -3,7 +3,7 @@ Storage Node Protocol ("Great Black Swamp", "GBS") ================================================== -The target audience for this document is Tahoe-LAFS developers. +The target audience for this document is developers working on Tahoe-LAFS or on an alternate implementation intended to be interoperable. After reading this document, one should expect to understand how Tahoe-LAFS clients interact over the network with Tahoe-LAFS storage nodes. @@ -64,6 +64,10 @@ Glossary lease renew secret a short secret string which storage servers required to be presented before allowing a particular lease to be renewed +The key words +"MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" +in this document are to be interpreted as described in RFC 2119. + Motivation ---------- @@ -119,8 +123,8 @@ An HTTP-based protocol can make use of TLS in largely the same way to provide th Provision of these properties *is* dependant on implementers following Great Black Swamp's rules for x509 certificate validation (rather than the standard "web" rules for validation). -Requirements ------------- +Design Requirements +------------------- Security ~~~~~~~~ @@ -189,6 +193,9 @@ Solutions An HTTP-based protocol, dubbed "Great Black Swamp" (or "GBS"), is described below. This protocol aims to satisfy the above requirements at a lower level of complexity than the current Foolscap-based protocol. +Summary (Non-normative) +!!!!!!!!!!!!!!!!!!!!!!! + Communication with the storage node will take place using TLS. The TLS version and configuration will be dictated by an ongoing understanding of best practices. The storage node will present an x509 certificate during the TLS handshake. @@ -240,7 +247,7 @@ When Bob's client issues HTTP requests to Alice's storage node it includes the * They are encoded with Base32 for a length of 32 bytes. SPKI information discussed here is 32 bytes (SHA256 digest). They would be encoded in Base32 for a length of 52 bytes. - `base64url`_ provides a more compact encoding of the information while remaining URL-compatible. + `unpadded base64url`_ provides a more compact encoding of the information while remaining URL-compatible. This would encode the SPKI information for a length of merely 43 bytes. SHA1, the current Foolscap hash function, @@ -332,12 +339,15 @@ Details about the interface are encoded in the HTTP message body. Message Encoding ~~~~~~~~~~~~~~~~ -The preferred encoding for HTTP message bodies is `CBOR`_. -A request may be submitted using an alternate encoding by declaring this in the ``Content-Type`` header. -A request may indicate its preference for an alternate encoding in the response using the ``Accept`` header. -These two headers are used in the typical way for an HTTP application. +Clients and servers MUST use the ``Content-Type`` and ``Accept`` header fields as specified in `RFC 9110`_ for message body negotiation. -The only other encoding support for which is currently recommended is JSON. +The encoding for HTTP message bodies SHOULD be `CBOR`_. +Clients submitting requests using this encoding MUST include a ``Content-Type: application/cbor`` request header field. +A request MAY be submitted using an alternate encoding by declaring this in the ``Content-Type`` header field. +A request MAY indicate its preference for an alternate encoding in the response using the ``Accept`` header field. +A request which includes no ``Accept`` header field MUST be interpreted in the same way as a request including a ``Accept: application/cbor`` header field. + +Clients and servers SHOULD support ``application/json`` request and response message body encoding. For HTTP messages carrying binary share data, this is expected to be a particularly poor encoding. However, @@ -350,10 +360,19 @@ Because of the simple types used throughout and the equivalence described in `RFC 7049`_ these examples should be representative regardless of which of these two encodings is chosen. -The one exception is sets. -For CBOR messages, any sequence that is semantically a set (i.e. no repeated values allowed, order doesn't matter, and elements are hashable in Python) should be sent as a set. -Tag 6.258 is used to indicate sets in CBOR; see `the CBOR registry `_ for more details. -Sets will be represented as JSON lists in examples because JSON doesn't support sets. +One exception to this rule is for sets. +For CBOR messages, +any sequence that is semantically a set (i.e. no repeated values allowed, order doesn't matter, and elements are hashable in Python) should be sent as a set. +Tag 6.258 is used to indicate sets in CBOR; +see `the CBOR registry `_ for more details. +The JSON encoding does not support sets. +Sets MUST be represented as arrays in JSON-encoded messages. + +Another exception to this rule is for bytes. +The CBOR encoding natively supports a bytes type while the JSON encoding does not. +Bytes MUST be represented as strings giving the `Base64`_ representation of the original bytes value. + +Clients and servers MAY support additional request and response message body encodings. HTTP Design ~~~~~~~~~~~ @@ -368,29 +387,49 @@ one branch contains all of the share data; another branch contains all of the lease data; etc. -An ``Authorization`` header in requests is required for all endpoints. -The standard HTTP authorization protocol is used. -The authentication *type* used is ``Tahoe-LAFS``. -The swissnum from the NURL used to locate the storage service is used as the *credentials*. -If credentials are not presented or the swissnum is not associated with a storage service then no storage processing is performed and the request receives an ``401 UNAUTHORIZED`` response. +Clients and servers MUST use the ``Authorization`` header field, +as specified in `RFC 9110`_, +for authorization of all requests to all endpoints specified here. +The authentication *type* MUST be ``Tahoe-LAFS``. +Clients MUST present the swissnum from the NURL used to locate the storage service as the *credentials*. -There are also, for some endpoints, secrets sent via ``X-Tahoe-Authorization`` headers. -If these are: +If credentials are not presented or the swissnum is not associated with a storage service then the server MUST issue a ``401 UNAUTHORIZED`` response and perform no other processing of the message. + +Requests to certain endpoints MUST include additional secrets in the ``X-Tahoe-Authorization`` headers field. +The endpoints which require these secrets are: + +* ``PUT /storage/v1/lease/:storage_index``: + The secrets included MUST be ``lease-renew-secret`` and ``lease-cancel-secret``. + +* ``POST /storage/v1/immutable/:storage_index``: + The secrets included MUST be ``lease-renew-secret``, ``lease-cancel-secret``, and ``upload-secret``. + +* ``PATCH /storage/v1/immutable/:storage_index/:share_number``: + The secrets included MUST be ``upload-secret``. + +* ``PUT /storage/v1/immutable/:storage_index/:share_number/abort``: + The secrets included MUST be ``upload-secret``. + +* ``POST /storage/v1/mutable/:storage_index/read-test-write``: + The secrets included MUST be ``lease-renew-secret``, ``lease-cancel-secret``, and ``write-enabler``. + +If these secrets are: 1. Missing. 2. The wrong length. 3. Not the expected kind of secret. 4. They are otherwise unparseable before they are actually semantically used. -the server will respond with ``400 BAD REQUEST``. +the server MUST respond with ``400 BAD REQUEST`` and perform no other processing of the message. 401 is not used because this isn't an authorization problem, this is a "you sent garbage and should know better" bug. -If authorization using the secret fails, then a ``401 UNAUTHORIZED`` response should be sent. +If authorization using the secret fails, +then the server MUST send a ``401 UNAUTHORIZED`` response and perform no other processing of the message. Encoding ~~~~~~~~ -* ``storage_index`` should be base32 encoded (RFC3548) in URLs. +* ``storage_index`` MUST be `Base32`_ encoded in URLs. General ~~~~~~~ @@ -398,11 +437,14 @@ General ``GET /storage/v1/version`` !!!!!!!!!!!!!!!!!!!!!!!!!!! -Retrieve information about the version of the storage server. -Information is returned as an encoded mapping. -For example:: +This endpoint allows clients to retrieve some basic metadata about a storage server from the storage service. +The response MUST represent a mapping from schema identifiers to the metadata. - { "http://allmydata.org/tahoe/protocols/storage/v1" : +The only schema identifier specified is ``"http://allmydata.org/tahoe/protocols/storage/v1"``. +The server MUST include an entry in the mapping with this key. +The value for the key MUST be another mapping with the following keys and value types:: + + { "http://allmydata.org/tahoe/protocols/storage/v1": { "maximum-immutable-share-size": 1234, "maximum-mutable-share-size": 1235, "available-space": 123456, @@ -414,6 +456,11 @@ For example:: "application-version": "1.13.0" } +The server SHOULD populate as many fields as possible with accurate information about itself. + +XXX Document every single field + + ``PUT /storage/v1/lease/:storage_index`` !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -926,10 +973,18 @@ otherwise it will read a byte which won't match `b""`:: 204 NO CONTENT +.. _Base64: https://www.rfc-editor.org/rfc/rfc4648#section-4 + +.. _Base32: https://www.rfc-editor.org/rfc/rfc4648#section-6 + +.. _RFC 4648: https://tools.ietf.org/html/rfc4648 + .. _RFC 7469: https://tools.ietf.org/html/rfc7469#section-2.4 .. _RFC 7049: https://tools.ietf.org/html/rfc7049#section-4 +.. _RFC 9110: https://tools.ietf.org/html/rfc9110 + .. _CBOR: http://cbor.io/ .. [#] @@ -974,7 +1029,7 @@ otherwise it will read a byte which won't match `b""`:: spki_encoded = urlsafe_b64encode(spki_sha256) assert spki_encoded == tub_id - Note we use `base64url`_ rather than the Foolscap- and Tahoe-LAFS-preferred Base32. + Note we use `unpadded base64url`_ rather than the Foolscap- and Tahoe-LAFS-preferred Base32. .. [#] https://www.cvedetails.com/cve/CVE-2017-5638/ @@ -985,6 +1040,6 @@ otherwise it will read a byte which won't match `b""`:: .. [#] https://efail.de/ -.. _base64url: https://tools.ietf.org/html/rfc7515#appendix-C +.. _unpadded base64url: https://tools.ietf.org/html/rfc7515#appendix-C .. _attacking SHA1: https://en.wikipedia.org/wiki/SHA-1#Attacks From 7b207383088ce1f866bc6442e07b5f675ceea4b7 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 23 Jan 2023 10:40:41 -0500 Subject: [PATCH 02/18] some more edits --- docs/proposed/http-storage-node-protocol.rst | 128 ++++++++++--------- 1 file changed, 70 insertions(+), 58 deletions(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index 397d64ec2..cff6dc67b 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -438,27 +438,28 @@ General !!!!!!!!!!!!!!!!!!!!!!!!!!! This endpoint allows clients to retrieve some basic metadata about a storage server from the storage service. -The response MUST represent a mapping from schema identifiers to the metadata. +The response MUST validate against this CDDL schema:: -The only schema identifier specified is ``"http://allmydata.org/tahoe/protocols/storage/v1"``. -The server MUST include an entry in the mapping with this key. -The value for the key MUST be another mapping with the following keys and value types:: + {'http://allmydata.org/tahoe/protocols/storage/v1' => { + 'maximum-immutable-share-size' => uint + 'maximum-mutable-share-size' => uint + 'available-space' => uint + 'tolerates-immutable-read-overrun' => bool + 'delete-mutable-shares-with-zero-length-writev' => bool + 'fills-holes-with-zero-bytes' => bool + 'prevents-read-past-end-of-share-data' => bool + } + 'application-version' => bstr + } - { "http://allmydata.org/tahoe/protocols/storage/v1": - { "maximum-immutable-share-size": 1234, - "maximum-mutable-share-size": 1235, - "available-space": 123456, - "tolerates-immutable-read-overrun": true, - "delete-mutable-shares-with-zero-length-writev": true, - "fills-holes-with-zero-bytes": true, - "prevents-read-past-end-of-share-data": true - }, - "application-version": "1.13.0" - } +The server SHOULD populate as many fields as possible with accurate information about its behavior. -The server SHOULD populate as many fields as possible with accurate information about itself. +For fields which relate to a specific API +the semantics are documented below in the section for that API. +For fields that are more general than a single API the semantics are as follows: -XXX Document every single field +* available-space: + The server SHOULD use this field to advertise the amount of space that it currently considers unused and is willing to allocate for client requests. ``PUT /storage/v1/lease/:storage_index`` @@ -518,21 +519,23 @@ Writing !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Initialize an immutable storage index with some buckets. -The buckets may have share data written to them once. -A lease is also created for the shares. +The server MUST allow share data to be written to the buckets at most one time. +The server MAY create a lease for the buckets. Details of the buckets to create are encoded in the request body. For example:: {"share-numbers": [1, 7, ...], "allocated-size": 12345} -The request must include ``X-Tahoe-Authorization`` HTTP headers that set the various secrets—upload, lease renewal, lease cancellation—that will be later used to authorize various operations. +The server SHOULD accept a value for **allocated-size** that is less than or equal to the value for the server's version message's **maximum-immutable-share-size** value. + +The request MUST include ``X-Tahoe-Authorization`` HTTP headers that set the various secrets—upload, lease renewal, lease cancellation—that will be later used to authorize various operations. For example:: X-Tahoe-Authorization: lease-renew-secret X-Tahoe-Authorization: lease-cancel-secret X-Tahoe-Authorization: upload-secret -The response body includes encoded information about the created buckets. +The response body MUST include encoded information about the created buckets. For example:: {"already-have": [1, ...], "allocated": [7, ...]} @@ -589,26 +592,28 @@ Rejected designs for upload secrets: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Write data for the indicated share. -The share number must belong to the storage index. -The request body is the raw share data (i.e., ``application/octet-stream``). -*Content-Range* requests are required; for large transfers this allows partially complete uploads to be resumed. +The share number MUST belong to the storage index. +The request body MUST be the raw share data (i.e., ``application/octet-stream``). +The request MUST include a *Content-Range* header field; +for large transfers this allows partially complete uploads to be resumed. + For example, a 1MiB share can be divided in to eight separate 128KiB chunks. Each chunk can be uploaded in a separate request. Each request can include a *Content-Range* value indicating its placement within the complete share. If any one of these requests fails then at most 128KiB of upload work needs to be retried. -The server must recognize when all of the data has been received and mark the share as complete +The server MUST recognize when all of the data has been received and mark the share as complete (which it can do because it was informed of the size when the storage index was initialized). -The request must include a ``X-Tahoe-Authorization`` header that includes the upload secret:: +The request MUST include a ``X-Tahoe-Authorization`` header that includes the upload secret:: X-Tahoe-Authorization: upload-secret Responses: -* When a chunk that does not complete the share is successfully uploaded the response is ``OK``. - The response body indicates the range of share data that has yet to be uploaded. +* When a chunk that does not complete the share is successfully uploaded the response MUST be ``OK``. + The response body MUST indicate the range of share data that has yet to be uploaded. That is:: { "required": @@ -620,11 +625,12 @@ Responses: ] } -* When the chunk that completes the share is successfully uploaded the response is ``CREATED``. +* When the chunk that completes the share is successfully uploaded the response MUST be ``CREATED``. * If the *Content-Range* for a request covers part of the share that has already, and the data does not match already written data, - the response is ``CONFLICT``. - At this point the only thing to do is abort the upload and start from scratch (see below). + the response MUST be ``CONFLICT``. + In this case the client MUST abort the upload. + The client MAY then restart the upload from scratch. Discussion `````````` @@ -650,34 +656,32 @@ From RFC 7231:: This cancels an *in-progress* upload. -The request must include a ``X-Tahoe-Authorization`` header that includes the upload secret:: +The request MUST include a ``X-Tahoe-Authorization`` header that includes the upload secret:: X-Tahoe-Authorization: upload-secret -The response code: - -* When the upload is still in progress and therefore the abort has succeeded, - the response is ``OK``. - Future uploads can start from scratch with no pre-existing upload state stored on the server. -* If the uploaded has already finished, the response is 405 (Method Not Allowed) - and no change is made. +If there is an incomplete upload with a matching upload-secret then the server MUST consider the abort to have succeeded. +In this case the response MUST be ``OK``. +The server MUST respond to all future requests as if the operations related to this upload did not take place. +If there is no incomplete upload with a matching upload-secret then the server MUST respond with ``Method Not Allowed`` (405). +The server MUST make no client-visible changes to its state in this case. ``POST /storage/v1/immutable/:storage_index/:share_number/corrupt`` !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -Advise the server the data read from the indicated share was corrupt. The -request body includes an human-meaningful text string with details about the -corruption. It also includes potentially important details about the share. +Advise the server the data read from the indicated share was corrupt. +The request body includes an human-meaningful text string with details about the corruption. +It also includes potentially important details about the share. For example:: {"reason": "expected hash abcd, got hash efgh"} -.. share-type, storage-index, and share-number are inferred from the URL - -The response code is OK (200) by default, or NOT FOUND (404) if the share -couldn't be found. +The report pertains to the immutable share with a **storage index** and **share number** given in the request path. +If the identified **storage index** and **share number** are known to the server then the response SHOULD be accepted and made available to server administrators. +In this case the response SHOULD be ``OK``. +If the response is not accepted then the response SHOULD be ``Not Found`` (404). Reading ~~~~~~~ @@ -685,26 +689,34 @@ Reading ``GET /storage/v1/immutable/:storage_index/shares`` !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -Retrieve a list (semantically, a set) indicating all shares available for the -indicated storage index. For example:: +Retrieve a list (semantically, a set) indicating all shares available for the indicated storage index. +For example:: [1, 5] -An unknown storage index results in an empty list. +If the **storage index** in the request path is not known to the server then the response MUST include an empty list. ``GET /storage/v1/immutable/:storage_index/:share_number`` !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Read a contiguous sequence of bytes from one share in one bucket. -The response body is the raw share data (i.e., ``application/octet-stream``). -The ``Range`` header may be used to request exactly one ``bytes`` range, in which case the response code will be 206 (partial content). -Interpretation and response behavior is as specified in RFC 7233 § 4.1. -Multiple ranges in a single request are *not* supported; open-ended ranges are also not supported. +The response body MUST be the raw share data (i.e., ``application/octet-stream``). +The ``Range`` header MAY be used to request exactly one ``bytes`` range, +in which case the response code MUST be ``Partial Content`` (206). +Interpretation and response behavior MUST be as specified in RFC 7233 § 4.1. +Multiple ranges in a single request are *not* supported; +open-ended ranges are also not supported. +Clients MUST NOT send requests using these features. -If the response reads beyond the end of the data, the response may be shorter than the requested range. -The resulting ``Content-Range`` header will be consistent with the returned data. +If the response reads beyond the end of the data, +the response MUST be shorter than the requested range. +It MUST contain all data in the share and then end. +The resulting ``Content-Range`` header MUST be consistent with the returned data. -If the response to a query is an empty range, the ``NO CONTENT`` (204) response code will be used. +The server MUST indicate this behavior by specifying **True** for **tolerates-immutable-read-overrun** in its version response. + +If the response to a query is an empty range, +the server MUST send a ``No Content`` (204) response. Discussion `````````` @@ -743,13 +755,13 @@ The first write operation on a mutable storage index creates it (that is, there is no separate "create this storage index" operation as there is for the immutable storage index type). -The request must include ``X-Tahoe-Authorization`` headers with write enabler and lease secrets:: +The request MUST include ``X-Tahoe-Authorization`` headers with write enabler and lease secrets:: X-Tahoe-Authorization: write-enabler X-Tahoe-Authorization: lease-cancel-secret X-Tahoe-Authorization: lease-renew-secret -The request body includes test, read, and write vectors for the operation. +The request body MUST include test, read, and write vectors for the operation. For example:: { From 98a3691891bfbe4af2871a58fba0e586400e90cf Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 25 Jan 2023 09:55:40 -0500 Subject: [PATCH 03/18] Add more CDDL to the spec; remove some server version flags from it --- docs/proposed/http-storage-node-protocol.rst | 95 ++++++++++++++++---- src/allmydata/storage/http_client.py | 16 +++- src/allmydata/storage/http_server.py | 21 ++++- 3 files changed, 109 insertions(+), 23 deletions(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index cff6dc67b..838f88426 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -444,10 +444,6 @@ The response MUST validate against this CDDL schema:: 'maximum-immutable-share-size' => uint 'maximum-mutable-share-size' => uint 'available-space' => uint - 'tolerates-immutable-read-overrun' => bool - 'delete-mutable-shares-with-zero-length-writev' => bool - 'fills-holes-with-zero-bytes' => bool - 'prevents-read-past-end-of-share-data' => bool } 'application-version' => bstr } @@ -522,11 +518,18 @@ Initialize an immutable storage index with some buckets. The server MUST allow share data to be written to the buckets at most one time. The server MAY create a lease for the buckets. Details of the buckets to create are encoded in the request body. +The request body MUST validate against this CDDL schema:: + + { + share-numbers: #6.258([0*256 uint]) + allocated-size: uint + } + For example:: {"share-numbers": [1, 7, ...], "allocated-size": 12345} -The server SHOULD accept a value for **allocated-size** that is less than or equal to the value for the server's version message's **maximum-immutable-share-size** value. +The server SHOULD accept a value for **allocated-size** that is less than or equal to the lesser of the values of the server's version message's **maximum-immutable-share-size** or **available-space** values. The request MUST include ``X-Tahoe-Authorization`` HTTP headers that set the various secrets—upload, lease renewal, lease cancellation—that will be later used to authorize various operations. For example:: @@ -536,6 +539,13 @@ For example:: X-Tahoe-Authorization: upload-secret The response body MUST include encoded information about the created buckets. +The response body MUST validate against this CDDL schema:: + + { + already-have: #6.258([0*256 uint]) + allocated: #6.258([0*256 uint]) + } + For example:: {"already-have": [1, ...], "allocated": [7, ...]} @@ -614,7 +624,13 @@ Responses: * When a chunk that does not complete the share is successfully uploaded the response MUST be ``OK``. The response body MUST indicate the range of share data that has yet to be uploaded. - That is:: + The response body MUST validate against this CDDL schema:: + + { + required: [0* {begin: uint, end: uint}] + } + + For example:: { "required": [ { "begin": @@ -673,6 +689,11 @@ The server MUST make no client-visible changes to its state in this case. Advise the server the data read from the indicated share was corrupt. The request body includes an human-meaningful text string with details about the corruption. It also includes potentially important details about the share. +The request body MUST validate against this CDDL schema:: + + { + reason: tstr + } For example:: @@ -690,6 +711,10 @@ Reading !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Retrieve a list (semantically, a set) indicating all shares available for the indicated storage index. +The response body MUST validate against this CDDL schema:: + + #6.258([0*256 uint]) + For example:: [1, 5] @@ -710,11 +735,9 @@ Clients MUST NOT send requests using these features. If the response reads beyond the end of the data, the response MUST be shorter than the requested range. -It MUST contain all data in the share and then end. +It MUST contain all data up to the end of the share and then end. The resulting ``Content-Range`` header MUST be consistent with the returned data. -The server MUST indicate this behavior by specifying **True** for **tolerates-immutable-read-overrun** in its version response. - If the response to a query is an empty range, the server MUST send a ``No Content`` (204) response. @@ -762,6 +785,20 @@ The request MUST include ``X-Tahoe-Authorization`` headers with write enabler an X-Tahoe-Authorization: lease-renew-secret The request body MUST include test, read, and write vectors for the operation. +The request body MUST validate against this CDDL schema:: + + { + "test-write-vectors": { + 0*256 share_number : { + "test": [0*30 {"offset": uint, "size": uint, "specimen": bstr}] + "write": [* {"offset": uint, "data": bstr}] + "new-length": uint / null + } + } + "read-vector": [0*30 {"offset": uint, "size": uint}] + } + share_number = uint + For example:: { @@ -784,6 +821,14 @@ For example:: The response body contains a boolean indicating whether the tests all succeed (and writes were applied) and a mapping giving read data (pre-write). +The response body MUST validate against this CDDL schema:: + + { + "success": bool, + "data": {0*256 share_number: [0* bstr]} + } + share_number = uint + For example:: { @@ -795,7 +840,7 @@ For example:: } } -A test vector or read vector that read beyond the boundaries of existing data will return nothing for any bytes past the end. +A server MUST return nothing for any bytes beyond the end of existing data for a test vector or read vector that reads tries to read such data. As a result, if there is no data at all, an empty bytestring is returned no matter what the offset or length. Reading @@ -805,23 +850,34 @@ Reading !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Retrieve a set indicating all shares available for the indicated storage index. -For example (this is shown as list, since it will be list for JSON, but will be set for CBOR):: +The response body MUST validate against this CDDL schema:: + + #6.258([0*256 uint]) + +For example:: [1, 5] ``GET /storage/v1/mutable/:storage_index/:share_number`` !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -Read data from the indicated mutable shares, just like ``GET /storage/v1/immutable/:storage_index`` +Read data from the indicated mutable shares, just like ``GET /storage/v1/immutable/:storage_index``. -The ``Range`` header may be used to request exactly one ``bytes`` range, in which case the response code will be 206 (partial content). -Interpretation and response behavior is as specified in RFC 7233 § 4.1. -Multiple ranges in a single request are *not* supported; open-ended ranges are also not supported. +The response body MUST be the raw share data (i.e., ``application/octet-stream``). +The ``Range`` header MAY be used to request exactly one ``bytes`` range, +in which case the response code MUST be ``Partial Content`` (206). +Interpretation and response behavior MUST be specified in RFC 7233 § 4.1. +Multiple ranges in a single request are *not* supported; +open-ended ranges are also not supported. +Clients MUST NOT send requests using these features. -If the response reads beyond the end of the data, the response may be shorter than the requested range. -The resulting ``Content-Range`` header will be consistent with the returned data. +If the response reads beyond the end of the data, +the response MUST be shorter than the requested range. +It MUST contain all data up to the end of the share and then end. +The resulting ``Content-Range`` header MUST be consistent with the returned data. -If the response to a query is an empty range, the ``NO CONTENT`` (204) response code will be used. +If the response to a query is an empty range, +the server MUST send a ``No Content`` (204) response. ``POST /storage/v1/mutable/:storage_index/:share_number/corrupt`` @@ -833,6 +889,9 @@ Just like the immutable version. Sample Interactions ------------------- +This section contains examples of client/server interactions to help illuminate the above specification. +This section is non-normative. + Immutable Data ~~~~~~~~~~~~~~ diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index 90bda7fc0..2f4f8398e 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -70,15 +70,14 @@ class ClientException(Exception): # indicates a set. _SCHEMAS = { "get_version": Schema( + # Note that the single-quoted (`'`) string keys in this schema + # represent *byte* strings - per the CDDL specification. Text strings + # are represented using strings with *double* quotes (`"`). """ response = {'http://allmydata.org/tahoe/protocols/storage/v1' => { 'maximum-immutable-share-size' => uint 'maximum-mutable-share-size' => uint 'available-space' => uint - 'tolerates-immutable-read-overrun' => bool - 'delete-mutable-shares-with-zero-length-writev' => bool - 'fills-holes-with-zero-bytes' => bool - 'prevents-read-past-end-of-share-data' => bool } 'application-version' => bstr } @@ -447,6 +446,15 @@ class StorageClientGeneral(object): decoded_response = yield self._client.decode_cbor( response, _SCHEMAS["get_version"] ) + # Add some features we know are true because the HTTP API + # specification requires them and because other parts of the storage + # client implementation assumes they will be present. + decoded_response[b"http://allmydata.org/tahoe/protocols/storage/v1"].update({ + b'tolerates-immutable-read-overrun': True, + b'delete-mutable-shares-with-zero-length-writev': True, + b'fills-holes-with-zero-bytes': True, + b'prevents-read-past-end-of-share-data': True, + }) returnValue(decoded_response) @inlineCallbacks diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index fd7fd1187..b7ca2d971 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -592,7 +592,26 @@ class HTTPServer(object): @_authorized_route(_app, set(), "/storage/v1/version", methods=["GET"]) def version(self, request, authorization): """Return version information.""" - return self._send_encoded(request, self._storage_server.get_version()) + return self._send_encoded(request, self._get_version()) + + def _get_version(self) -> dict[str, Any]: + """ + Get the HTTP version of the storage server's version response. + + This differs from the Foolscap version by omitting certain obsolete + fields. + """ + v = self._storage_server.get_version() + v1_identifier = b"http://allmydata.org/tahoe/protocols/storage/v1" + v1 = v[v1_identifier] + return { + v1_identifier: { + b"maximum-immutable-share-size": v1[b"maximum-immutable-share-size"], + b"maximum-mutable-share-size": v1[b"maximum-mutable-share-size"], + b"available-space": v1[b"available-space"], + }, + b"application-version": v[b"application-version"], + } ##### Immutable APIs ##### From 48a2d4d31d86bc39c2e1caa06f6e9c3e6baac741 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Fri, 17 Feb 2023 13:58:58 -0500 Subject: [PATCH 04/18] ``Authorization`` is the right header field --- src/allmydata/storage/http_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/storage/http_common.py b/src/allmydata/storage/http_common.py index 123ce403b..e5f07898e 100644 --- a/src/allmydata/storage/http_common.py +++ b/src/allmydata/storage/http_common.py @@ -28,7 +28,7 @@ def get_content_type(headers: Headers) -> Optional[str]: def swissnum_auth_header(swissnum: bytes) -> bytes: - """Return value for ``Authentication`` header.""" + """Return value for ``Authorization`` header.""" return b"Tahoe-LAFS " + b64encode(swissnum).strip() From 8645462f4e1b44507e9dcde63c05ff3ef9f30453 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Fri, 17 Feb 2023 14:00:03 -0500 Subject: [PATCH 05/18] Base64 encode the swissnum Typically swissnums themselves are base32 encoded but there's no requirement that this is the case. Base64 encoding in the header ensures we can represent whatever the value was. --- docs/proposed/http-storage-node-protocol.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index 838f88426..7f678d271 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -391,7 +391,7 @@ Clients and servers MUST use the ``Authorization`` header field, as specified in `RFC 9110`_, for authorization of all requests to all endpoints specified here. The authentication *type* MUST be ``Tahoe-LAFS``. -Clients MUST present the swissnum from the NURL used to locate the storage service as the *credentials*. +Clients MUST present the `Base64`_-encoded representation of the swissnum from the NURL used to locate the storage service as the *credentials*. If credentials are not presented or the swissnum is not associated with a storage service then the server MUST issue a ``401 UNAUTHORIZED`` response and perform no other processing of the message. From 369d26f0f8c4975c7855e43ce9033caf893c50f0 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Fri, 10 Mar 2023 11:17:09 -0500 Subject: [PATCH 06/18] There is a limit to the size of the corruption report a server must accept --- docs/proposed/http-storage-node-protocol.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index 7f678d271..4f5a53906 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -692,7 +692,7 @@ It also includes potentially important details about the share. The request body MUST validate against this CDDL schema:: { - reason: tstr + reason: tstr .size (1..32765) } For example:: @@ -704,6 +704,11 @@ If the identified **storage index** and **share number** are known to the server In this case the response SHOULD be ``OK``. If the response is not accepted then the response SHOULD be ``Not Found`` (404). +Discussion +`````````` + +The seemingly odd length limit on ``reason`` is chosen so that the *encoded* representation of the message is limited to 32768. + Reading ~~~~~~~ From b27946c3c6b590667ee54f43f61bc72e57780d6d Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Fri, 10 Mar 2023 11:17:23 -0500 Subject: [PATCH 07/18] trim overlong section marker --- docs/proposed/http-storage-node-protocol.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index 4f5a53906..6e5b85716 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -864,7 +864,7 @@ For example:: [1, 5] ``GET /storage/v1/mutable/:storage_index/:share_number`` -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Read data from the indicated mutable shares, just like ``GET /storage/v1/immutable/:storage_index``. From c3afab15ed43a729a8517e7ded6a6877b3c765f0 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Mar 2023 09:22:42 -0400 Subject: [PATCH 08/18] correct version type annotation --- src/allmydata/storage/http_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index b7ca2d971..5560d3a73 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -594,7 +594,7 @@ class HTTPServer(object): """Return version information.""" return self._send_encoded(request, self._get_version()) - def _get_version(self) -> dict[str, Any]: + def _get_version(self) -> dict[bytes, Any]: """ Get the HTTP version of the storage server's version response. From 7859ba733717dbc75b98554311cf7a59733ed5f7 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Mar 2023 09:25:49 -0400 Subject: [PATCH 09/18] fix title level inconsistency --- docs/proposed/http-storage-node-protocol.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index 6e5b85716..c9bdf3013 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -194,7 +194,7 @@ An HTTP-based protocol, dubbed "Great Black Swamp" (or "GBS"), is described belo This protocol aims to satisfy the above requirements at a lower level of complexity than the current Foolscap-based protocol. Summary (Non-normative) -!!!!!!!!!!!!!!!!!!!!!!! +~~~~~~~~~~~~~~~~~~~~~~~ Communication with the storage node will take place using TLS. The TLS version and configuration will be dictated by an ongoing understanding of best practices. From 5facd06725d2f0c11e497c84e4d90e90bc37dd95 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Mar 2023 10:42:30 -0400 Subject: [PATCH 10/18] adjust markup to clarify the encoding exceptions --- docs/proposed/http-storage-node-protocol.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index c9bdf3013..f6d90526e 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -347,6 +347,8 @@ A request MAY be submitted using an alternate encoding by declaring this in the A request MAY indicate its preference for an alternate encoding in the response using the ``Accept`` header field. A request which includes no ``Accept`` header field MUST be interpreted in the same way as a request including a ``Accept: application/cbor`` header field. +Clients and servers MAY support additional request and response message body encodings. + Clients and servers SHOULD support ``application/json`` request and response message body encoding. For HTTP messages carrying binary share data, this is expected to be a particularly poor encoding. @@ -360,7 +362,11 @@ Because of the simple types used throughout and the equivalence described in `RFC 7049`_ these examples should be representative regardless of which of these two encodings is chosen. -One exception to this rule is for sets. +There are two exceptions to this rule. + +1. Sets +!!!!!!! + For CBOR messages, any sequence that is semantically a set (i.e. no repeated values allowed, order doesn't matter, and elements are hashable in Python) should be sent as a set. Tag 6.258 is used to indicate sets in CBOR; @@ -368,12 +374,12 @@ see `the CBOR registry Date: Mon, 13 Mar 2023 10:44:09 -0400 Subject: [PATCH 11/18] nail it down --- docs/proposed/http-storage-node-protocol.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index f6d90526e..f9f2cd868 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -436,6 +436,7 @@ Encoding ~~~~~~~~ * ``storage_index`` MUST be `Base32`_ encoded in URLs. +* ``share_number`` MUST be a decimal representation General ~~~~~~~ From 6dc6d6f39f35fe6f51002b46b90727381b142e04 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Mar 2023 11:06:16 -0400 Subject: [PATCH 12/18] inline the actual base32 alphabet we use --- docs/proposed/http-storage-node-protocol.rst | 100 ++++++++++++++++++- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index f9f2cd868..21e27d7dd 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -244,9 +244,9 @@ When Bob's client issues HTTP requests to Alice's storage node it includes the * .. note:: Foolscap TubIDs are 20 bytes (SHA1 digest of the certificate). - They are encoded with Base32 for a length of 32 bytes. + They are encoded with `Base32_` for a length of 32 bytes. SPKI information discussed here is 32 bytes (SHA256 digest). - They would be encoded in Base32 for a length of 52 bytes. + They would be encoded in `Base32`_ for a length of 52 bytes. `unpadded base64url`_ provides a more compact encoding of the information while remaining URL-compatible. This would encode the SPKI information for a length of merely 43 bytes. SHA1, @@ -336,6 +336,100 @@ and shares. A particular resource is addressed by the HTTP request path. Details about the interface are encoded in the HTTP message body. +String Encoding +~~~~~~~~~~~~~~~ + +.. _Base32: + +Where the specification refers to Base32 the meaning is *unpadded* Base32 encoding as specified by `RFC 4648`_ using a *lowercase variation* of the alphabet from Section 6. + +That is, the alphabet is: + +.. list-table:: Base32 Alphabet + :header-rows: 1 + + * - Value + - Encoding + - Value + - Encoding + - Value + - Encoding + - Value + - Encoding + + * - 0 + - a + - 9 + - j + - 18 + - s + - 27 + - 3 + * - 1 + - b + - 10 + - k + - 19 + - t + - 28 + - 4 + * - 2 + - c + - 11 + - l + - 20 + - u + - 29 + - 5 + * - 3 + - d + - 12 + - m + - 21 + - v + - 30 + - 6 + * - 4 + - e + - 13 + - n + - 22 + - w + - 31 + - 7 + * - 5 + - f + - 14 + - o + - 23 + - x + - + - + * - 6 + - g + - 15 + - p + - 24 + - y + - + - + * - 7 + - h + - 16 + - q + - 25 + - z + - + - + * - 8 + - i + - 17 + - r + - 26 + - 2 + - + - + Message Encoding ~~~~~~~~~~~~~~~~ @@ -1058,8 +1152,6 @@ otherwise it will read a byte which won't match `b""`:: .. _Base64: https://www.rfc-editor.org/rfc/rfc4648#section-4 -.. _Base32: https://www.rfc-editor.org/rfc/rfc4648#section-6 - .. _RFC 4648: https://tools.ietf.org/html/rfc4648 .. _RFC 7469: https://tools.ietf.org/html/rfc7469#section-2.4 From 6771ca8ce4caf34cceacd806c8c7c45eb80af315 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Mar 2023 13:29:53 -0400 Subject: [PATCH 13/18] fix table markup --- docs/proposed/http-storage-node-protocol.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index 21e27d7dd..ebe39578c 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -346,7 +346,7 @@ Where the specification refers to Base32 the meaning is *unpadded* Base32 encodi That is, the alphabet is: .. list-table:: Base32 Alphabet - :header-rows: 1 + :header-rows: 1 * - Value - Encoding From fe0e159e52712c14557e0c188798f8286e33ca65 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Mar 2023 13:30:32 -0400 Subject: [PATCH 14/18] Give base32 a section heading We don't have any other sections but ... :shrug: --- docs/proposed/http-storage-node-protocol.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index ebe39578c..f81b2bc79 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -341,6 +341,9 @@ String Encoding .. _Base32: +Base32 +!!!!!! + Where the specification refers to Base32 the meaning is *unpadded* Base32 encoding as specified by `RFC 4648`_ using a *lowercase variation* of the alphabet from Section 6. That is, the alphabet is: From 6a0a895ee88e34da3c798acc19c5800af3fda414 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Mar 2023 13:37:01 -0400 Subject: [PATCH 15/18] Encode the reason limit in the implementation as well --- src/allmydata/storage/http_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index 5560d3a73..3ae16ae5c 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -273,7 +273,7 @@ _SCHEMAS = { "advise_corrupt_share": Schema( """ request = { - reason: tstr + reason: tstr .size (1..32765) } """ ), From 44f5057ed39cba4f853ad3aaf862244323b29858 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 22 Mar 2023 08:07:59 -0400 Subject: [PATCH 16/18] fix link markup --- docs/proposed/http-storage-node-protocol.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index f81b2bc79..493cf8f58 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -244,7 +244,7 @@ When Bob's client issues HTTP requests to Alice's storage node it includes the * .. note:: Foolscap TubIDs are 20 bytes (SHA1 digest of the certificate). - They are encoded with `Base32_` for a length of 32 bytes. + They are encoded with `Base32`_ for a length of 32 bytes. SPKI information discussed here is 32 bytes (SHA256 digest). They would be encoded in `Base32`_ for a length of 52 bytes. `unpadded base64url`_ provides a more compact encoding of the information while remaining URL-compatible. From 7c0b21916f376f139e2569242e443fea60c40723 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 22 Mar 2023 08:35:17 -0400 Subject: [PATCH 17/18] specify the unit of `available-space` --- docs/proposed/http-storage-node-protocol.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index 493cf8f58..3e74c94d6 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -560,6 +560,7 @@ For fields that are more general than a single API the semantics are as follows: * available-space: The server SHOULD use this field to advertise the amount of space that it currently considers unused and is willing to allocate for client requests. + The value is a number of bytes. ``PUT /storage/v1/lease/:storage_index`` From e7ed17af17c7c77daa24203954ff2ef2198875c6 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Wed, 22 Mar 2023 08:42:32 -0400 Subject: [PATCH 18/18] fix some editing errors about overreads and generally try to clarify --- docs/proposed/http-storage-node-protocol.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index 3e74c94d6..5009a992e 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -950,8 +950,17 @@ For example:: } } -A server MUST return nothing for any bytes beyond the end of existing data for a test vector or read vector that reads tries to read such data. -As a result, if there is no data at all, an empty bytestring is returned no matter what the offset or length. +A client MAY send a test vector or read vector to bytes beyond the end of existing data. +In this case a server MUST behave as if the test or read vector referred to exactly as much data exists. + +For example, +consider the case where the server has 5 bytes of data for a particular share. +If a client sends a read vector with an ``offset`` of 1 and a ``size`` of 4 then the server MUST respond with all of the data except the first byte. +If a client sends a read vector with the same ``offset`` and a ``size`` of 5 (or any larger value) then the server MUST respond in the same way. + +Similarly, +if there is no data at all, +an empty byte string is returned no matter what the offset or length. Reading ~~~~~~~