Commit 307b154f authored by acud's avatar acud Committed by GitHub

api: split chunk and soc api (#1143)

parent 42c12ede
...@@ -137,9 +137,8 @@ func NewApiStore(host string, port int, ssl bool) PutGetter { ...@@ -137,9 +137,8 @@ func NewApiStore(host string, port int, ssl bool) PutGetter {
// Put implements storage.Putter. // Put implements storage.Putter.
func (a *ApiStore) Put(ctx context.Context, mode storage.ModePut, chs ...swarm.Chunk) (exist []bool, err error) { func (a *ApiStore) Put(ctx context.Context, mode storage.ModePut, chs ...swarm.Chunk) (exist []bool, err error) {
for _, ch := range chs { for _, ch := range chs {
addr := ch.Address().String()
buf := bytes.NewReader(ch.Data()) buf := bytes.NewReader(ch.Data())
url := strings.Join([]string{a.baseUrl, addr}, "/") url := strings.Join([]string{a.baseUrl}, "/")
res, err := a.Client.Post(url, "application/octet-stream", buf) res, err := a.Client.Post(url, "application/octet-stream", buf)
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -6,6 +6,7 @@ package api_test ...@@ -6,6 +6,7 @@ package api_test
import ( import (
"errors" "errors"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
...@@ -88,6 +89,23 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket. ...@@ -88,6 +89,23 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.
return httpClient, conn, ts.Listener.Addr().String() return httpClient, conn, ts.Listener.Addr().String()
} }
func request(t *testing.T, client *http.Client, method, resource string, body io.Reader, responseCode int) *http.Response {
t.Helper()
req, err := http.NewRequest(method, resource, body)
if err != nil {
t.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != responseCode {
t.Fatalf("got response status %s, want %v %s", resp.Status, responseCode, http.StatusText(responseCode))
}
return resp
}
func TestParseName(t *testing.T) { func TestParseName(t *testing.T) {
const bzzHash = "89c17d0d8018a19057314aa035e61c9d23c47581a61dd3a79a7839692c617e4d" const bzzHash = "89c17d0d8018a19057314aa035e61c9d23c47581a61dd3a79a7839692c617e4d"
......
...@@ -12,9 +12,8 @@ import ( ...@@ -12,9 +12,8 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"github.com/ethersphere/bee/pkg/content" "github.com/ethersphere/bee/pkg/bmtpool"
"github.com/ethersphere/bee/pkg/netstore" "github.com/ethersphere/bee/pkg/netstore"
"github.com/ethersphere/bee/pkg/soc"
"github.com/ethersphere/bee/pkg/jsonhttp" "github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/sctx" "github.com/ethersphere/bee/pkg/sctx"
...@@ -24,20 +23,15 @@ import ( ...@@ -24,20 +23,15 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
func (s *server) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { type chunkAddressResponse struct {
nameOrHex := mux.Vars(r)["addr"] Reference swarm.Address `json:"reference"`
}
address, err := s.resolveNameOrAddress(nameOrHex)
if err != nil {
s.Logger.Debugf("chunk upload: parse chunk address %s: %v", nameOrHex, err)
s.Logger.Error("chunk upload: parse chunk address")
jsonhttp.BadRequest(w, "invalid chunk address")
return
}
func (s *server) chunkUploadHandler(w http.ResponseWriter, r *http.Request) {
var ( var (
tag *tags.Tag tag *tags.Tag
ctx = r.Context() ctx = r.Context()
err error
) )
if h := r.Header.Get(SwarmTagUidHeader); h != "" { if h := r.Header.Get(SwarmTagUidHeader); h != "" {
...@@ -68,22 +62,38 @@ func (s *server) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { ...@@ -68,22 +62,38 @@ func (s *server) chunkUploadHandler(w http.ResponseWriter, r *http.Request) {
if jsonhttp.HandleBodyReadError(err, w) { if jsonhttp.HandleBodyReadError(err, w) {
return return
} }
s.Logger.Debugf("chunk upload: read chunk data error: %v, addr %s", err, address) s.Logger.Debugf("chunk upload: read chunk data error: %v", err)
s.Logger.Error("chunk upload: read chunk data error") s.Logger.Error("chunk upload: read chunk data error")
jsonhttp.InternalServerError(w, "cannot read chunk data") jsonhttp.InternalServerError(w, "cannot read chunk data")
return return
} }
chunk := swarm.NewChunk(address, data) if len(data) < swarm.SpanSize {
if !content.Valid(chunk) { s.Logger.Debug("chunk upload: not enough data")
if !soc.Valid(chunk) { s.Logger.Error("chunk upload: data length")
s.Logger.Debugf("chunk upload: invalid chunk: %s", address) jsonhttp.BadRequest(w, "data length")
s.Logger.Error("chunk upload: invalid chunk") return
jsonhttp.BadRequest(w, nil) }
return
} hasher := bmtpool.Get()
defer bmtpool.Put(hasher)
err = hasher.SetSpanBytes(data[:swarm.SpanSize])
if err != nil {
s.Logger.Debugf("chunk upload: set span: %v", err)
s.Logger.Error("chunk upload: span error")
jsonhttp.InternalServerError(w, "span error")
return
} }
_, err = hasher.Write(data[swarm.SpanSize:])
if err != nil {
return
}
address := swarm.NewAddress(hasher.Sum(nil))
chunk := swarm.NewChunk(address, data)
seen, err := s.Storer.Put(ctx, requestModePut(r), chunk) seen, err := s.Storer.Put(ctx, requestModePut(r), chunk)
if err != nil { if err != nil {
s.Logger.Debugf("chunk upload: chunk write error: %v, addr %s", err, address) s.Logger.Debugf("chunk upload: chunk write error: %v, addr %s", err, address)
...@@ -113,7 +123,7 @@ func (s *server) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { ...@@ -113,7 +123,7 @@ func (s *server) chunkUploadHandler(w http.ResponseWriter, r *http.Request) {
} }
w.Header().Set("Access-Control-Expose-Headers", SwarmTagUidHeader) w.Header().Set("Access-Control-Expose-Headers", SwarmTagUidHeader)
jsonhttp.OK(w, nil) jsonhttp.OK(w, chunkAddressResponse{Reference: address})
} }
func (s *server) chunkGetHandler(w http.ResponseWriter, r *http.Request) { func (s *server) chunkGetHandler(w http.ResponseWriter, r *http.Request) {
......
...@@ -6,7 +6,6 @@ package api_test ...@@ -6,7 +6,6 @@ package api_test
import ( import (
"bytes" "bytes"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"testing" "testing"
...@@ -31,9 +30,9 @@ func TestChunkUploadDownload(t *testing.T) { ...@@ -31,9 +30,9 @@ func TestChunkUploadDownload(t *testing.T) {
var ( var (
targets = "0x222" targets = "0x222"
resource = func(addr swarm.Address) string { return "/chunks/" + addr.String() } chunksEndpoint = "/chunks"
chunksResource = func(a swarm.Address) string { return "/chunks/" + a.String() }
resourceTargets = func(addr swarm.Address) string { return "/chunks/" + addr.String() + "?targets=" + targets } resourceTargets = func(addr swarm.Address) string { return "/chunks/" + addr.String() + "?targets=" + targets }
someHash = swarm.MustParseHexAddress("aabbcc")
chunk = testingc.GenerateTestRandomChunk() chunk = testingc.GenerateTestRandomChunk()
mockStatestore = statestore.NewStateStore() mockStatestore = statestore.NewStateStore()
logger = logging.New(ioutil.Discard, 0) logger = logging.New(ioutil.Discard, 0)
...@@ -45,42 +44,23 @@ func TestChunkUploadDownload(t *testing.T) { ...@@ -45,42 +44,23 @@ func TestChunkUploadDownload(t *testing.T) {
}) })
) )
t.Run("invalid chunk", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, resource(someHash), http.StatusBadRequest,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
Message: http.StatusText(http.StatusBadRequest),
Code: http.StatusBadRequest,
}),
)
// make sure chunk is not retrievable
_ = request(t, client, http.MethodGet, resource(someHash), nil, http.StatusNotFound)
})
t.Run("empty chunk", func(t *testing.T) { t.Run("empty chunk", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, resource(someHash), http.StatusBadRequest, jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
Message: http.StatusText(http.StatusBadRequest), Message: "data length",
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
}), }),
) )
// make sure chunk is not retrievable
_ = request(t, client, http.MethodGet, resource(someHash), nil, http.StatusNotFound)
}) })
t.Run("ok", func(t *testing.T) { t.Run("ok", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, resource(chunk.Address()), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}),
Message: http.StatusText(http.StatusOK),
Code: http.StatusOK,
}),
) )
// try to fetch the same chunk // try to fetch the same chunk
resp := request(t, client, http.MethodGet, resource(chunk.Address()), nil, http.StatusOK) resp := request(t, client, http.MethodGet, chunksResource(chunk.Address()), nil, http.StatusOK)
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -92,12 +72,9 @@ func TestChunkUploadDownload(t *testing.T) { ...@@ -92,12 +72,9 @@ func TestChunkUploadDownload(t *testing.T) {
}) })
t.Run("pin-invalid-value", func(t *testing.T) { t.Run("pin-invalid-value", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, resource(chunk.Address()), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}),
Message: http.StatusText(http.StatusOK),
Code: http.StatusOK,
}),
jsonhttptest.WithRequestHeader(api.SwarmPinHeader, "invalid-pin"), jsonhttptest.WithRequestHeader(api.SwarmPinHeader, "invalid-pin"),
) )
...@@ -107,12 +84,9 @@ func TestChunkUploadDownload(t *testing.T) { ...@@ -107,12 +84,9 @@ func TestChunkUploadDownload(t *testing.T) {
} }
}) })
t.Run("pin-header-missing", func(t *testing.T) { t.Run("pin-header-missing", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, resource(chunk.Address()), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}),
Message: http.StatusText(http.StatusOK),
Code: http.StatusOK,
}),
) )
// Also check if the chunk is NOT pinned // Also check if the chunk is NOT pinned
...@@ -121,12 +95,9 @@ func TestChunkUploadDownload(t *testing.T) { ...@@ -121,12 +95,9 @@ func TestChunkUploadDownload(t *testing.T) {
} }
}) })
t.Run("pin-ok", func(t *testing.T) { t.Run("pin-ok", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, resource(chunk.Address()), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}),
Message: http.StatusText(http.StatusOK),
Code: http.StatusOK,
}),
jsonhttptest.WithRequestHeader(api.SwarmPinHeader, "True"), jsonhttptest.WithRequestHeader(api.SwarmPinHeader, "True"),
) )
...@@ -145,20 +116,3 @@ func TestChunkUploadDownload(t *testing.T) { ...@@ -145,20 +116,3 @@ func TestChunkUploadDownload(t *testing.T) {
} }
}) })
} }
func request(t *testing.T, client *http.Client, method, resource string, body io.Reader, responseCode int) *http.Response {
t.Helper()
req, err := http.NewRequest(method, resource, body)
if err != nil {
t.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != responseCode {
t.Fatalf("got response status %s, want %v %s", resp.Status, responseCode, http.StatusText(responseCode))
}
return resp
}
...@@ -10,6 +10,8 @@ type Server = server ...@@ -10,6 +10,8 @@ type Server = server
type ( type (
BytesPostResponse = bytesPostResponse BytesPostResponse = bytesPostResponse
ChunkAddressResponse = chunkAddressResponse
SocPostResponse = socPostResponse
FileUploadResponse = fileUploadResponse FileUploadResponse = fileUploadResponse
TagResponse = tagResponse TagResponse = tagResponse
TagRequest = tagRequest TagRequest = tagRequest
......
...@@ -65,7 +65,7 @@ func TestGatewayMode(t *testing.T) { ...@@ -65,7 +65,7 @@ func TestGatewayMode(t *testing.T) {
}) })
// should work without pinning // should work without pinning
jsonhttptest.Request(t, client, http.MethodPost, "/chunks/"+chunk.Address().String(), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, "/chunks", http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
) )
......
...@@ -18,7 +18,6 @@ import ( ...@@ -18,7 +18,6 @@ import (
"github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest" "github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/pkg/storage/mock" "github.com/ethersphere/bee/pkg/storage/mock"
testingc "github.com/ethersphere/bee/pkg/storage/testing" testingc "github.com/ethersphere/bee/pkg/storage/testing"
"github.com/ethersphere/bee/pkg/swarm"
"github.com/ethersphere/bee/pkg/tags" "github.com/ethersphere/bee/pkg/tags"
) )
...@@ -28,7 +27,7 @@ import ( ...@@ -28,7 +27,7 @@ import (
// it assumes some state of the DB before another case is run. // it assumes some state of the DB before another case is run.
func TestPinChunkHandler(t *testing.T) { func TestPinChunkHandler(t *testing.T) {
var ( var (
resource = func(addr swarm.Address) string { return "/chunks/" + addr.String() } chunksEndpoint = "/chunks"
chunk = testingc.GenerateTestRandomChunk() chunk = testingc.GenerateTestRandomChunk()
mockStorer = mock.NewStorer() mockStorer = mock.NewStorer()
mockStatestore = statestore.NewStateStore() mockStatestore = statestore.NewStateStore()
...@@ -74,12 +73,9 @@ func TestPinChunkHandler(t *testing.T) { ...@@ -74,12 +73,9 @@ func TestPinChunkHandler(t *testing.T) {
// unpin on a chunk which is not pinned // unpin on a chunk which is not pinned
t.Run("unpin-while-not-pinned", func(t *testing.T) { t.Run("unpin-while-not-pinned", func(t *testing.T) {
// Post a chunk // Post a chunk
jsonhttptest.Request(t, client, http.MethodPost, resource(chunk.Address()), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}),
Message: http.StatusText(http.StatusOK),
Code: http.StatusOK,
}),
) )
jsonhttptest.Request(t, client, http.MethodDelete, "/pin/chunks/"+chunk.Address().String(), http.StatusBadRequest, jsonhttptest.Request(t, client, http.MethodDelete, "/pin/chunks/"+chunk.Address().String(), http.StatusBadRequest,
...@@ -93,12 +89,9 @@ func TestPinChunkHandler(t *testing.T) { ...@@ -93,12 +89,9 @@ func TestPinChunkHandler(t *testing.T) {
// pin a existing chunk first time // pin a existing chunk first time
t.Run("pin-chunk-1", func(t *testing.T) { t.Run("pin-chunk-1", func(t *testing.T) {
// Post a chunk // Post a chunk
jsonhttptest.Request(t, client, http.MethodPost, resource(chunk.Address()), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}),
Message: http.StatusText(http.StatusOK),
Code: http.StatusOK,
}),
) )
jsonhttptest.Request(t, client, http.MethodPost, "/pin/chunks/"+chunk.Address().String(), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, "/pin/chunks/"+chunk.Address().String(), http.StatusOK,
...@@ -175,12 +168,9 @@ func TestPinChunkHandler(t *testing.T) { ...@@ -175,12 +168,9 @@ func TestPinChunkHandler(t *testing.T) {
// Add 2 chunks, pin it and check if they show up in the list // Add 2 chunks, pin it and check if they show up in the list
t.Run("list-chunks", func(t *testing.T) { t.Run("list-chunks", func(t *testing.T) {
// Post a chunk // Post a chunk
jsonhttptest.Request(t, client, http.MethodPost, resource(chunk.Address()), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}),
Message: http.StatusText(http.StatusOK),
Code: http.StatusOK,
}),
) )
jsonhttptest.Request(t, client, http.MethodPost, "/pin/chunks/"+chunk.Address().String(), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, "/pin/chunks/"+chunk.Address().String(), http.StatusOK,
...@@ -192,12 +182,9 @@ func TestPinChunkHandler(t *testing.T) { ...@@ -192,12 +182,9 @@ func TestPinChunkHandler(t *testing.T) {
// post another chunk // post another chunk
chunk2 := testingc.GenerateTestRandomChunk() chunk2 := testingc.GenerateTestRandomChunk()
jsonhttptest.Request(t, client, http.MethodPost, resource(chunk2.Address()), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, chunksEndpoint, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk2.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk2.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk2.Address()}),
Message: http.StatusText(http.StatusOK),
Code: http.StatusOK,
}),
) )
jsonhttptest.Request(t, client, http.MethodPost, "/pin/chunks/"+chunk2.Address().String(), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, "/pin/chunks/"+chunk2.Address().String(), http.StatusOK,
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
......
...@@ -71,11 +71,21 @@ func (s *server) setupRouting() { ...@@ -71,11 +71,21 @@ func (s *server) setupRouting() {
), ),
}) })
handle(router, "/chunks", jsonhttp.MethodHandler{
"POST": web.ChainHandlers(
jsonhttp.NewMaxBodyBytesHandler(swarm.ChunkWithSpanSize),
web.FinalHandlerFunc(s.chunkUploadHandler),
),
})
handle(router, "/chunks/{addr}", jsonhttp.MethodHandler{ handle(router, "/chunks/{addr}", jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.chunkGetHandler), "GET": http.HandlerFunc(s.chunkGetHandler),
})
handle(router, "/soc/{owner}/{id}", jsonhttp.MethodHandler{
"POST": web.ChainHandlers( "POST": web.ChainHandlers(
jsonhttp.NewMaxBodyBytesHandler(swarm.ChunkWithSpanSize), jsonhttp.NewMaxBodyBytesHandler(swarm.ChunkWithSpanSize),
web.FinalHandlerFunc(s.chunkUploadHandler), web.FinalHandlerFunc(s.socUploadHandler),
), ),
}) })
......
// Copyright 2021 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package api
import (
"encoding/hex"
"errors"
"io/ioutil"
"net/http"
"github.com/ethersphere/bee/pkg/bmtpool"
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/soc"
"github.com/ethersphere/bee/pkg/swarm"
"github.com/gorilla/mux"
)
var (
errBadRequestParams = errors.New("owner, id or span is not well formed")
)
type socPostResponse struct {
Reference swarm.Address `json:"reference"`
}
func (s *server) socUploadHandler(w http.ResponseWriter, r *http.Request) {
owner, err := hex.DecodeString(mux.Vars(r)["owner"])
if err != nil {
s.Logger.Debugf("soc upload: bad owner: %v", err)
s.Logger.Error("soc upload: %v", errBadRequestParams)
jsonhttp.BadRequest(w, "bad owner")
return
}
id, err := hex.DecodeString(mux.Vars(r)["id"])
if err != nil {
s.Logger.Debugf("soc upload: bad id: %v", err)
s.Logger.Error("soc upload: %v", errBadRequestParams)
jsonhttp.BadRequest(w, "bad id")
return
}
sigStr := r.URL.Query().Get("sig")
if sigStr == "" {
s.Logger.Debugf("soc upload: empty signature")
s.Logger.Error("soc upload: empty signature")
jsonhttp.BadRequest(w, "empty signature")
return
}
sig, err := hex.DecodeString(sigStr)
if err != nil {
s.Logger.Debugf("soc upload: bad signature: %v", err)
s.Logger.Error("soc upload: bad signature")
jsonhttp.BadRequest(w, "bad signature")
return
}
data, err := ioutil.ReadAll(r.Body)
if err != nil {
if jsonhttp.HandleBodyReadError(err, w) {
return
}
s.Logger.Debugf("soc upload: read chunk data error: %v", err)
s.Logger.Error("soc upload: read chunk data error")
jsonhttp.InternalServerError(w, "cannot read chunk data")
return
}
if len(data) < swarm.SpanSize {
s.Logger.Debugf("soc upload: chunk data too short")
s.Logger.Error("soc upload: %v", errBadRequestParams)
jsonhttp.BadRequest(w, "short chunk data")
return
}
if len(data) > swarm.ChunkSize+swarm.SpanSize {
s.Logger.Debugf("soc upload: chunk data exceeds %d bytes", swarm.ChunkSize+swarm.SpanSize)
s.Logger.Error("soc upload: chunk data error")
jsonhttp.RequestEntityTooLarge(w, "payload too large")
return
}
ch, err := chunk(data)
if err != nil {
s.Logger.Debugf("soc upload: create content addressed chunk: %v", err)
s.Logger.Error("soc upload: chunk data error")
jsonhttp.BadRequest(w, "chunk data error")
return
}
chunk, err := soc.NewSignedChunk(id, ch, owner, sig)
if err != nil {
s.Logger.Debugf("soc upload: read chunk data error: %v", err)
s.Logger.Error("soc upload: read chunk data error")
jsonhttp.InternalServerError(w, "cannot read chunk data")
return
}
if !soc.Valid(chunk) {
s.Logger.Debugf("soc upload: invalid chunk: %v", err)
s.Logger.Error("soc upload: invalid chunk")
jsonhttp.Unauthorized(w, "invalid chunk")
return
}
ctx := r.Context()
_, err = s.Storer.Put(ctx, requestModePut(r), chunk)
if err != nil {
s.Logger.Debugf("soc upload: chunk write error: %v", err)
s.Logger.Error("soc upload: chunk write error")
jsonhttp.BadRequest(w, "chunk write error")
return
}
jsonhttp.Created(w, chunkAddressResponse{Reference: chunk.Address()})
}
func chunk(data []byte) (swarm.Chunk, error) {
hasher := bmtpool.Get()
defer bmtpool.Put(hasher)
err := hasher.SetSpanBytes(data[:swarm.SpanSize])
if err != nil {
return nil, err
}
_, err = hasher.Write(data[swarm.SpanSize:])
if err != nil {
return nil, err
}
return swarm.NewChunk(swarm.NewAddress(hasher.Sum(nil)), data), nil
}
// Copyright 2021 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package api_test
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethersphere/bee/pkg/api"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/soc"
statestore "github.com/ethersphere/bee/pkg/statestore/mock"
"github.com/ethersphere/bee/pkg/storage/mock"
testingc "github.com/ethersphere/bee/pkg/storage/testing"
"github.com/ethersphere/bee/pkg/swarm"
"github.com/ethersphere/bee/pkg/tags"
)
func TestSoc(t *testing.T) {
var (
socResource = func(owner, id, sig string) string { return fmt.Sprintf("/soc/%s/%s?sig=%s", owner, id, sig) }
_ = testingc.GenerateTestRandomChunk()
mockStatestore = statestore.NewStateStore()
logger = logging.New(ioutil.Discard, 0)
tag = tags.NewTags(mockStatestore, logger)
_ = common.HexToAddress("8d3766440f0d7b949a5e32995d09619a7f86e632")
mockStorer = mock.NewStorer()
client, _, _ = newTestServer(t, testServerOptions{
Storer: mockStorer,
Tags: tag,
})
)
t.Run("cmpty data", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, socResource("8d3766440f0d7b949a5e32995d09619a7f86e632", "bb", "cc"), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
Message: "short chunk data",
Code: http.StatusBadRequest,
}),
)
})
t.Run("malformed id", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, socResource("8d3766440f0d7b949a5e32995d09619a7f86e632", "bbzz", "cc"), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
Message: "bad id",
Code: http.StatusBadRequest,
}),
)
})
t.Run("malformed owner", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, socResource("xyz", "aa", "bb"), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
Message: "bad owner",
Code: http.StatusBadRequest,
}),
)
})
t.Run("malformed signature", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, socResource("8d3766440f0d7b949a5e32995d09619a7f86e632", "aa", "badsig"), http.StatusBadRequest,
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
Message: "bad signature",
Code: http.StatusBadRequest,
}),
)
})
t.Run("signature invalid", func(t *testing.T) {
s, owner, payload := mockSoc(t)
id := make([]byte, soc.IdSize)
// modify the sign
sig := s.Signature()
sig[12] = 0x98
sig[10] = 0x12
jsonhttptest.Request(t, client, http.MethodPost, socResource(hex.EncodeToString(owner), hex.EncodeToString(id), hex.EncodeToString(sig)), http.StatusUnauthorized,
jsonhttptest.WithRequestBody(bytes.NewReader(payload)),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
Message: "invalid chunk",
Code: http.StatusUnauthorized,
}),
)
})
t.Run("ok", func(t *testing.T) {
s, owner, payload := mockSoc(t)
id := make([]byte, soc.IdSize)
sig := s.Signature()
addr, err := s.Address()
if err != nil {
t.Fatal(err)
}
jsonhttptest.Request(t, client, http.MethodPost, socResource(hex.EncodeToString(owner), hex.EncodeToString(id), hex.EncodeToString(sig)), http.StatusCreated,
jsonhttptest.WithRequestBody(bytes.NewReader(payload)),
jsonhttptest.WithExpectedJSONResponse(api.SocPostResponse{
Reference: addr,
}),
)
// try to fetch the same chunk
rsrc := fmt.Sprintf("/chunks/" + addr.String())
resp := request(t, client, http.MethodGet, rsrc, nil, http.StatusOK)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
ch, err := s.ToChunk()
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(ch.Data(), data) {
t.Fatal("data retrieved doesnt match uploaded content")
}
})
}
// returns a valid, mocked SOC
func mockSoc(t *testing.T) (*soc.Soc, []byte, []byte) {
// create a valid soc
id := make([]byte, soc.IdSize)
privKey, err := crypto.GenerateSecp256k1Key()
if err != nil {
t.Fatal(err)
}
signer := crypto.NewDefaultSigner(privKey)
bmtHashOfFoo := "2387e8e7d8a48c2a9339c97c1dc3461a9a7aa07e994c5cb8b38fd7c1b3e6ea48"
address := swarm.MustParseHexAddress(bmtHashOfFoo)
foo := "foo"
fooLength := len(foo)
fooBytes := make([]byte, 8+fooLength)
binary.LittleEndian.PutUint64(fooBytes, uint64(fooLength))
copy(fooBytes[8:], foo)
ch := swarm.NewChunk(address, fooBytes)
sch := soc.New(id, ch)
if err != nil {
t.Fatal(err)
}
err = sch.AddSigner(signer)
if err != nil {
t.Fatal(err)
}
_, _ = sch.ToChunk()
return sch, sch.OwnerAddress(), ch.Data()
}
...@@ -38,7 +38,7 @@ func TestTags(t *testing.T) { ...@@ -38,7 +38,7 @@ func TestTags(t *testing.T) {
filesResource = "/files" filesResource = "/files"
dirResource = "/dirs" dirResource = "/dirs"
bytesResource = "/bytes" bytesResource = "/bytes"
chunksResource = func(addr swarm.Address) string { return "/chunks/" + addr.String() } chunksResource = "/chunks"
tagsResource = "/tags" tagsResource = "/tags"
chunk = testingc.GenerateTestRandomChunk() chunk = testingc.GenerateTestRandomChunk()
someTagName = "file.jpg" someTagName = "file.jpg"
...@@ -78,7 +78,7 @@ func TestTags(t *testing.T) { ...@@ -78,7 +78,7 @@ func TestTags(t *testing.T) {
}) })
t.Run("create tag with invalid id", func(t *testing.T) { t.Run("create tag with invalid id", func(t *testing.T) {
jsonhttptest.Request(t, client, http.MethodPost, chunksResource(chunk.Address()), http.StatusBadRequest, jsonhttptest.Request(t, client, http.MethodPost, chunksResource, http.StatusBadRequest,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
Message: "cannot get tag", Message: "cannot get tag",
...@@ -120,20 +120,14 @@ func TestTags(t *testing.T) { ...@@ -120,20 +120,14 @@ func TestTags(t *testing.T) {
t.Fatalf("sent tag name %s does not match received tag name %s", someTagName, tr.Name) t.Fatalf("sent tag name %s does not match received tag name %s", someTagName, tr.Name)
} }
_ = jsonhttptest.Request(t, client, http.MethodPost, chunksResource(chunk.Address()), http.StatusOK, _ = jsonhttptest.Request(t, client, http.MethodPost, chunksResource, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}),
Message: http.StatusText(http.StatusOK),
Code: http.StatusOK,
}),
) )
rcvdHeaders := jsonhttptest.Request(t, client, http.MethodPost, chunksResource(chunk.Address()), http.StatusOK, rcvdHeaders := jsonhttptest.Request(t, client, http.MethodPost, chunksResource, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ jsonhttptest.WithExpectedJSONResponse(api.ChunkAddressResponse{Reference: chunk.Address()}),
Message: http.StatusText(http.StatusOK),
Code: http.StatusOK,
}),
jsonhttptest.WithRequestHeader(api.SwarmTagUidHeader, strconv.FormatUint(uint64(tr.Uid), 10)), jsonhttptest.WithRequestHeader(api.SwarmTagUidHeader, strconv.FormatUint(uint64(tr.Uid), 10)),
) )
...@@ -218,7 +212,7 @@ func TestTags(t *testing.T) { ...@@ -218,7 +212,7 @@ func TestTags(t *testing.T) {
addr := test.RandomAddress() addr := test.RandomAddress()
// upload content with tag // upload content with tag
jsonhttptest.Request(t, client, http.MethodPost, chunksResource(chunk.Address()), http.StatusOK, jsonhttptest.Request(t, client, http.MethodPost, chunksResource, http.StatusOK,
jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())), jsonhttptest.WithRequestBody(bytes.NewReader(chunk.Data())),
jsonhttptest.WithRequestHeader(api.SwarmTagUidHeader, fmt.Sprint(tagId)), jsonhttptest.WithRequestHeader(api.SwarmTagUidHeader, fmt.Sprint(tagId)),
) )
......
...@@ -58,6 +58,25 @@ func NewChunk(id Id, ch swarm.Chunk, signer crypto.Signer) (swarm.Chunk, error) ...@@ -58,6 +58,25 @@ func NewChunk(id Id, ch swarm.Chunk, signer crypto.Signer) (swarm.Chunk, error)
return s.ToChunk() return s.ToChunk()
} }
// NewChunk is a convenience function to create a single-owner chunk ready to be sent
// on the network.
func NewSignedChunk(id Id, ch swarm.Chunk, owner, sig []byte) (swarm.Chunk, error) {
s := New(id, ch)
s.signature = sig
o, err := NewOwner(owner)
if err != nil {
return nil, err
}
s.owner = o
// create chunk
socAddress, err := s.Address()
if err != nil {
return nil, err
}
return swarm.NewChunk(socAddress, s.toBytes()), nil
}
// New creates a new Soc representation from arbitrary soc id and // New creates a new Soc representation from arbitrary soc id and
// a content-addressed chunk. // a content-addressed chunk.
// //
...@@ -109,6 +128,10 @@ func (s *Soc) Address() (swarm.Address, error) { ...@@ -109,6 +128,10 @@ func (s *Soc) Address() (swarm.Address, error) {
return CreateAddress(s.id, s.owner) return CreateAddress(s.id, s.owner)
} }
func (s *Soc) Signature() []byte {
return s.signature
}
// FromChunk recreates an Soc representation from swarm.Chunk data. // FromChunk recreates an Soc representation from swarm.Chunk data.
func FromChunk(sch swarm.Chunk) (*Soc, error) { func FromChunk(sch swarm.Chunk) (*Soc, error) {
chunkData := sch.Data() chunkData := sch.Data()
...@@ -174,19 +197,22 @@ func (s *Soc) ToChunk() (swarm.Chunk, error) { ...@@ -174,19 +197,22 @@ func (s *Soc) ToChunk() (swarm.Chunk, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.signature = signature
// prepare the payload
buf := bytes.NewBuffer(nil)
buf.Write(s.id)
buf.Write(signature)
buf.Write(s.Chunk.Data())
// create chunk // create chunk
socAddress, err := s.Address() socAddress, err := s.Address()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return swarm.NewChunk(socAddress, buf.Bytes()), nil return swarm.NewChunk(socAddress, s.toBytes()), nil
}
func (s *Soc) toBytes() []byte {
buf := bytes.NewBuffer(nil)
buf.Write(s.id)
buf.Write(s.signature)
buf.Write(s.Chunk.Data())
return buf.Bytes()
} }
// toSignDigest creates a digest suitable for signing to represent the soc. // toSignDigest creates a digest suitable for signing to represent the soc.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment