diff --git a/docs/proposed/http-storage-node-protocol.rst b/docs/proposed/http-storage-node-protocol.rst index a8555cd26..26f1a2bb7 100644 --- a/docs/proposed/http-storage-node-protocol.rst +++ b/docs/proposed/http-storage-node-protocol.rst @@ -382,6 +382,11 @@ the server will respond with ``400 BAD REQUEST``. If authorization using the secret fails, then a ``401 UNAUTHORIZED`` response should be sent. +Encoding +~~~~~~~~ + +* ``storage_index`` should be base32 encoded (RFC3548) in URLs. + General ~~~~~~~ diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index e38525583..5e964bfbe 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -34,6 +34,12 @@ from hyperlink import DecodedURL import treq from .http_common import swissnum_auth_header, Secrets +from .common import si_b2a + + +def _encode_si(si): # type: (bytes) -> str + """Encode the storage index into Unicode string.""" + return str(si_b2a(si), "ascii") class ClientException(Exception): @@ -151,7 +157,7 @@ class StorageClientImmutables(object): Result fires when creating the storage index succeeded, if creating the storage index failed the result will fire with an exception. """ - url = self._client._url("/v1/immutable/" + str(storage_index, "ascii")) + url = self._client._url("/v1/immutable/" + _encode_si(storage_index)) message = dumps( {"share-numbers": share_numbers, "allocated-size": allocated_size} ) @@ -189,7 +195,7 @@ class StorageClientImmutables(object): been uploaded. """ url = self._client._url( - "/v1/immutable/{}/{}".format(str(storage_index, "ascii"), share_number) + "/v1/immutable/{}/{}".format(_encode_si(storage_index), share_number) ) response = yield self._client._request( "POST", @@ -238,7 +244,7 @@ class StorageClientImmutables(object): https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3777 """ url = self._client._url( - "/v1/immutable/{}/{}".format(str(storage_index, "ascii"), share_number) + "/v1/immutable/{}/{}".format(_encode_si(storage_index), share_number) ) response = yield self._client._request( "GET", diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index b371fc395..23f0d2f1c 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -28,6 +28,7 @@ from cbor2 import dumps, loads from .server import StorageServer from .http_common import swissnum_auth_header, Secrets +from .common import si_a2b from .immutable import BucketWriter from ..util.hashutil import timing_safe_compare @@ -174,6 +175,7 @@ class HTTPServer(object): ) def allocate_buckets(self, request, authorization, storage_index): """Allocate buckets.""" + storage_index = si_a2b(storage_index.encode("ascii")) info = loads(request.content.read()) upload_key = authorization[Secrets.UPLOAD] @@ -191,13 +193,29 @@ class HTTPServer(object): pass else: # New upload. - # TODO self._storage_server.allocate_buckets() with given inputs. - # TODO add results to self._uploads. - pass + already_got, sharenum_to_bucket = self._storage_server.allocate_buckets( + storage_index, + renew_secret=authorization[Secrets.LEASE_RENEW], + cancel_secret=authorization[Secrets.LEASE_CANCEL], + sharenums=info["share-numbers"], + allocated_size=info["allocated-size"], + ) + self._uploads[storage_index] = StorageIndexUploads( + shares=sharenum_to_bucket, upload_key=authorization[Secrets.UPLOAD] + ) + return self._cbor( + request, + { + "already-have": set(already_got), + "allocated": set(sharenum_to_bucket), + }, + ) @_authorized_route( _app, - {Secrets.UPLOAD}, "/v1/immutable//", methods=["PATCH"] + {Secrets.UPLOAD}, + "/v1/immutable//", + methods=["PATCH"], ) def write_share_data(self, request, authorization, storage_index, share_number): """Write data to an in-progress immutable upload.""" @@ -212,7 +230,10 @@ class HTTPServer(object): # TODO if it finished writing altogether, 201 CREATED. Otherwise 200 OK. @_authorized_route( - _app, set(), "/v1/immutable//", methods=["GET"] + _app, + set(), + "/v1/immutable//", + methods=["GET"], ) def read_share_chunk(self, request, authorization, storage_index, share_number): """Read a chunk for an already uploaded immutable.""" @@ -221,4 +242,3 @@ class HTTPServer(object): # TODO lookup the share # TODO if not found, 404 # TODO otherwise, return data from that offset -